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