Skip to content

Commit 02a79cd

Browse files
authored
Include new columns to VectorItem and TabularItem tables (#19)
1 parent f839bbc commit 02a79cd

File tree

12 files changed

+511
-113
lines changed

12 files changed

+511
-113
lines changed

vbos/datasets/admin.py

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,12 @@
88
from django.shortcuts import render, redirect, reverse
99
from django.urls import path
1010

11+
from vbos.datasets.utils import CSVRow, GeoJSONProperties
12+
1113
from .models import (
14+
AreaCouncil,
1215
Cluster,
16+
Province,
1317
RasterDataset,
1418
RasterFile,
1519
TabularDataset,
@@ -44,8 +48,8 @@ class VectorDatasetAdmin(admin.ModelAdmin):
4448

4549
@admin.register(VectorItem)
4650
class VectorItemAdmin(admin.GISModelAdmin):
47-
list_display = ["id", "dataset", "metadata"]
48-
list_filter = ["dataset"]
51+
list_display = ["id", "dataset", "name", "attribute", "province", "area_council"]
52+
list_filter = ["dataset", "province", "area_council"]
4953

5054
def get_urls(self):
5155
urls = super().get_urls()
@@ -82,10 +86,20 @@ def import_file(self, request):
8286
error_count = 0
8387

8488
for item in geojson_content["features"]:
89+
metadata = GeoJSONProperties(item["properties"])
8590
try:
8691
VectorItem.objects.create(
8792
dataset=form.cleaned_data["dataset"],
88-
metadata=item["properties"],
93+
metadata=metadata.properties,
94+
name=metadata.name,
95+
ref=metadata.ref,
96+
attribute=metadata.attribute,
97+
province=Province.objects.filter(
98+
name__iexact=metadata.province
99+
).first(),
100+
area_council=AreaCouncil.objects.filter(
101+
name__iexact=metadata.area_council
102+
).first(),
89103
geometry=GEOSGeometry(json.dumps(item["geometry"])),
90104
)
91105
created_count += 1
@@ -126,8 +140,8 @@ class TabularDatasetAdmin(admin.ModelAdmin):
126140

127141
@admin.register(TabularItem)
128142
class TabularItemAdmin(admin.GISModelAdmin):
129-
list_display = ["id", "dataset", "data"]
130-
list_filter = ["dataset"]
143+
list_display = ["id", "dataset", "province", "area_council", "attribute", "value"]
144+
list_filter = ["dataset", "province", "area_council"]
131145

132146
def get_urls(self):
133147
urls = super().get_urls()
@@ -166,8 +180,19 @@ def import_file(self, request):
166180

167181
for row in reader: # start=2 to account for header row
168182
try:
183+
csv_row = CSVRow(row)
169184
TabularItem.objects.create(
170-
dataset=form.cleaned_data["dataset"], data=row
185+
dataset=form.cleaned_data["dataset"],
186+
metadata=csv_row.metadata,
187+
attribute=csv_row.attribute,
188+
value=csv_row.value,
189+
date=csv_row.date,
190+
province=Province.objects.filter(
191+
name__iexact=csv_row.province
192+
).first(),
193+
area_council=AreaCouncil.objects.filter(
194+
name__iexact=csv_row.area_council
195+
).first(),
171196
)
172197
created_count += 1
173198
except Exception as e:

vbos/datasets/filters.py

Lines changed: 33 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
)
88

99
from .models import (
10+
AreaCouncil,
11+
Province,
1012
RasterDataset,
1113
TabularItem,
1214
VectorDataset,
@@ -50,9 +52,20 @@ class Meta:
5052
fields = ["name", "source", "cluster", "created", "updated"]
5153

5254

53-
class TabularItemFilter(FilterSet):
54-
filter = CharFilter(
55-
field_name="data",
55+
class DataItemsBaseFilter(FilterSet):
56+
attribute = CharFilter(lookup_expr="icontains")
57+
province = ModelChoiceFilter(
58+
field_name="province__name",
59+
to_field_name="name__iexact",
60+
queryset=Province.objects.all(),
61+
)
62+
area_council = ModelChoiceFilter(
63+
field_name="area_council__name",
64+
to_field_name="name__iexact",
65+
queryset=AreaCouncil.objects.all(),
66+
)
67+
metadata = CharFilter(
68+
field_name="metadata",
5669
method="filter_metadata",
5770
help_text="""Filter by the content of the data JSONField.""",
5871
)
@@ -91,18 +104,27 @@ def filter_metadata(self, queryset, name, value):
91104

92105
return queryset
93106

107+
108+
class TabularItemFilter(DataItemsBaseFilter):
109+
date = DateFromToRangeFilter()
110+
94111
class Meta:
95112
model = TabularItem
96-
fields = ["filter", "id"]
113+
fields = ["metadata", "attribute", "province", "area_council", "id", "date"]
97114

98115

99-
class VectorItemFilter(TabularItemFilter):
100-
filter = CharFilter(
101-
field_name="metadata",
102-
method="filter_metadata",
103-
help_text="""Filter by the content of the metadata JSONField.""",
104-
)
116+
class VectorItemFilter(DataItemsBaseFilter):
117+
name = CharFilter(lookup_expr="icontains")
118+
ref = CharFilter(lookup_expr="icontains")
105119

106120
class Meta:
107121
model = VectorItem
108-
fields = ["filter", "id"]
122+
fields = [
123+
"metadata",
124+
"attribute",
125+
"province",
126+
"area_council",
127+
"id",
128+
"name",
129+
"ref",
130+
]

vbos/datasets/fixtures/test.csv

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Year,Month,Attribute,Province,Area Council,Value,Other
2+
2024,January,ecce,TAFEA,Futuna,1154,yes
3+
2022,may,secondary,TAFEA,Futuna,1154,no
4+
2025,,primary,,,1154

vbos/datasets/fixtures/test.geojson

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,12 @@
44
{
55
"type": "Feature",
66
"properties": {
7-
"name": "Area 1"
7+
"name": "Area 1",
8+
"ref": "12NC",
9+
"Province": "Torba",
10+
"Area Council": "East Gaua",
11+
"Attribute": "Schools",
12+
"extra": "value"
813
},
914
"geometry": {
1015
"coordinates": [
@@ -36,7 +41,13 @@
3641
},
3742
{
3843
"type": "Feature",
39-
"properties": {"name": "Line 1", "col": "val"},
44+
"properties": {
45+
"name": "Line 1",
46+
"ref": "13NC",
47+
"Province": "TAFEA",
48+
"Area Council": "Futuna",
49+
"Attribute": "Roads"
50+
},
4051
"geometry": {
4152
"coordinates": [
4253
[
@@ -53,7 +64,14 @@
5364
},
5465
{
5566
"type": "Feature",
56-
"properties": {"name": "Point 1", "source": "OpenStreetMap"},
67+
"properties": {
68+
"name": "Point 2",
69+
"ref": "14NC",
70+
"Province": "TAFEA",
71+
"Area Council": "Futuna",
72+
"Attribute": "Business",
73+
"source": "OpenStreetMap"
74+
},
5775
"geometry": {
5876
"coordinates": [
5977
167.72470,
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
# Generated by Django 5.2.5 on 2025-10-03 12:44
2+
3+
import django.db.models.deletion
4+
from django.db import migrations, models
5+
6+
7+
class Migration(migrations.Migration):
8+
9+
dependencies = [
10+
("datasets", "0010_auto_20251001_1132"),
11+
]
12+
13+
operations = [
14+
migrations.RenameField(
15+
model_name="tabularitem",
16+
old_name="data",
17+
new_name="metadata",
18+
),
19+
migrations.AddField(
20+
model_name="tabularitem",
21+
name="area_council",
22+
field=models.ForeignKey(
23+
null=True,
24+
on_delete=django.db.models.deletion.PROTECT,
25+
to="datasets.areacouncil",
26+
),
27+
),
28+
migrations.AddField(
29+
model_name="tabularitem",
30+
name="attribute",
31+
field=models.CharField(blank=True, max_length=155, null=True),
32+
),
33+
migrations.AddField(
34+
model_name="tabularitem",
35+
name="date",
36+
field=models.DateField(null=True),
37+
),
38+
migrations.AddField(
39+
model_name="tabularitem",
40+
name="province",
41+
field=models.ForeignKey(
42+
null=True,
43+
on_delete=django.db.models.deletion.PROTECT,
44+
to="datasets.province",
45+
),
46+
),
47+
migrations.AddField(
48+
model_name="tabularitem",
49+
name="value",
50+
field=models.FloatField(default=0),
51+
),
52+
migrations.AddField(
53+
model_name="vectoritem",
54+
name="area_council",
55+
field=models.ForeignKey(
56+
null=True,
57+
on_delete=django.db.models.deletion.PROTECT,
58+
to="datasets.areacouncil",
59+
),
60+
),
61+
migrations.AddField(
62+
model_name="vectoritem",
63+
name="attribute",
64+
field=models.CharField(blank=True, max_length=155, null=True),
65+
),
66+
migrations.AddField(
67+
model_name="vectoritem",
68+
name="name",
69+
field=models.CharField(blank=True, max_length=155, null=True),
70+
),
71+
migrations.AddField(
72+
model_name="vectoritem",
73+
name="province",
74+
field=models.ForeignKey(
75+
null=True,
76+
on_delete=django.db.models.deletion.PROTECT,
77+
to="datasets.province",
78+
),
79+
),
80+
migrations.AddField(
81+
model_name="vectoritem",
82+
name="ref",
83+
field=models.CharField(blank=True, max_length=50, null=True),
84+
),
85+
]

vbos/datasets/models.py

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -119,11 +119,19 @@ class Meta:
119119

120120
class VectorItem(models.Model):
121121
dataset = models.ForeignKey(VectorDataset, on_delete=models.CASCADE)
122+
name = models.CharField(max_length=155, blank=True, null=True)
123+
ref = models.CharField(max_length=50, blank=True, null=True)
124+
attribute = models.CharField(max_length=155, blank=True, null=True)
125+
province = models.ForeignKey(Province, null=True, on_delete=models.PROTECT)
126+
area_council = models.ForeignKey(AreaCouncil, null=True, on_delete=models.PROTECT)
122127
geometry = models.GeometryField()
123128
metadata = models.JSONField(default=dict, blank=True, null=True)
124129

125130
def __str__(self):
126-
return f"{self.id}"
131+
if self.name:
132+
return f"{self.id} ({self.name})"
133+
else:
134+
return f"{self.id}"
127135

128136
class Meta:
129137
ordering = ["id"]
@@ -149,7 +157,12 @@ class Meta:
149157

150158
class TabularItem(models.Model):
151159
dataset = models.ForeignKey(TabularDataset, on_delete=models.CASCADE)
152-
data = models.JSONField(default=dict)
160+
date = models.DateField(null=True)
161+
attribute = models.CharField(max_length=155, blank=True, null=True)
162+
value = models.FloatField(default=0)
163+
province = models.ForeignKey(Province, null=True, on_delete=models.PROTECT)
164+
area_council = models.ForeignKey(AreaCouncil, null=True, on_delete=models.PROTECT)
165+
metadata = models.JSONField(default=dict)
153166

154167
def __str__(self):
155168
return f"{self.id}"

vbos/datasets/serializers.py

Lines changed: 29 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -44,25 +44,21 @@ class Meta:
4444

4545

4646
class VectorItemSerializer(GeoFeatureModelSerializer):
47+
province = serializers.ReadOnlyField(source="province.name")
48+
area_council = serializers.ReadOnlyField(source="area_council.name")
49+
4750
class Meta:
4851
model = VectorItem
4952
geo_field = "geometry"
50-
fields = ["id", "metadata"]
51-
52-
def get_properties(self, instance, fields):
53-
# This is a PostgreSQL HStore field, which django maps to a dict
54-
return instance.metadata
55-
56-
def unformat_geojson(self, feature):
57-
attrs = {
58-
self.Meta.geo_field: feature["geometry"],
59-
"metadata": feature["properties"],
60-
}
61-
62-
if self.Meta.bbox_geo_field and "bbox" in feature:
63-
attrs[self.Meta.bbox_geo_field] = Polygon.from_bbox(feature["bbox"])
64-
65-
return attrs
53+
fields = [
54+
"id",
55+
"name",
56+
"ref",
57+
"attribute",
58+
"province",
59+
"area_council",
60+
"metadata",
61+
]
6662

6763

6864
class TabularDatasetSerializer(serializers.ModelSerializer):
@@ -74,15 +70,26 @@ class Meta:
7470

7571

7672
class TabularItemSerializer(serializers.ModelSerializer):
73+
province = serializers.ReadOnlyField(source="province.name")
74+
area_council = serializers.ReadOnlyField(source="area_council.name")
75+
7776
class Meta:
7877
model = TabularItem
79-
fields = ["id", "data"]
78+
fields = [
79+
"id",
80+
"attribute",
81+
"date",
82+
"value",
83+
"province",
84+
"area_council",
85+
"metadata",
86+
]
8087

8188
def to_representation(self, instance):
8289
representation = super().to_representation(instance)
8390

8491
# Extract the data field and merge it with the top level fields
85-
data_content = representation.pop("data", {})
92+
data_content = representation.pop("metadata", {})
8693

8794
return {**representation, **data_content}
8895

@@ -103,7 +110,10 @@ def __init__(self, *args, **kwargs):
103110
# Create a field for each key
104111
for key in all_keys:
105112
self.fields[key] = serializers.CharField(
106-
source=f"data.{key}", required=False, allow_blank=True, default=""
113+
source=f"metadata.{key}",
114+
required=False,
115+
allow_blank=True,
116+
default="",
107117
)
108118

109119
class Meta:

0 commit comments

Comments
 (0)