Skip to content

Add api for history objects. #217

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 16 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion project/.pylintrc
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,10 @@ disable=parameter-unpacking,
dict-values-not-iterating,
fixme,
bad-continuation,
no-member
no-member,
invalid-name,
too-many-locals,
no-name-in-module,

# Enable the message, report, category or checker with the given id(s). You can
# either give multiple identifier separated by comma (,) or put this option
Expand Down
25 changes: 13 additions & 12 deletions project/api/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"""

from django.contrib import admin
from simple_history.admin import SimpleHistoryAdmin

from .models import (
TerritorialEntity,
Expand All @@ -35,15 +36,15 @@
)

# Register your models here.
admin.site.register(TerritorialEntity)
admin.site.register(PoliticalRelation)
admin.site.register(SpacetimeVolume)
admin.site.register(CachedData)
admin.site.register(Narration)
admin.site.register(Narrative)
admin.site.register(MapSettings)
admin.site.register(City)
admin.site.register(Profile)
admin.site.register(NarrativeVote)
admin.site.register(Symbol)
admin.site.register(SymbolFeature)
admin.site.register(TerritorialEntity, SimpleHistoryAdmin)
admin.site.register(PoliticalRelation, SimpleHistoryAdmin)
admin.site.register(SpacetimeVolume, SimpleHistoryAdmin)
admin.site.register(CachedData, SimpleHistoryAdmin)
admin.site.register(Narration, SimpleHistoryAdmin)
admin.site.register(Narrative, SimpleHistoryAdmin)
admin.site.register(MapSettings, SimpleHistoryAdmin)
admin.site.register(City, SimpleHistoryAdmin)
admin.site.register(Profile, SimpleHistoryAdmin)
admin.site.register(NarrativeVote, SimpleHistoryAdmin)
admin.site.register(Symbol, SimpleHistoryAdmin)
admin.site.register(SymbolFeature, SimpleHistoryAdmin)
18 changes: 18 additions & 0 deletions project/api/apps.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,17 @@
"""

from django.apps import AppConfig
from simple_history.signals import pre_create_historical_record


def add_group_to_historical_record(sender, **kwargs): # pylint: disable=W0613
"""
Save group to historical record
"""
instance = kwargs["instance"]
if getattr(instance, "group", None):
kwargs["history_instance"].group = instance.group
del instance.group


class ApiConfig(AppConfig):
Expand All @@ -26,3 +37,10 @@ class ApiConfig(AppConfig):
"""

name = "api"

def ready(self):
from .models import HistoricalSpacetimeVolume # pylint: disable=C0415

pre_create_historical_record.connect(
add_group_to_historical_record, sender=HistoricalSpacetimeVolume
)
19 changes: 19 additions & 0 deletions project/api/migrations/0022_historicalspacetimevolume_group.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Generated by Django 2.2.9 on 2020-03-15 06:27

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('api', '0021_merge_20200213_1800'),
]

operations = [
migrations.AddField(
model_name='historicalspacetimevolume',
name='group',
field=models.PositiveIntegerField(default=0),
preserve_default=False,
),
]
18 changes: 18 additions & 0 deletions project/api/migrations/0023_auto_20200315_0628.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 2.2.9 on 2020-03-15 06:28

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('api', '0022_historicalspacetimevolume_group'),
]

operations = [
migrations.AlterField(
model_name='historicalspacetimevolume',
name='group',
field=models.PositiveIntegerField(blank=True, null=True),
),
]
14 changes: 14 additions & 0 deletions project/api/migrations/0024_merge_20200330_1854.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Generated by Django 2.2.11 on 2020-03-30 18:54

from django.db import migrations


class Migration(migrations.Migration):

dependencies = [
('api', '0023_auto_20200315_0628'),
('api', '0023_mapcolorscheme'),
]

operations = [
]
27 changes: 25 additions & 2 deletions project/api/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,18 @@ def save(self, *args, **kwargs): # pylint: disable=W0221
super(City, self).save(*args, **kwargs)


class OverlapGroupedHistoricalModel(models.Model):
"""
Abstract model for history models in which
the territory changed due to an overlap
"""

group = models.PositiveIntegerField(null=True, blank=True)

class Meta:
abstract = True


class SpacetimeVolume(models.Model):
"""
Maps a set of Territories to a TerritorialEntity at a specific time
Expand All @@ -306,7 +318,7 @@ class SpacetimeVolume(models.Model):
references = ArrayField(models.TextField(max_length=500), blank=True, null=True)
visual_center = models.PointField(blank=True, null=True)
related_events = models.ManyToManyField(CachedData, blank=True)
history = HistoricalRecords()
history = HistoricalRecords(bases=[OverlapGroupedHistoricalModel,])

def calculate_center(self):
"""
Expand Down Expand Up @@ -364,11 +376,22 @@ def clean(self, *args, **kwargs): # pylint: disable=W0221
super(SpacetimeVolume, self).clean(*args, **kwargs)

def save(self, *args, **kwargs): # pylint: disable=W0221
self.full_clean()
self.full_clean(exclude=["id"])
super(SpacetimeVolume, self).save(*args, **kwargs)
if not self.visual_center:
self.calculate_center()

def save_without_historical_record(self, *args, **kwargs):
"""
Saves the model without creating a new historical record
"""
self.skip_history_when_saving = True # pylint: disable=W0201
try:
ret = self.save(*args, **kwargs) # pylint: disable=E1111
finally:
del self.skip_history_when_saving
return ret


class Narrative(models.Model):
"""
Expand Down
94 changes: 94 additions & 0 deletions project/api/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@
Narration,
NarrativeVote,
Profile,
HistoricalSpacetimeVolume,
HistoricalTerritorialEntity,
)


Expand Down Expand Up @@ -263,3 +265,95 @@ def get_narration_count(self, obj): # pylint: disable=R0201
"""

return obj.narration_set.count()


class StvHistoryListSerializer(ModelSerializer):
"""
Serializes the HistoricalSpacetimeVolume model
"""

history_user = SerializerMethodField()
history_user_id = SerializerMethodField()

def get_history_user(self, obj): # pylint: disable=R0201
"""
Returns the username of the user who made the change.
"""

if obj.history_user is not None:
return obj.history_user.username
return None

def get_history_user_id(self, obj): # pylint: disable=R0201
"""
Returns the id of the user who made the change.
"""

if obj.history_user is not None:
return obj.history_user.id
return None

class Meta:
model = HistoricalSpacetimeVolume
exclude = ["territory"]


class StvHistoryRetrieveSerializer(ModelSerializer):
"""
Serializes the HistoricalSpacetimeVolume model
"""

history_user = SerializerMethodField()
history_user_id = SerializerMethodField()

def get_history_user(self, obj): # pylint: disable=R0201
"""
Returns the username of the user who made the change.
"""

if obj.history_user is not None:
return obj.history_user.username
return None

def get_history_user_id(self, obj): # pylint: disable=R0201
"""
Returns the id of the user who made the change.
"""

if obj.history_user is not None:
return obj.history_user.id
return None

class Meta:
model = HistoricalSpacetimeVolume
fields = "__all__"


class TeHistorySerializer(ModelSerializer):
"""
Serializes the HistoricalTerritorialEntity model
"""

history_user = SerializerMethodField()
history_user_id = SerializerMethodField()

def get_history_user(self, obj): # pylint: disable=R0201
"""
Returns the username of the user who made the change.
"""

if obj.history_user is not None:
return obj.history_user.username
return None

def get_history_user_id(self, obj): # pylint: disable=R0201
"""
Returns the id of the user who made the change.
"""
if obj.history_user is not None:
return obj.history_user.id
return None

class Meta:
model = HistoricalTerritorialEntity
fields = "__all__"
23 changes: 23 additions & 0 deletions project/api/tests/stv_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -166,3 +166,26 @@ def test_api_can_not_create_stv(self):
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
response = self.client.post(url, data_overlapping)
self.assertEqual(response.status_code, status.HTTP_409_CONFLICT)

@authorized
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need an authorization to perform a get request for stv-history-list?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After closer inspection yes we do, do you think an unauthorized user would have need to see historical records? If so we can change it

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For me it would be easier without extra headers on GET, because I already have all the coding done for this.
On the other side, I can't object against doing it as a protected with authorization method.

def test_api_can_query_stv_history(self):
"""
Ensure we can query for all Cities
"""

url = reverse("stv-history-list")
response = self.client.get(url, format="json")

self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data[0]["id"], 11)

@authorized
def test_api_can_query_te_history_detail(self):
"""
Ensure we can query for all Cities
"""

url = reverse("stv-history-detail", args=[11])
response = self.client.get(url, format="json")
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data["id"], 11)
28 changes: 28 additions & 0 deletions project/api/tests/te_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,3 +91,31 @@ def test_api_can_query_te(self):
response = self.client.get(url, format="json")
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data["admin_level"], 1)

@authorized
def test_api_can_query_te_history(self):
"""
Ensure we can query for all Cities
"""

url = reverse("te-history-list")
response = self.client.get(url, format="json")
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data[0]["label"], "France")

params = {"entity": "105"}
response = self.client.get(url, params, format="json")

for record in response.data:
self.assertEqual(record["id"], 105)

@authorized
def test_api_can_query_te_history_detail(self):
"""
Ensure we can query for all Cities
"""

url = reverse("te-history-detail", args=[126])
response = self.client.get(url, format="json")
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data["label"], "France")
4 changes: 4 additions & 0 deletions project/api/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@
ROUTER.register(r"profiles", views.ProfileViewSet)
ROUTER.register(r"symbols", views.SymbolViewSet)
ROUTER.register(r"symbol-features", views.SymbolFeatureViewSet)
ROUTER.register(r"stv-history", views.StvHistoryViewSet, basename="stv-history")
ROUTER.register(r"te-history", views.TeHistoryViewSet, basename="te-history")


urlpatterns = [
path(
Expand All @@ -55,6 +58,7 @@
),
path("mvt/stv/<int:zoom>/<int:x_coor>/<int:y_coor>", views.mvt_stv, name="mvt-stv"),
path("spacetime-volumes/<int:primary_key>/download", views.stv_downloader),
path("stv-history/<int:primary_key>/download", views.historical_stv_downloader),
path("territorial-entities/list", views.te_list),
path("", include(ROUTER.urls)),
]
Loading