forked from Github-Mirrors/canaille
Refactored utils
This commit is contained in:
parent
81e80b8a59
commit
d53fdde986
18 changed files with 112 additions and 101 deletions
|
@ -138,7 +138,7 @@ def setup_flask(app):
|
||||||
|
|
||||||
@app.context_processor
|
@app.context_processor
|
||||||
def global_processor():
|
def global_processor():
|
||||||
from .flaskutils import current_user
|
from .utils.flask import current_user
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"has_smtp": "SMTP" in app.config,
|
"has_smtp": "SMTP" in app.config,
|
||||||
|
|
|
@ -21,17 +21,6 @@ from flask_themer import render_template
|
||||||
from werkzeug.datastructures import CombinedMultiDict
|
from werkzeug.datastructures import CombinedMultiDict
|
||||||
from werkzeug.datastructures import FileStorage
|
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 FirstLoginForm
|
||||||
from .forms import ForgottenPasswordForm
|
from .forms import ForgottenPasswordForm
|
||||||
from .forms import InvitationForm
|
from .forms import InvitationForm
|
||||||
|
@ -39,12 +28,23 @@ from .forms import LoginForm
|
||||||
from .forms import PasswordForm
|
from .forms import PasswordForm
|
||||||
from .forms import PasswordResetForm
|
from .forms import PasswordResetForm
|
||||||
from .forms import profile_form
|
from .forms import profile_form
|
||||||
from .forms import TableForm
|
|
||||||
from .mails import send_invitation_mail
|
from .mails import send_invitation_mail
|
||||||
from .mails import send_password_initialization_mail
|
from .mails import send_password_initialization_mail
|
||||||
from .mails import send_password_reset_mail
|
from .mails import send_password_reset_mail
|
||||||
from .models import Group
|
from .models import Group
|
||||||
from .models import User
|
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__)
|
bp = Blueprint("account", __name__)
|
||||||
|
|
|
@ -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 profile_hash
|
||||||
from canaille.mails import send_test_mail
|
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 Blueprint
|
||||||
from flask import current_app
|
from flask import current_app
|
||||||
from flask import flash
|
from flask import flash
|
||||||
|
|
|
@ -1,26 +1,20 @@
|
||||||
import math
|
|
||||||
|
|
||||||
import wtforms.form
|
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 abort
|
||||||
from flask import current_app
|
from flask import current_app
|
||||||
from flask import g
|
from flask import g
|
||||||
from flask import make_response
|
from flask import make_response
|
||||||
from flask import request
|
from flask import request
|
||||||
from flask_babel import lazy_gettext as _
|
from flask_babel import lazy_gettext as _
|
||||||
from flask_wtf import FlaskForm
|
|
||||||
from flask_wtf.file import FileAllowed
|
from flask_wtf.file import FileAllowed
|
||||||
from flask_wtf.file import FileField
|
from flask_wtf.file import FileField
|
||||||
|
|
||||||
from .apputils import validate_uri
|
|
||||||
from .i18n import native_language_name_from_code
|
from .i18n import native_language_name_from_code
|
||||||
from .models import Group
|
from .models import Group
|
||||||
from .models import User
|
from .models import User
|
||||||
|
from .utils.forms import HTMXBaseForm
|
||||||
|
from .utils.forms import HTMXForm
|
||||||
def is_uri(form, field):
|
from .utils.forms import is_uri
|
||||||
if not validate_uri(field.data):
|
|
||||||
raise wtforms.ValidationError(_("This is not a valid URL"))
|
|
||||||
|
|
||||||
|
|
||||||
def unique_login(form, field):
|
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):
|
class LoginForm(HTMXForm):
|
||||||
login = wtforms.StringField(
|
login = wtforms.StringField(
|
||||||
_("Login"),
|
_("Login"),
|
||||||
|
|
|
@ -7,13 +7,13 @@ from flask import url_for
|
||||||
from flask_babel import gettext as _
|
from flask_babel import gettext as _
|
||||||
from flask_themer import render_template
|
from flask_themer import render_template
|
||||||
|
|
||||||
from .flaskutils import permissions_needed
|
|
||||||
from .flaskutils import render_htmx_template
|
|
||||||
from .forms import CreateGroupForm
|
from .forms import CreateGroupForm
|
||||||
from .forms import EditGroupForm
|
from .forms import EditGroupForm
|
||||||
from .forms import TableForm
|
|
||||||
from .models import Group
|
from .models import Group
|
||||||
from .models import User
|
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")
|
bp = Blueprint("groups", __name__, url_prefix="/groups")
|
||||||
|
|
||||||
|
|
|
@ -27,7 +27,7 @@ def setup_i18n(app):
|
||||||
|
|
||||||
|
|
||||||
def locale_selector():
|
def locale_selector():
|
||||||
from .flaskutils import current_user
|
from .utils.flask import current_user
|
||||||
|
|
||||||
user = current_user()
|
user = current_user()
|
||||||
available_language_codes = getattr(g, "available_language_codes", [])
|
available_language_codes = getattr(g, "available_language_codes", [])
|
||||||
|
|
|
@ -10,9 +10,9 @@ from flask import url_for
|
||||||
from flask_babel import gettext as _
|
from flask_babel import gettext as _
|
||||||
from flask_themer import render_template
|
from flask_themer import render_template
|
||||||
|
|
||||||
from .apputils import get_current_domain
|
from .utils import get_current_domain
|
||||||
from .apputils import get_current_mail_domain
|
from .utils import get_current_mail_domain
|
||||||
from .apputils import profile_hash
|
from .utils import profile_hash
|
||||||
|
|
||||||
DEFAULT_SMTP_HOST = "localhost"
|
DEFAULT_SMTP_HOST = "localhost"
|
||||||
DEFAULT_SMTP_PORT = 25
|
DEFAULT_SMTP_PORT = 25
|
||||||
|
|
|
@ -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.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 abort
|
||||||
from flask import Blueprint
|
from flask import Blueprint
|
||||||
from flask import request
|
from flask import request
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
import datetime
|
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.forms import ClientAddForm
|
||||||
from canaille.oidc.models import Client
|
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 abort
|
||||||
from flask import Blueprint
|
from flask import Blueprint
|
||||||
from flask import flash
|
from flask import flash
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
import datetime
|
import datetime
|
||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
from canaille.flaskutils import user_needed
|
|
||||||
from canaille.oidc.models import Client
|
from canaille.oidc.models import Client
|
||||||
from canaille.oidc.models import Consent
|
from canaille.oidc.models import Consent
|
||||||
|
from canaille.utils.flask import user_needed
|
||||||
from flask import Blueprint
|
from flask import Blueprint
|
||||||
from flask import flash
|
from flask import flash
|
||||||
from flask import redirect
|
from flask import redirect
|
||||||
|
|
|
@ -19,10 +19,10 @@ from flask_babel import gettext as _
|
||||||
from flask_themer import render_template
|
from flask_themer import render_template
|
||||||
from werkzeug.datastructures import CombinedMultiDict
|
from werkzeug.datastructures import CombinedMultiDict
|
||||||
|
|
||||||
from ..flaskutils import current_user
|
|
||||||
from ..flaskutils import set_parameter_in_url_query
|
|
||||||
from ..forms import FullLoginForm
|
from ..forms import FullLoginForm
|
||||||
from ..models import User
|
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 AuthorizeForm
|
||||||
from .forms import LogoutForm
|
from .forms import LogoutForm
|
||||||
from .models import Client
|
from .models import Client
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import wtforms
|
import wtforms
|
||||||
from canaille.forms import HTMXForm
|
|
||||||
from canaille.forms import is_uri
|
|
||||||
from canaille.oidc.models import Client
|
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 _
|
from flask_babel import lazy_gettext as _
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
import datetime
|
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.models import User
|
||||||
from canaille.oidc.models import Client
|
from canaille.oidc.models import Client
|
||||||
from canaille.oidc.models import Token
|
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 abort
|
||||||
from flask import Blueprint
|
from flask import Blueprint
|
||||||
from flask import flash
|
from flask import flash
|
||||||
|
|
68
canaille/utils/forms.py
Normal file
68
canaille/utils/forms.py
Normal 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"))
|
|
@ -1,4 +1,4 @@
|
||||||
from canaille.apputils import validate_uri
|
from canaille.utils import validate_uri
|
||||||
|
|
||||||
|
|
||||||
def test_validate_uri():
|
def test_validate_uri():
|
||||||
|
|
|
@ -4,7 +4,7 @@ import ldap
|
||||||
import pytest
|
import pytest
|
||||||
import toml
|
import toml
|
||||||
from canaille import create_app
|
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 import g
|
||||||
from flask_webtest import TestApp
|
from flask_webtest import TestApp
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue