Ver Fonte

feat: 优化表单

SongZihuan há 3 anos atrás
pai
commit
122c859928
8 ficheiros alterados com 175 adições e 152 exclusões
  1. 60 55
      app/home.py
  2. 55 52
      app/test.py
  3. 31 0
      app/tool.py
  4. 4 0
      app/user.py
  5. 2 0
      templates/base.html
  6. 8 32
      templates/index.html
  7. 9 0
      templates/macro.html
  8. 6 13
      templates/test.html

+ 60 - 55
app/home.py

@@ -1,92 +1,97 @@
-from flask import blueprints, url_for, request, redirect, render_template, flash, current_app, abort
+from flask import blueprints, url_for, request, redirect, render_template, flash, current_app, abort, g
 from flask_login import login_user, current_user, login_required, logout_user
-from flask_wtf import FlaskForm
-from wtforms import StringField, PasswordField, BooleanField, SubmitField, SelectField
-from wtforms.validators import DataRequired
-from app.user import load_user, create_user, check_template, get_template
-from typing import ClassVar
+from wtforms import PasswordField, BooleanField, SubmitField, SelectField
+from wtforms.validators import DataRequired, EqualTo, ValidationError
+from app.user import load_user, create_user, check_template, get_template, have_user
+from app.tool import form_required, AuthForm
 
 
-class LoginForm(FlaskForm):
-    name = StringField("User name", validators=[DataRequired()])
-    passwd = PasswordField("Passwd", validators=[DataRequired()])
+home = blueprints.Blueprint("home", __name__)
+
+
+class LoginForm(AuthForm):
     remember = BooleanField("Remember me")
     submit = SubmitField("Login")
 
+    def __init__(self):
+        super(LoginForm, self).__init__()
+        self.remember.data = True
 
-def register_form() -> ClassVar:
-    class Form(FlaskForm):
-        name = StringField("User name", validators=[DataRequired()])
-        template = SelectField("Template", choices=get_template())
-        passwd = PasswordField("Passwd", validators=[DataRequired()])
-        passwd_again = PasswordField("Passwd again", validators=[DataRequired()])
-        submit = SubmitField("register")
-    return Form()
 
+class RegisterForm(AuthForm):
+    template = SelectField("Template", choices=get_template(), coerce=str,
+                           validators=[DataRequired(message="Template must be selected")])
+    passwd_again = PasswordField("Passwd again",
+                                 validators=[DataRequired(message="Must enter password again"),
+                                             EqualTo("passwd", message="The password entered twice is different")])
+    submit = SubmitField("register")
 
-home = blueprints.Blueprint("home", __name__)
+    def __init__(self):
+        super(RegisterForm, self).__init__()
+        self.template.data = "base"
+
+    def validate_name(self, field):
+        if have_user(field.data):
+            raise ValidationError("User is already exist")
+
+    def validate_template(self, field):
+        if not check_template(field.data):
+            raise ValidationError("Template does not exist")
+
+
+def __load_index(login_form: LoginForm, register_form: RegisterForm):
+    if not current_user.is_anonymous:
+        return redirect(url_for("test.question"))
+    return render_template("index.html", login_form=login_form, register_form=register_form)
 
 
 @home.route("/", methods=["GET"])
 def index():
     if not current_user.is_anonymous:
         return redirect(url_for("test.question"))
-    return render_template("index.html", login_form=LoginForm(), register_form=register_form())
+    return __load_index(LoginForm(), RegisterForm())
 
 
 @home.route("/login", methods=["POST"])
+@form_required(LoginForm, lambda form: __load_index(form, RegisterForm()))
 def login():
     if not current_user.is_anonymous:
         current_app.logger.debug(f"re-login and abort(304)")
         flash(f"You are login as {current_user.user}")
         abort(304)
 
-    login_form = LoginForm()
-    if login_form.validate_on_submit():
-        user = load_user(login_form.name.data, login_form.passwd.data)
-        if user is not None:
-            login_user(user, login_form.remember.data)
-            next_page = request.args.get("next")
-            if next_page is None or not next_page.startswith('/'):
-                next_page = url_for('home.index')
-            flash("Login success")
-            current_app.logger.info(f"{login_form.name.data} login success")
-            return redirect(next_page)
-        flash("Login fail")
-        current_app.logger.debug(f"{login_form.name.data} login fail")
+    login_form: LoginForm = g.form
+    user = load_user(login_form.name.data, login_form.passwd.data)
+    if user is not None:
+        login_user(user, login_form.remember.data)
+        next_page = request.args.get("next")
+        if next_page is None or not next_page.startswith('/'):
+            next_page = url_for('home.index')
+        flash("Login success")
+        current_app.logger.info(f"{login_form.name.data} login success")
+        return redirect(next_page)
+    flash("Login fail")
+    current_app.logger.debug(f"{login_form.name.data} login fail")
     return redirect(url_for("home.index"))
 
 
 @home.route("/register", methods=["POST"])
+@form_required(RegisterForm, lambda form: __load_index(LoginForm(), form))
 def register():
     if not current_user.is_anonymous:
         current_app.logger.debug(f"re-login and register(304)")
         flash(f"You are login as {current_user.user}")
         abort(304)
 
-    register_ = register_form()
-    if register_.validate_on_submit():
-        if register_.passwd.data != register_.passwd_again.data:
-            flash("The two entered passwords do not match")
-        elif len(register_.passwd.data) < 4 or len(register_.passwd.data) > 32:
-            flash("Please enter a password of length 4-32")
-        elif len(register_.name.data) > 16:
-            flash("Please enter a username of length 1-16")
-        else:
-            template = register_.template.data
-            if len(template) == 0:
-                template = "base"
-            if not check_template(template):
-                flash(f"Template '{template}' not exist")
-                abort(400)
-            flat, user = create_user(template, register_.name.data, register_.passwd.data)
-            if user is not None:
-                current_app.logger.debug(f"{register_.name.data} with {register_.template.data} register success")
-                flash("Register success")
-            else:
-                current_app.logger.debug(
-                    f"{register_.name.data} with {register_.template.data} register fail [{flat}]")
-                flash("User is already exist")
+    register_form: RegisterForm = g.form
+    flat, user = create_user(register_form.template.data, register_form.name.data, register_form.passwd.data)
+    if user is not None:
+        current_app.logger.debug(f"{register_form.name.data} with {register_form.template.data} register success")
+        flash("Register success")
+    else:
+        current_app.logger.debug(
+            f"{register_form.name.data} with {register_form.template.data} register fail [{flat}]")
+        flash("User is already exist")
     return redirect(url_for("home.index"))
 
 

+ 55 - 52
app/test.py

@@ -1,9 +1,9 @@
-from flask import blueprints, render_template, current_app, abort, redirect, url_for, flash, make_response, request
+from flask import blueprints, render_template, current_app, abort, redirect, url_for, flash, make_response, request, g
 from flask import send_file
 from flask_login import current_user, login_required, logout_user
 from flask_wtf import FlaskForm
-from wtforms import StringField, SubmitField, BooleanField, PasswordField, FileField
-from wtforms.validators import DataRequired
+from wtforms import SearchField, SubmitField, BooleanField, PasswordField, FileField
+from wtforms.validators import DataRequired, Length
 from app.user import UserWordDataBase
 from itsdangerous import URLSafeTimedSerializer
 from itsdangerous.exc import BadData
@@ -11,36 +11,36 @@ from threading import Thread
 from typing import Optional, List
 from core.word import Word
 import io
+from app.tool import AuthForm, form_required
 
 test = blueprints.Blueprint("test", __name__)
 
 
 class SearchForm(FlaskForm):
-    search = StringField("Word", validators=[DataRequired()])
+    search = SearchField("Word", description="Search word",
+                         validators=[DataRequired(message="Must enter word"),
+                                     Length(1, 20, message="Length: 1- 20")])
     from_internet = BooleanField("Internet")
     add_to_db = BooleanField("Add")
     submit = SubmitField("Search")
 
 
-class ResetDeleteForm(FlaskForm):
-    name = StringField("User name", validators=[DataRequired()])
-    passwd = PasswordField("Passwd", validators=[DataRequired()])
-    new_passwd = PasswordField("New passwd")
+class ResetDeleteForm(AuthForm):
+    new_passwd = PasswordField("New Password", description="new password",
+                               validators=[DataRequired(message="Must enter new password"),
+                                           Length(4, 32, "Length: 4 - 32")])
     submit = SubmitField("Submit")
 
 
-class UploadFile(FlaskForm):
-    file = FileField("File")
+class UploadFileForm(FlaskForm):
+    file = FileField("File", description="Upload file", validators=[DataRequired("Must upload file")])
     submit = SubmitField("Upload")
 
 
-def __load_word(word):
+def __load_word(word, search_from: SearchForm, reset_delete_form: ResetDeleteForm, upload_form: UploadFileForm):
     user: UserWordDataBase = current_user
     box, box_distinct, box_sum, box_sum_distinct = user.get_box_count()
     right_count, wrong_count, history = user.get_history_info()
-    search_from = SearchForm()
-    reset_delete_form = ResetDeleteForm()
-    upload_form = UploadFile()
     job: Upload = Upload.upload.get(user.user)
     if Upload.upload.get(user.user) is not None:
         if job.is_alive():
@@ -62,11 +62,15 @@ def __load_word(word):
     return render_template("test.html", **template_var, word_id=word_id, have_word=True)
 
 
+def __load_question(search_from: SearchForm, reset_delete_form: ResetDeleteForm, upload_form: UploadFileForm):
+    word = current_user.rand_word()
+    return __load_word(word, search_from, reset_delete_form, upload_form)
+
+
 @test.route("/")
 @login_required
 def question():
-    word = current_user.rand_word()
-    return __load_word(word)
+    return __load_question(SearchForm(), ResetDeleteForm(), UploadFileForm())
 
 
 @test.route("/right/<string:word_id>")
@@ -152,6 +156,8 @@ class Search(Thread):
 def search():
     form = SearchForm()
     if not form.validate_on_submit():
+        if request.method == "POST":
+            return __load_question(form, ResetDeleteForm(), UploadFileForm())
         word = request.args.get("word", "")
         if len(word) == 0:
             abort(400)
@@ -161,7 +167,7 @@ def search():
         word = th.wait_event()
         if th.is_alive():
             flash("Search timeout")
-        return __load_word(word)
+        return __load_word(word, SearchForm(), ResetDeleteForm(), UploadFileForm())
     return redirect(url_for("test.search",
                             word=form.search.data, internet=int(form.from_internet.data), add=int(form.add_to_db.data)))
 
@@ -204,53 +210,49 @@ def download_table(file_type: str):
 
 @test.route("/reset/user", methods=["POST"])
 @login_required
+@form_required(ResetDeleteForm, lambda form: __load_question(SearchForm(), form, UploadFileForm()))
 def reset_user():
-    reset_form = ResetDeleteForm()
-    if reset_form.validate_on_submit():
-        user: UserWordDataBase = current_user
-        if not user.check_passwd(reset_form.passwd.data):
-            flash("Passwd error.")
-        else:
-            flash("User reset")
-            user.reset()
-        return redirect(url_for("test.question"))
-    abort(400)
+    user: UserWordDataBase = current_user
+    if not user.check_passwd(g.form.passwd.data):
+        flash("Passwd error.")
+    else:
+        flash("User reset")
+        user.reset()
+    return redirect(url_for("test.question"))
 
 
 @test.route("/delete/user", methods=["POST"])
 @login_required
+@form_required(ResetDeleteForm, lambda form: __load_question(SearchForm(), form, UploadFileForm()))
 def delete_user():
-    delete_form = ResetDeleteForm()
-    if delete_form.validate_on_submit():
-        user: UserWordDataBase = current_user
-        if not user.check_passwd(delete_form.passwd.data):
-            flash("Passwd error.")
-        else:
-            flash("User reset")
-            logout_user()
-            user.delete_user()
-        return redirect(url_for("test.question"))
-    abort(400)
+    delete_form: ResetDeleteForm = g.form
+    user: UserWordDataBase = current_user
+    if not user.check_passwd(delete_form.passwd.data):
+        flash("Passwd error.")
+    else:
+        flash("User reset")
+        logout_user()
+        user.delete_user()
+    return redirect(url_for("test.question"))
 
 
 @test.route("/reset/passwd", methods=["POST"])
 @login_required
+@form_required(ResetDeleteForm, lambda form: __load_question(SearchForm(), form, UploadFileForm()))
 def reset_passwd():
-    reset_form = ResetDeleteForm()
-    if reset_form.validate_on_submit():
-        if len(reset_form.new_passwd.data) < 4 or len(reset_form.new_passwd.data) > 32:
-            flash("Please enter a password of length 4-32")
+    reset_form: ResetDeleteForm = g.form
+    if len(reset_form.new_passwd.data) < 4 or len(reset_form.new_passwd.data) > 32:
+        flash("Please enter a password of length 4-32")
+    else:
+        user: UserWordDataBase = current_user
+        if not user.check_passwd(reset_form.passwd.data):
+            flash("Passwd error.")
         else:
-            user: UserWordDataBase = current_user
-            if not user.check_passwd(reset_form.passwd.data):
-                flash("Passwd error.")
-            else:
-                flash("User passwd reset")
-                user.set_passwd(reset_form.new_passwd.data)
-                logout_user()
-                return redirect(url_for("home.index"))
-        return redirect(url_for("test.question"))
-    abort(400)
+            flash("User passwd reset")
+            user.set_passwd(reset_form.new_passwd.data)
+            logout_user()
+            return redirect(url_for("home.index"))
+    return redirect(url_for("test.question"))
 
 
 class Upload(Thread):
@@ -270,6 +272,7 @@ class Upload(Thread):
 
 @test.route("/upload", methods=["POST"])
 @login_required
+@form_required(UploadFileForm, lambda form: __load_question(SearchForm(), ResetDeleteForm(), form))
 def upload():
     file = request.files["file"]
     user = current_user._get_current_object()

+ 31 - 0
app/tool.py

@@ -0,0 +1,31 @@
+from functools import wraps
+from flask import abort, g
+from flask_wtf import FlaskForm
+from typing import ClassVar, Optional, Callable
+from wtforms.fields import StringField, PasswordField
+from wtforms.validators import DataRequired, Length, Regexp
+
+
+class AuthForm(FlaskForm):
+    name = StringField("User name", description="User name",
+                       validators=[DataRequired(message="Must enter user name"),
+                                   Length(1, 10, message="Length: 1 - 10"),
+                                   Regexp(r"^[a-zA-Z0-9_]+$", message="Only letters and numbers and _ are accepted")])
+    passwd = PasswordField("Password", description="Password",
+                           validators=[DataRequired(message="Must enter password"),
+                                       Length(4, 32, "Length: 4 - 32")])
+
+
+def form_required(form: ClassVar[FlaskForm], callback: Optional[Callable] = None, **kw):
+    def required(func):
+        @wraps(func)
+        def new_func(*args, **kwargs):
+            f = form()
+            if not f.validate_on_submit():
+                if callback is None:
+                    return abort(404)
+                return callback(form=f, **kw, **kwargs)
+            g.form = f
+            return func(*args, **kwargs)
+        return new_func
+    return required

+ 4 - 0
app/user.py

@@ -143,6 +143,10 @@ def create_user(template: str, name: str, passwd: str):
     return 1, user
 
 
+def have_user(name: str):
+    return os.path.exists(os.path.join(conf["DB_PATH"], f"{name}.db"))
+
+
 def load_user(name: str, passwd: Optional[str]):
     if not os.path.exists(os.path.join(conf["DB_PATH"], f"{name}.db")):
         return None

+ 2 - 0
templates/base.html

@@ -1,3 +1,5 @@
+{% import "macro.html" as macro %}
+
 <!DOCTYPE html>
 <html lang="en">
 <head>

+ 8 - 32
templates/index.html

@@ -10,19 +10,11 @@
 
             <div id="authContent" class="tab-content">
                 <div class="tab-pane fade" id="login">
-                    <div class="col-12 col-lg-6 offset-lg-3">
+                    <div class="col-12 col-lg-6 offset-lg-3 text-left">
                         <form method="post" action="{{ url_for("home.login") }}">
                             {{ login_form.hidden_tag() }}
-
-                            <div class="form-group text-left">
-                                {{ login_form.name.label }}
-                                {{ login_form.name(class="form-control") }}
-                            </div>
-
-                            <div class="form-group text-left">
-                                {{ login_form.passwd.label }}
-                                {{ login_form.passwd(class="form-control") }}
-                            </div>
+                            {{ macro.render_field(login_form.name) }}
+                            {{ macro.render_field(login_form.passwd) }}
 
                             <div class="text-left">
                                 {{ login_form.submit(class='btn btn-info mr-2') }}
@@ -32,29 +24,13 @@
                     </div>
                 </div>
                 <div class="tab-pane fade" id="register">
-                    <div class="col-12 col-lg-6 offset-lg-3">
+                    <div class="col-12 col-lg-6 offset-lg-3 text-left">
                         <form method="post" action="{{ url_for("home.register") }}">
                             {{ register_form.hidden_tag() }}
-
-                            <div class="form-group text-left">
-                                {{ register_form.name.label }}
-                                {{ register_form.name(class="form-control") }}
-                            </div>
-
-                            <div class="form-group text-left">
-                                {{ register_form.template.label }}
-                                {{ register_form.template(class="form-control") }}
-                            </div>
-
-                            <div class="form-group text-left">
-                                {{ register_form.passwd.label }}
-                                {{ register_form.passwd(class="form-control") }}
-                            </div>
-
-                            <div class="form-group text-left">
-                                {{ register_form.passwd_again.label }}
-                                {{ register_form.passwd_again(class="form-control") }}
-                            </div>
+                            {{ macro.render_field(register_form.name) }}
+                            {{ macro.render_field(register_form.template) }}
+                            {{ macro.render_field(register_form.passwd) }}
+                            {{ macro.render_field(register_form.passwd_again) }}
 
                             <div class="text-left">
                                 {{ register_form.submit(class='btn btn-info mr-2') }}

+ 9 - 0
templates/macro.html

@@ -0,0 +1,9 @@
+{% macro render_field(field) %}
+    <div class="form-group">
+        {{ field.label }}
+        {{ field(class="form-control", **kwargs) | safe }}
+        {% for error in field.errors %}
+            <small class="text-danger form-text"> {{ error }} </small>
+        {% endfor %}
+    </div>
+{% endmacro %}

+ 6 - 13
templates/test.html

@@ -119,19 +119,9 @@
                                     </div>
                                     <div class="modal-body">
                                         <p> Are you sure you want to reset or delete '{{ current_user.user }}' ? Or you can just set new passwd. </p>
-                                        <div class="form-group text-left">
-                                            {{ reset_delete.name.label }}
-                                            {{ reset_delete.name(class="form-control") }}
-                                        </div>
-                                        <div class="form-group text-left">
-                                            {{ reset_delete.passwd.label }}
-                                            {{ reset_delete.passwd(class="form-control") }}
-                                        </div>
-
-                                        <div class="form-group text-left">
-                                            {{ reset_delete.new_passwd.label }}
-                                            {{ reset_delete.new_passwd(class="form-control") }}
-                                        </div>
+                                        {{ macro.render_field(reset_delete.name) }}
+                                        {{ macro.render_field(reset_delete.passwd) }}
+                                        {{ macro.render_field(reset_delete.new_passwd) }}
                                     </div>
                                     <div class="modal-footer">
                                         {{ reset_delete.submit(class='btn btn-outline-danger mr-2', formmethod="post", formaction=url_for("test.reset_passwd"), value="Set new passwd") }}
@@ -184,6 +174,9 @@
                             <div class="form-group text-left">
                                 {{ search.search(class="form-control") }}
                             </div>
+                            {% for error in search.search.errors %}
+                                <small class="text-danger form-text"> {{ error }} </small>
+                            {% endfor %}
 
                             <div class="text-left">
                                 {{ search.submit(class='btn btn-outline-primary mr-2') }}