Improved and documented i18n and background tasks

This commit is contained in:
Raphael Michel
2016-05-29 20:02:31 +02:00
parent 8369ad291e
commit ead7d8ed78
12 changed files with 273 additions and 101 deletions

View File

@@ -1,12 +1,12 @@
import copy
import logging
import os
from django import forms
from django.core.files import File
from django.core.files.storage import default_storage
from django.core.files.uploadedfile import UploadedFile
from django.forms.models import BaseModelForm, ModelFormMetaclass
from django.forms.models import (
BaseInlineFormSet, BaseModelForm, BaseModelFormSet, ModelFormMetaclass,
)
from django.utils import six
from django.utils.translation import ugettext_lazy as _
@@ -18,7 +18,7 @@ logger = logging.getLogger('pretix.plugins.ticketoutputpdf')
class BaseI18nModelForm(BaseModelForm):
"""
This is a helperclass to construct I18nModelForm
This is a helperclass to construct an I18nModelForm.
"""
def __init__(self, *args, **kwargs):
event = kwargs.pop('event', None)
@@ -33,13 +33,54 @@ class I18nModelForm(six.with_metaclass(ModelFormMetaclass, BaseI18nModelForm)):
"""
This is a modified version of Django's ModelForm which differs from ModelForm in
only one way: The constructor takes one additional optional argument ``event``
which may be given an :pyclass:`pretix.base.models.Event` instance. If given, this
instance is used to select the visible languages in all I18nFormFields of the form. If
not given, all languages will be displayed.
which may be given an `Event` instance. If given, this instance is used to select
the visible languages in all I18nFormFields of the form. If not given, all languages
will be displayed.
"""
pass
class I18nFormSet(BaseModelFormSet):
"""
This is equivalent to a normal BaseModelFormset, but cares for the special needs
of I18nForms (see there for more information).
"""
def __init__(self, *args, **kwargs):
self.event = kwargs.pop('event', None)
super().__init__(*args, **kwargs)
def _construct_form(self, i, **kwargs):
kwargs['event'] = self.event
return super()._construct_form(i, **kwargs)
@property
def empty_form(self):
form = self.form(
auto_id=self.auto_id,
prefix=self.add_prefix('__prefix__'),
empty_permitted=True,
event=self.event
)
self.add_fields(form, None)
return form
class I18nInlineFormSet(BaseInlineFormSet):
"""
This is equivalent to a normal BaseInlineFormset, but cares for the special needs
of I18nForms (see there for more information).
"""
def __init__(self, *args, **kwargs):
self.event = kwargs.pop('event', None)
super().__init__(*args, **kwargs)
def _construct_form(self, i, **kwargs):
kwargs['event'] = self.event
return super()._construct_form(i, **kwargs)
class SettingsForm(forms.Form):
"""
This form is meant to be used for modifying Event- or OrganizerSettings

View File

@@ -6,7 +6,6 @@ from django import forms
from django.conf import settings
from django.core.serializers.json import DjangoJSONEncoder
from django.db.models import Model, QuerySet, TextField
from django.forms import BaseModelFormSet
from django.utils import translation
from django.utils.formats import date_format, number_format
from django.utils.safestring import mark_safe
@@ -21,7 +20,11 @@ class LazyI18nString:
def __init__(self, data: Optional[Union[str, Dict[str, str]]]):
"""
Input data should be a dictionary which maps language codes to content.
Creates a new i18n-aware string.
:param data: If this is a dictionary, it is expected to map language codes to translations.
If this is a string that can be parsed as JSON, it will be parsed and used as such a dictionary.
If this is anything else, it will be casted to a string and used for all languages.
"""
self.data = data
if isinstance(self.data, str) and self.data is not None:
@@ -35,8 +38,10 @@ class LazyI18nString:
def __str__(self) -> str:
"""
Evaluate the given string with respect to the currently active locale.
This will rather return you a string in a wrong language than give you an
empty value.
If no string is available in the currently active language, this will give you
the string in the system's default language. If this is unavailable as well, it
will give you the string in the first language available.
"""
return self.localize(translation.get_language())
@@ -47,13 +52,24 @@ class LazyI18nString:
return any(self.data.values())
return True
def localize(self, lng):
def localize(self, lng: str) -> str:
"""
Evaluate the given string with respect to the locale defined by ``lng``.
If no string is available in the currently active language, this will give you
the string in the system's default language. If this is unavailable as well, it
will give you the string in the first language available.
:param lng: A locale code, e.g. ``de``. If you specify a code including a country
or region like ``de-AT``, exact matches will be used preferably, but if only
a ``de`` or ``de-AT`` translation exists, this might be returned as well.
"""
if self.data is None:
return ""
if isinstance(self.data, dict):
firstpart = lng.split('-')[0]
similar = [l for l in self.data.keys() if l.startswith(firstpart + "-")]
similar = [l for l in self.data.keys() if l.startswith(firstpart + "-") or firstpart == l]
if lng in self.data and self.data[lng]:
return self.data[lng]
elif firstpart in self.data:
@@ -181,6 +197,14 @@ class I18nFormField(forms.MultiValueField):
The form field that is used by I18nCharField and I18nTextField. It makes use
of Django's MultiValueField mechanism to create one sub-field per available
language.
It contains special treatment to make sure that a field marked as "required" is validated
as "filled out correctly" if *at least one* translation is filled it. It is never required
to fill in all of them. This has the drawback that the HTML property ``required`` is set on
none of the fields as this would lead to irritating behaviour.
:param langcodes: An iterable of locale codes that the widget should render a field for. If
omitted, fields will be rendered for all languages supported by pretix.
"""
def compress(self, data_list):
@@ -299,32 +323,6 @@ class I18nJSONEncoder(DjangoJSONEncoder):
return super().default(obj)
class I18nFormSet(BaseModelFormSet):
"""
This is equivalent to a normal BaseModelFormset, but cares for the special needs
of I18nForms (see there for more information).
"""
def __init__(self, *args, **kwargs):
self.event = kwargs.pop('event', None)
super().__init__(*args, **kwargs)
def _construct_form(self, i, **kwargs):
kwargs['event'] = self.event
return super()._construct_form(i, **kwargs)
@property
def empty_form(self):
form = self.form(
auto_id=self.auto_id,
prefix=self.add_prefix('__prefix__'),
empty_permitted=True,
event=self.event
)
self.add_fields(form, None)
return form
class LazyDate:
def __init__(self, value):
self.value = value

View File

@@ -1,62 +1,9 @@
import os
from functools import partial
from itertools import product
from django import forms
from django.core.exceptions import ValidationError
from django.db import transaction
from django.forms import (
BaseInlineFormSet, BaseModelFormSet, ModelForm, modelformset_factory,
)
from django.forms.widgets import flatatt
from django.utils.encoding import force_text
from django.utils.html import format_html
from django.utils.safestring import mark_safe
from django.utils.translation import ugettext_lazy as _
from pretix.base.forms import I18nModelForm
from pretix.base.models import Item, ItemVariation
class I18nInlineFormSet(BaseInlineFormSet):
"""
This is equivalent to a normal BaseInlineFormset, but cares for the special needs
of I18nForms (see there for more information).
"""
def __init__(self, *args, **kwargs):
self.event = kwargs.pop('event', None)
super().__init__(*args, **kwargs)
def _construct_form(self, i, **kwargs):
kwargs['event'] = self.event
return super()._construct_form(i, **kwargs)
class I18nFormSet(BaseModelFormSet):
"""
This is equivalent to a normal BaseModelFormset, but cares for the special needs
of I18nForms (see there for more information).
"""
def __init__(self, *args, **kwargs):
self.event = kwargs.pop('event', None)
super().__init__(*args, **kwargs)
def _construct_form(self, i, **kwargs):
kwargs['event'] = self.event
return super()._construct_form(i, **kwargs)
@property
def empty_form(self):
form = self.form(
auto_id=self.auto_id,
prefix=self.add_prefix('__prefix__'),
empty_permitted=True,
event=self.event
)
self.add_fields(form, None)
return form
from ...base.forms import I18nModelForm
class TolerantFormsetModelForm(I18nModelForm):

View File

@@ -12,7 +12,7 @@ from django.views.generic.base import TemplateView
from django.views.generic.detail import SingleObjectMixin
from django.views.generic.edit import DeleteView
from pretix.base.i18n import I18nFormSet
from pretix.base.forms import I18nFormSet
from pretix.base.models import (
Item, ItemCategory, ItemVariation, Question, QuestionOption, Quota,
)