mirror of
https://github.com/pretix/pretix.git
synced 2026-05-06 15:24:02 +00:00
Data structures and form elements for internationalized data
This commit is contained in:
141
src/pretix/base/i18n.py
Normal file
141
src/pretix/base/i18n.py
Normal file
@@ -0,0 +1,141 @@
|
||||
import copy
|
||||
import json
|
||||
from django.conf import settings
|
||||
from django.db.models import TextField, SubfieldBase
|
||||
from django import forms
|
||||
from django.utils import translation
|
||||
|
||||
|
||||
class LazyI18String:
|
||||
def __init__(self, data):
|
||||
self.data = data
|
||||
if isinstance(self.data, str) and self.data is not None:
|
||||
try:
|
||||
j = json.loads(self.data)
|
||||
except ValueError:
|
||||
pass
|
||||
else:
|
||||
self.data = j
|
||||
|
||||
def __str__(self):
|
||||
if self.data is None:
|
||||
return ""
|
||||
if isinstance(self.data, dict):
|
||||
lng = translation.get_language()
|
||||
if lng in self.data and self.data[lng]:
|
||||
return self.data[lng]
|
||||
elif settings.LANGUAGE_CODE in self.data and self.data[settings.LANGUAGE_CODE]:
|
||||
return self.data[settings.LANGUAGE_CODE]
|
||||
elif len(self.data):
|
||||
return self.data.items()[0][1]
|
||||
else:
|
||||
return ""
|
||||
else:
|
||||
return str(self.data)
|
||||
|
||||
def __repr__(self):
|
||||
return '<LazyI18nString: %s>' % repr(self.data)
|
||||
|
||||
def __lt__(self, other):
|
||||
return str(self) < str(other)
|
||||
|
||||
|
||||
class I18nWidget(forms.MultiWidget):
|
||||
widget = forms.TextInput
|
||||
|
||||
def langcodes(self):
|
||||
return [l[0] for l in settings.LANGUAGES]
|
||||
|
||||
def __init__(self, attrs=None):
|
||||
widgets = []
|
||||
for lng in self.langcodes():
|
||||
a = copy.copy(attrs) or {}
|
||||
a['data-lang'] = lng
|
||||
widgets.append(self.widget(attrs=a))
|
||||
super().__init__(widgets, attrs)
|
||||
|
||||
def decompress(self, value):
|
||||
data = []
|
||||
for lng in self.langcodes():
|
||||
data.append(
|
||||
value.data[lng]
|
||||
if value is not None and value.data is not None and lng in value.data
|
||||
else None
|
||||
)
|
||||
return data
|
||||
|
||||
|
||||
class I18nTextInput(I18nWidget):
|
||||
widget = forms.TextInput
|
||||
|
||||
|
||||
class I18nTextarea(I18nWidget):
|
||||
widget = forms.Textarea
|
||||
|
||||
|
||||
class I18nFormField(forms.MultiValueField):
|
||||
|
||||
def compress(self, data_list):
|
||||
langcodes = self.langcodes()
|
||||
data = {}
|
||||
for i, value in enumerate(data_list):
|
||||
data[langcodes[i]] = value
|
||||
return LazyI18String(data)
|
||||
|
||||
def langcodes(self):
|
||||
return [l[0] for l in settings.LANGUAGES]
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
fields = []
|
||||
defaults = {
|
||||
'widget': self.widget,
|
||||
'max_length': kwargs.pop('max_length', None),
|
||||
}
|
||||
kwargs['required'] = False
|
||||
defaults.update(**kwargs)
|
||||
for lngcode in self.langcodes():
|
||||
defaults['label'] = '%s (%s)' % (defaults.get('label'), lngcode)
|
||||
fields.append(forms.CharField(**defaults))
|
||||
super().__init__(
|
||||
fields=fields, require_all_fields=False, *args, **kwargs
|
||||
)
|
||||
|
||||
|
||||
class I18nFieldMixin:
|
||||
# TODO: Formfield
|
||||
# TODO: Correct null/blank validating
|
||||
form_class = I18nFormField
|
||||
widget = I18nTextInput
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.event = kwargs.pop('event', None)
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def to_python(self, value):
|
||||
if isinstance(value, LazyI18String):
|
||||
return value
|
||||
return LazyI18String(value)
|
||||
|
||||
def get_prep_value(self, value):
|
||||
if isinstance(value, LazyI18String):
|
||||
value = value.data
|
||||
if isinstance(value, dict):
|
||||
return json.dumps(value, sort_keys=True)
|
||||
return value
|
||||
|
||||
def get_prep_lookup(self, lookup_type, value):
|
||||
raise TypeError('Lookups on i18n string currently not supported.')
|
||||
|
||||
def formfield(self, **kwargs):
|
||||
defaults = {'form_class': self.form_class, 'widget': self.widget}
|
||||
defaults.update(kwargs)
|
||||
return super().formfield(**defaults)
|
||||
|
||||
|
||||
class I18nCharField(I18nFieldMixin, TextField, metaclass=SubfieldBase):
|
||||
# TODO: Check max length
|
||||
widget = I18nTextInput
|
||||
|
||||
|
||||
class I18nTextField(I18nFieldMixin, TextField, metaclass=SubfieldBase):
|
||||
widget = I18nTextarea
|
||||
50
src/pretix/base/migrations/0024_auto_20150401_1239.py
Normal file
50
src/pretix/base/migrations/0024_auto_20150401_1239.py
Normal file
@@ -0,0 +1,50 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import models, migrations
|
||||
import pretix.base.i18n
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('pretixbase', '0023_auto_20150401_0954'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='event',
|
||||
name='name',
|
||||
field=pretix.base.i18n.I18nCharField(max_length=200, verbose_name='Name'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='item',
|
||||
name='long_description',
|
||||
field=pretix.base.i18n.I18nTextField(null=True, blank=True, verbose_name='Long description'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='item',
|
||||
name='name',
|
||||
field=pretix.base.i18n.I18nCharField(max_length=255, verbose_name='Item name'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='item',
|
||||
name='short_description',
|
||||
field=pretix.base.i18n.I18nTextField(null=True, blank=True, verbose_name='Short description', help_text='This is shown below the product name in lists.'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='property',
|
||||
name='name',
|
||||
field=pretix.base.i18n.I18nCharField(max_length=250, verbose_name='Property name'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='propertyvalue',
|
||||
name='value',
|
||||
field=pretix.base.i18n.I18nCharField(max_length=250, verbose_name='Value'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='question',
|
||||
name='question',
|
||||
field=pretix.base.i18n.I18nTextField(verbose_name='Question'),
|
||||
),
|
||||
]
|
||||
@@ -15,6 +15,7 @@ from django.utils.timezone import now
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.template.defaultfilters import date as _date
|
||||
from django.core.validators import RegexValidator
|
||||
from pretix.base.i18n import I18nCharField, I18nTextField
|
||||
from pretix.base.settings import SettingsProxy
|
||||
import six
|
||||
from versions.models import Versionable as BaseVersionable
|
||||
@@ -379,8 +380,10 @@ class Event(Versionable):
|
||||
|
||||
organizer = VersionedForeignKey(Organizer, related_name="events",
|
||||
on_delete=models.PROTECT)
|
||||
name = models.CharField(max_length=200,
|
||||
verbose_name=_("Name"))
|
||||
name = I18nCharField(
|
||||
max_length=200,
|
||||
verbose_name=_("Name"),
|
||||
)
|
||||
slug = models.SlugField(
|
||||
max_length=50, db_index=True,
|
||||
help_text=_(
|
||||
@@ -424,7 +427,7 @@ class Event(Versionable):
|
||||
ordering = ("date_from", "name")
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
return str(self.name)
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
obj = super().save(*args, **kwargs)
|
||||
@@ -594,7 +597,7 @@ class Property(Versionable):
|
||||
Event,
|
||||
related_name="properties",
|
||||
)
|
||||
name = models.CharField(
|
||||
name = I18nCharField(
|
||||
max_length=250,
|
||||
verbose_name=_("Property name"),
|
||||
)
|
||||
@@ -604,7 +607,7 @@ class Property(Versionable):
|
||||
verbose_name_plural = _("Product properties")
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
return str(self.name)
|
||||
|
||||
def delete(self, *args, **kwargs):
|
||||
super().delete(*args, **kwargs)
|
||||
@@ -635,7 +638,7 @@ class PropertyValue(Versionable):
|
||||
on_delete=models.CASCADE,
|
||||
related_name="values"
|
||||
)
|
||||
value = models.CharField(
|
||||
value = I18nCharField(
|
||||
max_length=250,
|
||||
verbose_name=_("Value"),
|
||||
)
|
||||
@@ -704,7 +707,7 @@ class Question(Versionable):
|
||||
Event,
|
||||
related_name="questions",
|
||||
)
|
||||
question = models.TextField(
|
||||
question = I18nTextField(
|
||||
verbose_name=_("Question"),
|
||||
)
|
||||
type = models.CharField(
|
||||
@@ -722,7 +725,7 @@ class Question(Versionable):
|
||||
verbose_name_plural = _("Questions")
|
||||
|
||||
def __str__(self):
|
||||
return self.question
|
||||
return str(self.question)
|
||||
|
||||
def delete(self, *args, **kwargs):
|
||||
super().delete(*args, **kwargs)
|
||||
@@ -777,20 +780,20 @@ class Item(Versionable):
|
||||
blank=True, null=True,
|
||||
verbose_name=_("Category"),
|
||||
)
|
||||
name = models.CharField(
|
||||
name = I18nCharField(
|
||||
max_length=255,
|
||||
verbose_name=_("Item name")
|
||||
verbose_name=_("Item name"),
|
||||
)
|
||||
active = models.BooleanField(
|
||||
default=True,
|
||||
verbose_name=_("Active"),
|
||||
)
|
||||
short_description = models.TextField(
|
||||
short_description = I18nTextField(
|
||||
verbose_name=_("Short description"),
|
||||
help_text=_("This is shown below the product name in lists."),
|
||||
null=True, blank=True,
|
||||
)
|
||||
long_description = models.TextField(
|
||||
long_description = I18nTextField(
|
||||
verbose_name=_("Long description"),
|
||||
null=True, blank=True,
|
||||
)
|
||||
@@ -839,7 +842,7 @@ class Item(Versionable):
|
||||
verbose_name_plural = _("Products")
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
return str(self.name)
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
@@ -80,7 +80,7 @@ class VariationDict(dict):
|
||||
]
|
||||
|
||||
def __str__(self):
|
||||
return " – ".join([v.value for v in self.ordered_values()])
|
||||
return " – ".join([str(v.value) for v in self.ordered_values()])
|
||||
|
||||
def copy(self) -> "VariationDict":
|
||||
"""
|
||||
|
||||
@@ -43,7 +43,7 @@
|
||||
{% for line in items.positions %}
|
||||
<div class="row-fluid product-row">
|
||||
<div class="col-md-4 col-xs-6">
|
||||
<strong>{{ line.item }}</strong>
|
||||
<strong>{{ line.item.name }}</strong>
|
||||
{% if line.variation %}
|
||||
– {{ line.variation }}
|
||||
{% endif %}
|
||||
|
||||
2
src/pretix/local_settings.py
Normal file
2
src/pretix/local_settings.py
Normal file
@@ -0,0 +1,2 @@
|
||||
EMAIL_PORT = 1025
|
||||
EMAIL_HOST = '127.0.0.1'
|
||||
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 11 KiB |
@@ -14,7 +14,7 @@
|
||||
<h4 class="panel-title">
|
||||
<a data-toggle="collapse" href="#cp{{ form.pos.identity }}"
|
||||
data-parent="#questions_accordion">
|
||||
<strong>{{ form.pos.item }}</strong>
|
||||
<strong>{{ form.pos.item.name }}</strong>
|
||||
{% if form.pos.variation %}
|
||||
– {{ form.pos.variation }}
|
||||
{% endif %}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
{% for line in cart.positions %}
|
||||
<div class="row-fluid cart-row">
|
||||
<div class="col-md-4 col-xs-6">
|
||||
<strong>{{ line.item }}</strong>
|
||||
<strong>{{ line.item.name }}</strong>
|
||||
{% if line.variation %}
|
||||
– {{ line.variation }}
|
||||
{% endif %}
|
||||
|
||||
@@ -77,7 +77,7 @@
|
||||
{% if item.short_description %}<p>{{ item.short_description }}</p>{% endif %}
|
||||
</div>
|
||||
<div class="col-md-2 col-xs-6 price">
|
||||
{{ event.currency }} {{ item.price|floatformat:2 }}
|
||||
{{ event.currency }} {{ item.price }}
|
||||
{% if item.tax_rate %}
|
||||
<br /><small>{% blocktrans trimmed with rate=item.tax_rate %}
|
||||
incl. {{ rate }}% taxes
|
||||
|
||||
@@ -112,8 +112,8 @@ LOCALE_PATHS = (
|
||||
|
||||
from django.utils.translation import ugettext_lazy as _ # NOQA
|
||||
LANGUAGES = (
|
||||
('de', _('German')),
|
||||
('en', _('English')),
|
||||
('de', _('German')),
|
||||
)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user