diff --git a/src/c3nav/editor/views.py b/src/c3nav/editor/views.py index ba4fd4f2..f7bd64af 100644 --- a/src/c3nav/editor/views.py +++ b/src/c3nav/editor/views.py @@ -21,13 +21,11 @@ def add_feature(request, feature_type): with transaction.atomic(): feature = form.instance feature.feature_type = feature_type.name - feature.save() - + feature.titles = {} for language, title in form.titles.items(): if title: - feature.featuretitles.update_or_create(language=language, defaults={'title': title}) - else: - feature.featuretitles.filter(language=language).delete() + feature.titles[language] = title + feature.save() return render(request, 'editor/feature_success.html', {}) else: diff --git a/src/c3nav/mapdata/api.py b/src/c3nav/mapdata/api.py index 05f017ad..e5b4636e 100644 --- a/src/c3nav/mapdata/api.py +++ b/src/c3nav/mapdata/api.py @@ -87,6 +87,6 @@ class FeatureViewSet(ReadOnlyModelViewSet): """ Get all Map Features including ones that are only part of the current session """ - queryset = Feature.objects.all().prefetch_related('featuretitles') + queryset = Feature.objects.all() serializer_class = FeatureSerializer lookup_value_regex = '[^/]+' diff --git a/src/c3nav/mapdata/fields.py b/src/c3nav/mapdata/fields.py index a3d7ffc1..cfabca86 100644 --- a/src/c3nav/mapdata/fields.py +++ b/src/c3nav/mapdata/fields.py @@ -17,3 +17,16 @@ class GeometryField(models.TextField): def get_prep_value(self, value): return json.dumps(sort_geojson(mapping(value))) + + +class JSONField(models.TextField): + def from_db_value(self, value, expression, connection, context): + if value is None: + return value + return json.loads(value) + + def to_python(self, value): + return json.loads(value) + + def get_prep_value(self, value): + return json.dumps(value) diff --git a/src/c3nav/mapdata/migrations/0002_auto_20160926_0858.py b/src/c3nav/mapdata/migrations/0002_auto_20160926_0858.py new file mode 100644 index 00000000..64849cb5 --- /dev/null +++ b/src/c3nav/mapdata/migrations/0002_auto_20160926_0858.py @@ -0,0 +1,33 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.1 on 2016-09-26 08:58 +from __future__ import unicode_literals + +import c3nav.mapdata.fields +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('mapdata', '0001_initial'), + ] + + operations = [ + migrations.AlterUniqueTogether( + name='featuretitle', + unique_together=set([]), + ), + migrations.RemoveField( + model_name='featuretitle', + name='feature', + ), + migrations.AddField( + model_name='feature', + name='titles', + field=c3nav.mapdata.fields.JSONField(default={}), + preserve_default=False, + ), + migrations.DeleteModel( + name='FeatureTitle', + ), + ] diff --git a/src/c3nav/mapdata/models/feature.py b/src/c3nav/mapdata/models/feature.py index a4e0dc89..77668afb 100644 --- a/src/c3nav/mapdata/models/feature.py +++ b/src/c3nav/mapdata/models/feature.py @@ -1,10 +1,13 @@ +import os from collections import OrderedDict, namedtuple from django.db import models from django.utils.translation import ugettext_lazy as _ from django.utils.translation import get_language +from shapely.geometry import mapping, shape -from ..fields import GeometryField +from c3nav.mapdata.utils import sort_geojson +from ..fields import GeometryField, JSONField class FeatureType(namedtuple('FeatureType', ('name', 'title', 'title_plural', 'geomtype', 'color'))): @@ -38,28 +41,54 @@ class Feature(models.Model): feature_type = models.CharField(max_length=50, choices=TYPES) level = models.ForeignKey('mapdata.Level', on_delete=models.CASCADE, related_name='features', verbose_name=_('level')) + titles = JSONField() geometry = GeometryField() path_regex = r'^features/('+'|'.join(name for name, title in TYPES)+')/' - @property - def titles(self): - return {title.language: title.title for title in self.featuretitles.all()} - @property def title(self): - titles = self.titles lang = get_language() - if lang in titles: - return titles[lang] - return next(iter(titles.values())) if titles else self.name + if lang in self.titles: + return self.titles[lang] + return next(iter(self.titles.values())) if self.titles else self.name + def tofilename(self): + return 'features/%s/%s.json' % (self.feature_type, self.name) -class FeatureTitle(models.Model): - feature = models.ForeignKey('Feature', on_delete=models.CASCADE, related_name='featuretitles', - verbose_name=_('map package')) - language = models.CharField(max_length=50) - title = models.CharField(max_length=50) + @classmethod + def fromfile(cls, data, file_path): + kwargs = {} + kwargs['feature_type'] = file_path.split(os.path.sep)[1] - class Meta: - unique_together = ('feature', 'language') + if 'geometry' not in data: + raise ValueError('missing geometry.') + try: + kwargs['geometry'] = shape(data['geometry']) + except: + raise ValueError(_('Invalid GeoJSON.')) + + if 'level' not in data: + raise ValueError('missing level.') + kwargs['level'] = data['level'] + + if 'titles' not in data: + raise ValueError('missing titles.') + titles = data['titles'] + if not isinstance(titles, dict): + raise ValueError('Invalid titles format.') + if any(not isinstance(lang, str) for lang in titles.keys()): + raise ValueError('titles: All languages have to be strings.') + if any(not isinstance(title, str) for title in titles.values()): + raise ValueError('titles: All titles have to be strings.') + if any(not title for title in titles.values()): + raise ValueError('titles: Titles must not be empty strings.') + kwargs['titles'] = titles + return kwargs + + def tofile(self): + return OrderedDict(( + ('titles', OrderedDict(sorted(self.titles.items()))), + ('level', self.level.name), + ('geometry', sort_geojson(mapping(self.geometry))) + )) diff --git a/src/c3nav/mapdata/models/level.py b/src/c3nav/mapdata/models/level.py index 637f4e66..d27756fe 100644 --- a/src/c3nav/mapdata/models/level.py +++ b/src/c3nav/mapdata/models/level.py @@ -18,7 +18,7 @@ class Level(models.Model): return 'levels/%s.json' % self.name @classmethod - def fromfile(cls, data): + def fromfile(cls, data, file_path): if 'altitude' not in data: raise ValueError('missing altitude.') diff --git a/src/c3nav/mapdata/models/package.py b/src/c3nav/mapdata/models/package.py index e1c68088..b9dcfc90 100644 --- a/src/c3nav/mapdata/models/package.py +++ b/src/c3nav/mapdata/models/package.py @@ -39,7 +39,7 @@ class Package(models.Model): return self.name in settings.PUBLIC_PACKAGES @classmethod - def fromfile(cls, data): + def fromfile(cls, data, file_path): kwargs = {} if 'name' not in data: diff --git a/src/c3nav/mapdata/models/source.py b/src/c3nav/mapdata/models/source.py index cf289e8e..c9c10919 100644 --- a/src/c3nav/mapdata/models/source.py +++ b/src/c3nav/mapdata/models/source.py @@ -32,7 +32,7 @@ class Source(models.Model): return 'sources/%s.json' % self.name @classmethod - def fromfile(cls, data): + def fromfile(cls, data, file_path): kwargs = {} if 'bounds' not in data: diff --git a/src/c3nav/mapdata/packageio/read.py b/src/c3nav/mapdata/packageio/read.py index 2055fe91..c7d9e358 100644 --- a/src/c3nav/mapdata/packageio/read.py +++ b/src/c3nav/mapdata/packageio/read.py @@ -136,7 +136,7 @@ class ReaderItem: self.data['commit_id'] = result.stdout.read().strip() try: - add_data = self.model.fromfile(self.json_data) + add_data = self.model.fromfile(self.json_data, self.path_in_package) except Exception as e: raise CommandError('Could not load data: %s' % e) self.data.update(add_data) diff --git a/src/c3nav/mapdata/packageio/write.py b/src/c3nav/mapdata/packageio/write.py index cacda6d6..36e5e4af 100644 --- a/src/c3nav/mapdata/packageio/write.py +++ b/src/c3nav/mapdata/packageio/write.py @@ -106,7 +106,7 @@ class MapdataWriter: for file_path, content in self.write: full_file_path = os.path.join(settings.MAP_ROOT, file_path) try: - os.makedirs(os.path.join(os.path.split(full_file_path)[:-1])) + os.makedirs(os.path.join(*os.path.split(full_file_path)[0])) except os.error: pass if content is not None: diff --git a/src/c3nav/mapdata/serializers.py b/src/c3nav/mapdata/serializers.py index a7690edd..5afaa40a 100644 --- a/src/c3nav/mapdata/serializers.py +++ b/src/c3nav/mapdata/serializers.py @@ -55,6 +55,7 @@ class FeatureTypeSerializer(serializers.Serializer): class FeatureSerializer(serializers.ModelSerializer): + titles = serializers.JSONField() geometry = GeometryField() class Meta: