diff --git a/project/api/admin.py b/project/api/admin.py index 9354dce..0f8eb24 100644 --- a/project/api/admin.py +++ b/project/api/admin.py @@ -1,6 +1,6 @@ from django.contrib import admin from simple_history.admin import SimpleHistoryAdmin -from .models import * +from .models import Territory, PoliticalEntity, DiplomaticRelation # Register your models here. admin.site.register(Territory, SimpleHistoryAdmin) diff --git a/project/api/filters.py b/project/api/filters.py index fded65f..940ad7b 100644 --- a/project/api/filters.py +++ b/project/api/filters.py @@ -14,10 +14,12 @@ class TerritoryFilter(FilterSet): ) def filter_bounds(self, queryset, field_name, value): + """Filters geometries that intersect @bounds.""" geom = Polygon(make_tuple(value), srid=4326) return queryset.filter(geo__intersects=geom) def filter_date(self, queryset, field_name, value): + """Filters territories that exist during @Date.""" return queryset.filter(start_date__lte=value, end_date__gte=value) class Meta: diff --git a/project/api/migrations/0005_auto_20181014_0009.py b/project/api/migrations/0005_auto_20181014_0009.py new file mode 100644 index 0000000..b5713bf --- /dev/null +++ b/project/api/migrations/0005_auto_20181014_0009.py @@ -0,0 +1,28 @@ +# Generated by Django 2.1.2 on 2018-10-14 00:09 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('api', '0004_auto_20181011_0231'), + ] + + operations = [ + migrations.AlterField( + model_name='entity', + name='name', + field=models.TextField(help_text='Canonical name, which should not include any epithets and must be unique', max_length=100, unique=True), + ), + migrations.AlterField( + model_name='historicalentity', + name='name', + field=models.TextField(db_index=True, help_text='Canonical name, which should not include any epithets and must be unique', max_length=100), + ), + migrations.AlterField( + model_name='historicalpoliticalentity', + name='name', + field=models.TextField(db_index=True, help_text='Canonical name, which should not include any epithets and must be unique', max_length=100), + ), + ] diff --git a/project/api/models.py b/project/api/models.py index b4c42ac..c3b9852 100644 --- a/project/api/models.py +++ b/project/api/models.py @@ -11,24 +11,24 @@ class EntityManager(PolymorphicManager): - """ - Manager for the Nation model to handle lookups by url_id - """ + """Manager for the Nation model to handle lookups by url_id.""" - def get_by_natural_key(self, url_id): + def get_by_natural_key(self, url_id): # noqa return self.get(url_id=url_id) class Entity(PolymorphicModel): - """ - Cultural/governmental entity. Serves as foreign key for most Territories + """Cultural/governmental entity. + + Serves as foreign key for most Territories. """ objects = EntityManager() name = models.TextField( max_length=100, - help_text="Canonical name, should not include any epithets, must be unique", + help_text="Canonical name, which should not include any epithets and" + " must be unique", unique=True, ) url_id = models.SlugField( @@ -52,6 +52,7 @@ class Entity(PolymorphicModel): ) def natural_key(self): + """Return the ID for lookups.""" return self.url_id def __str__(self): @@ -59,8 +60,9 @@ def __str__(self): class PoliticalEntity(Entity): - """ - Cultural/governmental entity. Serves as foreign key for most Territories + """Cultural/governmental entity. + + Serves as foreign key for most Territories. """ color = ColorField( @@ -79,16 +81,15 @@ class PoliticalEntity(Entity): # History fields - # Foreign key to auth.User which will be updated every time the model is changed, - # and is this stored in history as the user to update a specific revision - # Consider other metadata (DateTime) for the revision (may be handled by django-simple-history) + # Foreign key to auth.User which we update every time the model changes, + # and store in history as the user to update a specific revision. + # Consider other metadata (DateTime) for the revision + # (may be handled by django-simple-history). # TODO: implement this class Territory(models.Model): - """ - Defines the borders and controlled territories associated with an Entity. - """ + """Defines the borders and territories associated with an Entity.""" class Meta: verbose_name_plural = "territories" @@ -103,16 +104,17 @@ class Meta: history = HistoricalRecords() def clean(self, *args, **kwargs): + """Ensure that our geometry is a polygon and our dates do not intersect + with those of another Territory of the same Entity.""" if self.start_date > self.end_date: raise ValidationError("Start date cannot be later than end date") if ( - loads(self.geo.json)["type"] != "Polygon" - and loads(self.geo.json)["type"] != "MultiPolygon" + loads(self.geo.json)["type"] != "Polygon" and + loads(self.geo.json)["type"] != "MultiPolygon" ): raise ValidationError( - "Only Polygon and MultiPolygon objects are acceptable geometry types." - ) - + "Only Polygon and MultiPolygon objects are acceptable geometry" + " types.") try: # This date check is inculsive. if Territory.objects.filter( @@ -121,14 +123,15 @@ def clean(self, *args, **kwargs): entity__exact=self.entity, ).exists(): raise ValidationError( - "Another territory of this PoliticalEntity exists during this timeframe." + "Another territory of this PoliticalEntity exists during" + " this timeframe." ) except Entity.DoesNotExist: pass super(Territory, self).clean(*args, **kwargs) - def save(self, *args, **kwargs): + def save(self, *args, **kwargs): # noqa self.full_clean() super(Territory, self).save(*args, **kwargs) @@ -141,10 +144,7 @@ def __str__(self): class DiplomaticRelation(models.Model): - """ - Defines political and diplomatic interactions between PoliticalEntitys. - """ - + """Defines interactions between PoliticalEntitys.""" start_date = models.DateField(help_text="When this relation takes effect") end_date = models.DateField(help_text="When this relation ceases to exist") parent_parties = models.ManyToManyField( @@ -172,13 +172,13 @@ class DiplomaticRelation(models.Model): history = HistoricalRecords() - def clean(self, *args, **kwargs): + def clean(self, *args, **kwargs): # noqa if self.start_date > self.end_date: raise ValidationError("Start date cannot be later than end date") super(DiplomaticRelation, self).clean(*args, **kwargs) - def save(self, *args, **kwargs): + def save(self, *args, **kwargs): # noqa self.full_clean() super(DiplomaticRelation, self).save(*args, **kwargs) diff --git a/project/api/permissions.py b/project/api/permissions.py index 88a9a2d..7d60760 100644 --- a/project/api/permissions.py +++ b/project/api/permissions.py @@ -13,7 +13,7 @@ class IsStaffOrSpecificUser(permissions.BasePermission): """ - Permission to detect whether to user in question is staff or the target user + Permission to detect whether the user is staff or the target user. Example: John (regular user) should be able to access John's account Alice (staff) should be able to access John's account @@ -21,18 +21,17 @@ class IsStaffOrSpecificUser(permissions.BasePermission): """ def has_permission(self, request, view): - # allow user to list all users if logged in user is staff - return view.action == "retrieve" or request.user.is_staff + """Allow user to list all users if logged in user is staff.""" + return view.action == 'retrieve' or request.user.is_staff def has_object_permission(self, request, view, obj): - # allow all users to view specific user information + """Allow all users to view specific user information.""" + # TODO: Actually implement this. return True def get_token_auth_header(request): - """ - Obtains the Access Token from the Authorization Header - """ + """Obtains the Access Token from the Authorization Header.""" auth = request.META.get("HTTP_AUTHORIZATION", None) parts = auth.split() token = parts[1] @@ -55,9 +54,11 @@ def decorated(*args, **kwargs): "https://" + settings.AUTH0_DOMAIN + "/.well-known/jwks.json" ) jwks = json.loads(jsonurl.read()) - body = re.sub("(.{64})", "\\1\n", jwks["keys"][0]["x5c"][0], 0, re.DOTALL) + body = re.sub( + "(.{64})", "\\1\n", jwks["keys"][0]["x5c"][0], 0, re.DOTALL) cert = ( - "-----BEGIN CERTIFICATE-----\n" + body + "\n-----END CERTIFICATE-----" + "-----BEGIN CERTIFICATE-----\n" + body + + "\n-----END CERTIFICATE-----" ) certificate = load_pem_x509_certificate( cert.encode("utf-8"), default_backend() diff --git a/project/api/serializers.py b/project/api/serializers.py index 569ab7b..80fb4d2 100644 --- a/project/api/serializers.py +++ b/project/api/serializers.py @@ -7,37 +7,32 @@ class PoliticalEntitySerializer(serializers.ModelSerializer): - """ - Serializes the PoliticalEntity model - """ - + """Serializes the PoliticalEntity model.""" class Meta: model = PoliticalEntity exclude = ("polymorphic_ctype",) class GeoField(serializers.RelatedField): - """ - Field Serializer for Territories - """ + """Field Serializer for Territories.""" @classmethod - def to_representation(self, value): - # Compress geojson to geobuf and return as hexadecimal + def to_representation(cls, value): + """Compress geojson to geobuf and return as hexadecimal.""" gbuf = geobuf.encode(loads(value.geojson)) return gbuf.hex() class TerritorySerializer(serializers.ModelSerializer): - """ - Serializes the Territory model as GeoJSON compatible data - """ - - entity = serializers.SlugRelatedField(read_only=True, slug_field="url_id") - + """Serializes the Territory model as GeoJSON compatible data.""" + entity = serializers.SlugRelatedField( + read_only=True, + slug_field='url_id' + ) geo = GeoField(read_only=True) def to_internal_value(self, data): + """Return a dictionary of territory data.""" ret = {} # Update ret to include passed in data @@ -72,11 +67,8 @@ class Meta: class DiplomaticRelationSerializer(serializers.ModelSerializer): - """ - Serializes the DiplomaticRelation model - """ - - entity = serializers.SlugRelatedField(read_only=True, slug_field="url_id") + """Serializes the DiplomaticRelation model.""" + entity = serializers.SlugRelatedField(read_only=True, slug_field='url_id') class Meta: model = DiplomaticRelation diff --git a/project/api/tests.py b/project/api/tests.py index f16f7cd..39f57c0 100644 --- a/project/api/tests.py +++ b/project/api/tests.py @@ -16,10 +16,12 @@ DiplomaticRelationFactory, ) -# https://stackoverflow.com/a/815160/ - def memoize(function): + """General-purpose memoization decorator. + + From : https://stackoverflow.com/a/815160/ + """ memo = {} def wrapper(*args): @@ -34,9 +36,9 @@ def wrapper(*args): @memoize -def getUserToken( - client_id=settings.AUTH0_CLIENT_ID, client_secret=settings.AUTH0_CLIENT_SECRET -): +def getUserToken(client_id=settings.AUTH0_CLIENT_ID, # noqa + client_secret=settings.AUTH0_CLIENT_SECRET): + """Autheticate for tests.""" url = "https://" + settings.AUTH0_DOMAIN + "/oauth/token" headers = {"content-type": "application/json"} parameter = { @@ -45,16 +47,15 @@ def getUserToken( "audience": settings.API_IDENTIFIER, "grant_type": "client_credentials", } - response = json.loads(requests.post(url, json=parameter, headers=headers).text) + response = json.loads(requests.post( + url, json=parameter, headers=headers).text) return response["access_token"] class ModelTest(TestCase): @classmethod - def setUpTestData(cls): - """ - Create basic model instances and test user - """ + def setUpTestData(cls): # noqa + """Create basic model instances and test user.""" cls.new_nation = PoliticalEntityFactory( name="Test Nation", url_id="test_nation", @@ -78,7 +79,7 @@ def setUpTestData(cls): entity=cls.new_nation, references=["https://en.wikipedia.org/wiki/Test"], geo=GEOSGeometry( - '{"type": "MultiPolygon","coordinates": [[[ [102.0, 2.0], [103.0, 2.0], [103.0, 3.0], [102.0, 3.0], [102.0, 2.0] ]],[[ [100.0, 0.0], [101.0, 0.0], [101.0, 1.0], [100.0, 1.0], [100.0, 0.0] ],[ [100.2, 0.2], [100.8, 0.2], [100.8, 0.8], [100.2, 0.8], [100.2, 0.2] ]]]}' + '{"type": "MultiPolygon","coordinates": [[[ [102.0, 2.0], [103.0, 2.0], [103.0, 3.0], [102.0, 3.0], [102.0, 2.0] ]],[[ [100.0, 0.0], [101.0, 0.0], [101.0, 1.0], [100.0, 1.0], [100.0, 0.0] ],[ [100.2, 0.2], [100.8, 0.2], [100.8, 0.8], [100.2, 0.8], [100.2, 0.2] ]]]}' # noqa ), ) cls.diprel = DiplomaticRelationFactory( @@ -91,9 +92,7 @@ def setUpTestData(cls): cls.diprel.child_parties.add(cls.child_nation) def test_model_can_create_politicalentity(self): - """ - Ensure that we can create politicalentities. - """ + """Ensure that we can create politicalentities.""" new_politicalentity = PoliticalEntity.objects.create( name="Test Nation2", url_id="test_nation2", @@ -103,11 +102,13 @@ def test_model_can_create_politicalentity(self): links=[], ) new_politicalentity.save() - self.assertTrue(PoliticalEntity.objects.filter(url_id="test_nation2").exists()) + self.assertTrue(PoliticalEntity.objects.filter( + url_id="test_nation2").exists()) def test_model_can_create_territory(self): - """ - Ensure that we can create territories. Specifically checks if we can create [start_date+1,end_date-1] + """Ensure that we can create territories. + + Specifically checks if we can create [start_date+1,end_date-1]. """ politicalentity = PoliticalEntity.objects.get(url_id="test_nation") Territory.objects.create( @@ -116,12 +117,13 @@ def test_model_can_create_territory(self): entity=politicalentity, references=["https://en.wikipedia.org/wiki/Test"], geo=GEOSGeometry( - '{"type": "MultiPolygon","coordinates": [[[ [102.0, 2.0], [103.0, 2.0], [103.0, 3.0], [102.0, 3.0], [102.0, 2.0] ]],[[ [100.0, 0.0], [101.0, 0.0], [101.0, 1.0], [100.0, 1.0], [100.0, 0.0] ],[ [100.2, 0.2], [100.8, 0.2], [100.8, 0.8], [100.2, 0.8], [100.2, 0.2] ]]]}' + '{"type": "MultiPolygon","coordinates": [[[ [102.0, 2.0], [103.0, 2.0], [103.0, 3.0], [102.0, 3.0], [102.0, 2.0] ]],[[ [100.0, 0.0], [101.0, 0.0], [101.0, 1.0], [100.0, 1.0], [100.0, 0.0] ],[ [100.2, 0.2], [100.8, 0.2], [100.8, 0.8], [100.2, 0.8], [100.2, 0.2] ]]]}' # noqa ), ) self.assertTrue( Territory.objects.filter( - entity=politicalentity, start_date="0007-01-01", end_date="0008-01-01" + entity=politicalentity, start_date="0007-01-01", + end_date="0008-01-01" ).exists() ) politicalentity = PoliticalEntity.objects.get(url_id="test_nation") @@ -131,19 +133,18 @@ def test_model_can_create_territory(self): entity=politicalentity, references=["https://en.wikipedia.org/wiki/Test"], geo=GEOSGeometry( - '{"type": "MultiPolygon","coordinates": [[[ [102.0, 2.0], [103.0, 2.0], [103.0, 3.0], [102.0, 3.0], [102.0, 2.0] ]],[[ [100.0, 0.0], [101.0, 0.0], [101.0, 1.0], [100.0, 1.0], [100.0, 0.0] ],[ [100.2, 0.2], [100.8, 0.2], [100.8, 0.8], [100.2, 0.8], [100.2, 0.2] ]]]}' + '{"type": "MultiPolygon","coordinates": [[[ [102.0, 2.0], [103.0, 2.0], [103.0, 3.0], [102.0, 3.0], [102.0, 2.0] ]],[[ [100.0, 0.0], [101.0, 0.0], [101.0, 1.0], [100.0, 1.0], [100.0, 0.0] ],[ [100.2, 0.2], [100.8, 0.2], [100.8, 0.8], [100.2, 0.8], [100.2, 0.2] ]]]}' # noqa ), ) self.assertTrue( Territory.objects.filter( - entity=politicalentity, start_date="0004-01-02", end_date="0006-12-31" + entity=politicalentity, start_date="0004-01-02", + end_date="0006-12-31" ).exists() ) def test_model_can_not_create_territory(self): - """ - Ensure that date checks work. - """ + """Ensure that date checks work.""" new_politicalentity = PoliticalEntity.objects.get(url_id="test_nation") with self.assertRaises(ValidationError): Territory.objects.create( @@ -152,7 +153,7 @@ def test_model_can_not_create_territory(self): entity=new_politicalentity, references=["https://en.wikipedia.org/wiki/Test"], geo=GEOSGeometry( - '{"type": "MultiPolygon","coordinates": [[[ [102.0, 2.0], [103.0, 2.0], [103.0, 3.0], [102.0, 3.0], [102.0, 2.0] ]],[[ [100.0, 0.0], [101.0, 0.0], [101.0, 1.0], [100.0, 1.0], [100.0, 0.0] ],[ [100.2, 0.2], [100.8, 0.2], [100.8, 0.8], [100.2, 0.8], [100.2, 0.2] ]]]}' + '{"type": "MultiPolygon","coordinates": [[[ [102.0, 2.0], [103.0, 2.0], [103.0, 3.0], [102.0, 3.0], [102.0, 2.0] ]],[[ [100.0, 0.0], [101.0, 0.0], [101.0, 1.0], [100.0, 1.0], [100.0, 0.0] ],[ [100.2, 0.2], [100.8, 0.2], [100.8, 0.8], [100.2, 0.8], [100.2, 0.2] ]]]}' # noqa ), ) with self.assertRaises(ValidationError): @@ -162,7 +163,7 @@ def test_model_can_not_create_territory(self): entity=new_politicalentity, references=["https://en.wikipedia.org/wiki/Test"], geo=GEOSGeometry( - '{"type": "MultiPolygon","coordinates": [[[ [102.0, 2.0], [103.0, 2.0], [103.0, 3.0], [102.0, 3.0], [102.0, 2.0] ]],[[ [100.0, 0.0], [101.0, 0.0], [101.0, 1.0], [100.0, 1.0], [100.0, 0.0] ],[ [100.2, 0.2], [100.8, 0.2], [100.8, 0.8], [100.2, 0.8], [100.2, 0.2] ]]]}' + '{"type": "MultiPolygon","coordinates": [[[ [102.0, 2.0], [103.0, 2.0], [103.0, 3.0], [102.0, 3.0], [102.0, 2.0] ]],[[ [100.0, 0.0], [101.0, 0.0], [101.0, 1.0], [100.0, 1.0], [100.0, 0.0] ],[ [100.2, 0.2], [100.8, 0.2], [100.8, 0.8], [100.2, 0.8], [100.2, 0.2] ]]]}' # noqa ), ) with self.assertRaises(ValidationError): @@ -172,7 +173,7 @@ def test_model_can_not_create_territory(self): entity=new_politicalentity, references=["https://en.wikipedia.org/wiki/Test"], geo=GEOSGeometry( - '{"type": "MultiPolygon","coordinates": [[[ [102.0, 2.0], [103.0, 2.0], [103.0, 3.0], [102.0, 3.0], [102.0, 2.0] ]],[[ [100.0, 0.0], [101.0, 0.0], [101.0, 1.0], [100.0, 1.0], [100.0, 0.0] ],[ [100.2, 0.2], [100.8, 0.2], [100.8, 0.8], [100.2, 0.8], [100.2, 0.2] ]]]}' + '{"type": "MultiPolygon","coordinates": [[[ [102.0, 2.0], [103.0, 2.0], [103.0, 3.0], [102.0, 3.0], [102.0, 2.0] ]],[[ [100.0, 0.0], [101.0, 0.0], [101.0, 1.0], [100.0, 1.0], [100.0, 0.0] ],[ [100.2, 0.2], [100.8, 0.2], [100.8, 0.8], [100.2, 0.8], [100.2, 0.2] ]]]}' # noqa ), ) with self.assertRaises(ValidationError): @@ -182,17 +183,15 @@ def test_model_can_not_create_territory(self): entity=new_politicalentity, references=["https://en.wikipedia.org/wiki/Test"], geo=GEOSGeometry( - '{"type": "MultiPolygon","coordinates": [[[ [102.0, 2.0], [103.0, 2.0], [103.0, 3.0], [102.0, 3.0], [102.0, 2.0] ]],[[ [100.0, 0.0], [101.0, 0.0], [101.0, 1.0], [100.0, 1.0], [100.0, 0.0] ],[ [100.2, 0.2], [100.8, 0.2], [100.8, 0.8], [100.2, 0.8], [100.2, 0.2] ]]]}' + '{"type": "MultiPolygon","coordinates": [[[ [102.0, 2.0], [103.0, 2.0], [103.0, 3.0], [102.0, 3.0], [102.0, 2.0] ]],[[ [100.0, 0.0], [101.0, 0.0], [101.0, 1.0], [100.0, 1.0], [100.0, 0.0] ],[ [100.2, 0.2], [100.8, 0.2], [100.8, 0.8], [100.2, 0.8], [100.2, 0.2] ]]]}' # noqa ), ) class APITest(APITestCase): @classmethod - def setUpTestData(cls): - """ - Create basic model instances - """ + def setUpTestData(cls): # noqa + """Create basic model instances.""" cls.new_nation = PoliticalEntityFactory( name="Test Nation", url_id="test_nation", @@ -215,7 +214,7 @@ def setUpTestData(cls): entity=cls.new_nation, references=["https://en.wikipedia.org/wiki/Test"], geo=GEOSGeometry( - '{"type": "MultiPolygon","coordinates": [[[ [102.0, 2.0], [103.0, 2.0], [103.0, 3.0], [102.0, 3.0], [102.0, 2.0] ]],[[ [100.0, 0.0], [101.0, 0.0], [101.0, 1.0], [100.0, 1.0], [100.0, 0.0] ],[ [100.2, 0.2], [100.8, 0.2], [100.8, 0.8], [100.2, 0.8], [100.2, 0.2] ]]]}' + '{"type": "MultiPolygon","coordinates": [[[ [102.0, 2.0], [103.0, 2.0], [103.0, 3.0], [102.0, 3.0], [102.0, 2.0] ]],[[ [100.0, 0.0], [101.0, 0.0], [101.0, 1.0], [100.0, 1.0], [100.0, 0.0] ],[ [100.2, 0.2], [100.8, 0.2], [100.8, 0.8], [100.2, 0.8], [100.2, 0.2] ]]]}' # noqa ), ) cls.territory2 = TerritoryFactory( @@ -224,7 +223,7 @@ def setUpTestData(cls): entity=cls.new_nation, references=["https://en.wikipedia.org/wiki/Test"], geo=GEOSGeometry( - '{"type": "MultiPolygon","coordinates": [[[ [102.0, 2.0], [103.0, 2.0], [103.0, 3.0], [102.0, 3.0], [102.0, 2.0] ]],[[ [100.0, 0.0], [101.0, 0.0], [101.0, 1.0], [100.0, 1.0], [100.0, 0.0] ]]]}' + '{"type": "MultiPolygon","coordinates": [[[ [102.0, 2.0], [103.0, 2.0], [103.0, 3.0], [102.0, 3.0], [102.0, 2.0] ]],[[ [100.0, 0.0], [101.0, 0.0], [101.0, 1.0], [100.0, 1.0], [100.0, 0.0] ]]]}' # noqa ), ) cls.diprel = DiplomaticRelationFactory( @@ -236,10 +235,8 @@ def setUpTestData(cls): cls.diprel.parent_parties.add(cls.new_nation) cls.diprel.child_parties.add(cls.child_nation) - def test_api_can_create_PoliticalEntity(self): - """ - Ensure we can create a new PoliticalEntity - """ + def test_api_can_create_PoliticalEntity(self): # noqa + """Ensure we can create a new PoliticalEntity.""" url = reverse("politicalentity-list") data = { "name": "Created Test Nation", @@ -253,9 +250,10 @@ def test_api_can_create_PoliticalEntity(self): response = self.client.post(url, data, format="json") self.assertEqual(response.status_code, status.HTTP_201_CREATED) self.assertEqual(PoliticalEntity.objects.count(), 3) - self.assertEqual(PoliticalEntity.objects.get(pk=3).name, "Created Test Nation") + self.assertEqual( + PoliticalEntity.objects.get(pk=3).name, "Created Test Nation") - def test_api_can_create_territory_FC(self): + def test_api_can_create_territory_FC(self): # noqa """ Ensure we can create a new territory through a FeatureCollection @@ -266,7 +264,7 @@ def test_api_can_create_territory_FC(self): "end_date": "0009-01-01", "entity": self.new_nation.id, "references": ["https://en.wikipedia.org/wiki/Test"], - "geo": '{"type": "FeatureCollection","features": [{"type": "Feature","id": "id0","geometry": {"type": "Polygon","coordinates": [[[100,0],[101,0],[101,1],[100,1],[100,0]]]},"properties": {"prop0": "value0","prop1": "value1"}},{"type": "Feature","properties": {},"geometry": {"type": "Polygon","coordinates": [[[101.22802734375,-1.043643455908483],[102.601318359375,-2.2516174965491453],[102.864990234375,-0.36254640877525024],[101.22802734375,-1.043643455908483]]]}}]}', + "geo": '{"type": "FeatureCollection","features": [{"type": "Feature","id": "id0","geometry": {"type": "Polygon","coordinates": [[[100,0],[101,0],[101,1],[100,1],[100,0]]]},"properties": {"prop0": "value0","prop1": "value1"}},{"type": "Feature","properties": {},"geometry": {"type": "Polygon","coordinates": [[[101.22802734375,-1.043643455908483],[102.601318359375,-2.2516174965491453],[102.864990234375,-0.36254640877525024],[101.22802734375,-1.043643455908483]]]}}]}', # noqa } self.client.credentials(HTTP_AUTHORIZATION="Bearer " + getUserToken()) response = self.client.post(url, data, format="json") @@ -275,16 +273,14 @@ def test_api_can_create_territory_FC(self): self.assertEqual(Territory.objects.last().entity, self.new_nation) def test_api_can_create_territory(self): - """ - Ensure we can create a new territory - """ + """Ensure we can create a new territory.""" url = reverse("territory-list") data = { "start_date": "0006-01-01", "end_date": "0007-01-01", "entity": self.new_nation.id, "references": ["https://en.wikipedia.org/wiki/Test"], - "geo": '{"type": "MultiPolygon","coordinates": [[[ [102.0, 2.0], [103.0, 2.0], [103.0, 3.0], [102.0, 3.0], [102.0, 2.0] ]],[[ [100.0, 0.0], [101.0, 0.0], [101.0, 1.0], [100.0, 1.0], [100.0, 0.0] ],[ [100.2, 0.2], [100.8, 0.2], [100.8, 0.8], [100.2, 0.8], [100.2, 0.2] ]]]}', + "geo": '{"type": "MultiPolygon","coordinates": [[[ [102.0, 2.0], [103.0, 2.0], [103.0, 3.0], [102.0, 3.0], [102.0, 2.0] ]],[[ [100.0, 0.0], [101.0, 0.0], [101.0, 1.0], [100.0, 1.0], [100.0, 0.0] ],[ [100.2, 0.2], [100.8, 0.2], [100.8, 0.8], [100.2, 0.8], [100.2, 0.2] ]]]}', # noqa } self.client.credentials(HTTP_AUTHORIZATION="Bearer " + getUserToken()) response = self.client.post(url, data, format="json") @@ -292,17 +288,15 @@ def test_api_can_create_territory(self): self.assertEqual(Territory.objects.count(), 3) self.assertEqual(Territory.objects.last().entity, self.new_nation) - def test_api_can_update_PoliticalEntity(self): - """ - Ensure we can update individual PoliticalEntities - """ + def test_api_can_update_PoliticalEntity(self): # noqa + """Ensure we can update individual PoliticalEntities.""" url = reverse("politicalentity-detail", args=["test_nation"]) data = { "name": "Created Test Nation", "url_id": "created_test_nation", "color": "#ccffff", "references": ["https://en.wikipedia.org/wiki/Test"], - "geo": '{"type": "MultiPolygon","coordinates": [[[ [102.0, 2.0], [103.0, 2.0], [103.0, 3.0], [102.0, 3.0], [102.0, 2.0] ]],[[ [100.0, 0.0], [101.0, 0.0], [101.0, 1.0], [100.0, 1.0], [100.0, 0.0] ],[ [100.2, 0.2], [100.8, 0.2], [100.8, 0.8], [100.2, 0.8], [100.2, 0.2] ]]]}', + "geo": '{"type": "MultiPolygon","coordinates": [[[ [102.0, 2.0], [103.0, 2.0], [103.0, 3.0], [102.0, 3.0], [102.0, 2.0] ]],[[ [100.0, 0.0], [101.0, 0.0], [101.0, 1.0], [100.0, 1.0], [100.0, 0.0] ],[ [100.2, 0.2], [100.8, 0.2], [100.8, 0.8], [100.2, 0.8], [100.2, 0.2] ]]]}', # noqa } self.client.credentials(HTTP_AUTHORIZATION="Bearer " + getUserToken()) response = self.client.put(url, data, format="json") @@ -310,43 +304,35 @@ def test_api_can_update_PoliticalEntity(self): self.assertEqual(response.data["name"], "Created Test Nation") def test_api_can_update_territory(self): - """ - Ensure we can update individual territories - """ + """Ensure we can update individual territories.""" url = reverse("territory-detail", args=[self.territory.id]) data = { "start_date": "0010-01-01", "end_date": "0011-01-01", "entity": self.child_nation.id, - "geo": '{"type": "MultiPolygon","coordinates": [[[ [102.0, 2.0], [103.0, 2.0], [103.0, 3.0], [102.0, 3.0], [102.0, 2.0] ]],[[ [100.0, 0.0], [101.0, 0.0], [101.0, 1.0], [100.0, 1.0], [100.0, 0.0] ],[ [100.2, 0.2], [100.8, 0.2], [100.8, 0.8], [100.2, 0.8], [100.2, 0.2] ]]]}', + "geo": '{"type": "MultiPolygon","coordinates": [[[ [102.0, 2.0], [103.0, 2.0], [103.0, 3.0], [102.0, 3.0], [102.0, 2.0] ]],[[ [100.0, 0.0], [101.0, 0.0], [101.0, 1.0], [100.0, 1.0], [100.0, 0.0] ],[ [100.2, 0.2], [100.8, 0.2], [100.8, 0.8], [100.2, 0.8], [100.2, 0.2] ]]]}', # noqa } self.client.credentials(HTTP_AUTHORIZATION="Bearer " + getUserToken()) response = self.client.put(url, data, format="json") self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.data["entity"], self.child_nation.url_id) - def test_api_can_query_PoliticalEntities(self): - """ - Ensure we can query for all PoliticalEntities - """ + def test_api_can_query_PoliticalEntities(self): # noqa + """Ensure we can query for all PoliticalEntities.""" url = reverse("politicalentity-list") response = self.client.get(url, format="json") self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.data[0]["name"], "Test Nation") def test_api_can_query_territories(self): - """ - Ensure we can query for all territories - """ + """Ensure we can query for all territories.""" url = reverse("territory-list") response = self.client.get(url, format="json") self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.data[0]["entity"], "test_nation") def test_api_can_decompress_geojson(self): - """ - Ensure we send geometry as a valid geobuf. - """ + """Ensure we send geometry as a valid geobuf.""" url = reverse("territory-list") response = self.client.get(url, format="json") self.assertEqual(response.status_code, status.HTTP_200_OK) @@ -354,13 +340,11 @@ def test_api_can_decompress_geojson(self): geojson = json.dumps(geobuf.decode(gbuf)) self.assertEqual( geojson, - '{"type": "MultiPolygon", "coordinates": [[[[102.0, 2.0], [103.0, 2.0], [103.0, 3.0], [102.0, 3.0], [102.0, 2.0]]], [[[100.0, 0.0], [101.0, 0.0], [101.0, 1.0], [100.0, 1.0], [100.0, 0.0]], [[100.2, 0.2], [100.8, 0.2], [100.8, 0.8], [100.2, 0.8], [100.2, 0.2]]]]}', + '{"type": "MultiPolygon", "coordinates": [[[[102.0, 2.0], [103.0, 2.0], [103.0, 3.0], [102.0, 3.0], [102.0, 2.0]]], [[[100.0, 0.0], [101.0, 0.0], [101.0, 1.0], [100.0, 1.0], [100.0, 0.0]], [[100.2, 0.2], [100.8, 0.2], [100.8, 0.8], [100.2, 0.8], [100.2, 0.2]]]]}', # noqa ) def test_api_can_query_territory(self): - """ - Ensure we can query individual territories - """ + """Ensure we can query individual territories.""" url = reverse("territory-detail", args=[self.territory.id]) response = self.client.get(url, format="json") self.assertEqual(response.status_code, status.HTTP_200_OK) @@ -372,9 +356,9 @@ def test_api_can_query_territories_bounds(self): region """ url = ( - reverse("territory-list") - + "?bounds=((0.0, 0.0), (0.0, 150.0), (150.0, 150.0), (150.0, 0.0), (0.0, 0.0))" - ) + reverse("territory-list") + + "?bounds=((0.0, 0.0), (0.0, 150.0), (150.0, 150.0), (150.0, 0.0)" + + ", (0.0, 0.0))") response = self.client.get(url, format="json") self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.data[0]["entity"], "test_nation") @@ -382,20 +366,18 @@ def test_api_can_query_territories_bounds(self): def test_api_can_not_query_territories_bounds(self): """ Ensure querying for bounds in which the PoliticalEntity does not - lie in fails + lie in fails. """ url = ( - reverse("territory-list") - + "?bounds=((0.0, 0.0), (0.0, 50.0), (50.0, 50.0), (50.0, 0.0), (0.0, 0.0))" - ) + reverse("territory-list") + + "?bounds=((0.0, 0.0), (0.0, 50.0), (50.0, 50.0), (50.0, 0.0)," + + " (0.0, 0.0))") response = self.client.get(url, format="json") self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertTrue(not response.data) def test_api_can_query_territories_date(self): - """ - Ensure we can query for territories with a date - """ + """Ensure we can query for territories with a date.""" url = reverse("territory-list") + "?date=0001-01-01" response = self.client.get(url, format="json") self.assertEqual(response.status_code, status.HTTP_200_OK) @@ -404,27 +386,24 @@ def test_api_can_query_territories_date(self): def test_api_can_not_query_territories_date(self): """ Ensure querying for territories with an earlier start - date fails + date fails. """ url = reverse("territory-list") + "?date=2020-01-01" response = self.client.get(url, format="json") self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertTrue(not response.data) - def test_api_can_query_PoliticalEntity(self): - """ - Ensure we can query individual PoliticalEntities - """ + def test_api_can_query_PoliticalEntity(self): # noqa + """Ensure we can query individual PoliticalEntities.""" url = reverse("politicalentity-detail", args=["test_nation"]) response = self.client.get(url, format="json") self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.data["name"], "Test Nation") def test_api_can_query_territories_exclude(self): - """ - Ensure we can exclude territories by id - """ - url = reverse("territory-list") + "?exclude_ids=" + str(self.territory.id) + """.Ensure we can exclude territories by id.""" + url = (reverse("territory-list") + "?exclude_ids=" + + str(self.territory.id)) response = self.client.get(url, format="json") self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.data[0]["id"], self.territory2.id) @@ -432,9 +411,7 @@ def test_api_can_query_territories_exclude(self): self.assertTrue(not response.data) def test_api_can_create_diprel(self): - """ - Ensure we can create a new DiplomaticRelation - """ + """Ensure we can create a new DiplomaticRelation.""" url = reverse("diplomaticrelation-list") data = { "start_date": "0001-01-01", @@ -451,9 +428,7 @@ def test_api_can_create_diprel(self): self.assertEqual(DiplomaticRelation.objects.last().diplo_type, "A") def test_api_can_update_diprel(self): - """ - Ensure we can update individual DipRels - """ + """Ensure we can update individual DipRels.""" url = reverse("diplomaticrelation-detail", args=[self.diprel.id]) data = { "start_date": "0006-01-01", @@ -469,18 +444,14 @@ def test_api_can_update_diprel(self): self.assertEqual(response.data["diplo_type"], "A") def test_api_can_query_diprels(self): - """ - Ensure we can query for all DipRels - """ + """Ensure we can query for all DipRels.""" url = reverse("diplomaticrelation-list") response = self.client.get(url, format="json") self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.data[0]["diplo_type"], "A") def test_api_can_query_diprel(self): - """ - Ensure we can query individual DipRels - """ + """Ensure we can query individual DipRels.""" url = reverse("diplomaticrelation-detail", args=[self.diprel.id]) response = self.client.get(url, format="json") self.assertEqual(response.status_code, status.HTTP_200_OK) diff --git a/project/api/user.py b/project/api/user.py index 7322b6a..ee6934f 100644 --- a/project/api/user.py +++ b/project/api/user.py @@ -2,6 +2,7 @@ def jwt_get_username_from_payload_handler(payload): + """Handle for JWT authetication in settings.py.""" username = payload.get("sub").replace("|", ".") authenticate(remote_user=username) return username diff --git a/project/api/views.py b/project/api/views.py index 515c5ac..3759ee7 100644 --- a/project/api/views.py +++ b/project/api/views.py @@ -10,10 +10,7 @@ class PoliticalEntityViewSet(viewsets.ModelViewSet): - """ - Viewset for the PoliticalEntity model - """ - + """Viewset for the PoliticalEntity model.""" permission_classes = (permissions.IsAuthenticatedOrReadOnly,) queryset = PoliticalEntity.objects.all() serializer_class = PoliticalEntitySerializer @@ -23,10 +20,7 @@ class PoliticalEntityViewSet(viewsets.ModelViewSet): class TerritoryViewSet(viewsets.ModelViewSet): - """ - Viewset for the Territory model - """ - + """Viewset for the Territory model.""" permission_classes = (permissions.IsAuthenticatedOrReadOnly,) serializer_class = TerritorySerializer filter_class = TerritoryFilter @@ -37,10 +31,7 @@ class TerritoryViewSet(viewsets.ModelViewSet): class DiplomaticRelationViewSet(viewsets.ModelViewSet): - """ - Viewset for the DiplomaticRelation model - """ - + """Viewset for the DiplomaticRelation model.""" permission_classes = (permissions.IsAuthenticatedOrReadOnly,) queryset = DiplomaticRelation.objects.all() serializer_class = DiplomaticRelationSerializer diff --git a/project/interactivemap/settings.py b/project/interactivemap/settings.py index 6390c63..65ed8bc 100644 --- a/project/interactivemap/settings.py +++ b/project/interactivemap/settings.py @@ -113,12 +113,13 @@ # https://docs.djangoproject.com/en/2.0/ref/settings/#auth-password-validators AUTH_PASSWORD_VALIDATORS = [ - { - "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator" - }, + {"NAME": "django.contrib.auth.password_validation.UserAttributeSimilarity" + "Validator"}, {"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator"}, - {"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator"}, - {"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator"}, + {"NAME": "django.contrib.auth.password_validation.CommonPassword" + "Validator"}, + {"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator" + }, ] @@ -145,18 +146,22 @@ REST_FRAMEWORK = { # Use Django's standard `django.contrib.auth` permissions, # or allow read-only access for unauthenticated users. - "DEFAULT_PERMISSION_CLASSES": ["rest_framework.permissions.IsAuthenticated"], + "DEFAULT_PERMISSION_CLASSES": [ + "rest_framework.permissions.IsAuthenticated"], "DEFAULT_AUTHENTICATION_CLASSES": ( "rest_framework_jwt.authentication.JSONWebTokenAuthentication", ), - "DEFAULT_FILTER_BACKENDS": ("django_filters.rest_framework.DjangoFilterBackend",), + "DEFAULT_FILTER_BACKENDS": ( + "django_filters.rest_framework.DjangoFilterBackend",), } CORS_ORIGIN_WHITELIST = ("localhost:3000", "interactivemap-frontend-*.now.sh") AUTH0_DOMAIN = os.environ.get("AUTH0_DOMAIN", "dwaxe.auth0.com") -API_IDENTIFIER = os.environ.get("API_IDENTIFIER", "https://dwaxe.auth0.com/api/v2/") -AUTH0_CLIENT_ID = os.environ.get("AUTH0_CLIENT_ID", "iXGypOg8u5cvAhyY7OYxnkGbgNCK9gDT") +API_IDENTIFIER = os.environ.get( + "API_IDENTIFIER", "https://dwaxe.auth0.com/api/v2/") +AUTH0_CLIENT_ID = os.environ.get( + "AUTH0_CLIENT_ID", "iXGypOg8u5cvAhyY7OYxnkGbgNCK9gDT") AUTH0_CLIENT_SECRET = os.environ.get( "AUTH0_CLIENT_SECRET", "sQ-vtQ1Ltzy1JHqwH35M20E6tvuZznXytnPWxa8wUmyuCCn04LMLt342ygg9g51u", @@ -165,18 +170,22 @@ JWT_ISSUER = None if AUTH0_DOMAIN: - jsonurl = request.urlopen("https://" + AUTH0_DOMAIN + "/.well-known/jwks.json") + jsonurl = request.urlopen( + "https://" + AUTH0_DOMAIN + "/.well-known/jwks.json") jwks = json.loads(jsonurl.read()) # Add a line-break every 64 chars # https://stackoverflow.com/questions/2657693/insert-a-newline-character-every-64-characters-using-python - body = re.sub("(.{64})", "\\1\n", jwks["keys"][0]["x5c"][0], 0, re.DOTALL) - cert = "-----BEGIN CERTIFICATE-----\n" + body + "\n-----END CERTIFICATE-----" - certificate = load_pem_x509_certificate(cert.encode("utf-8"), default_backend()) + body = re.sub("(.{64})", "\\1\n", jwks['keys'][0]['x5c'][0], 0, re.DOTALL) + cert = ('-----BEGIN CERTIFICATE-----\n' + body + + '\n-----END CERTIFICATE-----') + certificate = load_pem_x509_certificate( + cert.encode('utf-8'), default_backend()) PUBLIC_KEY = certificate.public_key() JWT_ISSUER = "https://" + AUTH0_DOMAIN + "/" JWT_AUTH = { - "JWT_PAYLOAD_GET_USERNAME_HANDLER": "api.user.jwt_get_username_from_payload_handler", + "JWT_PAYLOAD_GET_USERNAME_HANDLER": + "api.user.jwt_get_username_from_payload_handler", "JWT_PUBLIC_KEY": PUBLIC_KEY, "JWT_ALGORITHM": "RS256", "JWT_AUDIENCE": API_IDENTIFIER, diff --git a/project/manage.py b/project/manage.py index 2f9cdd5..1a7439c 100755 --- a/project/manage.py +++ b/project/manage.py @@ -23,5 +23,5 @@ "Couldn't import Django. Are you sure it's installed and " "available on your PYTHONPATH environment variable? Did you " "forget to activate a virtual environment?" - ) from exc + ) from exc # noqa execute_from_command_line(sys.argv)