forked from Github-Mirrors/canaille
114 lines
3.6 KiB
Python
114 lines
3.6 KiB
Python
import datetime
|
|
import math
|
|
|
|
import pytz
|
|
import wtforms
|
|
from canaille.app.i18n import DEFAULT_LANGUAGE_CODE
|
|
from canaille.app.i18n import locale_selector
|
|
from canaille.app.i18n import timezone_selector
|
|
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 wtforms.meta import DefaultMeta
|
|
|
|
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"))
|
|
|
|
|
|
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)
|
|
|
|
|
|
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_input(field))
|
|
abort(response)
|
|
|
|
|
|
class HTMXForm(HTMXFormMixin, I18NFormMixin, FlaskForm):
|
|
pass
|
|
|
|
|
|
class HTMXBaseForm(HTMXFormMixin, I18NFormMixin, wtforms.form.BaseForm):
|
|
pass
|
|
|
|
|
|
class TableForm(I18NFormMixin, FlaskForm):
|
|
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 DateTimeUTCField(wtforms.DateTimeLocalField):
|
|
def _value(self):
|
|
if not self.data:
|
|
return ""
|
|
|
|
user_timezone = timezone_selector()
|
|
locale_dt = self.data.astimezone(user_timezone)
|
|
return locale_dt.strftime(self.format[0])
|
|
|
|
def process_formdata(self, valuelist):
|
|
if not valuelist:
|
|
return
|
|
|
|
date_str = " ".join(valuelist)
|
|
user_timezone = timezone_selector()
|
|
for format in self.strptime_format:
|
|
try:
|
|
unaware_dt = datetime.datetime.strptime(date_str, format)
|
|
locale_dt = user_timezone.localize(unaware_dt)
|
|
utc_dt = locale_dt.astimezone(pytz.utc)
|
|
self.data = utc_dt
|
|
return
|
|
except ValueError:
|
|
self.data = None
|
|
|
|
raise ValueError(self.gettext("Not a valid datetime value."))
|