Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add project and form level odk submission-list and download endpoints #2451

Merged
merged 3 commits into from
Sep 4, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
134 changes: 134 additions & 0 deletions onadata/apps/api/tests/viewsets/test_briefcase_viewset.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,68 @@ def test_view_submission_list(self):
'{{resumptionCursor}}', '%s' % last_index)
self.assertContains(response, expected_submission_list)

def test_view_submission_list_w_xformid(self):
view = BriefcaseViewset.as_view({'get': 'list'})
self._publish_xml_form()
self._make_submissions()
self._submission_list_url = reverse(
'view-submission-list',
kwargs={'xform_pk': self.xform.pk})
request = self.factory.get(
self._submission_list_url,
data={'formId': self.xform.id_string})
response = view(request, xform_pk=self.xform.pk)
self.assertEqual(response.status_code, 401)
auth = DigestAuth(self.login_username, self.login_password)
request.META.update(auth(request.META, response))
response = view(request, xform_pk=self.xform.pk)
self.assertEqual(response.status_code, 200)
submission_list_path = os.path.join(
self.main_directory, 'fixtures', 'transportation',
'view', 'submissionList.xml')
instances = ordered_instances(self.xform)

self.assertEqual(instances.count(), NUM_INSTANCES)

last_index = instances[instances.count() - 1].pk
with codecs.open(submission_list_path, 'rb', encoding='utf-8') as f:
expected_submission_list = f.read()
expected_submission_list = \
expected_submission_list.replace(
'{{resumptionCursor}}', '%s' % last_index)
self.assertContains(response, expected_submission_list)

def test_view_submission_list_w_projectid(self):
view = BriefcaseViewset.as_view({'get': 'list'})
self._publish_xml_form()
self._make_submissions()
self._submission_list_url = reverse(
'view-submission-list',
kwargs={'project_pk': self.xform.project.pk})
request = self.factory.get(
self._submission_list_url,
data={'formId': self.xform.id_string})
response = view(request, project_pk=self.xform.project.pk)
self.assertEqual(response.status_code, 401)
auth = DigestAuth(self.login_username, self.login_password)
request.META.update(auth(request.META, response))
response = view(request, project_pk=self.xform.project.pk)
self.assertEqual(response.status_code, 200)
submission_list_path = os.path.join(
self.main_directory, 'fixtures', 'transportation',
'view', 'submissionList.xml')
instances = ordered_instances(self.xform)

self.assertEqual(instances.count(), NUM_INSTANCES)

last_index = instances[instances.count() - 1].pk
with codecs.open(submission_list_path, 'rb', encoding='utf-8') as f:
expected_submission_list = f.read()
expected_submission_list = \
expected_submission_list.replace(
'{{resumptionCursor}}', '%s' % last_index)
self.assertContains(response, expected_submission_list)

def test_view_submission_list_w_soft_deleted_submission(self):
view = BriefcaseViewset.as_view({'get': 'list'})
self._publish_xml_form()
Expand Down Expand Up @@ -307,6 +369,78 @@ def test_view_downloadSubmission(self):
self.assertContains(response, instanceId, status_code=200)
self.assertMultiLineEqual(response.content.decode('utf-8'), text)

def test_view_downloadSubmission_w_xformid(self):
view = BriefcaseViewset.as_view({'get': 'retrieve'})
self._publish_xml_form()
self.maxDiff = None
self._submit_transport_instance_w_attachment()
instanceId = u'5b2cc313-fc09-437e-8149-fcd32f695d41'
instance = Instance.objects.get(uuid=instanceId)
formId = u'%(formId)s[@version=null and @uiVersion=null]/' \
u'%(formId)s[@key=uuid:%(instanceId)s]' % {
'formId': self.xform.id_string,
'instanceId': instanceId}
params = {'formId': formId}
auth = DigestAuth(self.login_username, self.login_password)
self._download_submission_url = reverse(
'view-download-submission',
kwargs={'xform_pk': self.xform.pk})
request = self.factory.get(
self._download_submission_url, data=params)
response = view(request, xform_pk=self.xform.pk)
self.assertEqual(response.status_code, 401)
request.META.update(auth(request.META, response))
response = view(request, xform_pk=self.xform.pk)
text = "uuid:%s" % instanceId
download_submission_path = os.path.join(
self.main_directory, 'fixtures', 'transportation',
'view', 'downloadSubmission.xml')
with codecs.open(download_submission_path, encoding='utf-8') as f:
text = f.read()
for var in ((u'{{submissionDate}}',
instance.date_created.isoformat()),
(u'{{form_id}}', str(self.xform.id)),
(u'{{media_id}}', str(self.attachment.id))):
text = text.replace(*var)
self.assertContains(response, instanceId, status_code=200)
self.assertMultiLineEqual(response.content.decode('utf-8'), text)

def test_view_downloadSubmission_w_projectid(self):
view = BriefcaseViewset.as_view({'get': 'retrieve'})
self._publish_xml_form()
self.maxDiff = None
self._submit_transport_instance_w_attachment()
instanceId = u'5b2cc313-fc09-437e-8149-fcd32f695d41'
instance = Instance.objects.get(uuid=instanceId)
formId = u'%(formId)s[@version=null and @uiVersion=null]/' \
u'%(formId)s[@key=uuid:%(instanceId)s]' % {
'formId': self.xform.id_string,
'instanceId': instanceId}
params = {'formId': formId}
auth = DigestAuth(self.login_username, self.login_password)
self._download_submission_url = reverse(
'view-download-submission',
kwargs={'project_pk': self.xform.project.pk})
request = self.factory.get(
self._download_submission_url, data=params)
response = view(request, project_pk=self.xform.project.pk)
self.assertEqual(response.status_code, 401)
request.META.update(auth(request.META, response))
response = view(request, project_pk=self.xform.project.pk)
text = "uuid:%s" % instanceId
download_submission_path = os.path.join(
self.main_directory, 'fixtures', 'transportation',
'view', 'downloadSubmission.xml')
with codecs.open(download_submission_path, encoding='utf-8') as f:
text = f.read()
for var in ((u'{{submissionDate}}',
instance.date_created.isoformat()),
(u'{{form_id}}', str(self.xform.id)),
(u'{{media_id}}', str(self.attachment.id))):
text = text.replace(*var)
self.assertContains(response, instanceId, status_code=200)
self.assertMultiLineEqual(response.content.decode('utf-8'), text)

def test_view_downloadSubmission_OtherUser(self):
view = BriefcaseViewset.as_view({'get': 'retrieve'})
self._publish_xml_form()
Expand Down
39 changes: 36 additions & 3 deletions onadata/apps/api/viewsets/briefcase_viewset.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,18 @@ def get_object(self, queryset=None):
id_string = _extract_id_string(form_id)
uuid = _extract_uuid(form_id)
username = self.kwargs.get("username")
form_pk = self.kwargs.get("xform_pk")
project_pk = self.kwargs.get("project_pk")

if not username:
if form_pk:
queryset = self.queryset.filter(pk=form_pk)
if queryset.first():
username = queryset.first().user.username
elif project_pk:
queryset = self.queryset.filter(project__pk=project_pk)
if queryset.first():
username = queryset.first().user.username

obj = get_object_or_404(
Instance,
Expand All @@ -109,24 +121,45 @@ def get_object(self, queryset=None):

return obj

# pylint: disable=too-many-branches
# pylint: disable=too-many-branches,too-many-statements
def filter_queryset(self, queryset):
"""
Filters an XForm submission instances using ODK Aggregate query parameters.
"""
username = self.kwargs.get("username")
if username is None and self.request.user.is_anonymous:
form_pk = self.kwargs.get("xform_pk")
project_pk = self.kwargs.get("project_pk")

if (
not username or not form_pk or not project_pk
) and self.request.user.is_anonymous:
# raises a permission denied exception, forces authentication
self.permission_denied(self.request)

if username is not None and self.request.user.is_anonymous:
profile = get_object_or_404(UserProfile, user__username__iexact=username)
profile = None
if username:
profile = get_object_or_404(
UserProfile, user__username__iexact=username
)
elif form_pk:
queryset = queryset.filter(pk=form_pk)
if queryset.first():
profile = queryset.first().user.profile
elif project_pk:
queryset = queryset.filter(project__pk=project_pk)
if queryset.first():
profile = queryset.first().user.profile

if profile.require_auth:
# raises a permission denied exception, forces authentication
self.permission_denied(self.request)
else:
queryset = queryset.filter(user=profile.user)
elif form_pk:
queryset = queryset.filter(pk=form_pk)
elif project_pk:
queryset = queryset.filter(project__pk=project_pk)
else:
queryset = super().filter_queryset(queryset)

Expand Down
20 changes: 20 additions & 0 deletions onadata/apps/main/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -201,11 +201,31 @@
BriefcaseViewset.as_view({"get": "list", "head": "list"}),
name="view-submission-list",
),
re_path(
r"^forms/(?P<xform_pk>\w+)/view/submissionList$",
Copy link
Contributor

@kelvin-muchiri kelvin-muchiri Jul 17, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@KipSigei Should we prefer submission-list over submissionList?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@kelvin-muchiri The endpoints are specific to the ODK Briefcase app and won't work if they are modified

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BriefcaseViewset.as_view({"get": "list", "head": "list"}),
name="view-submission-list",
),
re_path(
r"^projects/(?P<project_pk>\d+)/view/submissionList$",
BriefcaseViewset.as_view({"get": "list", "head": "list"}),
name="view-submission-list",
),
re_path(
r"^(?P<username>\w+)/view/downloadSubmission$",
BriefcaseViewset.as_view({"get": "retrieve", "head": "retrieve"}),
name="view-download-submission",
),
re_path(
r"^forms/(?P<xform_pk>\w+)/view/downloadSubmission$",
BriefcaseViewset.as_view({"get": "retrieve", "head": "retrieve"}),
name="view-download-submission",
),
re_path(
r"^projects/(?P<project_pk>\d+)/view/downloadSubmission$",
BriefcaseViewset.as_view({"get": "retrieve", "head": "retrieve"}),
name="view-download-submission",
),
re_path(
r"^(?P<username>\w+)/formUpload$",
BriefcaseViewset.as_view({"post": "create", "head": "create"}),
Expand Down
Loading