From b9297a7cd894bd9015e893f2d71ff670850b2e00 Mon Sep 17 00:00:00 2001 From: Oliver Roick Date: Sat, 9 Jul 2016 13:36:08 +0200 Subject: [PATCH] Adds JOSN attributes to UI - Adapts forms and templates for spatial units, parties and tenure relationships - Upgrade jsonattrs dependency to version 0.1.1 --- cadasta/party/forms.py | 10 +- cadasta/party/tests/test_forms.py | 49 ++++- cadasta/party/tests/test_views_default.py | 74 +++++++- cadasta/party/views/default.py | 10 ++ cadasta/spatial/forms.py | 58 +++++- cadasta/spatial/tests/test_forms.py | 169 +++++++++++++++++- cadasta/spatial/tests/test_views_default.py | 132 ++++++++++++-- cadasta/spatial/views/default.py | 39 ++++ cadasta/templates/party/party_detail.html | 6 + cadasta/templates/party/party_form.html | 10 ++ .../templates/party/relationship_detail.html | 12 +- .../templates/party/relationship_edit.html | 12 +- .../templates/spatial/location_detail.html | 6 + cadasta/templates/spatial/location_form.html | 18 +- .../templates/spatial/relationship_add.html | 22 ++- requirements/common.txt | 2 +- 16 files changed, 587 insertions(+), 42 deletions(-) diff --git a/cadasta/party/forms.py b/cadasta/party/forms.py index f7a2b36df..4788045d9 100644 --- a/cadasta/party/forms.py +++ b/cadasta/party/forms.py @@ -1,8 +1,10 @@ -from django import forms +from jsonattrs.forms import AttributeModelForm from .models import Party, TenureRelationshipType, TenureRelationship -class PartyForm(forms.ModelForm): +class PartyForm(AttributeModelForm): + attributes_field = 'attributes' + class Meta: model = Party fields = ['name', 'type'] @@ -18,7 +20,9 @@ def save(self): return instance -class TenureRelationshipEditForm(forms.ModelForm): +class TenureRelationshipEditForm(AttributeModelForm): + attributes_field = 'attributes' + class Meta: model = TenureRelationship fields = ['tenure_type'] diff --git a/cadasta/party/tests/test_forms.py b/cadasta/party/tests/test_forms.py index 9caa4370d..2f9616109 100644 --- a/cadasta/party/tests/test_forms.py +++ b/cadasta/party/tests/test_forms.py @@ -1,4 +1,6 @@ from django.test import TestCase +from django.contrib.contenttypes.models import ContentType +from jsonattrs.models import Attribute, AttributeType, Schema from organization.tests.factories import ProjectFactory from ..models import Party, TenureRelationshipType @@ -6,22 +8,63 @@ class PartyFormTest(TestCase): - def test_create_location(self): + def test_create_party(self): data = { 'name': 'Cadasta', 'type': 'IN' } project = ProjectFactory.create() - form = forms.PartyForm(project_id=project.id, data=data) + form = forms.PartyForm(project_id=project.id, + data=data, + schema_selectors=()) form.is_valid() form.save() assert Party.objects.filter(project=project).count() == 1 + def test_create_party_with_attributes(self): + data = { + 'name': 'Cadasta', + 'type': 'IN', + 'attributes::fname': 'test' + } + project = ProjectFactory.create() + + content_type = ContentType.objects.get( + app_label='party', model='party') + schema = Schema.objects.create( + content_type=content_type, + selectors=(project.organization.id, project.id, )) + + Attribute.objects.create( + schema=schema, + name='fname', + long_name='Test field', + attr_type=AttributeType.objects.get(name='text'), + index=0 + ) + + form = forms.PartyForm(project_id=project.id, + data=data, + schema_selectors=( + {'name': 'organization', + 'value': project.organization, + 'selector': project.organization.id}, + {'name': 'project', + 'value': project, + 'selector': project.id} + )) + form.is_valid() + form.save() + + assert Party.objects.filter(project=project).count() == 1 + party = Party.objects.filter(project=project).first() + assert party.attributes.get('fname') == 'test' + class TenureRelationshipEditFormTest(TestCase): def test_init(self): - form = forms.TenureRelationshipEditForm() + form = forms.TenureRelationshipEditForm(schema_selectors=()) tenure_types = TenureRelationshipType.objects.values_list('id', 'label') assert list(form.fields['tenure_type'].choices) == list(tenure_types) diff --git a/cadasta/party/tests/test_views_default.py b/cadasta/party/tests/test_views_default.py index 1a07bf8aa..f02c4be8e 100644 --- a/cadasta/party/tests/test_views_default.py +++ b/cadasta/party/tests/test_views_default.py @@ -4,10 +4,12 @@ from django.http import Http404 from django.conf import settings from django.contrib.messages.api import get_messages +from django.contrib.contenttypes.models import ContentType from buckets.test.utils import ensure_dirs from buckets.test.storage import FakeS3Storage from tutelary.models import Policy, assign_user_policies +from jsonattrs.models import Attribute, AttributeType, Schema from organization.tests.factories import ProjectFactory from resources.tests.factories import ResourceFactory @@ -108,7 +110,7 @@ def assign_policies(self): def get_template_context(self): return { 'object': self.project, - 'form': forms.PartyForm() + 'form': forms.PartyForm(schema_selectors=()) } def get_url_kwargs(self): @@ -173,12 +175,28 @@ class PartyDetailTest(TestCase): def set_up_models(self): self.project = ProjectFactory.create() self.party = PartyFactory.create(project=self.project) + content_type = ContentType.objects.get( + app_label='party', model='party') + schema = Schema.objects.create( + content_type=content_type, + selectors=(self.project.organization.id, self.project.id, )) + attr_type = AttributeType.objects.get(name='text') + Attribute.objects.create( + schema=schema, + name='fname', long_name='Test field', + attr_type=attr_type, index=0, + required=False, omit=False + ) + self.party = PartyFactory.create(project=self.project, + attributes={'fname': 'test'}) def assign_policies(self): assign_policies(self.authorized_user) def get_template_context(self): - return {'object': self.project, 'party': self.party} + return {'object': self.project, + 'party': self.party, + 'attributes': (('Test field', 'test', ), )} def get_url_kwargs(self): return { @@ -220,12 +238,27 @@ class PartiesEditTest(TestCase): success_url_name = 'parties:detail' post_data = { 'name': 'Party', - 'type': 'GR' + 'type': 'GR', + 'attributes::fname': 'New text' } def set_up_models(self): self.project = ProjectFactory.create() self.party = PartyFactory.create(project=self.project) + content_type = ContentType.objects.get( + app_label='party', model='party') + schema = Schema.objects.create( + content_type=content_type, + selectors=(self.project.organization.id, self.project.id, )) + attr_type = AttributeType.objects.get(name='text') + Attribute.objects.create( + schema=schema, + name='fname', long_name='Test field', + attr_type=attr_type, index=0, + required=False, omit=False + ) + self.party = PartyFactory.create(project=self.project, + attributes={'fname': 'test'}) def assign_policies(self): assign_policies(self.authorized_user) @@ -283,6 +316,7 @@ def test_post_with_authorized_user(self): self.party.refresh_from_db() assert self.party.name == self.post_data['name'] assert self.party.type == self.post_data['type'] + assert self.party.attributes.get('fname') == 'New text' def test_post_with_unauthorized_user(self): response = self.request(method='POST', user=self.unauthorized_user) @@ -576,8 +610,20 @@ class PartyRelationshipDetailTest(TestCase): def set_up_models(self): self.project = ProjectFactory.create() + content_type = ContentType.objects.get( + app_label='party', model='tenurerelationship') + schema = Schema.objects.create( + content_type=content_type, + selectors=(self.project.organization.id, self.project.id, )) + attr_type = AttributeType.objects.get(name='text') + Attribute.objects.create( + schema=schema, + name='fname', long_name='Test field', + attr_type=attr_type, index=0, + required=False, omit=False + ) self.relationship = TenureRelationshipFactory.create( - project=self.project) + project=self.project, attributes={'fname': 'test'}) def assign_policies(self): assign_policies(self.authorized_user) @@ -586,7 +632,8 @@ def get_template_context(self): return {'object': self.project, 'relationship': self.relationship, 'location': self.relationship.spatial_unit, - 'geojson': '{"type": "FeatureCollection", "features": []}'} + 'geojson': '{"type": "FeatureCollection", "features": []}', + 'attributes': (('Test field', 'test', ), )} def get_url_kwargs(self): return { @@ -626,12 +673,24 @@ class PartyRelationshipEditTest(TestCase): view = default.PartyRelationshipEdit template = 'party/relationship_edit.html' success_url_name = 'parties:relationship_detail' - post_data = {'tenure_type': 'LH'} + post_data = {'tenure_type': 'LH', 'attributes::fname': 'New text'} def set_up_models(self): self.project = ProjectFactory.create() + content_type = ContentType.objects.get( + app_label='party', model='tenurerelationship') + schema = Schema.objects.create( + content_type=content_type, + selectors=(self.project.organization.id, self.project.id, )) + attr_type = AttributeType.objects.get(name='text') + Attribute.objects.create( + schema=schema, + name='fname', long_name='Test field', + attr_type=attr_type, index=0, + required=False, omit=False + ) self.relationship = TenureRelationshipFactory.create( - project=self.project) + project=self.project, attributes={'fname': 'test'}) def assign_policies(self): assign_policies(self.authorized_user) @@ -687,6 +746,7 @@ def test_post_with_authorized_user(self): self.relationship.refresh_from_db() assert self.relationship.tenure_type_id == 'LH' + assert self.relationship.attributes.get('fname') == 'New text' def test_post_with_unauthorized_user(self): response = self.request(method='POST', user=self.unauthorized_user) diff --git a/cadasta/party/views/default.py b/cadasta/party/views/default.py index 2ba0ed312..3d097149c 100644 --- a/cadasta/party/views/default.py +++ b/cadasta/party/views/default.py @@ -1,5 +1,6 @@ from django.views import generic from django.core.urlresolvers import reverse +from jsonattrs.mixins import JsonAttrsMixin from core.mixins import LoginPermissionRequiredMixin from resources.forms import AddResourceFromLibraryForm @@ -28,13 +29,20 @@ class PartiesAdd(LoginPermissionRequiredMixin, def get_perms_objects(self): return [self.get_project()] + def get_form_kwargs(self, *args, **kwargs): + kwargs = super().get_form_kwargs(*args, **kwargs) + kwargs['schema_selectors'] = () + return kwargs + class PartiesDetail(LoginPermissionRequiredMixin, + JsonAttrsMixin, mixins.PartyObjectMixin, generic.DetailView): template_name = 'party/party_detail.html' permission_required = 'party.view' permission_denied_message = error_messages.PARTY_VIEW + attributes_field = 'attributes' class PartiesEdit(LoginPermissionRequiredMixin, @@ -85,11 +93,13 @@ class PartyResourcesNew(LoginPermissionRequiredMixin, class PartyRelationshipDetail(LoginPermissionRequiredMixin, + JsonAttrsMixin, mixins.PartyRelationshipObjectMixin, generic.DetailView): template_name = 'party/relationship_detail.html' permission_required = 'tenure_rel.view' permission_denied_message = error_messages.TENURE_REL_VIEW + attributes_field = 'attributes' class PartyRelationshipEdit(LoginPermissionRequiredMixin, diff --git a/cadasta/spatial/forms.py b/cadasta/spatial/forms.py index 36a29e5e0..2f95b7e0c 100644 --- a/cadasta/spatial/forms.py +++ b/cadasta/spatial/forms.py @@ -1,6 +1,10 @@ from django import forms from django.contrib.gis import forms as gisforms from django.utils.translation import ugettext as _ +from django.contrib.contenttypes.models import ContentType + +from jsonattrs.models import Schema, compose_schemas +from jsonattrs.forms import form_field_from_name, AttributeModelForm from leaflet.forms.widgets import LeafletWidget from core.util import ID_FIELD_LENGTH @@ -9,8 +13,9 @@ from .widgets import SelectPartyWidget, NewEntityWidget -class LocationForm(forms.ModelForm): +class LocationForm(AttributeModelForm): geometry = gisforms.GeometryField(widget=LeafletWidget()) + attributes_field = 'attributes' class Meta: model = SpatialUnit @@ -46,7 +51,8 @@ class TenureRelationshipForm(forms.Form): class Media: js = ('/static/js/rel_tenure.js',) - def __init__(self, project, spatial_unit, *args, **kwargs): + def __init__(self, project, spatial_unit, schema_selectors=(), + *args, **kwargs): super().__init__(*args, **kwargs) self.fields['id'].widget = SelectPartyWidget(project.id) tenure_types = TenureRelationshipType.objects.values_list('id', @@ -54,6 +60,7 @@ def __init__(self, project, spatial_unit, *args, **kwargs): self.fields['tenure_type'].choices = tenure_types self.project = project self.spatial_unit = spatial_unit + self.add_attribute_fields(schema_selectors) def clean_id(self): new_entity = self.data.get('new_entity', None) @@ -71,12 +78,54 @@ def clean_name(self): raise forms.ValidationError(_("This field is required.")) return name + def create_model_fields(self, model, field_prefix, selectors): + content_type = ContentType.objects.get_for_model(model) + schemas = Schema.objects.lookup( + content_type=content_type, selectors=selectors + ) + attrs, _, _ = compose_schemas(*schemas) + for name, attr in attrs.items(): + fieldname = field_prefix + '::' + name + atype = attr.attr_type + args = {'label': attr.long_name} + field = form_field_from_name(atype.form_field) + if atype.form_field == 'CharField': + args['max_length'] = 32 + if (atype.form_field == 'ChoiceField' or + atype.form_field == 'MultipleChoiceField'): + args['choices'] = list(map(lambda c: (c, c), attr.choices)) + if atype.form_field == 'BooleanField': + args['required'] = False + if len(attr.default) > 0: + args['initial'] = (attr.default != 'False') + elif attr.required: + args['required'] = True + if len(attr.default) > 0: + args['initial'] = attr.default + self.fields[fieldname] = field(**args) + + def add_attribute_fields(self, schema_selectors): + selectors = [s['selector'] for s in schema_selectors] + + self.create_model_fields(Party, 'party', selectors) + self.create_model_fields(TenureRelationship, 'relationship', selectors) + + def process_attributes(self, key): + attributes = {} + length = len(key + '::') + for k, v in self.cleaned_data.items(): + if k.startswith(key + '::'): + k = k[length::] + attributes[k] = v + return attributes + def save(self): if self.cleaned_data['new_entity']: party = Party.objects.create( name=self.cleaned_data['name'], type=self.cleaned_data['party_type'], - project=self.project + project=self.project, + attributes=self.process_attributes('party') ) else: party = Party.objects.get(pk=self.cleaned_data['id']) @@ -85,6 +134,7 @@ def save(self): party=party, spatial_unit=self.spatial_unit, tenure_type_id=self.cleaned_data['tenure_type'], - project=self.project + project=self.project, + attributes=self.process_attributes('relationship') ) return t diff --git a/cadasta/spatial/tests/test_forms.py b/cadasta/spatial/tests/test_forms.py index 7c06423b0..59d6e02d5 100644 --- a/cadasta/spatial/tests/test_forms.py +++ b/cadasta/spatial/tests/test_forms.py @@ -1,6 +1,8 @@ import pytest from django.test import TestCase -from django.forms import ValidationError +from django.forms import ValidationError, CharField, ChoiceField, BooleanField +from django.contrib.contenttypes.models import ContentType +from jsonattrs.models import Attribute, AttributeType, Schema from organization.tests.factories import ProjectFactory from party.tests.factories import PartyFactory @@ -22,12 +24,56 @@ def test_create_location(self): 'type': 'CB' } project = ProjectFactory.create() - form = forms.LocationForm(project_id=project.id, data=data) + form = forms.LocationForm(project_id=project.id, + data=data, + schema_selectors=()) form.is_valid() form.save() assert SpatialUnit.objects.filter(project=project).count() == 1 + def test_create_location_with_attributes(self): + data = { + 'geometry': '{"type": "Polygon","coordinates": [[[-0.1418137550354' + '004,51.55240622205599],[-0.14117002487182617,51.55167' + '905819532],[-0.1411914825439453,51.55181915488898],[-' + '0.1411271095275879,51.55254631651022],[-0.14181375503' + '54004,51.55240622205599]]]}', + 'type': 'CB', + 'attributes::fname': 'test' + } + + project = ProjectFactory.create() + content_type = ContentType.objects.get( + app_label='spatial', model='spatialunit') + schema = Schema.objects.create( + content_type=content_type, + selectors=(project.organization.id, project.id, )) + attr_type = AttributeType.objects.get(name='text') + Attribute.objects.create( + schema=schema, + name='fname', long_name='Test field', + attr_type=attr_type, index=0, + required=False, omit=False + ) + + form = forms.LocationForm(project_id=project.id, + data=data, + schema_selectors=( + {'name': 'organization', + 'value': project.organization, + 'selector': project.organization.id}, + {'name': 'project', + 'value': project, + 'selector': project.id} + )) + form.is_valid() + form.save() + + assert SpatialUnit.objects.filter(project=project).count() == 1 + unit = SpatialUnit.objects.filter(project=project).first() + assert unit.attributes.get('fname') == 'test' + class TenureRelationshipFormTest(TestCase): def test_init(self): @@ -35,9 +81,63 @@ def test_init(self): spatial_unit = SpatialUnitFactory.create(project=project) form = forms.TenureRelationshipForm(project=project, spatial_unit=spatial_unit) + content_type = ContentType.objects.get( + app_label='party', model='tenurerelationship') + schema = Schema.objects.create( + content_type=content_type, + selectors=(project.organization.id, project.id, )) + attr_type = AttributeType.objects.get(name='text') + Attribute.objects.create( + schema=schema, + name='r_name', long_name='Relationship field', + attr_type=attr_type, index=0, + required=False, omit=False + ) + + content_type = ContentType.objects.get( + app_label='party', model='party') + schema = Schema.objects.create( + content_type=content_type, + selectors=(project.organization.id, project.id, )) + + Attribute.objects.create( + schema=schema, name='p_name', long_name='Party field', + attr_type=AttributeType.objects.get(name='text'), + index=0, required=True, default='John' + ) + Attribute.objects.create( + schema=schema, name='p_choice', long_name='Party field', + attr_type=AttributeType.objects.get(name='select_one'), + index=1, choices=['1', '2'] + ) + Attribute.objects.create( + schema=schema, name='p_bool', long_name='Party field', + attr_type=AttributeType.objects.get(name='boolean'), + index=2, default='False' + ) + + form = forms.TenureRelationshipForm( + project=project, + spatial_unit=spatial_unit, + schema_selectors=( + {'name': 'organization', + 'value': project.organization, + 'selector': project.organization.id}, + {'name': 'project', + 'value': project, + 'selector': project.id} + )) + assert form.project == project assert form.spatial_unit == spatial_unit assert isinstance(form.fields['id'].widget, SelectPartyWidget) + assert isinstance(form.fields['party::p_name'], CharField) + assert form.fields['party::p_name'].initial == 'John' + assert form.fields['party::p_name'].required is True + assert isinstance(form.fields['party::p_choice'], ChoiceField) + assert isinstance(form.fields['party::p_bool'], BooleanField) + assert form.fields['party::p_bool'].initial is False + assert isinstance(form.fields['relationship::r_name'], CharField) def test_clean_invalid_id(self): project = ProjectFactory.create() @@ -122,3 +222,68 @@ def test_save_new_party(self): assert rel.party == party assert rel.spatial_unit == spatial_unit assert rel.tenure_type_id == 'CU' + + def test_save_new_party_with_attributes(self): + project = ProjectFactory.create() + spatial_unit = SpatialUnitFactory.create(project=project) + + content_type = ContentType.objects.get( + app_label='party', model='tenurerelationship') + schema = Schema.objects.create( + content_type=content_type, + selectors=(project.organization.id, project.id, )) + attr_type = AttributeType.objects.get(name='text') + Attribute.objects.create( + schema=schema, + name='r_name', long_name='Relationship field', + attr_type=attr_type, index=0, + required=False, omit=False + ) + + content_type = ContentType.objects.get( + app_label='party', model='party') + schema = Schema.objects.create( + content_type=content_type, + selectors=(project.organization.id, project.id, )) + attr_type = AttributeType.objects.get(name='text') + Attribute.objects.create( + schema=schema, + name='p_name', long_name='Party field', + attr_type=attr_type, index=0, + required=False, omit=False + ) + + form = forms.TenureRelationshipForm( + project=project, + spatial_unit=spatial_unit, + data={'new_entity': 'on', + 'id': '', + 'name': 'The Beatles', + 'party::p_name': 'Party Name', + 'party_type': 'GR', + 'tenure_type': 'CU', + 'relationship::r_name': 'Rel Name'}, + schema_selectors=( + {'name': 'organization', + 'value': project.organization, + 'selector': project.organization.id}, + {'name': 'project', + 'value': project, + 'selector': project.id} + )) + + form.is_valid() + form.save() + + assert Party.objects.count() == 1 + party = Party.objects.first() + assert party.name == 'The Beatles' + assert party.type == 'GR' + assert party.attributes.get('p_name') == 'Party Name' + + assert TenureRelationship.objects.count() == 1 + rel = TenureRelationship.objects.first() + assert rel.party == party + assert rel.spatial_unit == spatial_unit + assert rel.tenure_type_id == 'CU' + assert rel.attributes.get('r_name') == 'Rel Name' diff --git a/cadasta/spatial/tests/test_views_default.py b/cadasta/spatial/tests/test_views_default.py index dce0bda0c..9526ab404 100644 --- a/cadasta/spatial/tests/test_views_default.py +++ b/cadasta/spatial/tests/test_views_default.py @@ -5,10 +5,12 @@ from django.conf import settings from django.template.loader import render_to_string from django.contrib.messages.api import get_messages +from django.contrib.contenttypes.models import ContentType from buckets.test.utils import ensure_dirs from buckets.test.storage import FakeS3Storage from tutelary.models import Policy, assign_user_policies +from jsonattrs.models import Attribute, AttributeType, Schema from organization.tests.factories import ProjectFactory from core.tests.util import TestCase @@ -110,11 +112,24 @@ class LocationAddTest(TestCase): '905819532],[-0.1411914825439453,51.55181915488898],[-' '0.1411271095275879,51.55254631651022],[-0.14181375503' '54004,51.55240622205599]]]}', - 'type': 'CB' + 'type': 'CB', + 'attributes::fname': 'Test text' } def set_up_models(self): - self.project = ProjectFactory.create() + self.project = ProjectFactory.create(current_questionnaire='a1') + content_type = ContentType.objects.get( + app_label='spatial', model='spatialunit') + schema = Schema.objects.create( + content_type=content_type, + selectors=(self.project.organization.id, self.project.id, 'a1')) + attr_type = AttributeType.objects.get(name='text') + Attribute.objects.create( + schema=schema, + name='fname', long_name='Test field', + attr_type=attr_type, index=0, + required=False, omit=False + ) def assign_policies(self): assign_policies(self.authorized_user) @@ -122,7 +137,19 @@ def assign_policies(self): def get_template_context(self): return { 'object': self.project, - 'form': forms.LocationForm(), + 'form': forms.LocationForm( + schema_selectors=( + {'name': 'organization', + 'value': self.project.organization, + 'selector': self.project.organization.id}, + {'name': 'project', + 'value': self.project, + 'selector': self.project.id}, + {'name': 'questionnaire', + 'value': self.project.current_questionnaire, + 'selector': self.project.current_questionnaire} + ) + ), 'geojson': '{"type": "FeatureCollection", "features": []}' } @@ -165,6 +192,7 @@ def test_post_with_authorized_user(self): response = self.request(method='POST', user=self.authorized_user) assert SpatialUnit.objects.count() == 1 self.location_created = SpatialUnit.objects.first() + assert self.location_created.attributes.get('fname') == 'Test text' assert response.status_code == 302 assert response['location'] == self.expected_success_url @@ -189,7 +217,20 @@ class LocationDetailTest(TestCase): def set_up_models(self): self.project = ProjectFactory.create() - self.location = SpatialUnitFactory.create(project=self.project) + content_type = ContentType.objects.get( + app_label='spatial', model='spatialunit') + schema = Schema.objects.create( + content_type=content_type, + selectors=(self.project.organization.id, self.project.id, )) + attr_type = AttributeType.objects.get(name='text') + Attribute.objects.create( + schema=schema, + name='fname', long_name='Test field', + attr_type=attr_type, index=0, + required=False, omit=False + ) + self.location = SpatialUnitFactory.create( + project=self.project, attributes={'fname': 'test'}) def assign_policies(self): assign_policies(self.authorized_user) @@ -198,7 +239,8 @@ def get_template_context(self): return { 'object': self.project, 'location': self.location, - 'geojson': '{"type": "FeatureCollection", "features": []}' + 'geojson': '{"type": "FeatureCollection", "features": []}', + 'attributes': (('Test field', 'test', ), ) } def get_url_kwargs(self): @@ -245,12 +287,26 @@ class LocationEditTest(TestCase): '905819532],[-0.1411914825439453,51.55181915488898],[-' '0.1411271095275879,51.55254631651022],[-0.14181375503' '54004,51.55240622205599]]]}', - 'type': 'NP' + 'type': 'NP', + 'attributes::fname': 'New text' } def set_up_models(self): self.project = ProjectFactory.create() - self.location = SpatialUnitFactory.create(project=self.project) + content_type = ContentType.objects.get( + app_label='spatial', model='spatialunit') + schema = Schema.objects.create( + content_type=content_type, + selectors=(self.project.organization.id, self.project.id, )) + attr_type = AttributeType.objects.get(name='text') + Attribute.objects.create( + schema=schema, + name='fname', long_name='Test field', + attr_type=attr_type, index=0, + required=False, omit=False + ) + self.location = SpatialUnitFactory.create( + project=self.project, attributes={'fname': 'test'}) def assign_policies(self): assign_policies(self.authorized_user) @@ -308,6 +364,7 @@ def test_post_with_authorized_user(self): self.location.refresh_from_db() assert self.location.type == self.post_data['type'] + assert self.location.attributes.get('fname') == 'New text' def test_post_with_unauthorized_user(self): response = self.request(method='POST', user=self.unauthorized_user) @@ -600,8 +657,33 @@ def assign_policies(self): assign_policies(self.authorized_user) def set_up_models(self): - self.project = ProjectFactory.create() + self.project = ProjectFactory.create(current_questionnaire='a1') self.spatial_unit = SpatialUnitFactory(project=self.project) + content_type = ContentType.objects.get( + app_label='party', model='tenurerelationship') + schema = Schema.objects.create( + content_type=content_type, + selectors=(self.project.organization.id, self.project.id, 'a1')) + attr_type = AttributeType.objects.get(name='text') + Attribute.objects.create( + schema=schema, + name='r_name', long_name='Relationship field', + attr_type=attr_type, index=0, + required=False, omit=False + ) + + content_type = ContentType.objects.get( + app_label='party', model='party') + schema = Schema.objects.create( + content_type=content_type, + selectors=(self.project.organization.id, self.project.id, 'a1')) + attr_type = AttributeType.objects.get(name='text') + Attribute.objects.create( + schema=schema, + name='p_name', long_name='Party field', + attr_type=attr_type, index=0, + required=False, omit=False + ) def get_url_kwargs(self): return { @@ -619,7 +701,9 @@ def get_post_data(self): 'id': '', 'name': 'The Beatles', 'party_type': 'GR', - 'tenure_type': 'CU' + 'tenure_type': 'CU', + 'party::p_name': 'Party Name', + 'relationship::r_name': 'Rel Name' } def get_template_context(self): @@ -627,7 +711,18 @@ def get_template_context(self): 'location': self.spatial_unit, 'form': forms.TenureRelationshipForm( project=self.project, - spatial_unit=self.spatial_unit), + spatial_unit=self.spatial_unit, + schema_selectors=( + {'name': 'organization', + 'value': self.project.organization, + 'selector': self.project.organization.id}, + {'name': 'project', + 'value': self.project, + 'selector': self.project.id}, + {'name': 'questionnaire', + 'value': self.project.current_questionnaire, + 'selector': self.project.current_questionnaire} + )), 'geojson': json.dumps(SpatialUnitGeoJsonSerializer( [self.spatial_unit], many=True).data) } @@ -666,7 +761,11 @@ def test_post_with_authorized(self): self.expected_success_url + '#relationships') assert TenureRelationship.objects.count() == 1 + rel = TenureRelationship.objects.first() + assert rel.attributes.get('r_name') == 'Rel Name' assert Party.objects.count() == 1 + party = Party.objects.first() + assert party.attributes.get('p_name') == 'Party Name' def test_post_with_authorized_invalid_data(self): response, content = self.request(method='POST', @@ -679,7 +778,18 @@ def test_post_with_authorized_invalid_data(self): context['form'] = forms.TenureRelationshipForm( project=self.project, spatial_unit=self.spatial_unit, - data=data) + data=data, + schema_selectors=( + {'name': 'organization', + 'value': self.project.organization, + 'selector': self.project.organization.id}, + {'name': 'project', + 'value': self.project, + 'selector': self.project.id}, + {'name': 'questionnaire', + 'value': self.project.current_questionnaire, + 'selector': self.project.current_questionnaire} + )) expected = render_to_string( self.template, context, request=self.request) assert content == expected diff --git a/cadasta/spatial/views/default.py b/cadasta/spatial/views/default.py index ab799860f..acb2c625e 100644 --- a/cadasta/spatial/views/default.py +++ b/cadasta/spatial/views/default.py @@ -1,4 +1,5 @@ import json +from jsonattrs.mixins import JsonAttrsMixin from django.views import generic from django.core.urlresolvers import reverse @@ -33,13 +34,33 @@ class LocationsAdd(LoginPermissionRequiredMixin, def get_perms_objects(self): return [self.get_project()] + def get_form_kwargs(self): + kwargs = super().get_form_kwargs() + prj = self.get_project() + + kwargs['schema_selectors'] = ( + {'name': 'organization', + 'value': prj.organization, + 'selector': prj.organization.id}, + {'name': 'project', + 'value': prj, + 'selector': prj.id}, + {'name': 'questionaire', + 'value': prj.current_questionnaire, + 'selector': prj.current_questionnaire} + ) + + return kwargs + class LocationDetail(LoginPermissionRequiredMixin, + JsonAttrsMixin, mixins.SpatialUnitObjectMixin, generic.DetailView): template_name = 'spatial/location_detail.html' permission_required = 'spatial.view' permission_denied_message = error_messages.SPATIAL_VIEW + attributes_field = 'attributes' def get_context_data(self, *args, **kwargs): context = super().get_context_data(*args, **kwargs) @@ -125,6 +146,24 @@ def get_context_data(self, *args, **kwargs): return context + def get_form_kwargs(self): + kwargs = super().get_form_kwargs() + prj = self.get_project() + + kwargs['schema_selectors'] = ( + {'name': 'organization', + 'value': prj.organization, + 'selector': prj.organization.id}, + {'name': 'project', + 'value': prj, + 'selector': prj.id}, + {'name': 'questionaire', + 'value': prj.current_questionnaire, + 'selector': prj.current_questionnaire} + ) + + return kwargs + def get_success_url(self): return (reverse('locations:detail', kwargs=self.kwargs) + '#relationships') diff --git a/cadasta/templates/party/party_detail.html b/cadasta/templates/party/party_detail.html index 2ed0eb11f..0853e6102 100644 --- a/cadasta/templates/party/party_detail.html +++ b/cadasta/templates/party/party_detail.html @@ -24,6 +24,12 @@

{% trans "Party" %}

Type {{ party.get_type_display }} + {% for attr in attributes %} + + {{ attr.0 }} + {{ attr.1 }} + + {% endfor %} diff --git a/cadasta/templates/party/party_form.html b/cadasta/templates/party/party_form.html index 35930d348..b774bd564 100644 --- a/cadasta/templates/party/party_form.html +++ b/cadasta/templates/party/party_form.html @@ -13,3 +13,13 @@ {% render_field form.type class+="form-control input-lg" %} + +{% for field in form %} + {% if "attributes" in field.name %} +
+ {{ field.errors }} + + {% render_field field class="form-control input-lg" %} +
+ {% endif %} +{% endfor %} diff --git a/cadasta/templates/party/relationship_detail.html b/cadasta/templates/party/relationship_detail.html index b72f16949..3e4bb6da4 100644 --- a/cadasta/templates/party/relationship_detail.html +++ b/cadasta/templates/party/relationship_detail.html @@ -15,13 +15,19 @@

{% trans "Relationship" %}

- - + + - + + {% for attr in attributes %} + + + + + {% endfor %}
{% trans "Type" %}{{ relationship.tenure_type.label }}{% trans "Type" %}{{ relationship.tenure_type.label }}
{% trans "Party" %}{% trans "Party" %} {{ relationship.party.name }}
{{ attr.0 }}{{ attr.1 }}

{% trans "resources" %}

diff --git a/cadasta/templates/party/relationship_edit.html b/cadasta/templates/party/relationship_edit.html index 57a909177..cf70fcb1c 100644 --- a/cadasta/templates/party/relationship_edit.html +++ b/cadasta/templates/party/relationship_edit.html @@ -12,7 +12,17 @@

{% trans "Edit Relationship" %}

{{ form.tenure_type.errors }} {% render_field form.tenure_type class+="form-control input-lg" %} - + + + {% for field in form %} + {% if "attributes" in field.name %} +
+ {{ field.errors }} + + {% render_field field class+="form-control input-lg" %} +
+ {% endif %} + {% endfor %} Cancel diff --git a/cadasta/templates/spatial/location_detail.html b/cadasta/templates/spatial/location_detail.html index 572f81fc8..fef89efd0 100644 --- a/cadasta/templates/spatial/location_detail.html +++ b/cadasta/templates/spatial/location_detail.html @@ -37,6 +37,12 @@

Location

Type {{ location.get_type_display }} + {% for attr in attributes %} + + {{ attr.0 }} + {{ attr.1 }} + + {% endfor %} diff --git a/cadasta/templates/spatial/location_form.html b/cadasta/templates/spatial/location_form.html index c7e852cc0..1d9980d23 100644 --- a/cadasta/templates/spatial/location_form.html +++ b/cadasta/templates/spatial/location_form.html @@ -12,9 +12,9 @@
-

{{ title }}

-
-
+

{{ title }}

+
+

Draw location on map

@@ -25,9 +25,17 @@

Draw location on map

{% render_field form.type class+="form-control" %}
- + {% for field in form %} + {% if "attributes" in field.name %} +
+ {{ field.errors }} + + {% render_field field class+="form-control input-lg" %} +
+ {% endif %} + {% endfor %} {{ form.geometry.errors }} -
+
@@ -49,6 +57,16 @@ {% render_field form.tenure_type class+="form-control input-lg" %}
+ + {% for field in form %} + {% if "relationship::" in field.name %} +
+ {{ field.errors }} + + {% render_field field class+="form-control input-lg" %} +
+ {% endif %} + {% endfor %}