From f94be404705556132d11255b4f537b505a374b3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Laura=20Kl=C3=BCnder?= Date: Thu, 30 Nov 2017 00:43:39 +0100 Subject: [PATCH] add I18nField and replace the title JSONField with it --- src/c3nav/mapdata/fields.py | 69 +++++++++++- .../mapdata/migrations/0053_i18nfield.py | 104 ++++++++++++++++++ src/c3nav/mapdata/models/base.py | 13 +-- src/c3nav/mapdata/models/locations.py | 6 - 4 files changed, 174 insertions(+), 18 deletions(-) create mode 100644 src/c3nav/mapdata/migrations/0053_i18nfield.py diff --git a/src/c3nav/mapdata/fields.py b/src/c3nav/mapdata/fields.py index 7985aa96..f1df61e5 100644 --- a/src/c3nav/mapdata/fields.py +++ b/src/c3nav/mapdata/fields.py @@ -2,11 +2,14 @@ import json import logging import typing +from django.conf import settings from django.core.exceptions import ValidationError from django.core.validators import RegexValidator from django.db import models -from django.utils.functional import cached_property +from django.utils.functional import cached_property, lazy +from django.utils.text import format_lazy from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import get_language from shapely import validation from shapely.geometry import LineString, MultiPolygon, Point, Polygon, mapping, shape from shapely.geometry.base import BaseGeometry @@ -126,3 +129,67 @@ class JSONField(models.TextField): def value_to_string(self, obj): value = self.value_from_object(obj) return self.get_prep_value(value) + + +def get_i18n_value(i18n_dict, fallback_language, fallback_any, fallback_value): + lang = get_language() + if i18n_dict: + if lang in i18n_dict: + return i18n_dict[lang] + if fallback_language in i18n_dict: + return i18n_dict[fallback_language] + if fallback_any: + return next(iter(i18n_dict.values())) + return str(fallback_value) + + +lazy_get_i18n_value = lazy(get_i18n_value, str) + + +class I18nDescriptor: + def __init__(self, field): + self.field = field + + def __get__(self, instance, cls=None): + if instance is None: + return self + + fallback_value = self.field.fallback_value + if fallback_value is not None: + fallback_value = format_lazy(fallback_value, model_name=instance._meta.verbose_name, pk=instance.pk) + return lazy_get_i18n_value(getattr(instance, self.field.attname), + fallback_language=self.field.fallback_language, + fallback_any=self.field.fallback_any, + fallback_value=fallback_value) + + +class I18nField(JSONField): + def __init__(self, plural_name=None, fallback_language=settings.LANGUAGE_CODE, + fallback_any=False, fallback_value=None, default=None): + self.plural_name = plural_name + self.fallback_language = fallback_language + self.fallback_any = fallback_any + self.fallback_value = fallback_value + super().__init__(default=(dict(default) if default else {}), null=False) + + def deconstruct(self): + name, path, args, kwargs = super().deconstruct() + kwargs = {} + if self.default != {}: + kwargs['default'] = self.default + if self.plural_name is not None: + kwargs['plural_name'] = self.plural_name + if self.fallback_language != settings.LANGUAGE_CODE: + kwargs['fallback_language'] = self.fallback_language + if self.fallback_any: + kwargs['fallback_any'] = self.fallback_any + if self.fallback_value is not None: + kwargs['fallback_value'] = self.fallback_value + return name, path, args, kwargs + + def contribute_to_class(self, cls, name, *args, **kwargs): + super().contribute_to_class(cls, name, *args, **kwargs) + setattr(cls, self.name, I18nDescriptor(self)) + + def get_attname(self): + return self.name+'_i18n' if self.plural_name is None else self.plural_name diff --git a/src/c3nav/mapdata/migrations/0053_i18nfield.py b/src/c3nav/mapdata/migrations/0053_i18nfield.py new file mode 100644 index 00000000..fa9cb310 --- /dev/null +++ b/src/c3nav/mapdata/migrations/0053_i18nfield.py @@ -0,0 +1,104 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.6 on 2017-11-29 23:28 +from __future__ import unicode_literals + +import c3nav.mapdata.fields +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('mapdata', '0052_auto_20171125_1335'), + ] + + operations = [ + migrations.AlterField( + model_name='accessrestriction', + name='titles', + field=c3nav.mapdata.fields.I18nField(fallback_any=True, fallback_value='{model_name} {pk}', + plural_name='titles'), + ), + migrations.AlterField( + model_name='area', + name='titles', + field=c3nav.mapdata.fields.I18nField(fallback_any=True, fallback_value='{model_name} {pk}', + plural_name='titles'), + ), + migrations.AlterField( + model_name='level', + name='titles', + field=c3nav.mapdata.fields.I18nField(fallback_any=True, fallback_value='{model_name} {pk}', + plural_name='titles'), + ), + migrations.AlterField( + model_name='locationgroup', + name='titles', + field=c3nav.mapdata.fields.I18nField(fallback_any=True, fallback_value='{model_name} {pk}', + plural_name='titles'), + ), + migrations.AlterField( + model_name='locationgroupcategory', + name='titles', + field=c3nav.mapdata.fields.I18nField(fallback_any=True, fallback_value='{model_name} {pk}', + plural_name='titles'), + ), + migrations.AlterField( + model_name='poi', + name='titles', + field=c3nav.mapdata.fields.I18nField(fallback_any=True, fallback_value='{model_name} {pk}', + plural_name='titles'), + ), + migrations.AlterField( + model_name='space', + name='titles', + field=c3nav.mapdata.fields.I18nField(fallback_any=True, fallback_value='{model_name} {pk}', + plural_name='titles'), + ), + migrations.AlterField( + model_name='waytype', + name='titles', + field=c3nav.mapdata.fields.I18nField(fallback_any=True, fallback_value='{model_name} {pk}', + plural_name='titles'), + ), + migrations.RenameField( + model_name='accessrestriction', + old_name='titles', + new_name='title', + ), + migrations.RenameField( + model_name='area', + old_name='titles', + new_name='title', + ), + migrations.RenameField( + model_name='level', + old_name='titles', + new_name='title', + ), + migrations.RenameField( + model_name='locationgroup', + old_name='titles', + new_name='title', + ), + migrations.RenameField( + model_name='locationgroupcategory', + old_name='titles', + new_name='title', + ), + migrations.RenameField( + model_name='poi', + old_name='titles', + new_name='title', + ), + migrations.RenameField( + model_name='space', + old_name='titles', + new_name='title', + ), + migrations.RenameField( + model_name='waytype', + old_name='titles', + new_name='title', + ), + ] diff --git a/src/c3nav/mapdata/models/base.py b/src/c3nav/mapdata/models/base.py index fc7a83fd..79c57515 100644 --- a/src/c3nav/mapdata/models/base.py +++ b/src/c3nav/mapdata/models/base.py @@ -5,7 +5,7 @@ from django.db import models from django.utils.translation import ugettext_lazy as _ from django.utils.translation import get_language, get_language_info -from c3nav.mapdata.fields import JSONField +from c3nav.mapdata.fields import I18nField from c3nav.mapdata.models import MapUpdate @@ -47,7 +47,7 @@ class SerializableMixin(models.Model): class TitledMixin(SerializableMixin, models.Model): - titles = JSONField(default={}) + title = I18nField(plural_name='titles', fallback_any=True, fallback_value='{model_name} {pk}') class Meta: abstract = True @@ -75,15 +75,6 @@ class TitledMixin(SerializableMixin, models.Model): result['display'].append((language, title)) return result - @property - def title(self): - lang = get_language() - if self.titles: - if lang in self.titles: - return self.titles[lang] - return next(iter(self.titles.values())) - return super().title - class BoundsMixin(SerializableMixin, models.Model): bottom = models.DecimalField(_('bottom coordinate'), max_digits=6, decimal_places=2) diff --git a/src/c3nav/mapdata/models/locations.py b/src/c3nav/mapdata/models/locations.py index 0c782fbb..05b0764c 100644 --- a/src/c3nav/mapdata/models/locations.py +++ b/src/c3nav/mapdata/models/locations.py @@ -117,12 +117,6 @@ class Location(LocationSlug, AccessRestrictionMixin, TitledMixin, models.Model): return code+':'+str(self.id) return self.slug - @property - def title(self): - if not self.titles and self.slug: - return self._meta.verbose_name + ' ' + self.slug - return super().title - @property def subtitle(self): return ''