Skip to content

Commit 69112f2

Browse files
committed
Merge branch 'main' into feature/add-custom-tiler
2 parents d47280c + f6248c0 commit 69112f2

File tree

7 files changed

+128
-17
lines changed

7 files changed

+128
-17
lines changed

docs/data.md

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
# Data Imports
2+
3+
To add data into the system, the first step needed is to create a superuser account. Check the [index.md](/docs/index.md#initialize-the-project) for instructions on how to do it.
4+
5+
With the superuser account, you can login on the Administration Interface. The URL will be: `{backend-url}/admin`.
6+
7+
Once logged in, you will see a screen like this:
8+
9+
<img width="2764" height="1492" alt="image" src="https://github.com/user-attachments/assets/109f4712-9577-4bef-8b4c-153620f3a2b1" />
10+
11+
## Adding data
12+
13+
We can add data by uploading raster files (TIFF or VRT format), vector files in the GeoJSON format or Tabular data in CSV format.
14+
However, before uploading data, you should create the Clusters, and the Dataset entries.
15+
16+
### Creating clusters
17+
18+
On the Administrative Interface, click on Clusters, then on `Add Cluster` button.
19+
20+
You will see a form like this. Add a name and click on Save.
21+
22+
<img width="50%" alt="image" src="https://github.com/user-attachments/assets/c7f62081-3d2f-4fa7-8b24-0a8c032a018c" />
23+
24+
If you need to modify or delete a Cluster, you can do it by accessing the Cluster list page in the administrative interface.
25+
26+
Clicking on the id of the cluster, you will have access to the form to modify it. If you need to delete clusters, select it and then click on the action dropdown and select `Delete selected clusters`. Finally, click on the `Go` button.
27+
28+
<img width="50%" alt="image" src="https://github.com/user-attachments/assets/01f27226-566e-44c4-b279-6c818b53ff2e" />
29+
30+
31+
### Creating datasets
32+
33+
The exact same pattern applies when creating Raster, Vector or Tabular datasets. Here you can see the Raster Dataset creation form:
34+
35+
<img width="100%" alt="image" src="https://github.com/user-attachments/assets/01a838fb-8ab1-4cef-bab7-e1ebf624fa8e" />
36+
37+
The forms to create Vector and Tabular datasets are very similar to the Raster one.
38+
39+
### Uploading data
40+
41+
The last and most important step of the data import is to upload the files containing each datasets data.
42+
43+
### Tabular data
44+
45+
Click on the `Tabular Items` link in the Administrative Interface homepage. Then, click on `Import File` in the right-top corner of the page.
46+
47+
<img width="2210" height="1006" alt="image" src="https://github.com/user-attachments/assets/65dc540a-9bdf-4690-ab65-6d005aa22d96" />
48+
49+
Once clicked, you will see a form like this, where you can upload a CSV file and select the dataset to which the data belongs to:
50+
51+
<img width="80%" alt="image" src="https://github.com/user-attachments/assets/2702ae31-a4ec-4cfa-b782-7045876314c4" />
52+
53+
The CSV file needs to be separated by commas and should have the following columns:
54+
55+
- Year
56+
- Month `(optional)`
57+
- Province `(optional)`
58+
- Area Council `(optional)`
59+
- Attribute
60+
- Value
61+
62+
The name of the columns can be in lower, UPPER, or Camel Case. You can upload a file with additional columns, and the additional information will be stored in the database.
63+
64+
### Vector data
65+
66+
Click on the `Vector Items` link in the Administrative Interface homepage. Then, click on `Import File` in the right-top corner of the page.
67+
68+
<img width="2204" height="928" alt="image" src="https://github.com/user-attachments/assets/b6cd7cab-c9bd-4956-87f2-08268d848602" />
69+
70+
Once clicked, you will see a form like this, where you can upload a GeoJSON file and select the dataset to which the data belongs to:
71+
72+
<img width="50%" alt="image" src="https://github.com/user-attachments/assets/e65938e5-43b0-405d-9fdd-446b182a4e88" />
73+
74+
If you have never worked with GeoJSON files, you can convert any geospatial data format to GeoJSON using QGIS or ArcGis. The items in the GeoJSON file should have the following columsn:
75+
76+
- Name `(optional)`
77+
- Ref `(optional)`
78+
- Province `(optional)`
79+
- Area Council `(optional)`
80+
- Attribute `(optional)`

vbos/datasets/admin.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -91,14 +91,14 @@ def import_file(self, request):
9191
VectorItem.objects.create(
9292
dataset=form.cleaned_data["dataset"],
9393
metadata=metadata.properties,
94-
name=metadata.name,
94+
name=metadata.name.strip(),
9595
ref=metadata.ref,
96-
attribute=metadata.attribute,
96+
attribute=metadata.attribute.strip(),
9797
province=Province.objects.filter(
98-
name__iexact=metadata.province
98+
name__iexact=metadata.province.strip()
9999
).first(),
100100
area_council=AreaCouncil.objects.filter(
101-
name__iexact=metadata.area_council
101+
name__iexact=metadata.area_council.strip()
102102
).first(),
103103
geometry=GEOSGeometry(json.dumps(item["geometry"])),
104104
)
@@ -184,7 +184,7 @@ def import_file(self, request):
184184
TabularItem.objects.create(
185185
dataset=form.cleaned_data["dataset"],
186186
metadata=csv_row.metadata,
187-
attribute=csv_row.attribute,
187+
attribute=csv_row.attribute.strip(),
188188
value=csv_row.value,
189189
date=csv_row.date,
190190
province=Province.objects.filter(

vbos/datasets/fixtures/test.csv

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

vbos/datasets/serializers.py

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,15 +20,13 @@ class Meta:
2020

2121

2222
class ProvinceSerializer(GeoFeatureModelSerializer):
23-
2423
class Meta:
2524
model = Province
2625
geo_field = "geometry"
2726
fields = "__all__"
2827

2928

3029
class AreaCouncilSerializer(GeoFeatureModelSerializer):
31-
3230
class Meta:
3331
model = AreaCouncil
3432
geo_field = "geometry"
@@ -137,6 +135,9 @@ def to_representation(self, instance):
137135

138136

139137
class TabularItemExcelSerializer(serializers.ModelSerializer):
138+
province = serializers.ReadOnlyField(source="province.name")
139+
area_council = serializers.ReadOnlyField(source="area_council.name")
140+
140141
# Dynamically add fields based on all possible keys in the data
141142
def __init__(self, *args, **kwargs):
142143
super().__init__(*args, **kwargs)
@@ -146,8 +147,8 @@ def __init__(self, *args, **kwargs):
146147
queryset = self.context["view"].get_queryset()
147148
all_keys = set()
148149
for item in queryset:
149-
if item.data and isinstance(item.data, dict):
150-
all_keys.update(item.data.keys())
150+
if item.metadata and isinstance(item.metadata, dict):
151+
all_keys.update(item.metadata.keys())
151152

152153
# Create a field for each key
153154
for key in all_keys:
@@ -160,4 +161,11 @@ def __init__(self, *args, **kwargs):
160161

161162
class Meta:
162163
model = TabularItem
163-
fields = ["id"]
164+
fields = [
165+
"id",
166+
"attribute",
167+
"date",
168+
"value",
169+
"province",
170+
"area_council",
171+
]

vbos/datasets/test/test_tabular_views.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,3 +195,15 @@ def test_filter_data(self):
195195
req = self.client.get(url, {"attribute": "population"})
196196
assert req.status_code == status.HTTP_200_OK
197197
assert req.data.get("count") == 4
198+
199+
def test_xlsx_format(self):
200+
url = reverse("datasets:tabular-data-xlsx", args=[self.dataset_1.id])
201+
req = self.client.get(url)
202+
assert req.status_code == status.HTTP_200_OK
203+
assert (
204+
req.headers["Content-Type"]
205+
== "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet; charset=utf-8"
206+
)
207+
assert req.headers[
208+
"content-disposition"
209+
] == "attachment; filename=vbos-mis-tabular-{}.xlsx".format(self.dataset_1.id)

vbos/datasets/urls.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,4 +43,9 @@
4343
views.TabularDatasetDataView.as_view(),
4444
name="tabular-data",
4545
),
46+
path(
47+
"tabular/<int:pk>/data-xlsx/",
48+
views.TabularDatasetXSLXDataView.as_view(),
49+
name="tabular-data-xlsx",
50+
),
4651
]

vbos/datasets/views.py

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
1-
from django.shortcuts import render
21
import django_filters.rest_framework
32
from rest_framework.generics import ListAPIView, RetrieveAPIView
43
from rest_framework.permissions import IsAuthenticatedOrReadOnly
54
from rest_framework_gis.pagination import GeoJsonPagination
65
from rest_framework_gis.filters import InBBoxFilter
6+
from drf_excel.mixins import XLSXFileMixin
7+
from drf_excel.renderers import XLSXRenderer
8+
79

810
from vbos.datasets.filters import (
911
RasterDatasetFilter,
@@ -122,12 +124,16 @@ class TabularDatasetDetailView(RetrieveAPIView):
122124
class TabularDatasetDataView(ListAPIView):
123125
filterset_class = TabularItemFilter
124126
permission_classes = [IsAuthenticatedOrReadOnly]
127+
serializer_class = TabularItemSerializer
125128

126129
def get_queryset(self):
127130
return TabularItem.objects.filter(dataset=self.kwargs.get("pk"))
128131

129-
def get_serializer_class(self):
130-
# Use different serializer for Excel format
131-
if self.request.query_params.get("format") == "xlsx":
132-
return TabularItemExcelSerializer
133-
return TabularItemSerializer
132+
133+
class TabularDatasetXSLXDataView(XLSXFileMixin, TabularDatasetDataView):
134+
serializer_class = TabularItemExcelSerializer
135+
renderer_classes = (XLSXRenderer,)
136+
pagination_class = None
137+
138+
def get_filename(self, request, *args, **kwargs):
139+
return f"vbos-mis-tabular-{kwargs.get('pk')}.xlsx"

0 commit comments

Comments
 (0)