Skip to content

Commit cdf65d3

Browse files
Merge pull request #1375 from IFRCGo/feature/import-admin2
Add Admin2 to backend
2 parents 16b882b + e110973 commit cdf65d3

File tree

8 files changed

+180
-1
lines changed

8 files changed

+180
-1
lines changed

api/drf_views.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
CountrySnippet,
3737

3838
District,
39+
Admin2,
3940

4041
Event,
4142
Snippet,
@@ -80,6 +81,8 @@
8081
MiniDistrictGeoSerializer,
8182
DistrictSerializerRMD,
8283

84+
Admin2Serializer,
85+
8386
SnippetSerializer,
8487
ListMiniEventSerializer,
8588
ListEventSerializer,
@@ -337,6 +340,19 @@ def get_serializer_class(self):
337340
return DistrictSerializer
338341

339342

343+
class Admin2Filter(filters.FilterSet):
344+
class Meta:
345+
model = Admin2
346+
fields = ('admin1',)
347+
348+
349+
class Admin2Viewset(viewsets.ReadOnlyModelViewSet):
350+
queryset = Admin2.objects.all()
351+
filterset_class = Admin2Filter
352+
search_fields = ('name', 'district__name', 'district__country__name')
353+
serializer_class = Admin2Serializer
354+
355+
340356
class EventFilter(filters.FilterSet):
341357
dtype = filters.NumberFilter(field_name='dtype', lookup_expr='exact')
342358
is_featured = filters.BooleanFilter(field_name='is_featured')
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
from django.db import transaction
2+
import csv
3+
from django.core.management.base import BaseCommand, CommandError
4+
from api.models import District, Admin2
5+
6+
7+
class Command(BaseCommand):
8+
help = "Import admin2 data from CSV"
9+
missing_args_message = "Filename is missing. Filename / path to CSV file required. Required headers in CSV: distr_code, GOadm2code, ADM2"
10+
11+
def add_arguments(self, parser):
12+
parser.add_argument('filename', nargs='+', type=str)
13+
14+
@transaction.atomic
15+
def handle(self, *args, **options):
16+
filename = options['filename'][0]
17+
with open(filename) as csvfile:
18+
reader = csv.DictReader(csvfile)
19+
for row in reader:
20+
print(row)
21+
district_code = row['distr_code']
22+
district = District.objects.get(code=district_code)
23+
print(district.name)
24+
adm2_code = row['GOadm2code']
25+
try:
26+
adm2 = Admin2.objects.get(code=adm2_code)
27+
except:
28+
print('HELLO')
29+
adm2 = Admin2()
30+
adm2.code = adm2_code
31+
adm2.admin1 = district
32+
adm2.name = row['ADM2']
33+
adm2.save()
34+
print(f'{adm2.name} saved')
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# Generated by Django 2.2.27 on 2022-03-23 11:22
2+
3+
import django.contrib.gis.db.models.fields
4+
from django.db import migrations, models
5+
import django.db.models.deletion
6+
7+
8+
class Migration(migrations.Migration):
9+
10+
dependencies = [
11+
('api', '0149_auto_20220318_0413'),
12+
]
13+
14+
operations = [
15+
migrations.CreateModel(
16+
name='Admin2',
17+
fields=[
18+
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
19+
('name', models.CharField(max_length=100, verbose_name='name')),
20+
('code', models.CharField(max_length=64, unique=True, verbose_name='code')),
21+
('centroid', django.contrib.gis.db.models.fields.PointField(blank=True, null=True, srid=4326)),
22+
('bbox', django.contrib.gis.db.models.fields.PolygonField(blank=True, null=True, srid=4326)),
23+
('admin1', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='api.District', verbose_name='Admin 1')),
24+
],
25+
options={
26+
'verbose_name': 'admin2',
27+
'verbose_name_plural': 'admin2s',
28+
'ordering': ('code',),
29+
},
30+
),
31+
migrations.CreateModel(
32+
name='Admin2Geoms',
33+
fields=[
34+
('geom', django.contrib.gis.db.models.fields.MultiPolygonField(blank=True, null=True, srid=4326)),
35+
('admin2', models.OneToOneField(on_delete=django.db.models.deletion.DO_NOTHING, primary_key=True, serialize=False, to='api.Admin2', verbose_name='admin2')),
36+
],
37+
),
38+
]
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# Generated by Django 2.2.27 on 2022-03-25 10:27
2+
3+
from django.db import migrations
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
('api', '0150_event_emergency_response_contact_email'),
10+
('api', '0150_admin2_admin2geoms'),
11+
]
12+
13+
operations = [
14+
]

api/models.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,22 @@ def __str__(self):
271271
return f'{country_name} - {self.name}'
272272

273273

274+
class Admin2(models.Model):
275+
""" Used for admin2, District refers to admin1 """
276+
admin1 = models.ForeignKey(District, verbose_name=_('Admin 1'), on_delete=models.PROTECT)
277+
name = models.CharField(verbose_name=_('name'), max_length=100)
278+
code = models.CharField(verbose_name=_('code'), max_length=64, unique=True)
279+
centroid = models.PointField(srid=4326, blank=True, null=True)
280+
bbox = models.PolygonField(srid=4326, blank=True, null=True)
281+
282+
class Meta:
283+
verbose_name = _('admin2')
284+
verbose_name_plural = _('admin2s')
285+
ordering = ('code',)
286+
287+
def __str__(self):
288+
return f'{self.admin1} - {self.name}'
289+
274290
class CountryGeoms(models.Model):
275291
""" Admin0 geometries """
276292
geom = models.MultiPolygonField(srid=4326, blank=True, null=True)
@@ -282,6 +298,10 @@ class DistrictGeoms(models.Model):
282298
geom = models.MultiPolygonField(srid=4326, blank=True, null=True)
283299
district = models.OneToOneField(District, verbose_name=_('district'), on_delete=models.DO_NOTHING, primary_key=True)
284300

301+
class Admin2Geoms(models.Model):
302+
""" Admin2 geometries """
303+
geom = models.MultiPolygonField(srid=4326, blank=True, null=True)
304+
admin2 = models.OneToOneField(Admin2, verbose_name=_('admin2'), on_delete=models.DO_NOTHING, primary_key=True)
285305

286306
class VisibilityChoices(IntEnum):
287307
MEMBERSHIP = 1

api/serializers.py

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
Region,
1717
Country,
1818
District,
19+
Admin2,
1920
CountryKeyFigure,
2021
RegionKeyFigure,
2122
CountrySnippet,
@@ -52,6 +53,28 @@
5253
from notifications.models import Subscription
5354

5455

56+
class GeoSerializerMixin:
57+
'''
58+
A mixin class to encapsulate common methods
59+
used across serializers that deal with geo objects.
60+
Will allow us to avoid repeating code to convert objects
61+
to GeoJSON, etc.
62+
63+
FIXME: use this base class for existing serializers using geo objects.
64+
FIXME: the methods can probably be thought through a bit better
65+
'''
66+
def get_bbox(self, district):
67+
if district.bbox:
68+
return json.loads(district.bbox.geojson)
69+
else:
70+
return None
71+
72+
def get_centroid(self, district):
73+
if district.centroid:
74+
return json.loads(district.centroid.geojson)
75+
else:
76+
return None
77+
5578
class DisasterTypeSerializer(ModelSerializer):
5679
class Meta:
5780
model = DisasterType
@@ -185,13 +208,25 @@ class Meta:
185208
fields = ('name', 'code', 'country', 'id', 'is_deprecated',)
186209

187210

211+
212+
213+
class Admin2Serializer(GeoSerializerMixin, ModelSerializer):
214+
bbox = serializers.SerializerMethodField()
215+
centroid = serializers.SerializerMethodField()
216+
district_id = serializers.IntegerField(source='admin1.id', read_only=True)
217+
218+
class Meta:
219+
model = Admin2
220+
fields = ('district_id', 'name', 'code', 'bbox', 'centroid',)
221+
222+
188223
class MiniDistrictSerializer(ModelSerializer):
189224
class Meta:
190225
model = District
191226
fields = ('name', 'code', 'id', 'is_enclave', 'is_deprecated',)
192227

193228

194-
class MiniDistrictGeoSerializer(ModelSerializer):
229+
class MiniDistrictGeoSerializer(GeoSerializerMixin, ModelSerializer):
195230
bbox = serializers.SerializerMethodField()
196231
centroid = serializers.SerializerMethodField()
197232
country_name = serializers.CharField(source='country.name', read_only=True)

api/test_views.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -397,3 +397,24 @@ def test_historical_events(self):
397397
response = self.client.get(f'/api/v2/go-historical/?region={region1.id}').json()
398398
self.assertEqual(response['count'], 1)
399399
self.assertEqual(response['results'][0]['id'], event1.id)
400+
401+
402+
class Admin2Test(APITestCase):
403+
404+
def test_admin2_api(self):
405+
region = models.Region.objects.create(name=1)
406+
country = models.Country.objects.create(name='Nepal', iso3='NLP', region=region)
407+
admin1_1 = models.District.objects.create(name='admin1 1', code='NLP01', country=country)
408+
admin1_2 = models.District.objects.create(name='admin1 2', code='NLP02', country=country)
409+
admin2_1 = models.Admin2.objects.create(name='test 1', admin1=admin1_1, code='1')
410+
admin2_2 = models.Admin2.objects.create(name='test 2', admin1=admin1_2, code='2')
411+
412+
# test fetching all admin2
413+
response = self.client.get('/api/v2/admin2/').json()
414+
self.assertEqual(response['count'], 2)
415+
416+
# test filtering by district
417+
response = self.client.get(f'/api/v2/admin2/?admin1={admin1_1.id}').json()
418+
self.assertEqual(response['count'], 1)
419+
self.assertEqual(response['results'][0]['name'], 'test 1')
420+

main/urls.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@
8585
router.register(r'country_snippet', api_views.CountrySnippetViewset, basename='country_snippet')
8686
router.register(r'data-bank/country-overview', CountryOverviewViewSet)
8787
router.register(r'disaster_type', api_views.DisasterTypeViewset, basename='disaster_type')
88+
router.register(r'admin2', api_views.Admin2Viewset, basename='admin2')
8889
router.register(r'district', api_views.DistrictViewset, basename='district')
8990
router.register(r'district_rmd', api_views.DistrictRMDViewset, basename='district_rmd')
9091
router.register(r'domainwhitelist', registration_views.DomainWhitelistViewset)

0 commit comments

Comments
 (0)