diff --git a/project/api/admin.py b/project/api/admin.py index 3306c947..f82575c6 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) 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/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 8a541fdb..70dc6afa 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, HStoreField from django.db.models.signals import post_save from django.dispatch import receiver from ordered_model.models import OrderedModel @@ -385,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 3c92013f..ecf8e26e 100644 --- a/project/api/serializers.py +++ b/project/api/serializers.py @@ -25,10 +25,13 @@ PrimaryKeyRelatedField, SerializerMethodField, ) +from rest_framework_gis.serializers import GeoFeatureModelSerializer from .models import ( TerritorialEntity, PoliticalRelation, + Symbol, + SymbolFeature, CachedData, City, SpacetimeVolume, @@ -119,6 +122,38 @@ class Meta: read_only_fields = ("rank",) +class SymbolFeatureSerializer(GeoFeatureModelSerializer): + """ + Symbolizes SymbolFeatures as valid GeoJSON + """ + + class Meta: + model = SymbolFeature + geo_field = "geom" + fields = "__all__" + + def get_properties(self, instance, fields): + return instance.styling + + def unformat_geojson(self, feature): + attrs = { + self.Meta.geo_field: feature["geometry"], + "styling": feature["properties"], + } + + return attrs + + +class SymbolSerializer(ModelSerializer): + """ + Serializes the Symbol model + """ + + class Meta: + model = Symbol + fields = ("id", "name", "narrations", "features") + + class CitySerializer(ModelSerializer): """ Serializes the City model diff --git a/project/api/urls.py b/project/api/urls.py index 72e24da7..ed295a34 100644 --- a/project/api/urls.py +++ b/project/api/urls.py @@ -33,6 +33,8 @@ 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) +ROUTER.register(r"symbol-features", views.SymbolFeatureViewSet) urlpatterns = [ path( diff --git a/project/api/views.py b/project/api/views.py index 3b09fde2..4c8d3efc 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,31 @@ def get_queryset(self): return queryset +class SymbolFeatureViewSet(viewsets.ModelViewSet): + """ + ViewSet for SymbolFeatures, filterable by Symbol id + """ + + queryset = SymbolFeature.objects.all() + serializer_class = SymbolFeatureSerializer + + def get_queryset(self): + queryset = self.queryset + 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): """ 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",