Refactored utils

This commit is contained in:
Éloi Rivard 2023-04-02 00:30:39 +02:00
parent 81e80b8a59
commit d53fdde986
18 changed files with 112 additions and 101 deletions

View file

@ -138,7 +138,7 @@ def setup_flask(app):
@app.context_processor
def global_processor():
from .flaskutils import current_user
from .utils.flask import current_user
return {
"has_smtp": "SMTP" in app.config,

View file

@ -21,17 +21,6 @@ from flask_themer import render_template
from werkzeug.datastructures import CombinedMultiDict
from werkzeug.datastructures import FileStorage
from .apputils import b64_to_obj
from .apputils import default_fields
from .apputils import login_placeholder
from .apputils import obj_to_b64
from .apputils import profile_hash
from .flaskutils import current_user
from .flaskutils import permissions_needed
from .flaskutils import render_htmx_template
from .flaskutils import request_is_htmx
from .flaskutils import smtp_needed
from .flaskutils import user_needed
from .forms import FirstLoginForm
from .forms import ForgottenPasswordForm
from .forms import InvitationForm
@ -39,12 +28,23 @@ from .forms import LoginForm
from .forms import PasswordForm
from .forms import PasswordResetForm
from .forms import profile_form
from .forms import TableForm
from .mails import send_invitation_mail
from .mails import send_password_initialization_mail
from .mails import send_password_reset_mail
from .models import Group
from .models import User
from .utils import b64_to_obj
from .utils import default_fields
from .utils import login_placeholder
from .utils import obj_to_b64
from .utils import profile_hash
from .utils.flask import current_user
from .utils.flask import permissions_needed
from .utils.flask import render_htmx_template
from .utils.flask import request_is_htmx
from .utils.flask import smtp_needed
from .utils.flask import user_needed
from .utils.forms import TableForm
bp = Blueprint("account", __name__)

View file

@ -1,8 +1,8 @@
from canaille.apputils import obj_to_b64
from canaille.flaskutils import permissions_needed
from canaille.forms import HTMXForm
from canaille.mails import profile_hash
from canaille.mails import send_test_mail
from canaille.utils import obj_to_b64
from canaille.utils.flask import permissions_needed
from canaille.utils.forms import HTMXForm
from flask import Blueprint
from flask import current_app
from flask import flash

View file

@ -1,26 +1,20 @@
import math
import wtforms.form
from canaille.flaskutils import request_is_htmx
from canaille.utils.flask import request_is_htmx
from flask import abort
from flask import current_app
from flask import g
from flask import make_response
from flask import request
from flask_babel import lazy_gettext as _
from flask_wtf import FlaskForm
from flask_wtf.file import FileAllowed
from flask_wtf.file import FileField
from .apputils import validate_uri
from .i18n import native_language_name_from_code
from .models import Group
from .models import User
def is_uri(form, field):
if not validate_uri(field.data):
raise wtforms.ValidationError(_("This is not a valid URL"))
from .utils.forms import HTMXBaseForm
from .utils.forms import HTMXForm
from .utils.forms import is_uri
def unique_login(form, field):
@ -57,57 +51,6 @@ def existing_login(form, field):
)
class HTMXFormMixin:
def validate(self, *args, **kwargs):
"""
If the request is a HTMX request, this will only render the field
that triggered the request (after having validated the form). This
uses the Flask abort method to interrupt the flow with an exception.
"""
if not request_is_htmx():
return super().validate(*args, **kwargs)
field = self[request.headers.get("HX-Trigger-Name")]
field.widget.hide_value = False
self.process(request.form)
super().validate(*args, **kwargs)
form_macro = current_app.jinja_env.get_template("macro/form.html")
response = make_response(form_macro.module.render_field(field))
abort(response)
class HTMXForm(HTMXFormMixin, FlaskForm):
pass
class HTMXBaseForm(HTMXFormMixin, wtforms.form.BaseForm):
pass
class TableForm(HTMXForm):
def __init__(self, cls=None, page_size=25, fields=None, filter=None, **kwargs):
filter = filter or {}
super().__init__(**kwargs)
if self.query.data:
self.items = cls.fuzzy(self.query.data, fields, **filter)
else:
self.items = cls.query(**filter)
self.page_size = page_size
self.nb_items = len(self.items)
self.page_max = max(1, math.ceil(self.nb_items / self.page_size))
first_item = (self.page.data - 1) * self.page_size
last_item = min((self.page.data) * self.page_size, self.nb_items)
self.items_slice = self.items[first_item:last_item]
page = wtforms.IntegerField(default=1)
query = wtforms.StringField(default="")
def validate_page(self, field):
if field.data < 1 or field.data > self.page_max:
raise wtforms.validators.ValidationError(_("The page number is not valid"))
class LoginForm(HTMXForm):
login = wtforms.StringField(
_("Login"),

View file

@ -7,13 +7,13 @@ from flask import url_for
from flask_babel import gettext as _
from flask_themer import render_template
from .flaskutils import permissions_needed
from .flaskutils import render_htmx_template
from .forms import CreateGroupForm
from .forms import EditGroupForm
from .forms import TableForm
from .models import Group
from .models import User
from .utils.flask import permissions_needed
from .utils.flask import render_htmx_template
from .utils.forms import TableForm
bp = Blueprint("groups", __name__, url_prefix="/groups")

View file

@ -27,7 +27,7 @@ def setup_i18n(app):
def locale_selector():
from .flaskutils import current_user
from .utils.flask import current_user
user = current_user()
available_language_codes = getattr(g, "available_language_codes", [])

View file

@ -10,9 +10,9 @@ from flask import url_for
from flask_babel import gettext as _
from flask_themer import render_template
from .apputils import get_current_domain
from .apputils import get_current_mail_domain
from .apputils import profile_hash
from .utils import get_current_domain
from .utils import get_current_mail_domain
from .utils import profile_hash
DEFAULT_SMTP_HOST = "localhost"
DEFAULT_SMTP_PORT = 25

View file

@ -1,7 +1,7 @@
from canaille.flaskutils import permissions_needed
from canaille.flaskutils import render_htmx_template
from canaille.forms import TableForm
from canaille.oidc.models import AuthorizationCode
from canaille.utils.flask import permissions_needed
from canaille.utils.flask import render_htmx_template
from canaille.utils.forms import TableForm
from flask import abort
from flask import Blueprint
from flask import request

View file

@ -1,11 +1,11 @@
import datetime
from canaille.flaskutils import permissions_needed
from canaille.flaskutils import render_htmx_template
from canaille.flaskutils import request_is_htmx
from canaille.forms import TableForm
from canaille.oidc.forms import ClientAddForm
from canaille.oidc.models import Client
from canaille.utils.flask import permissions_needed
from canaille.utils.flask import render_htmx_template
from canaille.utils.flask import request_is_htmx
from canaille.utils.forms import TableForm
from flask import abort
from flask import Blueprint
from flask import flash

View file

@ -1,9 +1,9 @@
import datetime
import uuid
from canaille.flaskutils import user_needed
from canaille.oidc.models import Client
from canaille.oidc.models import Consent
from canaille.utils.flask import user_needed
from flask import Blueprint
from flask import flash
from flask import redirect

View file

@ -19,10 +19,10 @@ from flask_babel import gettext as _
from flask_themer import render_template
from werkzeug.datastructures import CombinedMultiDict
from ..flaskutils import current_user
from ..flaskutils import set_parameter_in_url_query
from ..forms import FullLoginForm
from ..models import User
from ..utils.flask import current_user
from ..utils.flask import set_parameter_in_url_query
from .forms import AuthorizeForm
from .forms import LogoutForm
from .models import Client

View file

@ -1,7 +1,7 @@
import wtforms
from canaille.forms import HTMXForm
from canaille.forms import is_uri
from canaille.oidc.models import Client
from canaille.utils.forms import HTMXForm
from canaille.utils.forms import is_uri
from flask_babel import lazy_gettext as _

View file

@ -1,11 +1,11 @@
import datetime
from canaille.flaskutils import permissions_needed
from canaille.flaskutils import render_htmx_template
from canaille.forms import TableForm
from canaille.models import User
from canaille.oidc.models import Client
from canaille.oidc.models import Token
from canaille.utils.flask import permissions_needed
from canaille.utils.flask import render_htmx_template
from canaille.utils.forms import TableForm
from flask import abort
from flask import Blueprint
from flask import flash

68
canaille/utils/forms.py Normal file
View file

@ -0,0 +1,68 @@
import math
import wtforms
from flask import abort
from flask import current_app
from flask import make_response
from flask import request
from flask_babel import gettext as _
from flask_wtf import FlaskForm
from . import validate_uri
from .flask import request_is_htmx
def is_uri(form, field):
if not validate_uri(field.data):
raise wtforms.ValidationError(_("This is not a valid URL"))
class HTMXFormMixin:
def validate(self, *args, **kwargs):
"""
If the request is a HTMX request, this will only render the field
that triggered the request (after having validated the form). This
uses the Flask abort method to interrupt the flow with an exception.
"""
if not request_is_htmx():
return super().validate(*args, **kwargs)
field = self[request.headers.get("HX-Trigger-Name")]
field.widget.hide_value = False
self.process(request.form)
super().validate(*args, **kwargs)
form_macro = current_app.jinja_env.get_template("macro/form.html")
response = make_response(form_macro.module.render_field(field))
abort(response)
class HTMXForm(HTMXFormMixin, FlaskForm):
pass
class HTMXBaseForm(HTMXFormMixin, wtforms.form.BaseForm):
pass
class TableForm(HTMXForm):
def __init__(self, cls=None, page_size=25, fields=None, filter=None, **kwargs):
filter = filter or {}
super().__init__(**kwargs)
if self.query.data:
self.items = cls.fuzzy(self.query.data, fields, **filter)
else:
self.items = cls.query(**filter)
self.page_size = page_size
self.nb_items = len(self.items)
self.page_max = max(1, math.ceil(self.nb_items / self.page_size))
first_item = (self.page.data - 1) * self.page_size
last_item = min((self.page.data) * self.page_size, self.nb_items)
self.items_slice = self.items[first_item:last_item]
page = wtforms.IntegerField(default=1)
query = wtforms.StringField(default="")
def validate_page(self, field):
if field.data < 1 or field.data > self.page_max:
raise wtforms.validators.ValidationError(_("The page number is not valid"))

View file

@ -1,4 +1,4 @@
from canaille.apputils import validate_uri
from canaille.utils import validate_uri
def test_validate_uri():

View file

@ -4,7 +4,7 @@ import ldap
import pytest
import toml
from canaille import create_app
from canaille.flaskutils import set_parameter_in_url_query
from canaille.utils.flask import set_parameter_in_url_query
from flask import g
from flask_webtest import TestApp