feat: flask-babel and pytz are now part of the front extras

This commit is contained in:
Éloi Rivard 2023-09-01 10:46:56 +02:00
parent c0bf10dce7
commit a2e3fce204
No known key found for this signature in database
GPG key ID: 7EDA204EA57DD184
26 changed files with 99 additions and 51 deletions

View file

@ -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
=====================

View file

@ -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:

View file

@ -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

View file

@ -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:

View file

@ -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()

View file

@ -4,7 +4,7 @@ import flask
try:
import flask_themer
except ImportError: # pragma: no cover
except ImportError:
flask_themer = None

View file

@ -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)

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -4,7 +4,7 @@ from flask.cli import with_appcontext
try:
HAS_FAKER = True
except ImportError: # pragma: no cover
except ImportError:
HAS_FAKER = False

View file

@ -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

View file

@ -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

View file

@ -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):

View file

@ -18,7 +18,7 @@
<div class="sub header">{% trans %}Free and open-source identity provider.{% endtrans %}</div>
</h2>
<p style="text-align: center">
<a href="https://pypi.org/project/canaille/">{{ gettext("Version %(version)s", version=version) }}</a> ·
<a href="https://pypi.org/project/canaille/">{% trans %}Version {{ version }}{% endtrans %}</a> ·
<a href="https://canaille.yaal.coop">{% trans %}Homepage{% endtrans %}</a> ·
<a href="https://canaille.readthedocs.io">{% trans %}Documentation{% endtrans %}</a> ·
<a href="https://gitlab.com/yaal/canaille">{% trans %}Source code{% endtrans %}</a>

View file

@ -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()

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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):

View file

@ -7,7 +7,9 @@
<img class="ui centered tiny image" src="{{ client.logo_uri }}" alt="{{ client.client_name }}">
{% endif %}
<h2 class="ui header">{{ gettext('The application %(name)s is requesting access to:', name=client.client_name) }}</h2>
<h2 class="ui header">
{% trans name=client.client_name %}The application {{ name }} is requesting access to:{% endtrans %}
</h2>
<div class="ui divided items">
{% for scope in grant.request.scope.split(" ") %}

View file

@ -4,13 +4,13 @@ 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 TokenRevokationForm
from flask import abort
from flask import Blueprint
from flask import flash
from flask import request
from flask_babel import gettext as _
bp = Blueprint("tokens", __name__, url_prefix="/admin/token")

View file

@ -1,4 +1,4 @@
from flask_babel import lazy_gettext as _
from canaille.app.i18n import lazy_gettext as _
SCOPE_DETAILS = {
"profile": (

10
poetry.lock generated
View file

@ -1,4 +1,4 @@
# This file is automatically @generated by Poetry 1.5.1 and should not be changed by hand.
# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand.
[[package]]
name = "aiosmtpd"
@ -582,7 +582,7 @@ dotenv = ["python-dotenv"]
name = "flask-babel"
version = "3.1.0"
description = "Adds i18n/l10n support for Flask applications."
optional = false
optional = true
python-versions = ">=3.7,<4.0"
files = [
{file = "flask_babel-3.1.0-py3-none-any.whl", hash = "sha256:deb3ee272d5adf97f5974ed09ab501243d63e7fb4a047501a00de4bd4aca4830"},
@ -1861,8 +1861,8 @@ docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.link
testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-ignore-flaky", "pytest-mypy (>=0.9.1)", "pytest-ruff"]
[extras]
all = ["authlib", "email_validator", "flask-themer", "pycountry", "python-ldap", "sentry-sdk", "toml"]
front = ["email_validator", "flask-themer", "pycountry", "toml"]
all = ["authlib", "email_validator", "flask-babel", "flask-themer", "pycountry", "python-ldap", "pytz", "sentry-sdk", "toml"]
front = ["email_validator", "flask-babel", "flask-themer", "pycountry", "pytz", "toml"]
ldap = ["python-ldap"]
oidc = ["authlib"]
sentry = ["sentry-sdk"]
@ -1870,4 +1870,4 @@ sentry = ["sentry-sdk"]
[metadata]
lock-version = "2.0"
python-versions = "^3.8"
content-hash = "888a76fbcad92e1761f8000e7a1d69ac0035fc3024d259edc65e9a03acad18ac"
content-hash = "50fc7d78ae46cabdf2cd7403838c38c59412277bde3094f3fc36c221f7d3ec9f"

View file

@ -38,16 +38,16 @@ include = ["canaille/translations/*/LC_MESSAGES/*.mo"]
[tool.poetry.dependencies]
python = "^3.8"
flask = ">=2.2.2 <2.3"
flask-babel = "^3.0.0"
flask-wtf = "^1.1.1"
pytz = ">=2022.7"
wtforms = "^3.0.1"
werkzeug = ">=2.2.2 <2.3"
# extra : front
email_validator = {version = "^2.0.0", optional=true}
flask-babel = {version = "^3.0.0", optional=true}
flask-themer = {version = "^2.0.0", optional=true}
pycountry = {version = ">=22.1.10", optional=true}
pytz = {version = ">=2022.7", optional=true}
toml = {version = "^0.10.0", optional=true}
# extra : oidc
@ -97,8 +97,10 @@ requests = "*"
front = [
"click",
"email_validator",
"flask-babel",
"flask-themer",
"pycountry",
"pytz",
"toml",
]
ldap = [
@ -113,8 +115,10 @@ sentry = [
all = [
"click",
"email_validator",
"flask-babel",
"flask-themer",
"pycountry",
"pytz",
"toml",
"python-ldap",
"authlib",
@ -144,6 +148,14 @@ source = [
omit = [".tox/*"]
branch = true
[tool.coverage.report]
exclude_lines = [
"pragma: no cover",
"raise NotImplementedError",
"except ImportError",
"if app.debug",
]
[tool.ruff]
ignore = ["E501", "E722"]