Data structures and form elements for internationalized data

This commit is contained in:
Raphael Michel
2015-04-03 16:53:03 +02:00
parent f4d3679841
commit 03df35bccd
14 changed files with 345 additions and 68 deletions

141
src/pretix/base/i18n.py Normal file
View 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

View 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'),
),
]

View File

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

View File

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

View File

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

View 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

View File

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

View File

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

View File

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

View File

@@ -112,8 +112,8 @@ LOCALE_PATHS = (
from django.utils.translation import ugettext_lazy as _ # NOQA
LANGUAGES = (
('de', _('German')),
('en', _('English')),
('de', _('German')),
)