Skip to content

Commit

Permalink
Refactoring and permissions fix
Browse files Browse the repository at this point in the history
  • Loading branch information
oliverroick committed Nov 29, 2016
1 parent ef1cfd6 commit bcfe91a
Show file tree
Hide file tree
Showing 12 changed files with 245 additions and 159 deletions.
Empty file.
117 changes: 0 additions & 117 deletions cadasta/questionnaires/renderer/xform.py

This file was deleted.

10 changes: 0 additions & 10 deletions cadasta/questionnaires/tests/test_views_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,16 +51,6 @@ def test_get_questionnaire(self):
assert response.status_code == 200
assert response.content['id'] == questionnaire.id

def test_get_questionnaire_as_xform(self):
questionnaire = QuestionnaireFactory.create(project=self.prj)
response = self.request(user=self.user, get_data={'format': 'xform'})
assert response.status_code == 200
assert (response.headers['content-type'][1] ==
'application/xml; charset=utf-8')
assert '<{id} id="{id}" version="{v}"/>'.format(
id=questionnaire.id_string,
v=questionnaire.version) in response.content

def test_get_questionnaire_that_does_not_exist(self):
response = self.request(user=self.user)
assert response.status_code == 404
Expand Down
10 changes: 1 addition & 9 deletions cadasta/questionnaires/views/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,17 @@

from rest_framework import generics, mixins, status
from rest_framework.response import Response
from rest_framework.renderers import JSONRenderer, BrowsableAPIRenderer
from tutelary.mixins import APIPermissionRequiredMixin

from organization.models import Project
from ..models import Questionnaire
from ..serializers import QuestionnaireSerializer
from ..exceptions import InvalidXLSForm
from ..renderer.xform import XFormRenderer


class QuestionnaireDetail(APIPermissionRequiredMixin,
mixins.CreateModelMixin,
generics.RetrieveUpdateAPIView):
renderer_classes = (JSONRenderer, BrowsableAPIRenderer, XFormRenderer, )

def get_actions(self, request):
if request.GET.get('format') == 'xform':
return None
return 'questionnaire.view'

def patch_actions(self, request):
try:
Expand All @@ -31,7 +23,7 @@ def patch_actions(self, request):

serializer_class = QuestionnaireSerializer
permission_required = {
'GET': get_actions,
'GET': 'questionnaire.view',
'PUT': patch_actions,
}

Expand Down
138 changes: 138 additions & 0 deletions cadasta/xforms/renderers.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,14 @@
from django.utils.encoding import smart_text
from rest_framework.compat import six

from pyxform.builder import create_survey_element_from_dict
from lxml import etree
from rest_framework.renderers import BaseRenderer
from questionnaires.models import Question
from questionnaires.managers import fix_languages

QUESTION_TYPES = dict(Question.TYPE_CHOICES)


class XFormListRenderer(renderers.BaseRenderer):
"""
Expand Down Expand Up @@ -56,3 +64,133 @@ def _to_xml(self, xml, data):

else:
xml.characters(smart_text(data))


class XFormRenderer(BaseRenderer):
format = 'xform'
media_type = 'application/xml'

def transform_questions(self, questions):
children = []
for q in questions:
q['type'] = QUESTION_TYPES[q['type']]

if q.get('label', -1) is None:
del q['label']

if 'options' in q:
q['choices'] = q['options']

bind = {}
if q.get('required', False) is True:
bind['required'] = 'yes'
if q.get('relevant'):
bind['relevant'] = q.get('relevant')

if bind:
q['bind'] = bind

children.append(q)
return children

def transform_groups(self, groups):
transformed_groups = []
for g in groups:
group = {
'type': 'group',
'name': g.get('name'),
'label': g.get('label'),
'children': self.transform_questions(g.get('questions')),
'index': g.get('index')
}
if group['label'] is None:
del group['label']

bind = {}
if g.get('relevant'):
bind['relevant'] = g.get('relevant')

if bind:
group['bind'] = bind
transformed_groups.append(group)
return transformed_groups

def transform_to_xform_json(self, data):
json = {
'default_language': 'default',
'name': data.get('id_string'),
'sms_keyword': data.get('id_string'),
'type': 'survey',
'id_string': data.get('id_string'),
'title': data.get('id_string')
}

questions = self.transform_questions(data.get('questions', []))
question_groups = self.transform_groups(
data.get('question_groups', []))
json['children'] = sorted(questions + question_groups,
key=lambda x: x['index'])
return json

def insert_version_attribute(self, xform, root_node, version):
ns = {'xf': 'http://www.w3.org/2002/xforms'}
root = etree.fromstring(xform)
inst = root.find(
'.//xf:instance/xf:{root_node}'.format(
root_node=root_node
), namespaces=ns
)
inst.set('version', str(version))
xml = etree.tostring(
root, method='xml', encoding='utf-8', pretty_print=True
)
return xml

def insert_uuid_bind(self, xform, id_string):
ns = {'xf': 'http://www.w3.org/2002/xforms'}
root = etree.fromstring(xform)
model = root.find('.//xf:model', namespaces=ns)
etree.SubElement(model, 'bind', {
'calculate': "concat('uuid:', uuid())",
'nodeset': '/{}/meta/instanceID'.format(id_string),
'readonly': 'true()',
'type': 'string'
})
xml = etree.tostring(
root, method='xml', encoding='utf-8', pretty_print=True
)
return xml

def render(self, data, *args, **kwargs):
charset = 'utf-8'
root_node = 'xforms'
xmlns = "http://openrosa.org/xforms/xformsList"

if 'detail' in data.keys():
stream = StringIO()

xml = SimplerXMLGenerator(stream, charset)
xml.startDocument()
xml.startElement(root_node, {'xmlns': xmlns})

for key, value in six.iteritems(data):
xml.startElement(key, {})
xml.characters(smart_text(value))
xml.endElement(key)

xml.endElement(root_node)
xml.endDocument()
return stream.getvalue()
else:
json = self.transform_to_xform_json(data)
survey = create_survey_element_from_dict(json)
xml = survey.xml()
fix_languages(xml)
xml = xml.toxml()

xml = self.insert_version_attribute(xml,
data.get('id_string'),
data.get('version'))
xml = self.insert_uuid_bind(xml, data.get('id_string'))

return xml
8 changes: 2 additions & 6 deletions cadasta/xforms/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,9 @@ class XFormListSerializer(FieldSelectorSerializer,
downloadUrl = serializers.SerializerMethodField('get_xml_form')

def get_xml_form(self, obj):
url = reverse('api:v1:questionnaires:detail',
kwargs={
'organization': obj.project.organization.slug,
'project': obj.project.slug
}) + '?format=xform'
url = reverse('form-download', args=[obj.id])
url = self.context['request'].build_absolute_uri(url)
if self.context['request'].META['SERVER_PROTOCOL'] != 'HTTP/1.1':
if self.context['request'].META['SERVER_PROTOCOL'] == 'HTTPS/1.1':
url = url.replace('http://', 'https://')
return url

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from django.test import TestCase
from ..renderer.xform import XFormRenderer
from ..renderers import XFormRenderer


class XFormRendererTest(TestCase):
Expand Down
9 changes: 4 additions & 5 deletions cadasta/xforms/tests/test_serializers.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import pytest

from django.test import TestCase
from django.core.urlresolvers import reverse
from rest_framework.test import APIRequestFactory, force_authenticate

from core.tests.utils.cases import UserTestCase, FileStorageTestCase
Expand Down Expand Up @@ -40,16 +41,14 @@ def _test_serialize(self, https=False):

serializer = serializers.XFormListSerializer(
form, context={'request': request})
url_refix = 'https' if https else 'http'

assert serializer.data['formID'] == questionnaire.data['id_string']
assert serializer.data['name'] == questionnaire.data['title']
assert serializer.data['version'] == questionnaire.data['version']
assert (serializer.data['downloadUrl'] ==
'{}://testserver/api/v1/organizations/{}/projects/{}'
'/questionnaire/?format=xform'.format(
('https' if https else 'http'),
project.organization.slug,
project.slug))
url_refix + '://testserver' +
reverse('form-download', args=[form.id]))
assert serializer.data['hash'] == questionnaire.data['md5_hash']

def test_serialize(self):
Expand Down
7 changes: 7 additions & 0 deletions cadasta/xforms/tests/test_urls_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,13 @@ def test_xforms_list(self):
resolved = resolve('/collect/')
assert resolved.func.__name__ == api.XFormListView.__name__

def test_xforms_download(self):
assert reverse('form-download', args=['a']) == '/collect/formList/a/'

resolved = resolve('/collect/formList/a/')
assert resolved.func.__name__ == api.XFormDownloadView.__name__
assert resolved.kwargs['questionnaire'] == 'a'

def test_xforms_submission(self):
assert reverse('submissions') == '/collect/submission'

Expand Down
Loading

0 comments on commit bcfe91a

Please sign in to comment.