2023-04-01 22:30:39 +00:00
|
|
|
import math
|
|
|
|
|
|
|
|
import wtforms
|
2023-04-02 11:24:06 +00:00
|
|
|
from canaille.i18n import DEFAULT_LANGUAGE_CODE
|
|
|
|
from canaille.i18n import locale_selector
|
2023-04-01 22:30:39 +00:00
|
|
|
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
|
2023-04-02 11:24:06 +00:00
|
|
|
from wtforms.meta import DefaultMeta
|
2023-04-01 22:30:39 +00:00
|
|
|
|
|
|
|
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"))
|
|
|
|
|
|
|
|
|
2023-04-02 11:24:06 +00:00
|
|
|
meta = DefaultMeta()
|
|
|
|
|
|
|
|
|
|
|
|
class I18NFormMixin:
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
|
|
preferred_locale = locale_selector()
|
|
|
|
meta.locales = (
|
|
|
|
[preferred_locale, DEFAULT_LANGUAGE_CODE] if preferred_locale else False
|
|
|
|
)
|
|
|
|
super().__init__(*args, meta=meta, **kwargs)
|
|
|
|
|
|
|
|
|
2023-04-01 22:30:39 +00:00
|
|
|
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)
|
|
|
|
|
|
|
|
|
2023-04-02 11:24:06 +00:00
|
|
|
class HTMXForm(HTMXFormMixin, I18NFormMixin, FlaskForm):
|
2023-04-01 22:30:39 +00:00
|
|
|
pass
|
|
|
|
|
|
|
|
|
2023-04-02 11:24:06 +00:00
|
|
|
class HTMXBaseForm(HTMXFormMixin, I18NFormMixin, wtforms.form.BaseForm):
|
2023-04-01 22:30:39 +00:00
|
|
|
pass
|
|
|
|
|
|
|
|
|
2023-04-04 07:43:56 +00:00
|
|
|
class TableForm(I18NFormMixin, FlaskForm):
|
2023-04-01 22:30:39 +00:00
|
|
|
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"))
|