Browse Source

Added version from HFI.

backports/m1-live
Erik Stein 9 years ago
parent
commit
df0efdfa70
  1. 9
      __init__.py
  2. 224
      dateformat.py
  3. 79
      fields.py
  4. 51
      forms.py
  5. 1
      markdown_utils.py
  6. 0
      templatetags/__init__.py
  7. 67
      templatetags/daterange.py
  8. 33
      templatetags/markup_tags.py
  9. 26
      templatetags/text_tags.py
  10. 24
      templatetags/translation_tags.py
  11. 49
      text.py
  12. 15
      timezone.py
  13. 195
      translation.py

9
__init__.py

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

224
dateformat.py

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

79
fields.py

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

51
forms.py

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

1
markdown_utils.py

@ -0,0 +1 @@
from markup.utils import *

0
templatetags/__init__.py

67
templatetags/daterange.py

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

33
templatetags/markup_tags.py

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

26
templatetags/text_tags.py

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

24
templatetags/translation_tags.py

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

49
text.py

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

15
timezone.py

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

195
translation.py

@ -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…
Cancel
Save