diff --git a/cadasta/questionnaires/managers.py b/cadasta/questionnaires/managers.py index 9eb4468ec..8bca1f608 100644 --- a/cadasta/questionnaires/managers.py +++ b/cadasta/questionnaires/managers.py @@ -43,7 +43,9 @@ def create_children(children, errors=[], project=None, kwargs={}): if children: for c in children: - if c.get('type') == 'group': + if c.get('type') == 'repeat': + create_children(c['children'], errors, project, kwargs) + elif c.get('type') == 'group': model_name = 'QuestionGroup' # parse attribute group @@ -61,8 +63,9 @@ def create_children(children, errors=[], project=None, kwargs={}): else: model_name = 'Question' - model = apps.get_model('questionnaires', model_name) - model.objects.create_from_dict(dict=c, errors=errors, **kwargs) + if c.get('type') != 'repeat': + model = apps.get_model('questionnaires', model_name) + model.objects.create_from_dict(dict=c, errors=errors, **kwargs) def create_options(options, question, errors=[]): @@ -234,7 +237,6 @@ class QuestionManager(models.Manager): def create_from_dict(self, errors=[], **kwargs): dict = kwargs.pop('dict') instance = self.model(**kwargs) - type_dict = {name: code for code, name in instance.TYPE_CHOICES} instance.type = type_dict[dict.get('type')] diff --git a/cadasta/questionnaires/tests/files/t_questionnaire.xlsx b/cadasta/questionnaires/tests/files/t_questionnaire.xlsx new file mode 100644 index 000000000..868bbcd25 Binary files /dev/null and b/cadasta/questionnaires/tests/files/t_questionnaire.xlsx differ diff --git a/cadasta/questionnaires/tests/files/test_standard_questionnaire_bad.xlsx b/cadasta/questionnaires/tests/files/t_questionnaire_bad.xlsx similarity index 100% rename from cadasta/questionnaires/tests/files/test_standard_questionnaire_bad.xlsx rename to cadasta/questionnaires/tests/files/t_questionnaire_bad.xlsx diff --git a/cadasta/questionnaires/tests/files/test_standard_questionnaire_2.xlsx b/cadasta/questionnaires/tests/files/t_questionnaire_geotype_select.xlsx similarity index 100% rename from cadasta/questionnaires/tests/files/test_standard_questionnaire_2.xlsx rename to cadasta/questionnaires/tests/files/t_questionnaire_geotype_select.xlsx diff --git a/cadasta/questionnaires/tests/files/t_questionnaire_repeat_location.xlsx b/cadasta/questionnaires/tests/files/t_questionnaire_repeat_location.xlsx new file mode 100644 index 000000000..59ec8e252 Binary files /dev/null and b/cadasta/questionnaires/tests/files/t_questionnaire_repeat_location.xlsx differ diff --git a/cadasta/questionnaires/tests/files/t_questionnaire_repeat_minus_tenure.xlsx b/cadasta/questionnaires/tests/files/t_questionnaire_repeat_minus_tenure.xlsx new file mode 100644 index 000000000..ce2c0251a Binary files /dev/null and b/cadasta/questionnaires/tests/files/t_questionnaire_repeat_minus_tenure.xlsx differ diff --git a/cadasta/questionnaires/tests/files/t_questionnaire_repeat_party.xlsx b/cadasta/questionnaires/tests/files/t_questionnaire_repeat_party.xlsx new file mode 100644 index 000000000..3ccdbef78 Binary files /dev/null and b/cadasta/questionnaires/tests/files/t_questionnaire_repeat_party.xlsx differ diff --git a/cadasta/questionnaires/tests/files/t_questionnaire_repeat_party_minus_tenure.xlsx b/cadasta/questionnaires/tests/files/t_questionnaire_repeat_party_minus_tenure.xlsx new file mode 100644 index 000000000..af02224f6 Binary files /dev/null and b/cadasta/questionnaires/tests/files/t_questionnaire_repeat_party_minus_tenure.xlsx differ diff --git a/cadasta/questionnaires/tests/files/test_standard_questionnaire.xlsx b/cadasta/questionnaires/tests/files/test_standard_questionnaire.xlsx deleted file mode 100644 index abe282aeb..000000000 Binary files a/cadasta/questionnaires/tests/files/test_standard_questionnaire.xlsx and /dev/null differ diff --git a/cadasta/questionnaires/tests/test_managers.py b/cadasta/questionnaires/tests/test_managers.py index b86ee82c4..db555855c 100644 --- a/cadasta/questionnaires/tests/test_managers.py +++ b/cadasta/questionnaires/tests/test_managers.py @@ -58,6 +58,51 @@ def test_create_children(self): questionnaire=questionnaire, question_group__isnull=False).count() == 1 + def test_create_children_with_repeat_group(self): + questionnaire = factories.QuestionnaireFactory.create() + children = [{ + 'label': 'This form showcases the different question', + 'name': 'intro', + 'type': 'note' + }, { + 'label': 'Text question type', + 'name': 'text_questions', + 'type': 'repeat', + 'children': [ + { + 'hint': 'Can be short or long but ' + 'always one line (type = ' + 'text)', + 'label': 'Text', + 'name': 'my_string', + 'type': 'text' + }, + { + 'hint': 'Nested group', + 'label': 'Group', + 'name': 'my_group', + 'type': 'group', + 'children': [ + { + 'hint': 'More text', + 'label': 'Text', + 'name': 'my_group_string', + 'type': 'text' + }, + ] + } + ], + }] + create_children(children, kwargs={'questionnaire': questionnaire}) + + assert models.QuestionGroup.objects.filter( + questionnaire=questionnaire).count() == 1 + assert models.Question.objects.filter( + questionnaire=questionnaire).count() == 3 + assert models.Question.objects.filter( + questionnaire=questionnaire, + question_group__isnull=False).count() == 1 + class CreateOptionsTest(TestCase): diff --git a/cadasta/xforms/mixins/model_helper.py b/cadasta/xforms/mixins/model_helper.py index 3270198e0..bd7d20db0 100644 --- a/cadasta/xforms/mixins/model_helper.py +++ b/cadasta/xforms/mixins/model_helper.py @@ -24,7 +24,7 @@ def __init__(self, *arg): self.arg = arg def create_models(self, data): - questionnaire = self.get_questionnaire( + questionnaire = self._get_questionnaire( id_string=data['id'], version=data['version'] ) project = questionnaire.project @@ -32,96 +32,265 @@ def create_models(self, data): if project.current_questionnaire != questionnaire.id: raise InvalidXMLSubmission(_('Form out of date')) - party = self.create_party( + party, party_resources = self.create_party( data=data, project=project ) - location = self.create_spatial_unit( + location, location_resources = self.create_spatial_unit( data=data, project=project, questionnaire=questionnaire, party=party ) - tenure = self.create_tenure_relationship( + tenure_resources = self.create_tenure_relationship( data=data, project=project, party=party, location=location ) - return questionnaire, party.id, location.id, tenure.id + return (questionnaire, party_resources, location_resources, + tenure_resources) def create_party(self, data, project): + party_objects = [] + party_resources = [] try: - party = Party.objects.create( - project=project, - name=data['party_name'], - type=data['party_type'], - attributes=self.get_attributes(data, 'party') - ) + party_groups = self._format_repeat(data, ['party']) + for group in party_groups: + party = Party.objects.create( + project=project, + name=group['party_name'], + type=group['party_type'], + attributes=self._get_attributes(group, 'party') + ) + + party_resources.append( + self._get_resource_names(group, party, 'party') + ) + party_objects.append(party) + except Exception as e: raise InvalidXMLSubmission(_( "Party error: {}".format(e))) - return party + return party_objects, party_resources def create_spatial_unit(self, data, project, questionnaire, party=None): - if 'location_geotrace' in data.keys(): - location_geometry = data['location_geotrace'] - geoshape = False - elif 'location_geoshape' in data.keys(): - location_geometry = data['location_geoshape'] - geoshape = True - else: - location_geometry = data['location_geometry'] - geoshape = Question.objects.filter( - questionnaire=questionnaire, type='GS').exists() + location_resources = [] + location_objects = [] try: - location = SpatialUnit.objects.create( - project=project, - type=data['location_type'], - geometry=self._format_geometry(location_geometry, geoshape), - attributes=self.get_attributes(data, 'location') - ) + location_group = self._format_repeat(data, ['location']) + + for group in location_group: + if 'location_geotrace' in group.keys(): + location_geometry = group['location_geotrace'] + geoshape = False + elif 'location_geoshape' in group.keys(): + location_geometry = group['location_geoshape'] + geoshape = True + else: + location_geometry = group['location_geometry'] + geoshape = Question.objects.filter( + questionnaire=questionnaire, type='GS').exists() + + location = SpatialUnit.objects.create( + project=project, + type=group['location_type'], + geometry=self._format_geometry( + location_geometry, + geoshape + ), + attributes=self._get_attributes(group, 'location') + ) + + location_resources.append( + self._get_resource_names(group, location, 'location') + ) + location_objects.append(location) + except Exception as e: raise InvalidXMLSubmission(_( 'Location error: {}'.format(e))) - return location + return location_objects, location_resources def create_tenure_relationship(self, data, party, location, project): + tenure_resources = [] try: - tenure = TenureRelationship.objects.create( - project=project, - party=party, - spatial_unit=location, - tenure_type=TenureRelationshipType.objects.get( - id=data['tenure_type']), - attributes=self.get_attributes(data, 'tenure_relationship') - ) + if data.get('tenure_type'): + tenure_group = [data] + else: + tenure_group = self._format_repeat(data, ['party', 'location']) + + for p in range(len(party)): + for l in range(len(location)): + t = 0 + if len(tenure_group) > 1: + if p > l: + t = p + else: + t = l + tenure = TenureRelationship.objects.create( + project=project, + party=party[p], + spatial_unit=location[l], + tenure_type=TenureRelationshipType.objects.get( + id=tenure_group[t]['tenure_type']), + attributes=self._get_attributes( + tenure_group[t], + 'tenure_relationship') + ) + tenure_resources.append( + self._get_resource_names( + tenure_group[t], tenure, 'tenure') + ) + except Exception as e: raise InvalidXMLSubmission(_( "Tenure relationship error: {}".format(e))) - return tenure + return tenure_resources - def add_file_to_resource(self, data, user, project, content_object=None): + def create_resource(self, data, user, project, content_object=None): Storage = get_storage_class() - storage = Storage() - url = storage.save('resources/' + data.name, data.file.read()) + file = data.file.read() try: - resource = Resource.objects.create( - name=data.name, - file=url, - content_object=content_object, - mime_type=data.content_type, - contributor=user, - project=project, - original_file=data.name - ) - resource.full_clean() + if file == b'': + Resource.objects.get( + name=data.name, + contributor=user, + mime_type=data.content_type, + project=project, + original_file=data.name + ).content_objects.create( + content_object=content_object + ) + else: + url = Storage().save('resources/' + data.name, file) + Resource.objects.create( + name=data.name, + file=url, + content_object=content_object, + mime_type=data.content_type, + contributor=user, + project=project, + original_file=data.name + ).full_clean() except Exception as e: raise InvalidXMLSubmission(_("{}".format(e))) + def upload_submission_data(self, request): + if 'xml_submission_file' not in request.data.keys(): + raise InvalidXMLSubmission(_('XML submission not found')) + + xml_submission_file = request.data['xml_submission_file'].read() + full_submission = XFormToDict( + xml_submission_file.decode('utf-8')).get_dict() + + submission = full_submission[list(full_submission.keys())[0]] + + with transaction.atomic(): + questionnaire, party, location, tenure = self.create_models( + submission) + + party_submission = [submission] + location_submission = [submission] + tenure_submission = [submission] + + if 'party_repeat' in submission: + party_submission = self._format_repeat(submission, ['party']) + if 'tenure_type' in party_submission[0]: + tenure_submission = party_submission + + elif 'location_repeat' in submission: + location_submission = self._format_repeat( + submission, ['location']) + if 'tenure_type' in location_submission[0]: + tenure_submission = location_submission + + party_resources = [] + location_resources = [] + tenure_resources = [] + + for group in party_submission: + party_resources.extend( + self._get_resource_files(group, 'party') + ) + + for group in location_submission: + location_resources.extend( + self._get_resource_files(group, 'location') + ) + + for group in tenure_submission: + tenure_resources.extend( + self._get_resource_files(group, 'tenure') + ) + + resource_data = { + 'project': questionnaire.project, + 'location_resources': location_resources, + 'locations': location, + 'party_resources': party_resources, + 'parties': party, + 'tenure_resources': tenure_resources, + 'tenures': tenure, + } + self.upload_resource_files(request, resource_data) + + return XFormSubmission( + json_submission=full_submission, + user=request.user, + questionnaire=questionnaire) + + def upload_resource_files(self, request, data): + user = request.user + files = request.FILES + files.pop('xml_submission_file') + project = data['project'] + for file_name in files: + if file_name in data['location_resources']: + for location in data['locations']: + if file_name in location['resources']: + content_object = SpatialUnit.objects.get( + id=location['id']) + + self.create_resource(data=files[file_name], + user=user, + project=project, + content_object=content_object + ) + + elif file_name in data['party_resources']: + for party in data['parties']: + if file_name in party['resources']: + content_object = Party.objects.get( + id=party['id']) + + self.create_resource(data=files[file_name], + user=user, + project=project, + content_object=content_object + ) + + elif file_name in data['tenure_resources']: + for tenure in data['tenures']: + if file_name in tenure['resources']: + content_object = TenureRelationship.objects.get( + id=tenure['id']) + + self.create_resource(data=files[file_name], + user=user, + project=project, + content_object=content_object + ) + else: + self.create_resource(data=files[file_name], + user=user, + project=project, + content_object=None + ) + def _format_geometry(self, coords, geoshape=False): if coords == '': return '' @@ -153,7 +322,18 @@ def _format_geometry(self, coords, geoshape=False): latlng = [x for x in latlng if x] return dumps(Point(float(latlng[1]), float(latlng[0]))) - def get_questionnaire(self, id_string, version): + def _format_repeat(self, data, model_type): + repeat_group = [data] + for model in model_type: + if '{}_repeat'.format(model) in data: + repeat_group = data['{}_repeat'.format(model)] + + if type(repeat_group) != list: + repeat_group = [repeat_group] + + return repeat_group + + def _get_questionnaire(self, id_string, version): try: return Questionnaire.objects.get( id_string=id_string, version=int(version) @@ -161,7 +341,7 @@ def get_questionnaire(self, id_string, version): except Questionnaire.DoesNotExist: raise ValidationError(_('Questionnaire not found.')) - def get_attributes(self, data, model_type): + def _get_attributes(self, data, model_type): attributes = {} for attr_group in data: if '{model}_attributes'.format(model=model_type) in attr_group: @@ -169,73 +349,28 @@ def get_attributes(self, data, model_type): attributes[item] = data[attr_group][item] return attributes + def _get_resource_files(self, data, model_type): + resources = [] + for file_name in data.keys(): + if ("{}_resource".format(model_type) in file_name or + "{}_photo".format(model_type) in file_name): + resources.append(data[file_name]) + return resources + + def _get_resource_names(self, data, model, model_type): + resources = {'id': model.id, 'resources': []} + # for legacy xlsforms + if '{}_photo'.format(model_type) in data.keys(): + resources['resources'].append( + data['{}_photo'.format(model_type)]) + + for key in data.keys(): + if '{}_resource'.format(model_type) in key: + resources['resources'].append( + data[key]) + return resources + # ~~~~~~~~~~~~~~~ # To Do: # Add location<->location and party<->party relationship # ~~~~~~~~~~~~~~~ - def upload_submission_data(self, request): - if 'xml_submission_file' not in request.data.keys(): - raise InvalidXMLSubmission(_('XML submission not found')) - - xml_submission_file = request.data['xml_submission_file'].read() - full_submission = XFormToDict( - xml_submission_file.decode('utf-8') - ).get_dict() - - submission = full_submission[list(full_submission.keys())[0]] - - with transaction.atomic(): - questionnaire, party, location, tenure = self.create_models( - submission) - party_resources = [] - location_resources = [] - tenure_resources = [] - for file_name in submission.keys(): - if ("party_resource" in file_name or - "party_photo" in file_name): - party_resources.append(submission[file_name]) - - if ("location_resource" in file_name or - "location_photo" in file_name): - location_resources.append(submission[file_name]) - - if ("tenure_resource" in file_name): - tenure_resources.append(submission[file_name]) - - resource_data = { - 'project': questionnaire.project, - 'location_resources': location_resources, - 'location': location, - 'party_resources': party_resources, - 'party_id': party, - 'tenure_resources': tenure_resources, - 'tenure_id': tenure, - } - self.upload_files(request, resource_data) - - return XFormSubmission( - json_submission=full_submission, - user=request.user, - questionnaire=questionnaire) - - def upload_files(self, request, data): - user = request.user - files = request.FILES - files.pop('xml_submission_file') - project = data['project'] - for file_name in files: - content_object = None - if file_name in data['location_resources']: - content_object = SpatialUnit.objects.get( - id=data['location']) - elif file_name in data['party_resources']: - content_object = Party.objects.get( - id=data['party_id']) - elif file_name in data['tenure_resources']: - content_object = TenureRelationship.objects.get( - id=data['tenure_id']) - - self.add_file_to_resource(data=files[file_name], - user=user, - project=project, - content_object=content_object) diff --git a/cadasta/xforms/tests/files/test_image_five.png b/cadasta/xforms/tests/files/test_image_five.png new file mode 100644 index 000000000..e092ea3a5 Binary files /dev/null and b/cadasta/xforms/tests/files/test_image_five.png differ diff --git a/cadasta/xforms/tests/files/test_image_four.png b/cadasta/xforms/tests/files/test_image_four.png new file mode 100644 index 000000000..3f35d2d7a Binary files /dev/null and b/cadasta/xforms/tests/files/test_image_four.png differ diff --git a/cadasta/xforms/tests/files/test_image_three.png b/cadasta/xforms/tests/files/test_image_three.png new file mode 100644 index 000000000..994331cdd Binary files /dev/null and b/cadasta/xforms/tests/files/test_image_three.png differ diff --git a/cadasta/xforms/tests/files/test_resources.py b/cadasta/xforms/tests/files/test_resources.py index 81dc161da..161f1368d 100644 --- a/cadasta/xforms/tests/files/test_resources.py +++ b/cadasta/xforms/tests/files/test_resources.py @@ -1,6 +1,6 @@ -FORM = ''' - +STANDARD = ''' + 2016-07-07T16:38:20.310-04 2016-07-07T16:39:23.673-04 2016-07-07 @@ -13,6 +13,9 @@ test_image_one.png test_image_two.png test_audio_one.mp3 + test_image_one.png + test_image_two.png + test_image_three.png LH Middle Earth @@ -34,48 +37,11 @@ uuid:b3f225d3-0fac-4a0b-80c7-60e6db4cc0ad - '''.strip() + '''.strip() -INVALID_FORM = ''' - - 2016-07-07T16:38:20.310-04 - 2016-07-07T16:39:23.673-04 - 2016-07-07 - 00:bb:3a:44:d0:fb - - <party_type>IN</party_type> - <party_name></party_name> - <location_geometry>40.6890612 -73.9925067 0.0 0.0;</location_geometry> - <location_type>MI</location_type> - <location_resource_photo>test_image.png</location_resource_photo> - <party_resource_photo /> - <tenure_type>LH</tenure_type> - <location_attributes> - <name>Null Island</name> - </location_attributes> - <party_attributes_default> - <notes>Party attribute default notes.</notes> - </party_attributes_default> - <party_attributes_individual> - <gender>f</gender> - <homeowner>no</homeowner> - <dob>2016-07-07</dob> - </party_attributes_individual> - <party_relationship_attributes> - <notes>Party relationship notes.</notes> - </party_relationship_attributes> - <tenure_relationship_attributes> - <notes>Tenure relationship notes.</notes> - </tenure_relationship_attributes> - <meta> - <instanceID>uuid:b3f225d3-0fac-4a0b-80c7-60e6db4cc0ad</instanceID> - </meta> - </test_standard_questionnaire>'''.strip() - -POLY_FORM = '''<?xml version=\'1.0\' ?> - <test_standard_questionnaire - id="test_standard_questionnaire" version="20160727122110"> +POLY = '''<?xml version=\'1.0\' ?> + <t_questionnaire + id="t_questionnaire" version="20160727122110"> <start>2016-07-07T16:38:20.310-04</start> <end>2016-07-07T16:39:23.673-04</end> <today>2016-07-07</today> @@ -96,11 +62,11 @@ <meta> <instanceID>uuid:b3f225d3-0fac-4a0b-80c7-60e6db4cc0ad</instanceID> </meta> - </test_standard_questionnaire>'''.strip() + </t_questionnaire>'''.strip() -LINE_FORM = '''<?xml version=\'1.0\' ?> - <test_standard_questionnaire - id="test_standard_questionnaire" version="20160727122110"> +LINE = '''<?xml version=\'1.0\' ?> + <t_questionnaire + id="t_questionnaire" version="20160727122110"> <start>2016-07-07T16:38:20.310-04</start> <end>2016-07-07T16:39:23.673-04</end> <today>2016-07-07</today> @@ -122,11 +88,11 @@ <meta> <instanceID>uuid:b3f225d3-0fac-4a0b-80c7-60e6db4cc0ad</instanceID> </meta> - </test_standard_questionnaire>'''.strip() + </t_questionnaire>'''.strip() -MISSING_SEMI_FORM = '''<?xml version=\'1.0\' ?> - <test_standard_questionnaire - id="test_standard_questionnaire" version="20160727122110"> +MISSING_SEMI = '''<?xml version=\'1.0\' ?> + <t_questionnaire + id="t_questionnaire" version="20160727122110"> <start>2016-07-07T16:38:20.310-04</start> <end>2016-07-07T16:39:23.673-04</end> <today>2016-07-07</today> @@ -145,11 +111,11 @@ <meta> <instanceID>uuid:b3f225d3-0fac-4a0b-80c7-60e6db4cc0ad</instanceID> </meta> - </test_standard_questionnaire>'''.strip() + </t_questionnaire>'''.strip() -GEOSHAPE_FORM = '''<?xml version=\'1.0\' ?> - <test_standard_questionnaire_2 - id="test_standard_questionnaire_2" version="20160727122111"> +GEOTYPE_SELECT = '''<?xml version=\'1.0\' ?> + <t_questionnaire_geotype_select + id="t_questionnaire_geotype_select" version="20160727122111"> <start>2016-07-07T16:38:20.310-04</start> <end>2016-07-07T16:39:23.673-04</end> <today>2016-07-07</today> @@ -172,11 +138,11 @@ <meta> <instanceID>uuid:b3f225d3-0fac-4a0b-80c7-60e6db4cc0ad</instanceID> </meta> - </test_standard_questionnaire_2>'''.strip() + </t_questionnaire_geotype_select>'''.strip() -NEITHER_FORM = '''<?xml version=\'1.0\' ?> - <test_standard_questionnaire_2 - id="test_standard_questionnaire_2" version="20160727122111"> +GEOTYPE_NEITHER = '''<?xml version=\'1.0\' ?> + <t_questionnaire_geotype_select + id="t_questionnaire_geotype_select" version="20160727122111"> <start>2016-07-07T16:38:20.310-04</start> <end>2016-07-07T16:39:23.673-04</end> <today>2016-07-07</today> @@ -199,7 +165,7 @@ <meta> <instanceID>uuid:b3f225d3-0fac-4a0b-80c7-60e6db4cc0ad</instanceID> </meta> - </test_standard_questionnaire_2>'''.strip() + </t_questionnaire_geotype_select>'''.strip() BAD_QUESTIONNAIRE = '''<?xml version=\'1.0\' ?> <tax_return id="tax_return" version="20160727122110"> @@ -220,9 +186,9 @@ </meta> </tax_return>'''.strip() -BAD_LOCATION_FORM = '''<?xml version=\'1.0\' ?> - <test_standard_questionnaire - id="test_standard_questionnaire" version="20160727122110"> +BAD_LOCATION = '''<?xml version=\'1.0\' ?> + <t_questionnaire + id="t_questionnaire" version="20160727122110"> <start>2016-07-07T16:38:20.310-04</start> <end>2016-07-07T16:39:23.673-04</end> <today>2016-07-07</today> @@ -236,11 +202,11 @@ <meta> <instanceID>uuid:b3f225d3-0fac-4a0b-80c7-60e6db4cc0ad</instanceID> </meta> - </test_standard_questionnaire>'''.strip() + </t_questionnaire>'''.strip() -BAD_PARTY_FORM = '''<?xml version=\'1.0\' ?> - <test_standard_questionnaire - id="test_standard_questionnaire" version="20160727122110"> +BAD_PARTY = '''<?xml version=\'1.0\' ?> + <t_questionnaire + id="t_questionnaire" version="20160727122110"> <start>2016-07-07T16:38:20.310-04</start> <end>2016-07-07T16:39:23.673-04</end> <today>2016-07-07</today> @@ -254,11 +220,11 @@ <meta> <instanceID>uuid:b3f225d3-0fac-4a0b-80c7-60e6db4cc0ad</instanceID> </meta> - </test_standard_questionnaire>'''.strip() + </t_questionnaire>'''.strip() -BAD_TENURE_FORM = '''<?xml version=\'1.0\' ?> - <test_standard_questionnaire - id="test_standard_questionnaire" version="20160727122110"> +BAD_TENURE = '''<?xml version=\'1.0\' ?> + <t_questionnaire + id="t_questionnaire" version="20160727122110"> <start>2016-07-07T16:38:20.310-04</start> <end>2016-07-07T16:39:23.673-04</end> <today>2016-07-07</today> @@ -272,11 +238,11 @@ <meta> <instanceID>uuid:b3f225d3-0fac-4a0b-80c7-60e6db4cc0ad</instanceID> </meta> - </test_standard_questionnaire>'''.strip() + </t_questionnaire>'''.strip() -BAD_RESOURCE_FORM = '''<?xml version=\'1.0\' ?> - <test_standard_questionnaire_bad - id="test_standard_questionnaire_bad" version="20160727122112"> +BAD_RESOURCE = '''<?xml version=\'1.0\' ?> + <t_questionnaire_bad + id="t_questionnaire_bad" version="20160727122112"> <start>2016-07-07T16:38:20.310-04</start> <end>2016-07-07T16:39:23.673-04</end> <today>2016-07-07</today> @@ -291,19 +257,353 @@ <meta> <instanceID>uuid:b3f225d3-0fac-4a0b-80c7-60e6db4cc0ad</instanceID> </meta> - </test_standard_questionnaire_bad>'''.strip() + </t_questionnaire_bad>'''.strip() + +REPEAT_PARTY = '''<?xml version=\'1.0\' ?> + <t_questionnaire_repeat_party + id="t_questionnaire_repeat_party" version="20160727122113"> + <start>2016-07-07T16:38:20.310-04</start> + <end>2016-07-07T16:39:23.673-04</end> + <today>2016-07-07</today> + <deviceid>00:bb:3a:44:d0:fb</deviceid> + <title /> + <location_geometry>40.6890612 -73.9925067 0.0 0.0;</location_geometry> + <location_type>MI</location_type> + <location_resource_audio>test_audio_one.mp3</location_resource_audio> + <location_photo>test_image_one.png</location_photo> + <location_attributes> + <name>Middle Earth</name> + </location_attributes> + <party_repeat> + <party_type>IN</party_type> + <party_name>Bilbo Baggins</party_name> + <party_photo>test_image_two.png</party_photo> + <party_resource_photo>test_image_three.png</party_resource_photo> + <party_attributes_default> + <notes>Party attribute default notes.</notes> + </party_attributes_default> + <party_attributes_individual> + <gender>f</gender> + <homeowner>no</homeowner> + <dob>2016-07-07</dob> + </party_attributes_individual> + <party_relationship_attributes> + <notes>Party relationship notes.</notes> + </party_relationship_attributes> + <tenure_type>LH</tenure_type> + <tenure_relationship_attributes> + <notes>Tenure relationship notes.</notes> + </tenure_relationship_attributes> + <tenure_resource_photo>test_image_four.png</tenure_resource_photo> + </party_repeat> + <party_repeat> + <party_type>IN</party_type> + <party_name>Samwise Gamgee</party_name> + <party_photo>test_image_five.png</party_photo> + <party_resource_photo /> + <party_attributes_default> + <notes>Repeated party attribute default notes.</notes> + </party_attributes_default> + <party_attributes_individual> + <gender>f</gender> + <homeowner>no</homeowner> + <dob>2016-07-07</dob> + </party_attributes_individual> + <party_relationship_attributes> + <notes>Party relationship notes.</notes> + </party_relationship_attributes> + <tenure_type>LH</tenure_type> + <tenure_relationship_attributes> + <notes>Tenure relationship notes.</notes> + </tenure_relationship_attributes> + <tenure_resource_photo /> + </party_repeat> + <meta> + <instanceID>uuid:b3f225d3-0fac-4a0b-80c7-60e6db4cc0ad</instanceID> + </meta> + </t_questionnaire_repeat_party>'''.strip() + +REPEAT_ONE_PARTY = '''<?xml version=\'1.0\' ?> + <t_questionnaire_repeat_party + id="t_questionnaire_repeat_party" version="20160727122113"> + <start>2016-07-07T16:38:20.310-04</start> + <end>2016-07-07T16:39:23.673-04</end> + <today>2016-07-07</today> + <deviceid>00:bb:3a:44:d0:fb</deviceid> + <title /> + <location_geometry>40.6890612 -73.9925067 0.0 0.0;</location_geometry> + <location_type>MI</location_type> + <location_resource_audio>test_audio_one.mp3</location_resource_audio> + <location_photo>test_image_one.png</location_photo> + <location_attributes> + <name>Middle Earth</name> + </location_attributes> + <party_repeat> + <party_type>IN</party_type> + <party_name>Bilbo Baggins</party_name> + <party_photo>test_image_two.png</party_photo> + <party_resource_photo>test_image_three.png</party_resource_photo> + <party_attributes_default> + <notes>Party attribute default notes.</notes> + </party_attributes_default> + <party_attributes_individual> + <gender>f</gender> + <homeowner>no</homeowner> + <dob>2016-07-07</dob> + </party_attributes_individual> + <party_relationship_attributes> + <notes>Party relationship notes.</notes> + </party_relationship_attributes> + <tenure_type>LH</tenure_type> + <tenure_relationship_attributes> + <notes>Tenure relationship notes.</notes> + </tenure_relationship_attributes> + <tenure_resource_photo>test_image_four.png</tenure_resource_photo> + </party_repeat> + <meta> + <instanceID>uuid:b3f225d3-0fac-4a0b-80c7-60e6db4cc0ad</instanceID> + </meta> + </t_questionnaire_repeat_party>'''.strip() + +REPEAT_LOCATION = '''<?xml version=\'1.0\' ?> + <t_questionnaire_repeat_location + id="t_questionnaire_repeat_location" + version="20160727122114"> + <start>2016-07-07T16:38:20.310-04</start> + <end>2016-07-07T16:39:23.673-04</end> + <today>2016-07-07</today> + <deviceid>00:bb:3a:44:d0:fb</deviceid> + <title /> + <location_repeat> + <location_geometry>40.6890612 -73.9925067 0.0 0.0; + </location_geometry> + <location_type>MI</location_type> + <location_resource_audio>test_audio_one.mp3</location_resource_audio> + <location_photo>test_image_one.png</location_photo> + <location_attributes> + <name>Middle Earth</name> + </location_attributes> + <tenure_type>CR</tenure_type> + <tenure_relationship_attributes> + <notes>Tenure relationship notes.</notes> + </tenure_relationship_attributes> + <tenure_resource_photo>test_image_two.png</tenure_resource_photo> + </location_repeat> + <location_repeat> + <location_geometry>40.6890612 -73.9925067 0.0 0.0; + </location_geometry> + <location_type>CB</location_type> + <location_resource_audio /> + <location_photo>test_image_three.png</location_photo> + <location_attributes> + <name>Middle Earth</name> + </location_attributes> + <tenure_type>LH</tenure_type> + <tenure_relationship_attributes> + <notes>Tenure relationship notes.</notes> + </tenure_relationship_attributes> + <tenure_resource_photo /> + </location_repeat> + <party_type>IN</party_type> + <party_name>Bilbo Baggins</party_name> + <party_photo>test_image_four.png</party_photo> + <party_resource_photo>test_image_five.png</party_resource_photo> + <party_attributes_default> + <notes>Party attribute default notes.</notes> + </party_attributes_default> + <party_attributes_individual> + <gender>f</gender> + <homeowner>no</homeowner> + <dob>2016-07-07</dob> + </party_attributes_individual> + <party_relationship_attributes> + <notes>Party relationship notes.</notes> + </party_relationship_attributes> + <meta> + <instanceID>uuid:b3f225d3-0fac-4a0b-80c7-60e6db4cc0ad</instanceID> + </meta> + </t_questionnaire_repeat_location>'''.strip() + +REPEAT_ONE_LOCATION = '''<?xml version=\'1.0\' ?> + <t_questionnaire_repeat_location + id="t_questionnaire_repeat_location" + version="20160727122114"> + <start>2016-07-07T16:38:20.310-04</start> + <end>2016-07-07T16:39:23.673-04</end> + <today>2016-07-07</today> + <deviceid>00:bb:3a:44:d0:fb</deviceid> + <title /> + <location_repeat> + <location_geometry>40.6890612 -73.9925067 0.0 0.0; + </location_geometry> + <location_type>MI</location_type> + <location_resource_audio>test_audio_one.mp3</location_resource_audio> + <location_photo>test_image_one.png</location_photo> + <location_attributes> + <name>Middle Earth</name> + </location_attributes> + <tenure_type>CR</tenure_type> + <tenure_relationship_attributes> + <notes>Tenure relationship notes.</notes> + </tenure_relationship_attributes> + <tenure_resource_photo>test_image_two.png</tenure_resource_photo> + </location_repeat> + <party_type>IN</party_type> + <party_name>Bilbo Baggins</party_name> + <party_photo>test_image_four.png</party_photo> + <party_resource_photo>test_image_five.png</party_resource_photo> + <party_attributes_default> + <notes>Party attribute default notes.</notes> + </party_attributes_default> + <party_attributes_individual> + <gender>f</gender> + <homeowner>no</homeowner> + <dob>2016-07-07</dob> + </party_attributes_individual> + <party_relationship_attributes> + <notes>Party relationship notes.</notes> + </party_relationship_attributes> + <meta> + <instanceID>uuid:b3f225d3-0fac-4a0b-80c7-60e6db4cc0ad</instanceID> + </meta> + </t_questionnaire_repeat_location>'''.strip() + +REPEAT_MINUS_TENURE = '''<?xml version=\'1.0\' ?> + <t_questionnaire_repeat_minus_tenure + id="t_questionnaire_repeat_minus_tenure" + version="20160727122115"> + <start>2016-07-07T16:38:20.310-04</start> + <end>2016-07-07T16:39:23.673-04</end> + <today>2016-07-07</today> + <deviceid>00:bb:3a:44:d0:fb</deviceid> + <title /> + <location_repeat> + <location_geometry>40.6890612 -73.9925067 0.0 0.0; + </location_geometry> + <location_type>MI</location_type> + <location_resource_audio>test_audio_one.mp3</location_resource_audio> + <location_photo>test_image_one.png</location_photo> + <location_attributes> + <name>Middle Earth</name> + </location_attributes> + </location_repeat> + <location_repeat> + <location_geometry>40.6890612 -73.9925067 0.0 0.0; + </location_geometry> + <location_type>CB</location_type> + <location_resource_audio /> + <location_photo>test_image_three.png</location_photo> + <location_attributes> + <name>Middle Earth</name> + </location_attributes> + </location_repeat> + <tenure_type>CR</tenure_type> + <tenure_relationship_attributes> + <notes>Tenure relationship notes.</notes> + </tenure_relationship_attributes> + <tenure_resource_photo>test_image_two.png</tenure_resource_photo> + <party_type>IN</party_type> + <party_name>Bilbo Baggins</party_name> + <party_photo>test_image_four.png</party_photo> + <party_resource_photo>test_image_five.png</party_resource_photo> + <party_attributes_default> + <notes>Party attribute default notes.</notes> + </party_attributes_default> + <party_attributes_individual> + <gender>f</gender> + <homeowner>no</homeowner> + <dob>2016-07-07</dob> + </party_attributes_individual> + <party_relationship_attributes> + <notes>Party relationship notes.</notes> + </party_relationship_attributes> + <meta> + <instanceID>uuid:b3f225d3-0fac-4a0b-80c7-60e6db4cc0ad</instanceID> + </meta> + </t_questionnaire_repeat_minus_tenure>'''.strip() + +REPEAT_PARTY_MINUS_TENURE = '''<?xml version=\'1.0\' ?> + <t_questionnaire_repeat_party_minus_tenure + id="t_questionnaire_repeat_party_minus_tenure" + version="20160727122116"> + <start>2016-07-07T16:38:20.310-04</start> + <end>2016-07-07T16:39:23.673-04</end> + <today>2016-07-07</today> + <deviceid>00:bb:3a:44:d0:fb</deviceid> + <title /> + <location_geometry>40.6890612 -73.9925067 0.0 0.0; + </location_geometry> + <location_type>MI</location_type> + <location_resource_audio>test_audio_one.mp3</location_resource_audio> + <location_photo>test_image_one.png</location_photo> + <location_attributes> + <name>Middle Earth</name> + </location_attributes> + <tenure_type>CR</tenure_type> + <tenure_relationship_attributes> + <notes>Tenure relationship notes.</notes> + </tenure_relationship_attributes> + <tenure_resource_photo>test_image_two.png</tenure_resource_photo> + <party_repeat> + <party_type>IN</party_type> + <party_name>Bilbo Baggins</party_name> + <party_photo>test_image_three.png</party_photo> + <party_resource_photo>test_image_four.png</party_resource_photo> + <party_attributes_default> + <notes>Party attribute default notes.</notes> + </party_attributes_default> + <party_attributes_individual> + <gender>f</gender> + <homeowner>no</homeowner> + <dob>2016-07-07</dob> + </party_attributes_individual> + <party_relationship_attributes> + <notes>Party relationship notes.</notes> + </party_relationship_attributes> + </party_repeat> + <party_repeat> + <party_type>IN</party_type> + <party_name>Samwise Gamgee</party_name> + <party_photo>test_image_five.png</party_photo> + <party_resource_photo /> + <party_attributes_default> + <notes>Repeated party attribute default notes.</notes> + </party_attributes_default> + <party_attributes_individual> + <gender>f</gender> + <homeowner>no</homeowner> + <dob>2016-07-07</dob> + </party_attributes_individual> + <party_relationship_attributes> + <notes>Party relationship notes.</notes> + </party_relationship_attributes> + <tenure_type>LH</tenure_type> + <tenure_relationship_attributes> + <notes>Tenure relationship notes.</notes> + </tenure_relationship_attributes> + <tenure_resource_photo /> + </party_repeat> + <meta> + <instanceID>uuid:b3f225d3-0fac-4a0b-80c7-60e6db4cc0ad</instanceID> + </meta> + </t_questionnaire_repeat_party_minus_tenure>'''.strip() responses = { - 'form': FORM, - 'invalid_form': INVALID_FORM, - 'line_form': LINE_FORM, - 'poly_form': POLY_FORM, - 'missing_semi_form': MISSING_SEMI_FORM, - 'geoshape_form': GEOSHAPE_FORM, - 'location_geoshape_form': NEITHER_FORM, - 'bad_questionnaire': BAD_QUESTIONNAIRE, - 'bad_location_form': BAD_LOCATION_FORM, - 'bad_party_form': BAD_PARTY_FORM, - 'bad_tenure_form': BAD_TENURE_FORM, - 'bad_resource_form': BAD_RESOURCE_FORM, + 'submission': STANDARD, + 'submission_line': LINE, + 'submission_poly': POLY, + 'submission_missing_semi': MISSING_SEMI, + 'submission_geotype_select': GEOTYPE_SELECT, + 'submission_geotype_neither': GEOTYPE_NEITHER, + 'submission_bad_questionnaire': BAD_QUESTIONNAIRE, + 'submission_bad_location': BAD_LOCATION, + 'submission_bad_party': BAD_PARTY, + 'submission_bad_tenure': BAD_TENURE, + 'submission_bad_resource': BAD_RESOURCE, + 'submission_party_repeat': REPEAT_PARTY, + 'submission_party_one_repeat': REPEAT_ONE_PARTY, + 'submission_location_repeat': REPEAT_LOCATION, + 'submission_location_one_repeat': REPEAT_ONE_LOCATION, + 'submission_repeat_minus_tenure': REPEAT_MINUS_TENURE, + 'submission_repeat_party_minus_tenure': REPEAT_PARTY_MINUS_TENURE, } diff --git a/cadasta/xforms/tests/test_model_helper.py b/cadasta/xforms/tests/test_model_helper.py new file mode 100644 index 000000000..3c0742460 --- /dev/null +++ b/cadasta/xforms/tests/test_model_helper.py @@ -0,0 +1,778 @@ +import os +import io +import pytest +from django.conf import settings +from django.test import TestCase +from django.core.exceptions import ValidationError +from django.core.files.uploadedfile import InMemoryUploadedFile +from django.contrib.contenttypes.models import ContentType +from jsonattrs.models import Attribute, AttributeType, Schema +from jsonattrs.management.commands import loadattrtypes +from jsonattrs.models import create_attribute_types + +from accounts.tests.factories import UserFactory +from core.tests.factories import PolicyFactory +from party.tests.factories import PartyFactory +from organization.tests.factories import ProjectFactory +from spatial.tests.factories import SpatialUnitFactory +from questionnaires.tests.factories import (QuestionnaireFactory, + QuestionFactory,) + +from party.models import (Party, TenureRelationship, + load_tenure_relationship_types) +from organization.models import OrganizationRole +from resources.models import Resource +from spatial.models import SpatialUnit +from xforms.mixins.model_helper import ModelHelper as mh +from xforms.exceptions import InvalidXMLSubmission + +path = os.path.dirname(settings.BASE_DIR) + + +class XFormModelHelperTest(TestCase): + def setUp(self): + super().setUp() + PolicyFactory.load_policies() + create_attribute_types() + + loadattrtypes.Command().handle(force=True) + load_tenure_relationship_types(force=True) + + self.user = UserFactory.create() + self.project = ProjectFactory.create( + current_questionnaire='a1') + + self.questionnaire = QuestionnaireFactory.create( + id_string='a1', version=0, project=self.project, id='a1') + QuestionFactory.create( + name='location_geometry', + label='Location of Parcel', + type='GS', + questionnaire=self.questionnaire) + + content_type_party = ContentType.objects.get( + app_label='party', model='party') + content_type_spatial = ContentType.objects.get( + app_label='spatial', model='spatialunit') + content_type_tenure = ContentType.objects.get( + app_label='party', model='tenurerelationship') + for content_type in [content_type_party, content_type_tenure, + content_type_spatial]: + schema = Schema.objects.create( + content_type=content_type, + selectors=(self.project.organization.id, self.project.id, 'a1') + ) + attr_type = AttributeType.objects.get(name='boolean') + Attribute.objects.create( + schema=schema, + name='fname', long_name='True or False', + attr_type=attr_type, index=0, + required=False, omit=False + ) + attr_type = AttributeType.objects.get(name='text') + Attribute.objects.create( + schema=schema, + name='fname_two', long_name='Notes', + attr_type=attr_type, index=1, + required=False, omit=False + ) + + OrganizationRole.objects.create( + user=self.user, organization=self.project.organization) + + def test_create_models(self): + geoshape = ('45.56342779158167 -122.67650283873081 0.0 0.0;' + '45.56176327330353 -122.67669159919024 0.0 0.0;' + '45.56151562182025 -122.67490658909082 0.0 0.0;' + '45.563479432877415 -122.67494414001703 0.0 0.0;' + '45.56176327330353 -122.67669159919024 0.0 0.0') + data = { + 'id': 'a1', + 'version': str(self.questionnaire.version), + 'party_name': 'Party One', + 'party_type': 'IN', + 'party_attributes_individual': { + 'fname': False, + 'fname_two': 'socks', + }, + 'party_photo': 'sad_birthday.png', + 'party_resource_invite': 'invitation.pdf', + 'location_type': 'BU', + 'location_geometry': geoshape, + 'location_attributes': { + 'fname': False, + 'fname_two': 'Location One', + }, + 'location_photo': 'resource_one.png', + 'location_resource_invite': 'resource_two.pdf', + 'tenure_type': 'CO', + 'tenure_relationship_attributes': { + 'fname': False, + 'fname_two': 'Tenure One' + }, + 'tenure_resource_photo': 'resource_three.png' + } + + (questionnaire, + party_resources, + location_resources, + tenure_resources) = mh.create_models(mh(), data) + + assert questionnaire == self.questionnaire + party = Party.objects.get(name='Party One') + assert party_resources[0]['id'] == party.id + assert 'sad_birthday.png' in party_resources[0]['resources'] + assert 'invitation.pdf' in party_resources[0]['resources'] + + location = SpatialUnit.objects.get(type='BU') + assert location_resources[0]['id'] == location.id + assert 'resource_two.pdf' in location_resources[0]['resources'] + + tenure = TenureRelationship.objects.get(spatial_unit=location) + assert tenure.party == party + assert tenure_resources[0]['id'] == tenure.id + assert 'resource_three.png' in tenure_resources[0]['resources'] + + def test_create_party(self): + # ~~~~~~~~~~~~~~~~~~~~~~~~~~ + # test without repeats + # ~~~~~~~~~~~~~~~~~~~~~~~~~~ + data = { + 'party_name': 'Party One', + 'party_type': 'IN', + 'party_attributes_individual': { + 'fname': False, + 'fname_two': 'socks', + }, + 'party_photo': 'sad_birthday.png', + 'party_resource_invite': 'invitation.pdf', + } + + party_objects, party_resources = mh.create_party( + mh(), data, self.project + ) + assert len(party_objects) == 1 + party = Party.objects.get(name='Party One') + assert party.type == 'IN' + assert party.attributes == {'fname': False, 'fname_two': 'socks'} + assert len(party_resources) == 1 + assert party_resources[0]['id'] == party.id + assert len(party_resources[0]['resources']) == 2 + assert 'sad_birthday.png' in party_resources[0]['resources'] + assert 'invitation.pdf' in party_resources[0]['resources'] + assert party.project == self.project + + # ~~~~~~~~~~~~~~~~~~~~~~~~~~ + # test with repeats + # ~~~~~~~~~~~~~~~~~~~~~~~~~~ + data = { + 'party_repeat': [{ + 'party_name': 'Party Two', + 'party_type': 'IN', + 'party_attributes_individual': { + 'fname': False, + 'fname_two': 'socks', + }, + 'party_photo': 'sad_birthday.png', + 'party_resource_invite': 'invitation.pdf', + + }, { + 'party_name': 'Party Three', + 'party_type': 'GR', + 'party_attributes_group': { + 'fname': True, + 'fname_two': 'video games', + }, + 'party_photo': 'awesome_birthday.png', + 'party_resource_invite': 'invitation_two.pdf', + + }] + } + party_objects, party_resources = mh.create_party( + mh(), data, self.project + ) + assert len(party_objects) == 2 + party = Party.objects.get(name='Party Two') + assert party.type == 'IN' + assert party.attributes == {'fname': False, 'fname_two': 'socks'} + party2 = Party.objects.get(name='Party Three') + assert party2.type == 'GR' + assert party2.attributes == { + 'fname': True, 'fname_two': 'video games'} + + assert len(party_resources) == 2 + assert party_resources[0]['id'] == party.id + assert len(party_resources[0]['resources']) == 2 + assert 'sad_birthday.png' in party_resources[0]['resources'] + assert 'invitation.pdf' in party_resources[0]['resources'] + assert party.project == self.project + + assert party_resources[1]['id'] == party2.id + assert len(party_resources[1]['resources']) == 2 + assert 'awesome_birthday.png' in party_resources[1]['resources'] + assert 'invitation_two.pdf' in party_resources[1]['resources'] + assert party2.project == self.project + + # ~~~~~~~~~~~~~~~~~~~~~~~~~~ + # test without fails + # ~~~~~~~~~~~~~~~~~~~~~~~~~~ + data = { + 'party_nonsense': 'Blah blah blah', + 'party_type': 'IN', + 'party_attributes_individual': { + 'fname': False, + 'fname_two': 'socks', + }, + 'party_photo': 'sad_birthday.png', + 'party_resource_invite': 'invitation.pdf', + } + + with pytest.raises(InvalidXMLSubmission): + mh.create_party( + mh(), data, self.project + ) + assert Party.objects.count() == 3 + + def test_create_spatial_unit(self): + geoshape = ('45.56342779158167 -122.67650283873081 0.0 0.0;' + '45.56176327330353 -122.67669159919024 0.0 0.0;' + '45.56151562182025 -122.67490658909082 0.0 0.0;' + '45.563479432877415 -122.67494414001703 0.0 0.0;' + '45.56176327330353 -122.67669159919024 0.0 0.0') + + line = ('45.56342779158167 -122.67650283873081 0.0 0.0;' + '45.56176327330353 -122.67669159919024 0.0 0.0;' + '45.56151562182025 -122.67490658909082 0.0 0.0;') + + # ~~~~~~~~~~~~~~~~~~~~~~~~~~ + # test without repeats + # ~~~~~~~~~~~~~~~~~~~~~~~~~~ + data = { + 'location_type': 'BU', + 'location_geometry': geoshape, + 'location_attributes': { + 'fname': False, + 'fname_two': 'Location One', + }, + 'location_photo': 'resource.png', + 'location_resource_invite': 'resource_two.pdf', + } + + location_objects, location_resources = mh.create_spatial_unit( + mh(), data, self.project, self.questionnaire) + assert len(location_objects) == 1 + location = SpatialUnit.objects.get(type='BU') + assert location.attributes == { + 'fname': False, 'fname_two': 'Location One'} + assert location.geometry.geom_type == 'Polygon' + assert len(location_resources) == 1 + assert location_resources[0]['id'] == location.id + assert len(location_resources[0]['resources']) == 2 + assert 'resource.png' in location_resources[0]['resources'] + assert 'resource_two.pdf' in location_resources[0]['resources'] + assert location.project == self.project + + # ~~~~~~~~~~~~~~~~~~~~~~~~~~ + # test with repeats + # ~~~~~~~~~~~~~~~~~~~~~~~~~~ + data = { + 'location_repeat': [{ + 'location_type': 'PA', + 'location_geotrace': line, + 'location_attributes': { + 'fname': False, + 'fname_two': 'Location One', + }, + 'location_photo': 'resource.png', + 'location_resource_invite': 'resource_two.pdf', + }, { + 'location_type': 'CB', + 'location_geoshape': geoshape, + 'location_attributes': { + 'fname': True, + 'fname_two': 'Location Two', + }, + 'location_photo': 'resource_three.png', + 'location_resource_invite': 'resource_four.pdf', + }] + } + + location_objects, location_resources = mh.create_spatial_unit( + mh(), data, self.project, self.questionnaire) + + assert len(location_objects) == 2 + location = SpatialUnit.objects.get(type='PA') + assert location.geometry.geom_type == 'LineString' + assert location.attributes == { + 'fname': False, 'fname_two': 'Location One'} + location2 = SpatialUnit.objects.get(type='CB') + assert location2.geometry.geom_type == 'Polygon' + assert location2.attributes == { + 'fname': True, 'fname_two': 'Location Two'} + + assert len(location_resources) == 2 + assert location_resources[0]['id'] == location.id + assert len(location_resources[0]['resources']) == 2 + assert 'resource.png' in location_resources[0]['resources'] + assert 'resource_two.pdf' in location_resources[0]['resources'] + assert location.project == self.project + + assert location_resources[1]['id'] == location2.id + assert len(location_resources[1]['resources']) == 2 + assert 'resource_three.png' in location_resources[1]['resources'] + assert 'resource_four.pdf' in location_resources[1]['resources'] + assert location2.project == self.project + + # ~~~~~~~~~~~~~~~~~~~~~~~~~~ + # test fails + # ~~~~~~~~~~~~~~~~~~~~~~~~~~ + data = { + 'location_nonsense': 'BLAH BLAH', + 'location_geometry': line, + 'location_attributes': { + 'fname': False, + 'fname_two': 'Location One', + }, + 'location_photo': 'resource.png', + 'location_resource_invite': 'resource_two.pdf', + } + + with pytest.raises(InvalidXMLSubmission): + mh.create_spatial_unit( + mh(), data, self.project, self.questionnaire) + assert SpatialUnit.objects.count() == 3 + + def test_create_tenure_relationship(self): + # ~~~~~~~~~~~~~~~~~~~~~~~~~~ + # test without repeats + # ~~~~~~~~~~~~~~~~~~~~~~~~~~ + party = PartyFactory.create(project=self.project) + location = SpatialUnitFactory.create(project=self.project) + + data = { + 'tenure_type': 'CO', + 'tenure_relationship_attributes': { + 'fname': False, + 'fname_two': 'Tenure One' + }, + 'tenure_resource_photo': 'resource.png' + } + + tenure_resources = mh.create_tenure_relationship( + mh(), data, [party], [location], self.project) + tenure = TenureRelationship.objects.get(tenure_type='CO') + assert tenure.party == party + assert tenure.spatial_unit == location + assert tenure.attributes == {'fname': False, 'fname_two': 'Tenure One'} + assert len(tenure_resources) == 1 + assert tenure_resources[0]['id'] == tenure.id + assert 'resource.png' in tenure_resources[0]['resources'] + + # ~~~~~~~~~~~~~~~~~~~~~~~~~~ + # inside party_repeat + # ~~~~~~~~~~~~~~~~~~~~~~~~~~ + party2 = PartyFactory.create(project=self.project) + party3 = PartyFactory.create(project=self.project) + + data = { + 'party_repeat': [{ + 'tenure_type': 'WR', + 'tenure_relationship_attributes': { + 'fname': False, + 'fname_two': 'Tenure Two' + }, + 'tenure_resource_photo': 'resource_two.png' + }, { + 'tenure_type': 'CO', + 'tenure_relationship_attributes': { + 'fname': True, + 'fname_two': 'Tenure Three' + }, + 'tenure_resource_photo': 'resource_three.png' + }] + } + + tenure_resources = mh.create_tenure_relationship( + mh(), data, [party2, party3], [location], self.project) + tenure2 = TenureRelationship.objects.get(party=party2) + tenure3 = TenureRelationship.objects.get(party=party3) + + assert tenure2.spatial_unit == location + assert tenure2.tenure_type.id == 'WR' + assert tenure2.attributes == { + 'fname': False, 'fname_two': 'Tenure Two'} + + assert tenure3.spatial_unit == location + assert tenure3.tenure_type.id == 'CO' + assert tenure3.attributes == { + 'fname': True, 'fname_two': 'Tenure Three'} + + assert len(tenure_resources) == 2 + assert tenure_resources[0]['id'] == tenure2.id + assert 'resource_two.png' in tenure_resources[0]['resources'] + + assert tenure_resources[1]['id'] == tenure3.id + assert 'resource_three.png' in tenure_resources[1]['resources'] + + # ~~~~~~~~~~~~~~~~~~~~~~~~~~ + # inside location_repeat + # ~~~~~~~~~~~~~~~~~~~~~~~~~~ + location2 = SpatialUnitFactory.create(project=self.project) + location3 = SpatialUnitFactory.create(project=self.project) + + data = { + 'location_repeat': [{ + 'tenure_type': 'WR', + 'tenure_relationship_attributes': { + 'fname': False, + 'fname_two': 'Tenure Four' + }, + 'tenure_resource_photo': 'resource_four.png' + }, { + 'tenure_type': 'CO', + 'tenure_relationship_attributes': { + 'fname': True, + 'fname_two': 'Tenure Five' + }, + 'tenure_resource_photo': 'resource_five.png' + }] + } + + tenure_resources = mh.create_tenure_relationship( + mh(), data, [party], [location2, location3], self.project) + + tenure4 = TenureRelationship.objects.get(spatial_unit=location2) + tenure5 = TenureRelationship.objects.get(spatial_unit=location3) + + assert tenure4.party == party + assert tenure4.tenure_type.id == 'WR' + assert tenure4.attributes == { + 'fname': False, 'fname_two': 'Tenure Four'} + + assert tenure5.party == party + assert tenure5.tenure_type.id == 'CO' + assert tenure5.attributes == { + 'fname': True, 'fname_two': 'Tenure Five'} + + assert len(tenure_resources) == 2 + assert tenure_resources[0]['id'] == tenure4.id + assert 'resource_four.png' in tenure_resources[0]['resources'] + + assert tenure_resources[1]['id'] == tenure5.id + assert 'resource_five.png' in tenure_resources[1]['resources'] + + # ~~~~~~~~~~~~~~~~~~~~~~~~~~ + # outside party_repeat + # ~~~~~~~~~~~~~~~~~~~~~~~~~~ + party4 = PartyFactory.create(project=self.project) + party5 = PartyFactory.create(project=self.project) + + data = { + 'party_repeat': [], + 'tenure_type': 'CO', + 'tenure_relationship_attributes': { + 'fname': True, + 'fname_two': 'Tenure 6, 7' + }, + 'tenure_resource_photo': 'resource_six.png' + } + + tenure_resources = mh.create_tenure_relationship( + mh(), data, [party4, party5], [location], self.project) + tenure6 = TenureRelationship.objects.get(party=party4) + tenure7 = TenureRelationship.objects.get(party=party5) + + assert tenure6.spatial_unit == location + assert tenure6.tenure_type.id == 'CO' + assert tenure6.attributes == { + 'fname': True, 'fname_two': 'Tenure 6, 7'} + + assert tenure7.spatial_unit == location + assert tenure7.tenure_type.id == 'CO' + assert tenure7.attributes == { + 'fname': True, 'fname_two': 'Tenure 6, 7'} + + assert len(tenure_resources) == 2 + assert tenure_resources[0]['id'] == tenure6.id + assert 'resource_six.png' in tenure_resources[0]['resources'] + + assert tenure_resources[1]['id'] == tenure7.id + assert 'resource_six.png' in tenure_resources[1]['resources'] + + # ~~~~~~~~~~~~~~~~~~~~~~~~~~ + # outside location_repeat + # ~~~~~~~~~~~~~~~~~~~~~~~~~~ + location4 = SpatialUnitFactory.create(project=self.project) + location5 = SpatialUnitFactory.create(project=self.project) + + data = { + 'location_repeat': [], + 'tenure_type': 'WR', + 'tenure_relationship_attributes': { + 'fname': False, + 'fname_two': 'Tenure 8, 9' + }, + 'tenure_resource_photo': 'resource_seven.png' + } + + tenure_resources = mh.create_tenure_relationship( + mh(), data, [party], [location4, location5], self.project) + + tenure8 = TenureRelationship.objects.get(spatial_unit=location4) + tenure9 = TenureRelationship.objects.get(spatial_unit=location5) + + assert tenure8.party == party + assert tenure8.tenure_type.id == 'WR' + assert tenure8.attributes == { + 'fname': False, 'fname_two': 'Tenure 8, 9'} + + assert tenure9.party == party + assert tenure9.tenure_type.id == 'WR' + assert tenure9.attributes == { + 'fname': False, 'fname_two': 'Tenure 8, 9'} + + assert len(tenure_resources) == 2 + assert tenure_resources[0]['id'] == tenure8.id + assert 'resource_seven.png' in tenure_resources[0]['resources'] + + assert tenure_resources[1]['id'] == tenure9.id + assert 'resource_seven.png' in tenure_resources[1]['resources'] + + data = { + 'location_repeat': [], + 'tenure_nonsense': 'Blah blah blah', + 'tenure_relationship_attributes': { + 'fname': False, + 'fname_two': 'Tenure 8, 9' + }, + 'tenure_resource_photo': 'resource_seven.png' + } + + # ~~~~~~~~~~~~~~~~~~~~~~~~~~ + # test failing + # ~~~~~~~~~~~~~~~~~~~~~~~~~~ + with pytest.raises(InvalidXMLSubmission): + mh.create_tenure_relationship( + mh(), data, [party], [location4, location5], self.project) + assert TenureRelationship.objects.count() == 9 + + def test_create_resource(self): + # ~~~~~~~~~~~~~~~~~~~~~~~~~~ + # test attaching resources + # ~~~~~~~~~~~~~~~~~~~~~~~~~~ + file = open( + path + '/xforms/tests/files/test_image_one.png', 'rb' + ).read() + + data = InMemoryUploadedFile( + file=io.BytesIO(file), + field_name='test_image_one', + name='{}.png'.format('test_image_one'), + content_type='image/png', + size=len(file), + charset='utf-8', + ) + party = PartyFactory.create(project=self.project) + mh.create_resource( + self, data, self.user, self.project, content_object=party) + assert len(party.resources) == 1 + resource = Resource.objects.get(name='test_image_one.png') + assert resource in party.resources + + # ~~~~~~~~~~~~~~~~~~~~~~~~~~ + # test attaching existing resources + # ~~~~~~~~~~~~~~~~~~~~~~~~~~ + party2 = PartyFactory.create(project=self.project) + mh.create_resource( + self, data, self.user, self.project, content_object=party2) + + assert Resource.objects.count() == 1 + assert len(party2.resources) == 1 + assert resource in party2.resources + + # ~~~~~~~~~~~~~~~~~~~~~~~~~~ + # test without content object + # ~~~~~~~~~~~~~~~~~~~~~~~~~~ + file = open( + path + '/xforms/tests/files/test_image_two.png', 'rb' + ).read() + + data = InMemoryUploadedFile( + file=io.BytesIO(file), + field_name='test_image_two', + name='{}.png'.format('test_image_two'), + content_type='image/png', + size=len(file), + charset='utf-8', + ) + + mh.create_resource( + self, data, self.user, self.project, content_object=None) + + assert Resource.objects.count() == 2 + resource = Resource.objects.get(name='test_image_two.png') + assert resource.content_objects.count() == 0 + + # ~~~~~~~~~~~~~~~~~~~~~~~~~~ + # test failing + # ~~~~~~~~~~~~~~~~~~~~~~~~~~ + with pytest.raises(InvalidXMLSubmission): + mh.create_resource( + self, data, self.user, self.project, content_object='ardvark') + assert Resource.objects.count() == 2 + + # def test_upload_submission_data(self): + # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # covered by the view tests + # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + # def test_upload_resource_files(self): + # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # covered by the view tests + # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + def test_format_geometry(self): + point = '40.6890612 -73.9925067 0.0 0.0;' + geometry = mh._format_geometry(self, point, False) + assert 'POINT' in geometry + + point_minus_semi = '340.6890612 -373.9925067 0.0 0.0' + geometry = mh._format_geometry(self, point_minus_semi, False) + assert 'POINT' in geometry + + polygon = ('40.6890612 -73.9925067 0.0 0.0;' + '41.6890612 -73.9925067 0.0 0.0;' + '41.6890612 -72.9925067 0.0 0.0;' + '40.6890612 -72.9925067 0.0 0.0;' + '40.6890612 -73.9925067 0.0 0.0;') + geometry = mh._format_geometry(self, polygon, False) + assert 'POLYGON' in geometry + + line = ('45.56342779158167 -122.67650283873081 0.0 0.0;' + '45.56176327330353 -122.67669159919024 0.0 0.0;' + '45.56151562182025 -122.67490658909082 0.0 0.0;') + geometry = mh._format_geometry(self, line, False) + assert 'LINESTRING' in geometry + + geoshape = ('45.56342779158167 -122.67650283873081 0.0 0.0;' + '45.56176327330353 -122.67669159919024 0.0 0.0;' + '45.56151562182025 -122.67490658909082 0.0 0.0;' + '45.563479432877415 -122.67494414001703 0.0 0.0;' + '45.56176327330353 -122.67669159919024 0.0 0.0') + geometry = mh._format_geometry(self, geoshape, True) + assert 'POLYGON' in geometry + + def test_format_repeat(self): + data = { + 'party_type': 'Not repeating', + 'party_name': 'Still not repeating' + } + group = mh._format_repeat(self, data, ['party']) + assert type(group) == list + assert group[0] == data + + data = { + 'party_type': 'Repeating', + 'party_name': 'Totally repeating', + 'party_repeat': [{ + 'repeat_type': 'Just One' + }] + } + + group = mh._format_repeat(self, data, ['party']) + assert type(group) == list + assert len(group) == 1 + assert group[0]['repeat_type'] == 'Just One' + + assert 'party_repeat' not in group + assert 'party_type' not in group + assert 'party_name' not in group + + data = { + 'party_type': 'Repeating', + 'party_name': 'Totally repeating', + 'party_repeat': [{ + 'repeat_type': 'First!' + }, { + 'repeat_type': 'Second!' + }] + } + + group = mh._format_repeat(self, data, ['party']) + assert type(group) == list + assert len(group) == 2 + assert group[0]['repeat_type'] == 'First!' + assert group[1]['repeat_type'] == 'Second!' + + assert 'party_repeat' not in group + assert 'party_type' not in group + assert 'party_name' not in group + + def test_get_questionnaire(self): + questionnaire = mh._get_questionnaire( + self, 'a1', '0') + assert questionnaire == self.questionnaire + + with pytest.raises(ValidationError): + mh._get_questionnaire( + self, 'bad_info', '0') + + def test_get_attributes(self): + data = { + 'party_type': 'Party Type', + 'party_attributes_individual': { + 'name_indv': 'Party Indv Attrs', + 'type_indv': 'Party for one', + }, + 'party_attributes_people': { + 'name_ppl': 'Party People Attrs', + 'type_ppl': 'Where my party people at?', + }, + 'party_name': 'House Party' + } + attributes = mh._get_attributes(self, data, 'party') + + assert attributes['name_indv'] == 'Party Indv Attrs' + assert attributes['type_indv'] == 'Party for one' + assert attributes['name_ppl'] == 'Party People Attrs' + assert attributes['type_ppl'] == 'Where my party people at?' + assert 'party_name' not in attributes + assert 'party_type' not in attributes + + def test_get_resource_files(self): + data = { + 'ardvark': 'Ardvark!', + 'party_resource_thing': 'Party Resource Thing!', + 'location_resource_thing': 'Location Resource!', + 'party_photo': 'Party Photo!' + } + resources = mh._get_resource_files(self, data, 'party') + assert type(resources) == list + assert 'Party Resource Thing!' in resources + assert 'Party Photo!' in resources + + assert 'Ardvark!' not in resources + assert 'Location Resource!' not in resources + + resources = mh._get_resource_files(self, data, 'location') + assert 'Location Resource!' in resources + + assert 'Ardvark!' not in resources + assert 'Party Resource Thing!' not in resources + assert 'Party Photo!' not in resources + + def test_get_resource_names(self): + data = { + 'party_type': 'Party Type', + 'party_photo': 'Party Photo', + 'party_resource_thing': 'Party Resource Thing', + 'tenure_resource_thing': 'Tenure Resource Thing', + } + model = PartyFactory.create() + resources = mh._get_resource_names(self, data, model, 'party') + assert resources['id'] == model.id + assert 'Party Photo' in resources['resources'] + assert 'Party Resource Thing' in resources['resources'] + + assert 'Tenure Resource Thing' not in resources['resources'] + assert 'Party Type' not in resources['resources'] diff --git a/cadasta/xforms/tests/test_views_api.py b/cadasta/xforms/tests/test_views_api.py index 0dec2680a..2af3d9bae 100644 --- a/cadasta/xforms/tests/test_views_api.py +++ b/cadasta/xforms/tests/test_views_api.py @@ -26,6 +26,7 @@ from spatial.models import SpatialUnit from tutelary.models import Role from xforms.tests.files.test_resources import responses +from xforms.models import XFormSubmission from ..views import api from .attr_schemas import (default_party_xform_group, @@ -95,7 +96,6 @@ def test_get_xforms_with_no_superuser(self): response = self.request(user=self.user) assert response.status_code == 200 - print(response.content) xml = etree.fromstring(response.content.encode('utf-8')) ns = {'xf': 'http://openrosa.org/xforms/xformsList'} @@ -123,72 +123,39 @@ def setup_models(self): self.user = UserFactory.create() self.org = OrganizationFactory.create() self.prj = ProjectFactory.create(organization=self.org) - self.prj_2 = ProjectFactory.create(organization=self.org) - self.prj_3 = ProjectFactory.create(organization=self.org) OrganizationRole.objects.create( organization=self.org, user=self.user, admin=True) - QuestionnaireFactory.create( - project=self.prj, - xls_form=get_form('test_standard_questionnaire'), - filename='test_standard_questionnaire', - id_string='test_standard_questionnaire', - version=20160727122110) - + def _create_questionnaire(self, questionnaire_name, version, + schema=True): questionnaire = QuestionnaireFactory.create( - project=self.prj_2, - xls_form=get_form('test_standard_questionnaire_2'), - filename='test_standard_questionnaire_2', - id_string='test_standard_questionnaire_2', - version=20160727122111) - - QuestionFactory.create( - name='location_geometry', - label='Location of Parcel', - type='GS', - questionnaire=questionnaire) + project=self.prj, + xls_form=get_form(questionnaire_name), + filename=questionnaire_name, + id_string=questionnaire_name, + version=(20160727122110 + version)) - QuestionnaireFactory.create( - project=self.prj_3, - xls_form=get_form('test_standard_questionnaire_bad'), - filename='test_standard_questionnaire_bad', - id_string='test_standard_questionnaire_bad', - version=20160727122112) + if schema: + self._create_attrs_schema(self.prj) - # project 1 - create_attrs_schema( - project=self.prj, dict=default_party_xform_group, - content_type=ContentType.objects.get( - app_label='party', model='party'), errors=[]) - create_attrs_schema( - project=self.prj, dict=individual_party_xform_group, - content_type=ContentType.objects.get( - app_label='party', model='party'), errors=[]) - create_attrs_schema( - project=self.prj, dict=location_xform_group, - content_type=ContentType.objects.get( - app_label='spatial', model='spatialunit'), errors=[]) - create_attrs_schema( - project=self.prj, dict=tenure_relationship_xform_group, - content_type=ContentType.objects.get( - app_label='party', model='tenurerelationship'), errors=[]) + return questionnaire - # project 2 + def _create_attrs_schema(self, prj): create_attrs_schema( - project=self.prj_2, dict=default_party_xform_group, + project=prj, dict=default_party_xform_group, content_type=ContentType.objects.get( app_label='party', model='party'), errors=[]) create_attrs_schema( - project=self.prj_2, dict=individual_party_xform_group, + project=prj, dict=individual_party_xform_group, content_type=ContentType.objects.get( app_label='party', model='party'), errors=[]) create_attrs_schema( - project=self.prj_2, dict=location_xform_group, + project=prj, dict=location_xform_group, content_type=ContentType.objects.get( app_label='spatial', model='spatialunit'), errors=[]) create_attrs_schema( - project=self.prj_2, dict=tenure_relationship_xform_group, + project=prj, dict=tenure_relationship_xform_group, content_type=ContentType.objects.get( app_label='party', model='tenurerelationship'), errors=[]) @@ -265,9 +232,16 @@ def _getResponseMessage(self, response): ns = {'or': 'http://openrosa.org/http/response'} return xml.find('.//or:message', namespaces=ns).text + def _test_resource(self, resource, model): + assert Resource.objects.get( + name__contains=resource) in model.resources + def test_submission_upload(self): - data = self._submission(form='form', - image=['test_image_one', 'test_image_two'], + questionnaire = self._create_questionnaire('t_questionnaire', 0) + data = self._submission(form='submission', + image=['test_image_one', + 'test_image_two', + 'test_image_three'], audio=['test_audio_one']) response = self.request(method='POST', user=self.user, post_data=data, @@ -276,18 +250,21 @@ def test_submission_upload(self): party = Party.objects.get(name='Bilbo Baggins') location = SpatialUnit.objects.get(attributes={'name': 'Middle Earth'}) - assert location in party.tenure_relationships.all() - assert len(location.resources) == 1 - assert location.resources[0] == Resource.objects.get( - name__contains='test_image_one') - assert len(party.resources) == 2 - assert Resource.objects.get( - name__contains='test_image_two') in party.resources - assert Resource.objects.get( - name__contains='test_audio_one') in party.resources + tenure = TenureRelationship.objects.get(party=party) + assert tenure.spatial_unit == location + self._test_resource('test_image_one', location) + self._test_resource('test_image_two', party) + self._test_resource('test_audio_one', party) + self._test_resource('test_image_three', tenure) + + response = XFormSubmission.objects.get(user=self.user) + assert response.questionnaire == questionnaire + assert ('Bilbo Baggins' in + response.json_submission['t_questionnaire']['party_name']) def test_line_upload(self): - data = self._submission(form='line_form') + self._create_questionnaire('t_questionnaire', 0) + data = self._submission(form='submission_line') response = self.request(method='POST', user=self.user, post_data=data, content_type='multipart/form-data') @@ -296,7 +273,8 @@ def test_line_upload(self): assert geom.geometry.geom_type == 'LineString' def test_polygon_upload(self): - data = self._submission(form='poly_form', + self._create_questionnaire('t_questionnaire', 0) + data = self._submission(form='submission_poly', audio=['test_audio_one']) response = self.request(method='POST', user=self.user, post_data=data, content_type='multipart/form-data') @@ -307,11 +285,11 @@ def test_polygon_upload(self): assert geom.geometry.geom_type == 'Polygon' tenure = TenureRelationship.objects.get(tenure_type='LH') - assert Resource.objects.get( - name__contains='test_audio_one') in tenure.resources + self._test_resource('test_audio_one', tenure) def test_point_upload(self): - data = self._submission(form='missing_semi_form') + self._create_questionnaire('t_questionnaire', 0) + data = self._submission(form='submission_missing_semi') response = self.request(method='POST', user=self.user, post_data=data, content_type='multipart/form-data') @@ -320,7 +298,15 @@ def test_point_upload(self): assert geom.geometry.geom_type == 'Point' def test_geoshape_upload(self): - data = self._submission(form='geoshape_form') + questionnaire = self._create_questionnaire( + 't_questionnaire_geotype_select', 1) + QuestionFactory.create( + name='location_geometry', + label='Location of Parcel', + type='GS', + questionnaire=questionnaire) + + data = self._submission(form='submission_geotype_select') response = self.request(method='POST', user=self.user, post_data=data, content_type='multipart/form-data') @@ -329,7 +315,15 @@ def test_geoshape_upload(self): assert geom.geometry.geom_type == 'Polygon' def test_geoshape_as_location_geometry_upload(self): - data = self._submission(form='location_geoshape_form') + questionnaire = self._create_questionnaire( + 't_questionnaire_geotype_select', 1) + QuestionFactory.create( + name='location_geometry', + label='Location of Parcel', + type='GS', + questionnaire=questionnaire) + + data = self._submission(form='submission_geotype_neither') response = self.request(method='POST', user=self.user, post_data=data, content_type='multipart/form-data') @@ -338,6 +332,7 @@ def test_geoshape_as_location_geometry_upload(self): assert geom.geometry.geom_type == 'Polygon' def test_invalid_submission_upload(self): + self._create_questionnaire('t_questionnaire', 0) # testing submitting with a missing xml_submission_file data = self._invalid_submission(form='This is not an xml form!') response = self.request(method='POST', user=self.user, post_data=data, @@ -346,21 +341,21 @@ def test_invalid_submission_upload(self): msg = self._getResponseMessage(response) assert msg == "XML submission not found" - data = self._submission(form='bad_location_form') + data = self._submission(form='submission_bad_location') response = self.request(method='POST', user=self.user, post_data=data, content_type='multipart/form-data') assert response.status_code == 400 msg = self._getResponseMessage(response) assert msg == "Location error: 'location_type'" - data = self._submission(form='bad_party_form') + data = self._submission(form='submission_bad_party') response = self.request(method='POST', user=self.user, post_data=data, content_type='multipart/form-data') assert response.status_code == 400 msg = self._getResponseMessage(response) assert msg == "Party error: 'party_name'" - data = self._submission(form='bad_tenure_form') + data = self._submission(form='submission_bad_tenure') response = self.request(method='POST', user=self.user, post_data=data, content_type='multipart/form-data') assert response.status_code == 400 @@ -373,7 +368,8 @@ def test_invalid_submission_upload(self): ).read() bad_file = bad_file.decode('utf-8') - data = self._submission(form='bad_resource_form', + self._create_questionnaire('t_questionnaire_bad', 2, False) + data = self._submission(form='submission_bad_resource', image=['test_image_one'], file=bad_file) response = self.request(method='POST', user=self.user, post_data=data, @@ -388,14 +384,15 @@ def test_invalid_submission_upload(self): assert len(Resource.objects.all()) == 0 def test_anonymous_user(self): - data = self._submission(form='form') + self._create_questionnaire('t_questionnaire', 0) + data = self._submission(form='submission') response = self.request(method='POST', post_data=data, content_type='multipart/form-data') assert response.status_code == 403 def test_questionnaire_not_found(self): with pytest.raises(ValidationError): - data = self._submission(form='bad_questionnaire') + data = self._submission(form='submission_bad_questionnaire') response = self.request(method='POST', post_data=data, user=self.user, @@ -408,16 +405,180 @@ def test_no_content_head(self): def test_form_not_current_questionnaire(self): # update the default form to a new version - QuestionnaireFactory.create( - project=self.prj, - xls_form=get_form('test_standard_questionnaire'), - filename='test_standard_questionnaire_updated', - id_string='test_standard_questionnaire', - version=20160727122111 - ) - data = self._submission(form='form') + self._create_questionnaire('t_questionnaire', 0) + self._create_questionnaire('t_questionnaire', 1) + + data = self._submission(form='submission') response = self.request(method='POST', post_data=data, user=self.user, content_type='multipart/form-data') msg = self._getResponseMessage(response) assert msg == 'Form out of date' + + def test_form_with_repeat_party(self): + self._create_questionnaire('t_questionnaire_repeat_party', 3) + data = self._submission(form='submission_party_repeat', + image=['test_image_one', + 'test_image_two', + 'test_image_three', + 'test_image_four', + 'test_image_five'], + audio=['test_audio_one']) + + response = self.request(method='POST', user=self.user, post_data=data, + content_type='multipart/form-data') + assert response.status_code == 201 + + party_one = Party.objects.get(name='Bilbo Baggins') + party_two = Party.objects.get(name='Samwise Gamgee') + location = SpatialUnit.objects.get(type='MI') + tenure = TenureRelationship.objects.get(party=party_one) + assert tenure.spatial_unit == location + self._test_resource('test_audio_one', location) + self._test_resource('test_image_one', location) + self._test_resource('test_image_two', party_one) + self._test_resource('test_image_three', party_one) + self._test_resource('test_image_four', tenure) + self._test_resource('test_image_five', party_two) + + def test_form_repeat_with_one_party(self): + self._create_questionnaire('t_questionnaire_repeat_party', 3) + data = self._submission(form='submission_party_one_repeat', + image=['test_image_one', + 'test_image_two', + 'test_image_three', + 'test_image_four'], + audio=['test_audio_one']) + + response = self.request(method='POST', user=self.user, post_data=data, + content_type='multipart/form-data') + assert response.status_code == 201 + + party = Party.objects.get(name='Bilbo Baggins') + location = SpatialUnit.objects.get(type='MI') + tenure = TenureRelationship.objects.get( + party=party) + assert tenure.spatial_unit == location + self._test_resource('test_audio_one', location) + self._test_resource('test_image_one', location) + self._test_resource('test_image_two', party) + self._test_resource('test_image_three', party) + self._test_resource('test_image_four', tenure) + + def test_form_with_repeat_location(self): + self._create_questionnaire('t_questionnaire_repeat_location', 4) + data = self._submission(form='submission_location_repeat', + image=['test_image_one', + 'test_image_two', + 'test_image_three', + 'test_image_four', + 'test_image_five'], + audio=['test_audio_one']) + + response = self.request(method='POST', user=self.user, post_data=data, + content_type='multipart/form-data') + assert response.status_code == 201 + + party = Party.objects.get(name='Bilbo Baggins') + location_one = SpatialUnit.objects.get(type='MI') + location_two = SpatialUnit.objects.get(type='CB') + tenure_one = TenureRelationship.objects.get( + spatial_unit=location_one) + assert tenure_one.party == party + tenure = TenureRelationship.objects.get( + spatial_unit=location_two) + + assert tenure.party == party + self._test_resource('test_audio_one', location_one) + self._test_resource('test_image_one', location_one) + self._test_resource('test_image_two', tenure_one) + self._test_resource('test_image_three', location_two) + self._test_resource('test_image_four', party) + self._test_resource('test_image_five', party) + + def test_form_repeat_with_one_location(self): + self._create_questionnaire('t_questionnaire_repeat_location', 4) + data = self._submission(form='submission_location_one_repeat', + image=['test_image_one', + 'test_image_two', + 'test_image_four', + 'test_image_five'], + audio=['test_audio_one']) + + response = self.request(method='POST', user=self.user, post_data=data, + content_type='multipart/form-data') + assert response.status_code == 201 + + party = Party.objects.get(name='Bilbo Baggins') + location = SpatialUnit.objects.get(type='MI') + tenure = TenureRelationship.objects.get( + spatial_unit=location) + + assert tenure.party == party + self._test_resource('test_audio_one', location) + self._test_resource('test_image_one', location) + self._test_resource('test_image_two', tenure) + self._test_resource('test_image_four', party) + self._test_resource('test_image_five', party) + + def test_form_repeat_minus_tenure(self): + self._create_questionnaire('t_questionnaire_repeat_minus_tenure', 5) + data = self._submission(form='submission_repeat_minus_tenure', + image=['test_image_one', + 'test_image_two', + 'test_image_three', + 'test_image_four', + 'test_image_five'], + audio=['test_audio_one']) + + response = self.request(method='POST', user=self.user, post_data=data, + content_type='multipart/form-data') + assert response.status_code == 201 + + party = Party.objects.get(name='Bilbo Baggins') + location_one = SpatialUnit.objects.get(type='MI') + location_two = SpatialUnit.objects.get(type='CB') + tenure_one = TenureRelationship.objects.get( + spatial_unit=location_one) + tenure_two = TenureRelationship.objects.get( + spatial_unit=location_two) + assert tenure_one.party == party and tenure_two.party == party + + self._test_resource('test_audio_one', location_one) + self._test_resource('test_image_one', location_one) + self._test_resource('test_image_two', tenure_one) + self._test_resource('test_image_two', tenure_two) + self._test_resource('test_image_three', location_two) + self._test_resource('test_image_four', party) + self._test_resource('test_image_five', party) + + def test_form_repeat_party_minus_tenure(self): + self._create_questionnaire( + 't_questionnaire_repeat_party_minus_tenure', 6) + data = self._submission(form='submission_repeat_party_minus_tenure', + image=['test_image_one', + 'test_image_two', + 'test_image_three', + 'test_image_four', + 'test_image_five'], + audio=['test_audio_one']) + + response = self.request(method='POST', user=self.user, post_data=data, + content_type='multipart/form-data') + assert response.status_code == 201 + + party_one = Party.objects.get(name='Bilbo Baggins') + party_two = Party.objects.get(name='Samwise Gamgee') + location = SpatialUnit.objects.get(type='MI') + tenure_one = TenureRelationship.objects.get(party=party_one) + tenure_two = TenureRelationship.objects.get(party=party_two) + assert tenure_one.spatial_unit == location + assert tenure_two.spatial_unit == location + + self._test_resource('test_audio_one', location) + self._test_resource('test_image_one', location) + self._test_resource('test_image_two', tenure_one) + self._test_resource('test_image_two', tenure_two) + self._test_resource('test_image_three', party_one) + self._test_resource('test_image_four', party_one) + self._test_resource('test_image_five', party_two) diff --git a/cadasta/xforms/views/api.py b/cadasta/xforms/views/api.py index 8f551cf33..9a74c5a4e 100644 --- a/cadasta/xforms/views/api.py +++ b/cadasta/xforms/views/api.py @@ -17,7 +17,8 @@ from ..exceptions import InvalidXMLSubmission -logger = logging.getLogger('xform.submissions') +logger = logging +# logger = logging.getLogger('xform.submissions') OPEN_ROSA_ENVELOPE = """ <OpenRosaResponse xmlns="http://openrosa.org/http/response"> @@ -46,9 +47,10 @@ def create(self, request, *args, **kwargs): status=status.HTTP_204_NO_CONTENT,) try: instance = ModelHelper( - ).upload_submission_data(request) + ).upload_submission_data(request) except InvalidXMLSubmission as e: - logger.debug(str(e)) + # logger.debug(str(e)) + logger.error(str(e)) return self._sendErrorResponse(request, e) serializer = XFormSubmissionSerializer(instance) @@ -93,11 +95,11 @@ class XFormListView(OpenRosaHeadersMixin, def get_user_forms(self): forms = [] policies = self.request.user.assigned_policies() - orgs = self.request.user.organizations.all() + orgs = self.request.user.organizations.filter(archived=False) if Role.objects.get(name='superuser') in policies: - return Questionnaire.objects.all() + return Questionnaire.objects.filter(project__archived=False) for org in orgs: - projects = org.projects.all() + projects = org.projects.filter(archived=False) for project in projects: try: questionnaire = Questionnaire.objects.get(