diff --git a/CHANGES.rst b/CHANGES.rst index 7efef4fb..dc6ecb9e 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -9,6 +9,7 @@ Added - If users register or authenticate during a OAuth Authorization phase, they get redirected back to that page afterwards. :issue:`168` :pr:`151` +- flask-babel and pytz are now part of the `front` extras [0.0.33] - 2023-08-26 ===================== diff --git a/canaille/app/configuration.py b/canaille/app/configuration.py index b070b158..afdc2c2a 100644 --- a/canaille/app/configuration.py +++ b/canaille/app/configuration.py @@ -44,7 +44,7 @@ def setup_config(app, config=None, validate_config=True): try: import toml - except ImportError: # pragma: no cover + except ImportError: toml = None app.config.from_mapping( @@ -64,7 +64,7 @@ def setup_config(app, config=None, validate_config=True): "Either create conf/config.toml or set the 'CONFIG' variable environment." ) - if app.debug: # pragma: no cover + if app.debug: install(app.config, debug=True) if validate_config: diff --git a/canaille/app/flask.py b/canaille/app/flask.py index 791bb337..bc5a4acf 100644 --- a/canaille/app/flask.py +++ b/canaille/app/flask.py @@ -4,13 +4,13 @@ from urllib.parse import urlsplit from urllib.parse import urlunsplit from canaille.app import models +from canaille.app.i18n import gettext as _ from canaille.app.themes import render_template from flask import abort from flask import current_app from flask import g from flask import request from flask import session -from flask_babel import gettext as _ from werkzeug.routing import BaseConverter diff --git a/canaille/app/forms.py b/canaille/app/forms.py index b827b76c..6f571d08 100644 --- a/canaille/app/forms.py +++ b/canaille/app/forms.py @@ -2,16 +2,15 @@ import datetime import math import re -import pytz import wtforms.validators from canaille.app.i18n import DEFAULT_LANGUAGE_CODE +from canaille.app.i18n import gettext as _ 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 @@ -47,7 +46,7 @@ def phone_number(form, field): def email_validator(form, field): try: import email_validator # noqa: F401 - except ImportError: # pragma: no cover + except ImportError: pass wtforms.validators.Email()(form, field) @@ -226,7 +225,7 @@ class DateTimeUTCField(wtforms.DateTimeLocalField): try: unaware_dt = datetime.datetime.strptime(date_str, format) locale_dt = user_timezone.localize(unaware_dt) - utc_dt = locale_dt.astimezone(pytz.utc) + utc_dt = locale_dt.astimezone(datetime.timezone.utc) self.data = utc_dt return except ValueError: diff --git a/canaille/app/i18n.py b/canaille/app/i18n.py index 9b54bd55..7c1ba0d7 100644 --- a/canaille/app/i18n.py +++ b/canaille/app/i18n.py @@ -1,23 +1,32 @@ -import gettext +import datetime -import pytz -from babel.dates import LOCALTZ from flask import current_app from flask import g from flask import request -from flask_babel import Babel -from flask_babel import get_locale DEFAULT_LANGUAGE_CODE = "en" -babel = Babel() +try: + from flask_babel import Babel + from flask_babel import get_locale + from flask_babel import gettext + from flask_babel import lazy_gettext + + babel = Babel() +except ImportError: + + def identity(string, *args, **kwargs): + return string + + def get_locale(): + return "en_US" + + gettext = identity + lazy_gettext = identity + babel = None def setup_i18n(app): - babel.init_app( - app, locale_selector=locale_selector, timezone_selector=timezone_selector - ) - @app.before_request def before_request(): g.available_language_codes = available_language_codes() @@ -28,6 +37,13 @@ def setup_i18n(app): "locale": get_locale(), } + if not babel: # pragma: no cover + return + + babel.init_app( + app, locale_selector=locale_selector, timezone_selector=timezone_selector + ) + def locale_selector(): from .flask import current_user @@ -44,6 +60,12 @@ def locale_selector(): def timezone_selector(): + if not babel: # pragma: no cover + return datetime.timezone.utc + + import pytz + from babel.dates import LOCALTZ + try: return pytz.timezone(current_app.config.get("TIMEZONE")) except pytz.exceptions.UnknownTimeZoneError: @@ -53,18 +75,30 @@ def timezone_selector(): def native_language_name_from_code(code): try: import pycountry - except ImportError: # pragma: no cover + from gettext import translation + except ImportError: return code language = pycountry.languages.get(alpha_2=code[:2]) if code == DEFAULT_LANGUAGE_CODE: return language.name - translation = gettext.translation( - "iso639-3", pycountry.LOCALES_DIR, languages=[code] - ) + translation = translation("iso639-3", pycountry.LOCALES_DIR, languages=[code]) return translation.gettext(language.name) def available_language_codes(): - return [str(translation) for translation in babel.list_translations()] + return ( + [str(translation) for translation in babel.list_translations()] + if babel + else [DEFAULT_LANGUAGE_CODE] + ) + + +def reload_translations(): + if not babel: # pragma: no cover + return + + from flask_babel import refresh + + return refresh() diff --git a/canaille/app/themes.py b/canaille/app/themes.py index 6ed2d821..7894e7d5 100644 --- a/canaille/app/themes.py +++ b/canaille/app/themes.py @@ -4,7 +4,7 @@ import flask try: import flask_themer -except ImportError: # pragma: no cover +except ImportError: flask_themer = None diff --git a/canaille/backends/__init__.py b/canaille/backends/__init__.py index acb52c1c..9b79a952 100644 --- a/canaille/backends/__init__.py +++ b/canaille/backends/__init__.py @@ -102,7 +102,7 @@ def setup_backend(app, backend): g.backend = backend app.backend = backend - if app.debug: # pragma: no cover + if app.debug: backend.install(app.config, True) diff --git a/canaille/backends/ldap/backend.py b/canaille/backends/ldap/backend.py index 5e03030c..62cc6e13 100644 --- a/canaille/backends/ldap/backend.py +++ b/canaille/backends/ldap/backend.py @@ -7,11 +7,11 @@ import ldap.modlist import ldif from canaille.app import models from canaille.app.configuration import ConfigurationException +from canaille.app.i18n import gettext as _ from canaille.app.themes import render_template from canaille.backends import BaseBackend from flask import current_app from flask import request -from flask_babel import gettext as _ from .utils import listify diff --git a/canaille/core/account.py b/canaille/core/account.py index e3cbb726..7681607f 100644 --- a/canaille/core/account.py +++ b/canaille/core/account.py @@ -24,6 +24,8 @@ from canaille.app.forms import is_readonly from canaille.app.forms import set_readonly from canaille.app.forms import set_writable from canaille.app.forms import TableForm +from canaille.app.i18n import gettext as _ +from canaille.app.i18n import reload_translations from canaille.app.themes import render_template from canaille.backends import BaseBackend from flask import abort @@ -36,8 +38,6 @@ from flask import request from flask import send_file from flask import session from flask import url_for -from flask_babel import gettext as _ -from flask_babel import refresh from werkzeug.datastructures import CombinedMultiDict from werkzeug.datastructures import FileStorage @@ -526,7 +526,7 @@ def profile_edition_main_form_validation(user, edited_user, profile_form): if "preferred_language" in request.form: # Refresh the babel cache in case the lang is updated - refresh() + reload_translations() if profile_form["preferred_language"].data == "auto": edited_user.preferred_language = None diff --git a/canaille/core/admin.py b/canaille/core/admin.py index d03c2c32..53bf24f0 100644 --- a/canaille/core/admin.py +++ b/canaille/core/admin.py @@ -2,6 +2,7 @@ from canaille.app import obj_to_b64 from canaille.app.flask import permissions_needed from canaille.app.forms import email_validator from canaille.app.forms import Form +from canaille.app.i18n import gettext as _ from canaille.app.themes import render_template from canaille.core.mails import build_hash from canaille.core.mails import send_test_mail @@ -10,7 +11,6 @@ from flask import current_app from flask import flash from flask import request from flask import url_for -from flask_babel import gettext as _ from wtforms import StringField from wtforms.validators import DataRequired diff --git a/canaille/core/auth.py b/canaille/core/auth.py index 2e12d8eb..53a93402 100644 --- a/canaille/core/auth.py +++ b/canaille/core/auth.py @@ -4,6 +4,7 @@ from canaille.app.flask import current_user from canaille.app.flask import login_user from canaille.app.flask import logout_user from canaille.app.flask import smtp_needed +from canaille.app.i18n import gettext as _ from canaille.app.themes import render_template from canaille.backends import BaseBackend from flask import abort @@ -14,7 +15,6 @@ from flask import redirect from flask import request from flask import session from flask import url_for -from flask_babel import gettext as _ from .forms import FirstLoginForm from .forms import ForgottenPasswordForm diff --git a/canaille/core/commands.py b/canaille/core/commands.py index 6ef1b39f..526c9185 100644 --- a/canaille/core/commands.py +++ b/canaille/core/commands.py @@ -4,7 +4,7 @@ from flask.cli import with_appcontext try: HAS_FAKER = True -except ImportError: # pragma: no cover +except ImportError: HAS_FAKER = False diff --git a/canaille/core/forms.py b/canaille/core/forms.py index 6e76c623..8c8c3646 100644 --- a/canaille/core/forms.py +++ b/canaille/core/forms.py @@ -9,10 +9,10 @@ from canaille.app.forms import phone_number from canaille.app.forms import ReadOnly from canaille.app.forms import set_readonly from canaille.app.forms import unique_values +from canaille.app.i18n import lazy_gettext as _ from canaille.app.i18n import native_language_name_from_code from flask import current_app from flask import g -from flask_babel import lazy_gettext as _ from flask_wtf.file import FileAllowed from flask_wtf.file import FileField diff --git a/canaille/core/groups.py b/canaille/core/groups.py index 788ca47e..fe202736 100644 --- a/canaille/core/groups.py +++ b/canaille/core/groups.py @@ -2,6 +2,7 @@ from canaille.app import models from canaille.app.flask import permissions_needed from canaille.app.flask import render_htmx_template from canaille.app.forms import TableForm +from canaille.app.i18n import gettext as _ from canaille.app.themes import render_template from flask import abort from flask import Blueprint @@ -9,7 +10,6 @@ from flask import flash from flask import redirect from flask import request from flask import url_for -from flask_babel import gettext as _ from .forms import CreateGroupForm from .forms import EditGroupForm diff --git a/canaille/core/mails.py b/canaille/core/mails.py index 6f5a11e1..66495255 100644 --- a/canaille/core/mails.py +++ b/canaille/core/mails.py @@ -1,10 +1,10 @@ from canaille.app import build_hash +from canaille.app.i18n import gettext as _ from canaille.app.mails import logo from canaille.app.mails import send_email from canaille.app.themes import render_template from flask import current_app from flask import url_for -from flask_babel import gettext as _ def send_test_mail(email): diff --git a/canaille/core/templates/about.html b/canaille/core/templates/about.html index 5edab60e..381d03bc 100644 --- a/canaille/core/templates/about.html +++ b/canaille/core/templates/about.html @@ -18,7 +18,7 @@
- {{ gettext("Version %(version)s", version=version) }} ·
+ {% trans %}Version {{ version }}{% endtrans %} ·
{% trans %}Homepage{% endtrans %} ·
{% trans %}Documentation{% endtrans %} ·
{% trans %}Source code{% endtrans %}
diff --git a/canaille/oidc/basemodels.py b/canaille/oidc/basemodels.py
index f7f7124f..1dd4cd4d 100644
--- a/canaille/oidc/basemodels.py
+++ b/canaille/oidc/basemodels.py
@@ -91,7 +91,7 @@ class Consent(Model):
revokation_date: datetime.datetime
def revoke(self):
- raise NotImplementedError() # pragma: no cover
+ raise NotImplementedError()
def restore(self):
- raise NotImplementedError() # pragma: no cover
+ raise NotImplementedError()
diff --git a/canaille/oidc/clients.py b/canaille/oidc/clients.py
index 7ef82195..d58add90 100644
--- a/canaille/oidc/clients.py
+++ b/canaille/oidc/clients.py
@@ -4,6 +4,7 @@ from canaille.app import models
from canaille.app.flask import permissions_needed
from canaille.app.flask import render_htmx_template
from canaille.app.forms import TableForm
+from canaille.app.i18n import gettext as _
from canaille.app.themes import render_template
from canaille.oidc.forms import ClientAddForm
from flask import abort
@@ -12,7 +13,6 @@ from flask import flash
from flask import redirect
from flask import request
from flask import url_for
-from flask_babel import gettext as _
from werkzeug.security import gen_salt
diff --git a/canaille/oidc/consents.py b/canaille/oidc/consents.py
index 54d71a88..60a90fe3 100644
--- a/canaille/oidc/consents.py
+++ b/canaille/oidc/consents.py
@@ -3,12 +3,12 @@ import uuid
from canaille.app import models
from canaille.app.flask import user_needed
+from canaille.app.i18n import gettext as _
from canaille.app.themes import render_template
from flask import Blueprint
from flask import flash
from flask import redirect
from flask import url_for
-from flask_babel import gettext as _
from .utils import SCOPE_DETAILS
diff --git a/canaille/oidc/endpoints.py b/canaille/oidc/endpoints.py
index 13fe31a3..00c4f861 100644
--- a/canaille/oidc/endpoints.py
+++ b/canaille/oidc/endpoints.py
@@ -10,6 +10,7 @@ from canaille.app import models
from canaille.app.flask import current_user
from canaille.app.flask import logout_user
from canaille.app.flask import set_parameter_in_url_query
+from canaille.app.i18n import gettext as _
from canaille.app.themes import render_template
from flask import abort
from flask import Blueprint
@@ -20,7 +21,6 @@ from flask import redirect
from flask import request
from flask import session
from flask import url_for
-from flask_babel import gettext as _
from werkzeug.datastructures import CombinedMultiDict
from .forms import AuthorizeForm
diff --git a/canaille/oidc/forms.py b/canaille/oidc/forms.py
index 7060ab59..c6f329a2 100644
--- a/canaille/oidc/forms.py
+++ b/canaille/oidc/forms.py
@@ -4,7 +4,7 @@ from canaille.app.forms import email_validator
from canaille.app.forms import Form
from canaille.app.forms import is_uri
from canaille.app.forms import unique_values
-from flask_babel import lazy_gettext as _
+from canaille.app.i18n import lazy_gettext as _
class AuthorizeForm(Form):
diff --git a/canaille/oidc/templates/authorize.html b/canaille/oidc/templates/authorize.html
index 49edc3ec..15f044be 100644
--- a/canaille/oidc/templates/authorize.html
+++ b/canaille/oidc/templates/authorize.html
@@ -7,7 +7,9 @@
{% endif %}
-