13 changed files with 773 additions and 0 deletions
			
			
		| @ -0,0 +1,9 @@ | ||||
| # -*- coding: utf-8 -*- | ||||
| from __future__ import unicode_literals | ||||
| # Erik Stein <code@classlibrary.net>, 2015 | ||||
| 
 | ||||
| from django.utils.translation import ugettext_lazy as _ | ||||
| 
 | ||||
| 
 | ||||
| SLUG_HELP = _("Kurzfassung des Namens für die Adresszeile im Browser. Vorzugsweise englisch, keine Umlaute, nur Bindestrich als Sonderzeichen.") | ||||
| 
 | ||||
| @ -0,0 +1,224 @@ | ||||
| # -*- coding: utf-8 -*- | ||||
| from __future__ import unicode_literals | ||||
| # Erik Stein <code@classlibrary.net>, 2016 | ||||
| """ | ||||
| Extends django.utils.dateformat | ||||
| """ | ||||
| 
 | ||||
| 
 | ||||
| import datetime | ||||
| import re | ||||
| from django.conf import settings | ||||
| from django.utils.dateformat import DateFormat, re_escaped | ||||
| from django.utils.formats import get_format | ||||
| from django.utils.encoding import force_text | ||||
| from django.utils.translation import get_language, ugettext_lazy as _ | ||||
| 
 | ||||
| 
 | ||||
| # Adding "q" | ||||
| re_formatchars = re.compile(r'(?<!\\)([aAbBcdDeEfFgGhHiIjlLmMnNoOPqrsStTUuwWyYzZ])') | ||||
| 
 | ||||
| 
 | ||||
| class ExtendedFormat(DateFormat): | ||||
|     def q(self): | ||||
|         """ | ||||
|         Time, in 24-hour hours and minutes, with minutes left off if they're | ||||
|         zero. | ||||
|         Examples: '1', '1:30', '13:05', '14' | ||||
|         Proprietary extension. | ||||
|         """ | ||||
|         if self.data.minute == 0: | ||||
|             return self.G() | ||||
|         return '%s:%s' % (self.G(), self.i()) | ||||
| 
 | ||||
|     def format(self, formatstr): | ||||
|         pieces = [] | ||||
|         for i, piece in enumerate(re_formatchars.split(force_text(formatstr))): | ||||
|             if i % 2: | ||||
|                 pieces.append(force_text(getattr(self, piece)())) | ||||
|             elif piece: | ||||
|                 pieces.append(re_escaped.sub(r'\1', piece)) | ||||
|         return ''.join(pieces) | ||||
| 
 | ||||
| 
 | ||||
| def format(value, format): | ||||
|     # Copy of django.utils.dateformat.format, using our extended formatter | ||||
|     df = ExtendedFormat(value) | ||||
|     return df.format(format) | ||||
| 
 | ||||
| 
 | ||||
| def time_format(value, format=None, use_l10n=None): | ||||
|     # Copy of django.utils.dateformat.time_format, using our extended formatter | ||||
|     tf = ExtendedFormat(value) | ||||
|     return tf.format(get_format(format or 'DATE_FORMAT', use_l10n=use_l10n)) | ||||
| 
 | ||||
| 
 | ||||
| def date_format(value, format=None, use_l10n=None): | ||||
|     df = ExtendedFormat(value) | ||||
|     return df.format(get_format(format or 'DATE_FORMAT', use_l10n=use_l10n)) | ||||
| 
 | ||||
| 
 | ||||
| def _normalize_variant(variant): | ||||
|     if variant.lower() not in ('short', 'long', ''): | ||||
|         variant = 'short' | ||||
|     if variant and not variant.endswith("_"): | ||||
|         variant = variant + "_" | ||||
|     return variant.upper() | ||||
| 
 | ||||
| 
 | ||||
| def format_date_range(from_date, to_date, variant='short'): | ||||
|     """ | ||||
|     >>> import datetime | ||||
|     >>> format_date_range(datetime.date(2009,1,15), datetime.date(2009,1,20)) | ||||
|     '15. - 20.01.2009.' | ||||
|     >>> format_date_range(datetime.date(2009,1,15), datetime.date(2009,2,20)) | ||||
|     '15.01. - 20.02.2009.' | ||||
|     >>> format_date_range(datetime.date(2009,1,15), datetime.date(2010,2,20)) | ||||
|     '15.01.2009. - 20.02.2010.' | ||||
|     >>> format_date_range(datetime.date(2009,1,15), datetime.date(2010,1,20)) | ||||
|     '15.01.2009. - 20.01.2010.' | ||||
|     """ | ||||
|     if not (from_date or to_date): | ||||
|         return "" | ||||
| 
 | ||||
|     variant = _normalize_variant(variant) | ||||
| 
 | ||||
|     # Only deal with dates, ignoring time | ||||
|     def datetime_to_date(dt): | ||||
|         try: | ||||
|             return dt.date() | ||||
|         except AttributeError: | ||||
|             return dt | ||||
|     from_date = datetime_to_date(from_date) | ||||
|     to_date = datetime_to_date(to_date) | ||||
| 
 | ||||
|     from_format = to_format = get_format(variant + 'DATE_FORMAT') | ||||
| 
 | ||||
|     if from_date == to_date or not to_date: | ||||
|         return date_format(from_date, get_format(from_format)) | ||||
|     else: | ||||
|         if (from_date.year == to_date.year): | ||||
|             from_format = get_format(variant + 'DAYMONTH_FORMAT') or 'd/m/' | ||||
|             if (from_date.month == to_date.month): | ||||
|                 from_format = get_format(variant + 'DAYONLY_FORMAT') or 'd' | ||||
| 
 | ||||
|         f = t = "" | ||||
|         if from_date: | ||||
|             f = date_format(from_date, get_format(from_format)) | ||||
|         if to_date: | ||||
|             t = date_format(to_date, get_format(to_format)) | ||||
| 
 | ||||
|         separator = get_format('DATE_RANGE_SEPARATOR') or " - " | ||||
|         return separator.join((f, t)) | ||||
| 
 | ||||
| 
 | ||||
| def format_time_range(from_time, to_time, variant='short'): | ||||
|     """ | ||||
|     Knows how to deal with left out from_time/to_time values. | ||||
|     """ | ||||
|     if not (from_time or to_time): | ||||
|         return "" | ||||
| 
 | ||||
|     variant = _normalize_variant(variant) | ||||
| 
 | ||||
|     from_format = to_format = "q"  # get_format(variant + 'TIME_FORMAT') | ||||
| 
 | ||||
|     if from_time == to_time or not to_time: | ||||
|         return time_format(from_time, get_format(from_format)) | ||||
|     else: | ||||
|         f = t = "" | ||||
|         if from_time: | ||||
|             f = time_format(from_time, get_format(from_format)) | ||||
|         if to_time: | ||||
|             t = time_format(to_time, get_format(to_format)) | ||||
| 
 | ||||
|         separator = get_format('DATE_RANGE_SEPARATOR') or "–" | ||||
|         return separator.join((f, t)) | ||||
| 
 | ||||
| 
 | ||||
| def format_timespan_range(timespan_object, force_wholeday=False, variant='short'): | ||||
|     """ | ||||
|     For Timespan-objects, i.e. object with start_date, end_date, start_time and end_time properties. | ||||
| 
 | ||||
|     Multiday or force_wholeday: | ||||
|     "10.07.2016-11.07.2016" | ||||
| 
 | ||||
|     Single days: | ||||
|     "10.07.2016 11 Uhr" | ||||
|     "10.07.2016 11-14 Uhr" | ||||
| 
 | ||||
|     >>> import datetime | ||||
|     >>> sd, ed = datetime.date(2009,1,15), datetime.date(2009,1,20) | ||||
|     >>> st, et = datetime.date(2009,1,15), datetime.date(2009,1,20) | ||||
|     >>> class TestObject(object): | ||||
|     >>>     start_date = None | ||||
|     >>>     end_date = None | ||||
|     >>>     start_time = None | ||||
|     >>>     end_time = None | ||||
|     >>> obj = TestObject() | ||||
|     >>> obj.start_date = obj.end_date = sd | ||||
|     >>> format_timespan_range(obj) | ||||
|     '15.01.–20.01.2009' | ||||
| 
 | ||||
|     """ | ||||
|     variant = _normalize_variant(variant) | ||||
| 
 | ||||
|     rv = format_date_range(timespan_object.start_date, timespan_object.end_date, variant) | ||||
| 
 | ||||
|     if (timespan_object.is_multiday() or | ||||
|        not timespan_object.start_time or | ||||
|        force_wholeday): | ||||
|         # Don't show timespan | ||||
|         return rv | ||||
|     else: | ||||
|         rv = _("%(daterange)s %(timespan)s Uhr") % { | ||||
|             'daterange': rv, | ||||
|             'timespan': format_time_range(timespan_object.start_time, timespan_object.end_time, variant) | ||||
|         } | ||||
|     return rv | ||||
| 
 | ||||
| 
 | ||||
| def format_partial_date(year=None, month=None, day=None, variant='short'): | ||||
|     """ | ||||
|     >>> format_partial_date(2008) | ||||
|     2008 | ||||
|     >>> format_partial_date(2008, 3) | ||||
|     2008 | ||||
|     >>> format_partial_date(2008) | ||||
|     2008 | ||||
|     >>> format_partial_date(2008) | ||||
|     2008 | ||||
|     >>> format_partial_date(2008) | ||||
|     2008 | ||||
|     >>> format_partial_date(2008) | ||||
|     2008 | ||||
|     """ | ||||
|     if year and month and day: | ||||
|         format_name = 'DATE_FORMAT' | ||||
|     elif year and month: | ||||
|         format_name = 'YEARMONTH_FORMAT' | ||||
|     elif month and day: | ||||
|         format_name = 'DAYMONTH_FORMAT' | ||||
|     elif year: | ||||
|         format_name = 'YEAR_FORMAT' | ||||
|     elif month: | ||||
|         format_name = 'MONTH_FORMAT' | ||||
|     elif day: | ||||
|         format_name = 'DAYONLY_FORMAT' | ||||
| 
 | ||||
|     name = _normalize_variant(variant) + format_name | ||||
|     # TODO Django bug or what? Sometimes get_language returns None, therefore force a language here | ||||
|     partial_date_format = get_format(name, lang=get_language() or settings.LANGUAGE_CODE) | ||||
|     return date_format(datetime.date(year or 2000, month or 1, day or 1), partial_date_format) | ||||
| 
 | ||||
| 
 | ||||
| # TODO Add format_partial_date_range function | ||||
| 
 | ||||
| 
 | ||||
| def _test(): | ||||
|     import doctest | ||||
|     doctest.testmod() | ||||
| 
 | ||||
| 
 | ||||
| if __name__ == "__main__": | ||||
|     _test() | ||||
| @ -0,0 +1,79 @@ | ||||
| # -*- coding: utf-8 -*- | ||||
| from __future__ import unicode_literals | ||||
| # Erik Stein <code@classlibrary.net>, 2008-2015 | ||||
| 
 | ||||
| import re | ||||
| from django.db.models import fields | ||||
| from django.template.defaultfilters import slugify | ||||
| from django.utils.translation import ugettext_lazy as _ | ||||
| 
 | ||||
| from . import SLUG_HELP | ||||
| 
 | ||||
| 
 | ||||
| DEFAULT_SLUG = _("item") | ||||
| 
 | ||||
| 
 | ||||
| def unique_slug(instance, slug_field, slug_value, max_length=50, queryset=None): | ||||
|     """ | ||||
|     TODO Doesn't work with model inheritance, where the slug field is part of the parent class. | ||||
|     """ | ||||
|     if not slug_value: | ||||
|         raise ValueError("Cannot uniquify empty slug") | ||||
|     orig_slug = slug = slugify(slug_value) | ||||
|     index = 0 | ||||
|     if not queryset: | ||||
|         queryset = instance.__class__._default_manager.get_queryset() | ||||
| 
 | ||||
|     def get_similar_slugs(slug): | ||||
|         return queryset.exclude(pk=instance.pk) \ | ||||
|             .filter(**{"%s__istartswith" % slug_field: slug}).values_list(slug_field, flat=True) | ||||
| 
 | ||||
|     similar_slugs = get_similar_slugs(slug) | ||||
|     while slug in similar_slugs or len(slug) > max_length: | ||||
|         index += 1 | ||||
|         slug = "%s-%i" % (orig_slug, index) | ||||
|         if len(slug) > max_length: | ||||
|             orig_slug = orig_slug[:-(len(slug) - max_length)] | ||||
|             slug = "%s-%i" % (orig_slug, index) | ||||
|             similar_slugs = get_similar_slugs(orig_slug) | ||||
|     return slug | ||||
| 
 | ||||
| 
 | ||||
| def unique_slug2(instance, slug_source, slug_field): | ||||
|     slug = slugify(slug_source) | ||||
|     all_slugs = [sl.values()[0] for sl in instance.__class__._default_manager.values(slug_field)] | ||||
|     if slug in all_slugs: | ||||
|         counter_finder = re.compile(r'-\d+$') | ||||
|         counter = 2 | ||||
|         slug = "%s-%i" % (slug, counter) | ||||
|         while slug in all_slugs: | ||||
|             slug = re.sub(counter_finder, "-%i" % counter, slug) | ||||
|             counter += 1 | ||||
|     return slug | ||||
| 
 | ||||
| 
 | ||||
| class AutoSlugField(fields.SlugField): | ||||
|     # AutoSlugField based on http://www.djangosnippets.org/snippets/728/ | ||||
| 
 | ||||
|     def __init__(self, *args, **kwargs): | ||||
|         kwargs.setdefault('max_length', 50) | ||||
|         kwargs.setdefault('help_text', SLUG_HELP) | ||||
|         if 'populate_from' in kwargs: | ||||
|             self.populate_from = kwargs.pop('populate_from') | ||||
|         self.unique_slug = kwargs.pop('unique_slug', False) | ||||
|         super(AutoSlugField, self).__init__(*args, **kwargs) | ||||
| 
 | ||||
|     def pre_save(self, model_instance, add): | ||||
|         value = getattr(model_instance, self.attname) | ||||
|         if not value: | ||||
|             if hasattr(self, 'populate_from'): | ||||
|                 # Follow dotted path (e.g. "occupation.corporation.name") | ||||
|                 value = reduce(lambda obj, attr: getattr(obj, attr), self.populate_from.split("."), model_instance) | ||||
|                 if callable(value): | ||||
|                     value = value() | ||||
|             if not value: | ||||
|                 value = DEFAULT_SLUG | ||||
|         if self.unique_slug: | ||||
|             return unique_slug(model_instance, self.name, value, max_length=self.max_length) | ||||
|         else: | ||||
|             return super(AutoSlugField, self).pre_save(model_instance, add) | ||||
| @ -0,0 +1,51 @@ | ||||
| # -*- coding: utf-8 -*- | ||||
| from __future__ import unicode_literals | ||||
| # Erik Stein <code@classlibrary.net>, 2016 | ||||
| 
 | ||||
| from django import forms | ||||
| from django.contrib import admin | ||||
| from django.utils.translation import ugettext_lazy as _ | ||||
| 
 | ||||
| 
 | ||||
| # from http://stackoverflow.com/questions/877723/inline-form-validation-in-django#877920 | ||||
| 
 | ||||
| class MandatoryInlineFormSet(forms.models.BaseInlineFormSet): | ||||
|     """ | ||||
|     Make sure at least one inline form is valid. | ||||
|     """ | ||||
|     mandatory_error_message = _("Bitte mindestens %(min_num)s %(name)s angeben.") | ||||
| 
 | ||||
|     def is_valid(self): | ||||
|         return super(MandatoryInlineFormSet, self).is_valid() and \ | ||||
|                      not any([bool(e) for e in self.errors]) | ||||
| 
 | ||||
|     def clean(self): | ||||
|         # get forms that actually have valid data | ||||
|         count = 0 | ||||
|         for form in self.forms: | ||||
|             try: | ||||
|                 if form.cleaned_data and not form.cleaned_data.get('DELETE', False): | ||||
|                     count += 1 | ||||
|             except AttributeError: | ||||
|                 # annoyingly, if a subform is invalid Django explicity raises | ||||
|                 # an AttributeError for cleaned_data | ||||
|                 pass | ||||
|         if count < self.min_num: | ||||
|             if self.min_num > 1: | ||||
|                 name = self.model._meta.verbose_name_plural | ||||
|             else: | ||||
|                 name = self.model._meta.verbose_name | ||||
|             raise forms.ValidationError( | ||||
|                 self.mandatory_error_message % { | ||||
|                     'min_num': self.min_num, | ||||
|                     'name': name, | ||||
|                 } | ||||
|             ) | ||||
| 
 | ||||
| 
 | ||||
| class MandatoryTabularInline(admin.TabularInline): | ||||
|     formset = MandatoryInlineFormSet | ||||
| 
 | ||||
| 
 | ||||
| class MandatoryStackedInline(admin.StackedInline): | ||||
|     formset = MandatoryInlineFormSet | ||||
| @ -0,0 +1,67 @@ | ||||
| # -*- coding: utf-8 -*- | ||||
| from __future__ import unicode_literals | ||||
| # Erik Stein <code@classlibrary.net>, 2016 | ||||
| #            improved from https://djangosnippets.org/snippets/1405/ | ||||
| 
 | ||||
| 
 | ||||
| from django import template | ||||
| from ..dateformat import date_format, get_format | ||||
| 
 | ||||
| 
 | ||||
| """ | ||||
| # TODO Describe custom formats | ||||
| """ | ||||
| 
 | ||||
| 
 | ||||
| register = template.Library() | ||||
| 
 | ||||
| 
 | ||||
| @register.simple_tag | ||||
| def format_date_range(from_date, to_date, variant='short'): | ||||
|     """ | ||||
|     >>> import datetime | ||||
|     >>> format_date_range(datetime.date(2009,1,15), datetime.date(2009,1,20)) | ||||
|     '15. - 20.01.2009.' | ||||
|     >>> format_date_range(datetime.date(2009,1,15), datetime.date(2009,2,20)) | ||||
|     '15.01. - 20.02.2009.' | ||||
|     >>> format_date_range(datetime.date(2009,1,15), datetime.date(2010,2,20)) | ||||
|     '15.01.2009. - 20.02.2010.' | ||||
|     >>> format_date_range(datetime.date(2009,1,15), datetime.date(2010,1,20)) | ||||
|     '15.01.2009. - 20.01.2010.' | ||||
| 
 | ||||
|     Use in django templates: | ||||
| 
 | ||||
|     {% load date_range %} | ||||
|     {% format_date_range exhibition.start_on exhibition.end_on %} | ||||
|     """ | ||||
|     if variant.lower() not in ('short', 'long', ''): | ||||
|         variant = 'short' | ||||
|     if variant.endswith("_"): | ||||
|         variant = variant + "_" | ||||
| 
 | ||||
|     from_format = to_format = get_format(variant.upper() + 'DATE_FORMAT') | ||||
| 
 | ||||
|     if from_date == to_date: | ||||
|         return date_format(to_date, get_format(to_format)) | ||||
| 
 | ||||
|     if (from_date.year == to_date.year): | ||||
|         from_format = get_format(variant.upper() + 'DAYMONTH_FORMAT') or 'd/m/' | ||||
|         if (from_date.month == to_date.month): | ||||
|             from_format = get_format(variant.upper() + 'DAYONLY_FORMAT') or 'd' | ||||
|     separator = get_format('DATE_RANGE_SEPARATOR') or "–" | ||||
|     # import ipdb; ipdb.set_trace() | ||||
| 
 | ||||
|     print from_format, to_format | ||||
| 
 | ||||
|     f = date_format(from_date, get_format(from_format)) | ||||
|     t = date_format(to_date, get_format(to_format)) | ||||
| 
 | ||||
|     return variant.upper() + " " + separator.join((f, t)) | ||||
| 
 | ||||
| 
 | ||||
| def _test(): | ||||
|     import doctest | ||||
|     doctest.testmod() | ||||
| 
 | ||||
| if __name__ == "__main__": | ||||
|     _test() | ||||
| @ -0,0 +1,33 @@ | ||||
| # -*- coding: utf-8 -*- | ||||
| from __future__ import unicode_literals | ||||
| # Erik Stein <code@classlibrary.net>, 2015 | ||||
| 
 | ||||
| from django import template | ||||
| from django.template.defaultfilters import stringfilter | ||||
| from django.utils.html import conditional_escape | ||||
| from django.utils.safestring import mark_safe | ||||
| from .. import markdown_utils | ||||
| 
 | ||||
| 
 | ||||
| register = template.Library() | ||||
| 
 | ||||
| 
 | ||||
| @register.filter(needs_autoescape=True) | ||||
| @stringfilter | ||||
| def inline_markdown(text, autoescape=None, **kwargs): | ||||
|     """ Doesn't wrap the markup in a HTML paragraph. """ | ||||
|     if autoescape: | ||||
|         esc = conditional_escape | ||||
|     else: | ||||
|         esc = lambda x: x | ||||
|     return mark_safe(markdown_utils.inline_markdown_processor.convert(esc(text), **kwargs)) | ||||
| 
 | ||||
| 
 | ||||
| @register.filter(needs_autoescape=True) | ||||
| @stringfilter | ||||
| def markdown(text, autoescape=None, **kwargs): | ||||
|     if autoescape: | ||||
|         esc = conditional_escape | ||||
|     else: | ||||
|         esc = lambda x: x | ||||
|     return mark_safe(markdown_utils.markdown_processor.convert(esc(text), **kwargs)) | ||||
| @ -0,0 +1,26 @@ | ||||
| # -*- coding: utf-8 -*- | ||||
| from __future__ import unicode_literals | ||||
| # Erik Stein <code@classlibrary.net>, 2015 | ||||
| 
 | ||||
| from django import template | ||||
| from django.template.defaultfilters import stringfilter | ||||
| from django.utils.html import conditional_escape | ||||
| from django.utils.safestring import mark_safe | ||||
| 
 | ||||
| 
 | ||||
| register = template.Library() | ||||
| 
 | ||||
| 
 | ||||
| @register.filter() | ||||
| def conditional_punctuation(value, punctuation=",", space=" "): | ||||
|     """ | ||||
|     Appends punctuation if the (stripped) value is not empty | ||||
|     and the value does not already end in a punctuation mark (.,:;!?). | ||||
|     """ | ||||
|     value = value.strip() | ||||
|     if value: | ||||
|         if value[-1] not in ".,:;!?": | ||||
|             value += conditional_escape(punctuation) | ||||
|         value += conditional_escape(space)  # Append previously stripped space | ||||
|     return value | ||||
| conditional_punctuation.is_safe = True | ||||
| @ -0,0 +1,24 @@ | ||||
| # -*- coding: utf-8 -*- | ||||
| from __future__ import unicode_literals | ||||
| # Erik Stein <code@classlibrary.net>, 2014-2015 | ||||
| 
 | ||||
| from django import template | ||||
| from django.db import models | ||||
| 
 | ||||
| from ..translation import get_translation, get_translated_field | ||||
| 
 | ||||
| 
 | ||||
| register = template.Library() | ||||
| 
 | ||||
| 
 | ||||
| @register.filter | ||||
| def translation(obj): | ||||
|     return get_translation(obj) | ||||
| 
 | ||||
| 
 | ||||
| @register.filter | ||||
| def translate(obj, field_name): | ||||
|     return get_translated_field(obj, field_name) | ||||
| 
 | ||||
| # Alias | ||||
| translated_field = translate | ||||
| @ -0,0 +1,49 @@ | ||||
| # -*- coding: utf-8 -*- | ||||
| from __future__ import unicode_literals | ||||
| # Erik Stein <code@classlibrary.net>, 2015 | ||||
| 
 | ||||
| from django.utils.text import slugify | ||||
| from django.utils import six | ||||
| from django.utils.encoding import force_text | ||||
| from django.utils.functional import allow_lazy | ||||
| from django.utils.safestring import SafeText | ||||
| 
 | ||||
| # import unicodedata | ||||
| import translitcodec | ||||
| import codecs | ||||
| 
 | ||||
| 
 | ||||
| def downgrade(value): | ||||
|     """ | ||||
|     Downgrade unicode to ascii, transliterating accented characters. | ||||
|     """ | ||||
|     value = force_text(value) | ||||
|     return codecs.encode(value, 'translit/long') | ||||
| downgrade = allow_lazy(downgrade, six.text_type, SafeText) | ||||
| 
 | ||||
| 
 | ||||
| def slugify_long(value): | ||||
|     value = force_text(value) | ||||
|     return slugify(downgrade(value)) | ||||
| slugify_long = allow_lazy(slugify_long, six.text_type, SafeText) | ||||
| 
 | ||||
| 
 | ||||
| def slugify_german(value): | ||||
|     """ | ||||
|     Transliterates Umlaute before calling django's slugify function. | ||||
|     """ | ||||
|     umlaute = { | ||||
|         'Ä': 'Ae', | ||||
|         'Ö': 'Oe', | ||||
|         'Ü': 'Ue', | ||||
|         'ä': 'ae', | ||||
|         'ö': 'oe', | ||||
|         'ü': 'ue', | ||||
|         'ß': 'ss', | ||||
|     } | ||||
| 
 | ||||
|     value = force_text(value) | ||||
|     umap = {ord(key): unicode(val) for key, val in umlaute.items()} | ||||
|     return slugify(value.translate(umap)) | ||||
| slugify_german = allow_lazy(slugify_german, six.text_type, SafeText) | ||||
| 
 | ||||
| @ -0,0 +1,15 @@ | ||||
| # -*- coding: utf-8 -*- | ||||
| from __future__ import unicode_literals | ||||
| # Erik Stein <code@classlibrary.net>, 2015 | ||||
| 
 | ||||
| from django.utils import timezone | ||||
| 
 | ||||
| 
 | ||||
| def smart_default_tz(datetime_value): | ||||
|     """ | ||||
|     Returns the give datetime with the default timezone applied. | ||||
|     """ | ||||
|     if timezone.is_naive(datetime_value): | ||||
|         datetime_value = timezone.make_aware(datetime_value, timezone=timezone.get_default_timezone()) | ||||
|     return timezone.localtime(datetime_value, timezone.get_default_timezone()) | ||||
| 
 | ||||
| @ -0,0 +1,195 @@ | ||||
| # -*- coding: utf-8 -*- | ||||
| from __future__ import unicode_literals | ||||
| # Erik Stein <code@classlibrary.net>, 2015 | ||||
| 
 | ||||
| import os | ||||
| from contextlib import contextmanager | ||||
| from django import http | ||||
| from django.conf import settings | ||||
| from django.core.exceptions import ObjectDoesNotExist, FieldDoesNotExist | ||||
| from django.core.urlresolvers import translate_url | ||||
| from django.template.loader import select_template | ||||
| from django.utils import translation | ||||
| from django.views.generic import TemplateView | ||||
| from django.utils.translation import check_for_language, LANGUAGE_SESSION_KEY | ||||
| from django.utils.http import is_safe_url | ||||
| from django.http import HttpResponseRedirect | ||||
| from django.views.i18n import LANGUAGE_QUERY_PARAMETER | ||||
| 
 | ||||
| 
 | ||||
| def lang_suffix(language_code=None): | ||||
|     """ | ||||
|     Returns the suffix appropriate for adding to field names for selecting | ||||
|     the current language. | ||||
|     """ | ||||
|     if not language_code: | ||||
|         language_code = translation.get_language() | ||||
|     if not language_code: | ||||
|         language_code = settings.LANGUAGE_CODE | ||||
|     language_code = language_code[:2] or 'de'  # FIXME Fall back to default language | ||||
|     return "_%s" % language_code | ||||
| 
 | ||||
| 
 | ||||
| class DirectTemplateView(TemplateView): | ||||
|     extra_context = None | ||||
| 
 | ||||
|     def get_context_data(self, **kwargs): | ||||
|         context = super(DirectTemplateView, self).get_context_data(**kwargs) | ||||
|         if self.extra_context is not None: | ||||
|             for key, value in self.extra_context.items(): | ||||
|                 if callable(value): | ||||
|                     context[key] = value() | ||||
|                 else: | ||||
|                     context[key] = value | ||||
|         return context | ||||
| 
 | ||||
| 
 | ||||
| class I18nDirectTemplateView(DirectTemplateView): | ||||
|     def get_template_names(self): | ||||
|         t_name, t_ext = os.path.splitext(self.template_name) | ||||
|         lang = translation.get_language() | ||||
|         template_name = select_template(( | ||||
|             "%s.%s%s" % (t_name, lang, t_ext), | ||||
|             self.template_name | ||||
|         )).name | ||||
|         return [template_name] | ||||
| 
 | ||||
| 
 | ||||
| def i18n_direct_to_template(request, *args, **kwargs): | ||||
|     return I18nDirectTemplateView(*args, **kwargs).as_view() | ||||
| 
 | ||||
| 
 | ||||
| def get_translation(obj, relation_name='translations', language_code=None): | ||||
|     language_code = language_code or translation.get_language()[:2] | ||||
|     try: | ||||
|         return getattr(obj, relation_name).get(language=language_code) | ||||
|     except ObjectDoesNotExist: | ||||
|         try: | ||||
|             return getattr(obj, relation_name).get(language=(language_code == 'en' and 'de' or 'en')) | ||||
|         except ObjectDoesNotExist: | ||||
|             return None | ||||
| 
 | ||||
| 
 | ||||
| # class FieldTranslationMixin(object): | ||||
| #     """ | ||||
| #     If the model has a field `attr` or `attr_<language_code>`, return it's | ||||
| #     value, else raise ValueError. | ||||
| #     """ | ||||
| 
 | ||||
| #     def __getattr__(self, attr): | ||||
| #         if attr in self.__dict__: | ||||
| #             return self.__dict__[attr] | ||||
| #         for field in self._meta.multilingual: | ||||
| #             code = None | ||||
| #             match = re.match(r'^%s_(?P<code>[a-z_]{2,5})$' % field, str(attr)) | ||||
| #             if match: | ||||
| #                 code = match.groups('code') | ||||
| #                 code = code[:2] # let's limit it to two letter | ||||
| #             elif attr in self._meta.multilingual: | ||||
| #                 code = self._language | ||||
| #                 field = attr | ||||
| #             if code is not None: | ||||
| #                 try: | ||||
| #                     return self._meta.translation.objects.select_related().get(model=self, language__code=code).__dict__[field] | ||||
| #                 except ObjectDoesNotExist: | ||||
| #                     if MULTILINGUAL_FALL_BACK_TO_DEFAULT and MULTILINGUAL_DEFAULT and code != MULTILINGUAL_DEFAULT: | ||||
| #                         try: | ||||
| #                             return self._meta.translation.objects.select_related().get(model=self, language__code=MULTILINGUAL_DEFAULT).__dict__[field] | ||||
| #                         except ObjectDoesNotExist: | ||||
| #                             pass | ||||
| #                     if MULTILINGUAL_FAIL_SILENTLY: | ||||
| #                         return None | ||||
| #                     raise ValueError, "'%s' has no translation in '%s'"%(self, code) | ||||
| #         raise AttributeError, "'%s' object has no attribute '%s'"%(self.__class__.__name__, str(attr)) | ||||
| 
 | ||||
| 
 | ||||
| def get_translated_field(obj, field_name, language_code=None): | ||||
|     """ | ||||
|     Tries to get the model attribute corresponding to the current | ||||
|     selected language by appending "_<language_code>" to the attribute | ||||
|     name and returning the value. | ||||
| 
 | ||||
|     On AttributeError try to return the other language or the attribute | ||||
|     without the language suffix. | ||||
| 
 | ||||
|     If the attribute is empty or null, try to return the value of | ||||
|     the other language's attribute. | ||||
| 
 | ||||
|     If there is an attribute with the name without any language code | ||||
|     extension, return the value of this. | ||||
| 
 | ||||
|     Best return value: | ||||
|         field_name + lang_suffix for current language | ||||
| 
 | ||||
|     If empty or field does not exist: | ||||
|         if default language and field_name | ||||
|             field_name | ||||
|         else | ||||
|             field_name + lang_suffix other language | ||||
|     """ | ||||
|     # TODO Implement multiple languages | ||||
|     language_code = (language_code or | ||||
|             translation.get_language() or | ||||
|             settings.LANGUAGE_CODE)[:2] | ||||
|     is_default_language = bool(language_code == settings.LANGUAGE_CODE[:2]) | ||||
|     if language_code == 'de': | ||||
|         other_language_code = 'en' | ||||
|     else: | ||||
|         other_language_code = 'de' | ||||
| 
 | ||||
|     def has_db_field(field_name): | ||||
|         try: | ||||
|             # Only try to access database fields to avoid recursion | ||||
|             obj._meta.get_field(field_name) | ||||
|             return True | ||||
|         except FieldDoesNotExist: | ||||
|             return False | ||||
| 
 | ||||
|     translated_field_name = '%s_%s' % (field_name, language_code) | ||||
|     other_translated_field_name = '%s_%s' % (field_name, other_language_code) | ||||
|     rv = "" | ||||
|     if hasattr(obj, translated_field_name): | ||||
|         rv = getattr(obj, translated_field_name) | ||||
|     if not rv: | ||||
|         if is_default_language and has_db_field(field_name): | ||||
|             rv = getattr(obj, field_name) | ||||
|         elif hasattr(obj, other_translated_field_name): | ||||
|             rv = getattr(obj, other_translated_field_name) | ||||
|     if not rv and has_db_field(field_name): | ||||
|         rv = getattr(obj, field_name) | ||||
|     # FIXME Raise error if neither field exists | ||||
|     return rv | ||||
| 
 | ||||
| 
 | ||||
| @contextmanager | ||||
| def active_language(lang='de'): | ||||
|     translation.activate(lang) | ||||
|     yield | ||||
|     translation.deactivate() | ||||
| 
 | ||||
| 
 | ||||
| def set_language(request): | ||||
|     """ | ||||
|     Modified copy from django.views.i18n | ||||
|     """ | ||||
|     next = request.POST.get('next', request.GET.get('next')) | ||||
|     if not is_safe_url(url=next, host=request.get_host()): | ||||
|         next = request.META.get('HTTP_REFERER') | ||||
|         if not is_safe_url(url=next, host=request.get_host()): | ||||
|             next = '/' | ||||
|     response = http.HttpResponseRedirect(next) | ||||
|     if request.method == 'GET': | ||||
|         lang_code = request.GET.get(LANGUAGE_QUERY_PARAMETER, None) | ||||
|         if lang_code and check_for_language(lang_code): | ||||
|             next_trans = translate_url(next, lang_code) | ||||
|             if next_trans != next: | ||||
|                 response = http.HttpResponseRedirect(next_trans) | ||||
| 
 | ||||
|             if hasattr(request, 'session'): | ||||
|                 request.session[LANGUAGE_SESSION_KEY] = lang_code | ||||
|             else: | ||||
|                 response.set_cookie(settings.LANGUAGE_COOKIE_NAME, lang_code, | ||||
|                                     max_age=settings.LANGUAGE_COOKIE_AGE, | ||||
|                                     path=settings.LANGUAGE_COOKIE_PATH, | ||||
|                                     domain=settings.LANGUAGE_COOKIE_DOMAIN) | ||||
|     return response | ||||
					Loading…
					
					
				
		Reference in new issue