Skip to content

Commit f2a6c0a

Browse files
authored
Add source fields to datasets and allow filter datasets by cluster name (#8)
1 parent 51a2bd6 commit f2a6c0a

15 files changed

+329
-28
lines changed

vbos/datasets/admin.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from django.urls import path
1010

1111
from .models import (
12+
Cluster,
1213
RasterDataset,
1314
RasterFile,
1415
TabularDataset,
@@ -19,19 +20,24 @@
1920
from .forms import CSVUploadForm, GeoJSONUploadForm
2021

2122

23+
@admin.register(Cluster)
24+
class ClusterAdmin(admin.ModelAdmin):
25+
list_display = ["id", "name"]
26+
27+
2228
@admin.register(RasterFile)
2329
class RasterFileAdmin(admin.ModelAdmin):
2430
list_display = ["id", "name", "created", "file"]
2531

2632

2733
@admin.register(RasterDataset)
2834
class RasterDatasetAdmin(admin.ModelAdmin):
29-
list_display = ["id", "name", "created", "updated", "file"]
35+
list_display = ["id", "cluster", "name", "created", "updated", "file"]
3036

3137

3238
@admin.register(VectorDataset)
3339
class VectorDatasetAdmin(admin.ModelAdmin):
34-
list_display = ["id", "name", "created", "updated"]
40+
list_display = ["id", "cluster", "name", "created", "updated"]
3541

3642

3743
@admin.register(VectorItem)
@@ -111,7 +117,7 @@ def import_file(self, request):
111117

112118
@admin.register(TabularDataset)
113119
class TabularDatasetAdmin(admin.ModelAdmin):
114-
list_display = ["id", "name", "created", "updated"]
120+
list_display = ["id", "cluster", "name", "created", "updated"]
115121

116122

117123
@admin.register(TabularItem)

vbos/datasets/filters.py

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,17 @@
77
ModelChoiceFilter,
88
)
99

10-
from .models import RasterDataset, VectorDataset, TabularDataset
10+
from .models import RasterDataset, VectorDataset, TabularDataset, Cluster
1111

1212

1313
class DatasetFilter(FilterSet):
1414
name = CharFilter(field_name="name", lookup_expr="icontains")
15+
source = CharFilter(field_name="source", lookup_expr="icontains")
16+
cluster = ModelChoiceFilter(
17+
field_name="cluster__name",
18+
to_field_name="name__iexact",
19+
queryset=Cluster.objects.all(),
20+
)
1521
created = DateFromToRangeFilter()
1622
updated = DateFromToRangeFilter()
1723
order_by = OrderingFilter(
@@ -22,16 +28,16 @@ class DatasetFilter(FilterSet):
2228
class RasterDatasetFilter(DatasetFilter):
2329
class Meta:
2430
model = RasterDataset
25-
fields = ["name", "created", "updated"]
31+
fields = ["name", "source", "cluster", "created", "updated"]
2632

2733

2834
class VectorDatasetFilter(DatasetFilter):
2935
class Meta:
3036
model = VectorDataset
31-
fields = ["name", "created", "updated"]
37+
fields = ["name", "source", "cluster", "created", "updated"]
3238

3339

3440
class TabularDatasetFilter(DatasetFilter):
3541
class Meta:
3642
model = TabularDataset
37-
fields = ["name", "created", "updated"]
43+
fields = ["name", "source", "cluster", "created", "updated"]
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
# Generated by Django 5.2.5 on 2025-09-11 13:55
2+
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
("datasets", "0004_alter_rasterdataset_name_alter_rasterfile_file_and_more"),
10+
]
11+
12+
operations = [
13+
migrations.CreateModel(
14+
name="Cluster",
15+
fields=[
16+
(
17+
"id",
18+
models.AutoField(
19+
auto_created=True,
20+
primary_key=True,
21+
serialize=False,
22+
verbose_name="ID",
23+
),
24+
),
25+
("name", models.CharField(max_length=100, unique=True)),
26+
],
27+
options={
28+
"ordering": ["name"],
29+
},
30+
),
31+
migrations.AddField(
32+
model_name="rasterdataset",
33+
name="source",
34+
field=models.CharField(blank=True, max_length=155, null=True),
35+
),
36+
migrations.AddField(
37+
model_name="tabulardataset",
38+
name="source",
39+
field=models.CharField(blank=True, max_length=155, null=True),
40+
),
41+
migrations.AddField(
42+
model_name="vectordataset",
43+
name="source",
44+
field=models.CharField(blank=True, max_length=155, null=True),
45+
),
46+
]
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
# Generated by Django 5.2.5 on 2025-09-11 13:59
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+
(
11+
"datasets",
12+
"0005_cluster_rasterdataset_source_tabulardataset_source_and_more",
13+
),
14+
]
15+
16+
operations = [
17+
migrations.AddField(
18+
model_name="rasterdataset",
19+
name="cluster",
20+
field=models.ForeignKey(
21+
null=True,
22+
on_delete=django.db.models.deletion.PROTECT,
23+
to="datasets.cluster",
24+
),
25+
),
26+
migrations.AddField(
27+
model_name="tabulardataset",
28+
name="cluster",
29+
field=models.ForeignKey(
30+
null=True,
31+
on_delete=django.db.models.deletion.PROTECT,
32+
to="datasets.cluster",
33+
),
34+
),
35+
migrations.AddField(
36+
model_name="vectordataset",
37+
name="cluster",
38+
field=models.ForeignKey(
39+
null=True,
40+
on_delete=django.db.models.deletion.PROTECT,
41+
to="datasets.cluster",
42+
),
43+
),
44+
]
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# Generated by Django 5.2.5 on 2025-09-11 14:09
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", "0006_rasterdataset_cluster_tabulardataset_cluster_and_more"),
11+
]
12+
13+
operations = [
14+
migrations.AlterField(
15+
model_name="rasterdataset",
16+
name="cluster",
17+
field=models.ForeignKey(
18+
on_delete=django.db.models.deletion.PROTECT, to="datasets.cluster"
19+
),
20+
),
21+
migrations.AlterField(
22+
model_name="tabulardataset",
23+
name="cluster",
24+
field=models.ForeignKey(
25+
on_delete=django.db.models.deletion.PROTECT, to="datasets.cluster"
26+
),
27+
),
28+
migrations.AlterField(
29+
model_name="vectordataset",
30+
name="cluster",
31+
field=models.ForeignKey(
32+
on_delete=django.db.models.deletion.PROTECT, to="datasets.cluster"
33+
),
34+
),
35+
]

vbos/datasets/models.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,16 @@
88
UPLOAD_TO = "staging/raster/" if settings.DEBUG else "production/raster/"
99

1010

11+
class Cluster(models.Model):
12+
name = models.CharField(max_length=100, unique=True)
13+
14+
def __str__(self):
15+
return self.name
16+
17+
class Meta:
18+
ordering = ["name"]
19+
20+
1121
class RasterFile(models.Model):
1222
name = models.CharField(max_length=155, unique=True)
1323
created = models.DateTimeField(auto_now_add=True)
@@ -43,6 +53,11 @@ class RasterDataset(models.Model):
4353
name = models.CharField(max_length=155, unique=True)
4454
created = models.DateTimeField(auto_now_add=True)
4555
updated = models.DateTimeField(auto_now=True)
56+
cluster = models.ForeignKey(
57+
Cluster,
58+
on_delete=models.PROTECT,
59+
)
60+
source = models.CharField(max_length=155, blank=True, null=True)
4661
file = models.ForeignKey(RasterFile, on_delete=models.PROTECT)
4762

4863
def __str__(self):
@@ -56,6 +71,11 @@ class VectorDataset(models.Model):
5671
name = models.CharField(max_length=155, unique=True)
5772
created = models.DateTimeField(auto_now_add=True)
5873
updated = models.DateTimeField(auto_now=True)
74+
cluster = models.ForeignKey(
75+
Cluster,
76+
on_delete=models.PROTECT,
77+
)
78+
source = models.CharField(max_length=155, blank=True, null=True)
5979

6080
def __str__(self):
6181
return self.name
@@ -80,6 +100,11 @@ class TabularDataset(models.Model):
80100
name = models.CharField(max_length=155, unique=True)
81101
created = models.DateTimeField(auto_now_add=True)
82102
updated = models.DateTimeField(auto_now=True)
103+
cluster = models.ForeignKey(
104+
Cluster,
105+
on_delete=models.PROTECT,
106+
)
107+
source = models.CharField(max_length=155, blank=True, null=True)
83108

84109
def __str__(self):
85110
return self.name

vbos/datasets/serializers.py

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from rest_framework_gis.serializers import GeoFeatureModelSerializer
33

44
from .models import (
5+
Cluster,
56
RasterDataset,
67
TabularDataset,
78
TabularItem,
@@ -10,18 +11,27 @@
1011
)
1112

1213

14+
class ClusterSerializer(serializers.ModelSerializer):
15+
class Meta:
16+
model = Cluster
17+
fields = ["id", "name"]
18+
19+
1320
class RasterDatasetSerializer(serializers.ModelSerializer):
1421
file = serializers.ReadOnlyField(source="file.file.url")
22+
cluster = serializers.ReadOnlyField(source="cluster.name")
1523

1624
class Meta:
1725
model = RasterDataset
18-
fields = ["id", "name", "created", "updated", "file"]
26+
fields = ["id", "name", "created", "updated", "cluster", "source", "file"]
1927

2028

2129
class VectorDatasetSerializer(serializers.ModelSerializer):
30+
cluster = serializers.ReadOnlyField(source="cluster.name")
31+
2232
class Meta:
2333
model = VectorDataset
24-
fields = "__all__"
34+
fields = ["id", "name", "created", "updated", "cluster", "source"]
2535

2636

2737
class VectorItemSerializer(GeoFeatureModelSerializer):
@@ -47,9 +57,11 @@ def unformat_geojson(self, feature):
4757

4858

4959
class TabularDatasetSerializer(serializers.ModelSerializer):
60+
cluster = serializers.ReadOnlyField(source="cluster.name")
61+
5062
class Meta:
5163
model = TabularDataset
52-
fields = "__all__"
64+
fields = ["id", "name", "created", "updated", "cluster", "source"]
5365

5466

5567
class TabularItemSerializer(serializers.ModelSerializer):

vbos/datasets/test/test_admin.py

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,13 @@
44
from django.test import TestCase, Client
55
from django.urls import reverse
66

7-
from vbos.datasets.models import TabularDataset, TabularItem, VectorDataset, VectorItem
7+
from vbos.datasets.models import (
8+
Cluster,
9+
TabularDataset,
10+
TabularItem,
11+
VectorDataset,
12+
VectorItem,
13+
)
814

915

1016
class TabularItemAdminImportFileTests(TestCase):
@@ -14,7 +20,10 @@ def setUp(self):
1420
username="admin", password="password", email="[email protected]"
1521
)
1622
self.client.login(username="admin", password="password")
17-
self.dataset = TabularDataset.objects.create(name="Test Dataset")
23+
self.cluster = Cluster.objects.create(name="Other")
24+
self.dataset = TabularDataset.objects.create(
25+
name="Test Dataset", cluster=self.cluster
26+
)
1827
self.upload_url = reverse("admin:datasets_tabularitem_import_file")
1928

2029
def test_change_list_has_link_to_import_file(self):
@@ -68,7 +77,10 @@ def setUp(self):
6877
username="admin", password="password", email="[email protected]"
6978
)
7079
self.client.login(username="admin", password="password")
71-
self.dataset = VectorDataset.objects.create(name="Test Dataset")
80+
self.cluster = Cluster.objects.create(name="Other")
81+
self.dataset = VectorDataset.objects.create(
82+
name="Test Dataset", cluster=self.cluster
83+
)
7284
self.upload_url = reverse("admin:datasets_vectoritem_import_file")
7385

7486
def test_change_list_has_link_to_import_file(self):
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
from django.urls.base import reverse
2+
from rest_framework import status
3+
from rest_framework.test import APITestCase
4+
5+
from vbos.datasets.models import Cluster
6+
7+
8+
class TestClusterListView(APITestCase):
9+
def setUp(self):
10+
self.c1 = Cluster.objects.create(name="Administrative")
11+
self.c2 = Cluster.objects.create(name="Transportation")
12+
self.c3 = Cluster.objects.create(name="Other")
13+
self.url = reverse("datasets:cluster-list")
14+
15+
def test_cluster_list_view(self):
16+
req = self.client.get(self.url)
17+
assert req.status_code == status.HTTP_200_OK
18+
assert req.data.get("count") == 3
19+
# It's ordered alphabetically by the name
20+
assert req.data.get("results")[0]["name"] == "Administrative"
21+
assert req.data.get("results")[1]["name"] == "Other"
22+
assert req.data.get("results")[2]["name"] == "Transportation"
23+
assert req.data.get("results")[0]["id"] == self.c1.id
24+
assert req.data.get("results")[1]["id"] == self.c3.id
25+
assert req.data.get("results")[2]["id"] == self.c2.id

0 commit comments

Comments
 (0)