Skip to content

Commit

Permalink
Merge pull request #2272 from onaio/2266-link-dataset-as-geojson
Browse files Browse the repository at this point in the history
2266 link dataset as geojson
  • Loading branch information
KipSigei authored Jul 4, 2022
2 parents 0226ff4 + 4e4c696 commit b4b5ec5
Show file tree
Hide file tree
Showing 18 changed files with 342 additions and 19 deletions.
21 changes: 21 additions & 0 deletions docs/metadata.rst
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,27 @@ Link XForm or Dataview as a media example:
"url": "https://api.ona.io/api/v1/metadata/7121.json"
}


Link XForm as a GeoJSON media attachment example:
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
::


curl -X POST -F 'data_type=media' -F 'xform=320' -F 'data_value="xform_geojson 328 places"' -F 'extra_data='{"data_title": "fruits", "data_simple_style": true, "data_geo_field": "geofied_1", "data_fields": "field_1,field_2"}'' https://api.ona.io/api/v1/metadata.json

::

HTTP 201 CREATED

{
"id": 7121,
"xform": 320,
"data_value": "xform_geojson 328 places",
"data_type": "media",
"extra_data": '{"data_title": "fruits", "data_simple_style": true, "data_geo_field": "geofied_1", "data_fields": "field_1,field_2"}'
"url": "https://api.ona.io/api/v1/metadata/7121.json"
}

Create XForm meta permissions
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Set meta permissions for a specific form by passing two roles that are pipe delimited.
Expand Down
25 changes: 22 additions & 3 deletions onadata/apps/api/tests/viewsets/test_abstract_viewset.py
Original file line number Diff line number Diff line change
Expand Up @@ -467,7 +467,11 @@ def _submit_transport_instance_w_attachment(
def _post_metadata(self, data, test=True):
count = MetaData.objects.count()
view = MetaDataViewSet.as_view({"post": "create"})
request = self.factory.post("/", data, **self.extra)
request = self.factory.post(
"/",
data=data,
**self.extra,
format='json' if 'extra_data' in data else None)

response = view(request)

Expand All @@ -481,8 +485,23 @@ def _post_metadata(self, data, test=True):

return response

def _add_form_metadata(self, xform, data_type, data_value, path=None, test=True):
data = {"data_type": data_type, "data_value": data_value, "xform": xform.id}
def _add_form_metadata(
self,
xform,
data_type,
data_value,
path=None,
test=True,
extra_data=None,
):
data = {
"data_type": data_type,
"data_value": data_value,
"xform": xform.id
}

if extra_data:
data.update({"extra_data": extra_data})

if path and data_value:
with open(path, "rb") as media_file:
Expand Down
4 changes: 2 additions & 2 deletions onadata/apps/api/tests/viewsets/test_data_viewset.py
Original file line number Diff line number Diff line change
Expand Up @@ -2045,7 +2045,7 @@ def test_geojson_simple_style_title_prop(self):
data_get = {
"geo_field": "location",
"simple_style": "true",
"title": "shape"
"title": "location"
}

view = DataViewSet.as_view({"get": "retrieve"})
Expand All @@ -2057,7 +2057,7 @@ def test_geojson_simple_style_title_prop(self):

self.assertEqual(response.status_code, 200)
self.assertIn('title', response.data["properties"])
self.assertEqual('shape', response.data["properties"]["title"])
self.assertEqual('-1.294197 36.787219 0 34', response.data["properties"]["title"])

def test_geojson_geofield(self):
self._publish_submit_geojson()
Expand Down
24 changes: 24 additions & 0 deletions onadata/apps/api/tests/viewsets/test_metadata_viewset.py
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,7 @@ def test_get_metadata(self):
'xform': self.xform.pk,
'data_value': u'1335783522563.jpg',
'data_type': u'media',
'extra_data': None,
'data_file': u'http://localhost:8000/media/%s/formid-media/'
'1335783522563.jpg' % self.user.username,
'data_file_type': u'image/jpeg',
Expand Down Expand Up @@ -271,6 +272,29 @@ def test_add_media_xform_link(self):
self.assertEqual(response['Content-Disposition'],
'attachment; filename=transportation.csv')

def test_add_media_geojson_link(self):
data_type = 'media'
data_value = 'xform_geojson {} transportation'.format(self.xform.pk)
extra_data = {
"data_title": "test",
"data_simple_style": True,
"data_geo_field": "test",
"data_fields": "transport/available_transportation_types_to_referral_facility/ambulance" # noqa
}
self._add_form_metadata(
self.xform,
data_type,
data_value,
extra_data=extra_data
)
self.assertIsNotNone(self.metadata_data['media_url'])
request = self.factory.get('/', **self.extra)
ext = self.data_value[self.data_value.rindex('.') + 1:]
response = self.view(request, pk=self.metadata.pk, format=ext)
self.assertEqual(response.status_code, 200)
self.assertEqual(response['Content-Disposition'],
'attachment; filename=transportation.geojson')

def test_add_media_dataview_link(self):
self._create_dataview()
data_type = 'media'
Expand Down
3 changes: 3 additions & 0 deletions onadata/apps/api/tests/viewsets/test_project_viewset.py
Original file line number Diff line number Diff line change
Expand Up @@ -557,6 +557,7 @@ def test_view_xls_form(self):
("data_value", "https://enketo.ona.io/::YY8M"),
("data_type", "enketo_url"),
("data_file", None),
("extra_data", {}),
("data_file_type", None),
("media_url", None),
("file_hash", None),
Expand All @@ -571,6 +572,7 @@ def test_view_xls_form(self):
("data_value", "https://enketo.ona.io/preview/::YY8M"),
("data_type", "enketo_preview_url"),
("data_file", None),
("extra_data", {}),
("data_file_type", None),
("media_url", None),
("file_hash", None),
Expand All @@ -588,6 +590,7 @@ def test_view_xls_form(self):
("data_value", "http://enketo.ona.io/single/::XZqoZ94y"),
("data_type", "enketo_single_submit_url"),
("data_file", None),
("extra_data", {}),
("data_file_type", None),
("media_url", None),
("file_hash", None),
Expand Down
6 changes: 6 additions & 0 deletions onadata/apps/api/tests/viewsets/test_xform_viewset.py
Original file line number Diff line number Diff line change
Expand Up @@ -383,6 +383,7 @@ def test_public_form_list(self):
("data_value", "https://enketo.ona.io/::YY8M"),
("data_type", "enketo_url"),
("data_file", None),
("extra_data", {}),
("data_file_type", None),
("media_url", None),
("file_hash", None),
Expand All @@ -397,6 +398,7 @@ def test_public_form_list(self):
("data_value", "https://enketo.ona.io/preview/::YY8M"),
("data_type", "enketo_preview_url"),
("data_file", None),
("extra_data", {}),
("data_file_type", None),
("media_url", None),
("file_hash", None),
Expand All @@ -414,6 +416,7 @@ def test_public_form_list(self):
("data_value", "http://enketo.ona.io/single/::XZqoZ94y"),
("data_type", "enketo_single_submit_url"),
("data_file", None),
("extra_data", {}),
("data_file_type", None),
("media_url", None),
("file_hash", None),
Expand Down Expand Up @@ -627,6 +630,7 @@ def test_form_get(self):
("data_value", "https://enketo.ona.io/::YY8M"),
("data_type", "enketo_url"),
("data_file", None),
("extra_data", {}),
("data_file_type", None),
("media_url", None),
("file_hash", None),
Expand All @@ -641,6 +645,7 @@ def test_form_get(self):
("data_value", "https://enketo.ona.io/preview/::YY8M"),
("data_type", "enketo_preview_url"),
("data_file", None),
("extra_data", {}),
("data_file_type", None),
("media_url", None),
("file_hash", None),
Expand All @@ -658,6 +663,7 @@ def test_form_get(self):
("data_value", "http://enketo.ona.io/single/::XZqoZ94y"),
("data_type", "enketo_single_submit_url"),
("data_file", None),
("extra_data", {}),
("data_file_type", None),
("media_url", None),
("file_hash", None),
Expand Down
9 changes: 6 additions & 3 deletions onadata/apps/api/tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@
from onadata.apps.logger.models import DataView, Instance, Project, XForm
from onadata.apps.main.forms import QuickConverter
from onadata.apps.main.models.meta_data import MetaData
from onadata.apps.viewer.models.export import Export
from onadata.apps.viewer.models.parsed_instance import datetime_from_str
from onadata.libs.baseviewset import DefaultBaseViewset
from onadata.libs.models.share_project import ShareProject
Expand All @@ -56,7 +55,9 @@
get_role_in_org,
is_organization,
)
from onadata.libs.utils.api_export_tools import custom_response_handler
from onadata.libs.utils.api_export_tools import (
custom_response_handler, get_metadata_format
)
from onadata.libs.utils.cache_tools import (
PROJ_BASE_FORMS_CACHE,
PROJ_FORMS_CACHE,
Expand Down Expand Up @@ -626,14 +627,16 @@ def get_data_value_objects(value):
if obj:
dataview = obj if isinstance(obj, DataView) else False
xform = obj.xform if isinstance(obj, DataView) else obj
export_type = get_metadata_format(metadata.data_value)

return custom_response_handler(
request,
xform,
{},
Export.CSV_EXPORT,
export_type,
filename=filename,
dataview=dataview,
metadata=metadata,
)

return HttpResponseRedirect(metadata.data_value)
Expand Down
1 change: 0 additions & 1 deletion onadata/apps/api/viewsets/metadata_viewset.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@ class MetaDataViewSet(AuthenticateHeaderMixin,

def retrieve(self, request, *args, **kwargs):
self.object = self.get_object()

if isinstance(request.accepted_renderer, MediaFileRenderer) \
and self.object.data_file is not None:

Expand Down
18 changes: 18 additions & 0 deletions onadata/apps/main/migrations/0012_metadata_extra_data.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 3.2.13 on 2022-06-24 11:47

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('main', '0011_auto_20220510_0907'),
]

operations = [
migrations.AddField(
model_name='metadata',
name='extra_data',
field=models.JSONField(default=dict, blank=True, null=True),
),
]
1 change: 1 addition & 0 deletions onadata/apps/main/models/meta_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,7 @@ class MetaData(models.Model):

data_type = models.CharField(max_length=255)
data_value = models.CharField(max_length=255)
extra_data = models.JSONField(default=dict, blank=True, null=True)
data_file = models.FileField(upload_to=upload_to, blank=True, null=True)
data_file_type = models.CharField(max_length=255, blank=True, null=True)
file_hash = models.CharField(max_length=50, blank=True, null=True)
Expand Down
3 changes: 3 additions & 0 deletions onadata/apps/viewer/models/export.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ class Export(models.Model):
EXTERNAL_EXPORT = "external"
OSM_EXPORT = OSM
GOOGLE_SHEETS_EXPORT = "gsheets"
GEOJSON_EXPORT = "geojson"

EXPORT_MIMES = {
"xls": "vnd.ms-excel",
Expand All @@ -87,6 +88,7 @@ class Export(models.Model):
"sav_zip": "zip",
"sav": "sav",
"kml": "vnd.google-earth.kml+xml",
"geojson": "geo+json",
OSM: OSM,
}

Expand All @@ -101,6 +103,7 @@ class Export(models.Model):
(EXTERNAL_EXPORT, "Excel"),
(OSM, OSM),
(GOOGLE_SHEETS_EXPORT, "Google Sheets"),
(GEOJSON_EXPORT, "geojson"),
]

EXPORT_OPTION_FIELDS = [
Expand Down
9 changes: 7 additions & 2 deletions onadata/libs/serializers/geojson_serializer.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,12 +111,17 @@ def to_representation(self, obj):
ret["properties"][field] = obj.json.get(field)

if obj and ret and request:
fields = request.query_params.get("fields")
geo_field = request.query_params.get("geo_field")
simple_style = request.query_params.get("simple_style")
title = request.query_params.get("title")
if geo_field:
if "properties" in ret and title:
ret["properties"]["title"] = title
if "properties" in ret:
if title:
ret["properties"]["title"] = obj.json.get(title)
if fields:
for field in fields.split(","):
ret["properties"][field] = obj.json.get(field)
points = obj.json.get(geo_field)
geometry = (
geometry_from_string(points, simple_style)
Expand Down
14 changes: 12 additions & 2 deletions onadata/libs/serializers/metadata_serializer.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,13 @@
from rest_framework import serializers
from rest_framework.reverse import reverse


from onadata.apps.api.tools import update_role_by_meta_xform_perms
from onadata.libs.utils.api_export_tools import get_metadata_format
from onadata.apps.logger.models import DataView, Instance, Project, XForm
from onadata.apps.main.models import MetaData
from onadata.libs.permissions import ROLES, ManagerRole
from onadata.libs.serializers.fields.json_field import JsonField
from onadata.libs.serializers.fields.instance_related_field import InstanceRelatedField
from onadata.libs.serializers.fields.project_related_field import ProjectRelatedField
from onadata.libs.serializers.fields.xform_related_field import XFormRelatedField
Expand Down Expand Up @@ -71,6 +74,8 @@ def get_linked_object(parts):
"""
if isinstance(parts, list) and parts:
obj_type = parts[0]
if "geojson" in obj_type:
obj_type = obj_type.split("_")[0]
if obj_type in [DATAVIEW_TAG, XFORM_TAG] and len(parts) > 1:
obj_pk = parts[1]
try:
Expand Down Expand Up @@ -103,6 +108,7 @@ class MetaDataSerializer(serializers.HyperlinkedModelSerializer):
data_value = serializers.CharField(max_length=255, required=True)
data_type = serializers.ChoiceField(choices=METADATA_TYPES)
data_file = serializers.FileField(required=False)
extra_data = JsonField(required=False)
data_file_type = serializers.CharField(
max_length=255, required=False, allow_blank=True
)
Expand All @@ -122,6 +128,7 @@ class Meta:
"data_value",
"data_type",
"data_file",
"extra_data",
"data_file_type",
"media_url",
"file_hash",
Expand All @@ -140,14 +147,15 @@ def get_media_url(self, obj):
):
return obj.data_file.url
if obj.data_type in [MEDIA_TYPE] and obj.is_linked_dataset:
request = self.context.get("request")
kwargs = {
"kwargs": {
"pk": obj.content_object.pk,
"username": obj.content_object.user.username,
"metadata": obj.pk,
},
"request": self.context.get("request"),
"format": "csv",
"request": request,
"format": get_metadata_format(obj.data_value),
}

return reverse("xform-media", **kwargs)
Expand Down Expand Up @@ -262,6 +270,7 @@ def create(self, validated_data):
data_type = validated_data.get("data_type")
data_file = validated_data.get("data_file")
data_file_type = validated_data.get("data_file_type")
extra_data = validated_data.get('extra_data')

content_object = self.get_content_object(validated_data)
data_value = data_file.name if data_file else validated_data.get("data_value")
Expand Down Expand Up @@ -303,6 +312,7 @@ def create(self, validated_data):
data_type=data_type,
data_value=data_value,
data_file=data_file,
extra_data=extra_data,
data_file_type=data_file_type,
object_id=content_object.id,
)
Expand Down
Loading

0 comments on commit b4b5ec5

Please sign in to comment.