From 8e659106ed0aa730cf089d177be1974c70e5669c Mon Sep 17 00:00:00 2001 From: whirish Date: Mon, 5 Aug 2019 02:59:40 +0100 Subject: [PATCH 1/4] Symbol model --- project/api/models.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/project/api/models.py b/project/api/models.py index 8a541fdb..2152fa7e 100644 --- a/project/api/models.py +++ b/project/api/models.py @@ -21,7 +21,7 @@ from django.core.exceptions import ValidationError from django.contrib.auth.models import User from django.contrib.gis.db import models -from django.contrib.postgres.fields import ArrayField +from django.contrib.postgres.fields import ArrayField, JSONField from django.db.models.signals import post_save from django.dispatch import receiver from ordered_model.models import OrderedModel @@ -231,6 +231,14 @@ def save(self, *args, **kwargs): # pylint: disable=W0221 class Meta: unique_together = ("wikidata_id", "event_type") +class Symbol(models.Model): + """ + Stores a geographical feature in representation of something + """ + + geom = GeometryCollectionField() + history = HistoricalRecords() + class City(models.Model): """ From ff7ac9dac1baf371a9066a0444d3bc6421b0ed91 Mon Sep 17 00:00:00 2001 From: whirish Date: Mon, 5 Aug 2019 02:59:47 +0100 Subject: [PATCH 2/4] WIP serialization --- project/api/serializers.py | 45 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/project/api/serializers.py b/project/api/serializers.py index 3c92013f..55657df8 100644 --- a/project/api/serializers.py +++ b/project/api/serializers.py @@ -19,6 +19,7 @@ from django.db.models import Count, Case, When from jdcal import jd2gcal +from jsonschema import validate from rest_framework.serializers import ( ModelSerializer, IntegerField, @@ -29,6 +30,7 @@ from .models import ( TerritorialEntity, PoliticalRelation, + Symbol, CachedData, City, SpacetimeVolume, @@ -119,6 +121,49 @@ class Meta: read_only_fields = ("rank",) +"""class SymbolSerializer(ModelSerializer): + "" + Serializes the Symbol model + "" + + class Meta: + model = Symbol + fields = "__all__" + + def validate_geom(self, value): + "" + Ensure the geom field is a properly structured FeatureCollection + "" + schema = { + "type": "FeatureCollection", + "features": { + + } + } +""" + +class SymbolSerializer(GeoFeatureModelSerializer): + + class Meta: + model = Symbol + geo_field = "geom" + + def get_properties(self, instance, fields): + print(fields) + return instance.styling + + def unformat_geojson(self, feature): + attrs = { + self.Meta.geo_field: feature["geometry"], + "styling": feature["properties"] + } + + if self.Meta.bbox_geom_field and "bbox" in feature: + attrs[self.Meta.bbox_geom_field] = Polygon.from_bbox(feature["bbox"]) + + return attrs + + class CitySerializer(ModelSerializer): """ Serializes the City model From 76bf3836b699bd6491feab33d7ff35f7bd28ce77 Mon Sep 17 00:00:00 2001 From: whirish Date: Thu, 8 Aug 2019 15:04:58 -0700 Subject: [PATCH 3/4] WIP symbolfeature serialization based on symbol pk --- project/api/admin.py | 4 ++ ...6_historicalsymbol_symbol_symbolfeature.py | 55 ++++++++++++++++++ project/api/models.py | 17 +++++- project/api/serializers.py | 57 ++++++++++--------- project/api/urls.py | 1 + project/api/views.py | 21 +++++++ project/chron/settings.py | 1 + 7 files changed, 125 insertions(+), 31 deletions(-) create mode 100644 project/api/migrations/0016_historicalsymbol_symbol_symbolfeature.py diff --git a/project/api/admin.py b/project/api/admin.py index 3306c947..59379173 100644 --- a/project/api/admin.py +++ b/project/api/admin.py @@ -30,6 +30,8 @@ City, Profile, NarrativeVote, + Symbol, + SymbolFeature ) # Register your models here. @@ -43,3 +45,5 @@ admin.site.register(City) admin.site.register(Profile) admin.site.register(NarrativeVote) +admin.site.register(Symbol) +admin.site.register(SymbolFeature) \ No newline at end of file diff --git a/project/api/migrations/0016_historicalsymbol_symbol_symbolfeature.py b/project/api/migrations/0016_historicalsymbol_symbol_symbolfeature.py new file mode 100644 index 00000000..3e5b2f9c --- /dev/null +++ b/project/api/migrations/0016_historicalsymbol_symbol_symbolfeature.py @@ -0,0 +1,55 @@ +# Generated by Django 2.2.4 on 2019-08-08 19:58 + +from django.conf import settings +import django.contrib.gis.db.models.fields +from django.contrib.postgres.operations import HStoreExtension +import django.contrib.postgres.fields.hstore +from django.db import migrations, models +import django.db.models.deletion +import simple_history.models + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('api', '0015_merge_20190627_1103'), + ] + + operations = [ + HStoreExtension(), + migrations.CreateModel( + name='Symbol', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.TextField(max_length=50)), + ], + ), + migrations.CreateModel( + name='SymbolFeature', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('geom', django.contrib.gis.db.models.fields.GeometryField(srid=4326)), + ('styling', django.contrib.postgres.fields.hstore.HStoreField()), + ('symbol', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='features', to='api.Symbol')), + ], + ), + migrations.CreateModel( + name='HistoricalSymbol', + fields=[ + ('id', models.IntegerField(auto_created=True, blank=True, db_index=True, verbose_name='ID')), + ('name', models.TextField(max_length=50)), + ('history_id', models.AutoField(primary_key=True, serialize=False)), + ('history_date', models.DateTimeField()), + ('history_change_reason', models.CharField(max_length=100, null=True)), + ('history_type', models.CharField(choices=[('+', 'Created'), ('~', 'Changed'), ('-', 'Deleted')], max_length=1)), + ('history_user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to=settings.AUTH_USER_MODEL)), + ], + options={ + 'verbose_name': 'historical symbol', + 'ordering': ('-history_date', '-history_id'), + 'get_latest_by': 'history_date', + }, + bases=(simple_history.models.HistoricalChanges, models.Model), + ), + ] diff --git a/project/api/models.py b/project/api/models.py index 2152fa7e..18e65466 100644 --- a/project/api/models.py +++ b/project/api/models.py @@ -21,7 +21,7 @@ from django.core.exceptions import ValidationError from django.contrib.auth.models import User from django.contrib.gis.db import models -from django.contrib.postgres.fields import ArrayField, JSONField +from django.contrib.postgres.fields import ArrayField, HStoreField from django.db.models.signals import post_save from django.dispatch import receiver from ordered_model.models import OrderedModel @@ -231,15 +231,26 @@ def save(self, *args, **kwargs): # pylint: disable=W0221 class Meta: unique_together = ("wikidata_id", "event_type") + class Symbol(models.Model): """ - Stores a geographical feature in representation of something + Stores a FeatureCollection in representation of something """ - geom = GeometryCollectionField() + name = models.TextField(max_length=50) history = HistoricalRecords() +class SymbolFeature(models.Model): + """ + Stores geometry to be used in a collection in a Symbol + """ + + geom = models.GeometryField() + styling = HStoreField() + symbol = models.ForeignKey(Symbol, related_name="features", on_delete=models.CASCADE) + + class City(models.Model): """ Stores a city represented by a point on the map diff --git a/project/api/serializers.py b/project/api/serializers.py index 55657df8..66c5fbe2 100644 --- a/project/api/serializers.py +++ b/project/api/serializers.py @@ -18,19 +18,21 @@ """ from django.db.models import Count, Case, When +from django.contrib.gis.geos import Polygon from jdcal import jd2gcal -from jsonschema import validate from rest_framework.serializers import ( ModelSerializer, IntegerField, PrimaryKeyRelatedField, SerializerMethodField, ) +from rest_framework_gis.serializers import GeoFeatureModelSerializer from .models import ( TerritorialEntity, PoliticalRelation, Symbol, + SymbolFeature, CachedData, City, SpacetimeVolume, @@ -121,35 +123,14 @@ class Meta: read_only_fields = ("rank",) -"""class SymbolSerializer(ModelSerializer): - "" - Serializes the Symbol model - "" - - class Meta: - model = Symbol - fields = "__all__" - - def validate_geom(self, value): - "" - Ensure the geom field is a properly structured FeatureCollection - "" - schema = { - "type": "FeatureCollection", - "features": { - - } - } -""" - -class SymbolSerializer(GeoFeatureModelSerializer): +class SymbolFeatureSerializer(GeoFeatureModelSerializer): class Meta: - model = Symbol + model = SymbolFeature geo_field = "geom" + fields = "__all__" def get_properties(self, instance, fields): - print(fields) return instance.styling def unformat_geojson(self, feature): @@ -158,12 +139,32 @@ def unformat_geojson(self, feature): "styling": feature["properties"] } - if self.Meta.bbox_geom_field and "bbox" in feature: - attrs[self.Meta.bbox_geom_field] = Polygon.from_bbox(feature["bbox"]) - return attrs +class SymbolSerializer(ModelSerializer): + """ + Serializes the Symbol model + """ + + features = SymbolFeatureSerializer() + + class Meta: + model = Symbol + fields = "__all__" + + """def validate_geom(self, value): + "" + Ensure the geom field is a properly structured FeatureCollection + "" + schema = { + "type": "FeatureCollection", + "features": { + + } + }""" + + class CitySerializer(ModelSerializer): """ Serializes the City model diff --git a/project/api/urls.py b/project/api/urls.py index 72e24da7..55d3cff1 100644 --- a/project/api/urls.py +++ b/project/api/urls.py @@ -33,6 +33,7 @@ ROUTER.register(r"narrations", views.NarrationViewSet) ROUTER.register(r"narrative-votes", views.NarrativeVoteViewSet) ROUTER.register(r"profiles", views.ProfileViewSet) +ROUTER.register(r"symbols", views.SymbolViewSet) urlpatterns = [ path( diff --git a/project/api/views.py b/project/api/views.py index 3b09fde2..c20ffc19 100644 --- a/project/api/views.py +++ b/project/api/views.py @@ -35,6 +35,8 @@ Narration, NarrativeVote, Profile, + Symbol, + SymbolFeature, ) from .serializers import ( TerritorialEntitySerializer, @@ -47,6 +49,8 @@ NarrationSerializer, NarrativeVoteSerializer, ProfileSerializer, + SymbolSerializer, + SymbolFeatureSerializer ) from .permissions import IsUserOrReadOnly @@ -184,6 +188,23 @@ def get_queryset(self): return queryset +class SymbolViewSet(viewsets.ModelViewSet): + """ + ViewSet for Symbols + """ + + queryset = SymbolFeature.objects.all() + serializer_class = SymbolFeatureSerializer + + def retrieve(self, request, pk=None): + queryset = self.queryset + user = queryset.filter(symbol=pk) + print(user) + serializer = SymbolSerializer(user, many=True) + return Response(serializer.data) + + + class ProfileViewSet(viewsets.ModelViewSet): """ ViewSet for Profile diff --git a/project/chron/settings.py b/project/chron/settings.py index 43de1cea..b2794d7d 100644 --- a/project/chron/settings.py +++ b/project/chron/settings.py @@ -46,6 +46,7 @@ "django.contrib.messages", "django.contrib.staticfiles", "django.contrib.gis", + "django.contrib.postgres", "django_extensions", "api.apps.ApiConfig", "colorfield", From dec56264b219a2e6081bbe7631b5b69cbf8952c8 Mon Sep 17 00:00:00 2001 From: whirish Date: Fri, 9 Aug 2019 01:44:24 +0100 Subject: [PATCH 4/4] Symbol & SymbolFeature serialization & viewsets --- project/api/admin.py | 4 +- .../api/migrations/0017_symbol_narration.py | 18 ++++++++ .../api/migrations/0018_auto_20190809_0035.py | 18 ++++++++ project/api/models.py | 41 ++++++++++--------- project/api/serializers.py | 21 +++------- project/api/urls.py | 1 + project/api/views.py | 24 +++++++---- 7 files changed, 82 insertions(+), 45 deletions(-) create mode 100644 project/api/migrations/0017_symbol_narration.py create mode 100644 project/api/migrations/0018_auto_20190809_0035.py diff --git a/project/api/admin.py b/project/api/admin.py index 59379173..f82575c6 100644 --- a/project/api/admin.py +++ b/project/api/admin.py @@ -31,7 +31,7 @@ Profile, NarrativeVote, Symbol, - SymbolFeature + SymbolFeature, ) # Register your models here. @@ -46,4 +46,4 @@ admin.site.register(Profile) admin.site.register(NarrativeVote) admin.site.register(Symbol) -admin.site.register(SymbolFeature) \ No newline at end of file +admin.site.register(SymbolFeature) diff --git a/project/api/migrations/0017_symbol_narration.py b/project/api/migrations/0017_symbol_narration.py new file mode 100644 index 00000000..5e6d7cf5 --- /dev/null +++ b/project/api/migrations/0017_symbol_narration.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.4 on 2019-08-08 23:07 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('api', '0016_historicalsymbol_symbol_symbolfeature'), + ] + + operations = [ + migrations.AddField( + model_name='symbol', + name='narration', + field=models.ManyToManyField(related_name='symbols', to='api.Narration'), + ), + ] diff --git a/project/api/migrations/0018_auto_20190809_0035.py b/project/api/migrations/0018_auto_20190809_0035.py new file mode 100644 index 00000000..767b14e7 --- /dev/null +++ b/project/api/migrations/0018_auto_20190809_0035.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.4 on 2019-08-09 00:35 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('api', '0017_symbol_narration'), + ] + + operations = [ + migrations.RenameField( + model_name='symbol', + old_name='narration', + new_name='narrations', + ), + ] diff --git a/project/api/models.py b/project/api/models.py index 18e65466..70dc6afa 100644 --- a/project/api/models.py +++ b/project/api/models.py @@ -232,25 +232,6 @@ class Meta: unique_together = ("wikidata_id", "event_type") -class Symbol(models.Model): - """ - Stores a FeatureCollection in representation of something - """ - - name = models.TextField(max_length=50) - history = HistoricalRecords() - - -class SymbolFeature(models.Model): - """ - Stores geometry to be used in a collection in a Symbol - """ - - geom = models.GeometryField() - styling = HStoreField() - symbol = models.ForeignKey(Symbol, related_name="features", on_delete=models.CASCADE) - - class City(models.Model): """ Stores a city represented by a point on the map @@ -404,3 +385,25 @@ class Narration(OrderedModel): location = models.PointField(blank=True, null=True) order_with_respect_to = "narrative" + + +class Symbol(models.Model): + """ + Stores a FeatureCollection in representation of something + """ + + name = models.TextField(max_length=50) + narrations = models.ManyToManyField(Narration, related_name="symbols") + history = HistoricalRecords() + + +class SymbolFeature(models.Model): + """ + Stores geometry to be used in a collection in a Symbol + """ + + geom = models.GeometryField() + styling = HStoreField() + symbol = models.ForeignKey( + Symbol, related_name="features", on_delete=models.CASCADE + ) diff --git a/project/api/serializers.py b/project/api/serializers.py index 66c5fbe2..ecf8e26e 100644 --- a/project/api/serializers.py +++ b/project/api/serializers.py @@ -18,7 +18,6 @@ """ from django.db.models import Count, Case, When -from django.contrib.gis.geos import Polygon from jdcal import jd2gcal from rest_framework.serializers import ( ModelSerializer, @@ -124,6 +123,9 @@ class Meta: class SymbolFeatureSerializer(GeoFeatureModelSerializer): + """ + Symbolizes SymbolFeatures as valid GeoJSON + """ class Meta: model = SymbolFeature @@ -136,7 +138,7 @@ def get_properties(self, instance, fields): def unformat_geojson(self, feature): attrs = { self.Meta.geo_field: feature["geometry"], - "styling": feature["properties"] + "styling": feature["properties"], } return attrs @@ -147,22 +149,9 @@ class SymbolSerializer(ModelSerializer): Serializes the Symbol model """ - features = SymbolFeatureSerializer() - class Meta: model = Symbol - fields = "__all__" - - """def validate_geom(self, value): - "" - Ensure the geom field is a properly structured FeatureCollection - "" - schema = { - "type": "FeatureCollection", - "features": { - - } - }""" + fields = ("id", "name", "narrations", "features") class CitySerializer(ModelSerializer): diff --git a/project/api/urls.py b/project/api/urls.py index 55d3cff1..ed295a34 100644 --- a/project/api/urls.py +++ b/project/api/urls.py @@ -34,6 +34,7 @@ ROUTER.register(r"narrative-votes", views.NarrativeVoteViewSet) ROUTER.register(r"profiles", views.ProfileViewSet) ROUTER.register(r"symbols", views.SymbolViewSet) +ROUTER.register(r"symbol-features", views.SymbolFeatureViewSet) urlpatterns = [ path( diff --git a/project/api/views.py b/project/api/views.py index c20ffc19..4c8d3efc 100644 --- a/project/api/views.py +++ b/project/api/views.py @@ -50,7 +50,7 @@ NarrativeVoteSerializer, ProfileSerializer, SymbolSerializer, - SymbolFeatureSerializer + SymbolFeatureSerializer, ) from .permissions import IsUserOrReadOnly @@ -188,21 +188,29 @@ def get_queryset(self): return queryset -class SymbolViewSet(viewsets.ModelViewSet): +class SymbolFeatureViewSet(viewsets.ModelViewSet): """ - ViewSet for Symbols + ViewSet for SymbolFeatures, filterable by Symbol id """ queryset = SymbolFeature.objects.all() serializer_class = SymbolFeatureSerializer - def retrieve(self, request, pk=None): + def get_queryset(self): queryset = self.queryset - user = queryset.filter(symbol=pk) - print(user) - serializer = SymbolSerializer(user, many=True) - return Response(serializer.data) + symbol = self.request.query_params.get("symbol", None) + if symbol is not None: + queryset = queryset.filter(symbol=symbol) + return queryset + + +class SymbolViewSet(viewsets.ModelViewSet): + """ + ViewSet for Symbols + """ + queryset = Symbol.objects.all() + serializer_class = SymbolSerializer class ProfileViewSet(viewsets.ModelViewSet):