Skip to content

Commit

Permalink
Fix #761: Attributes that depend on party type don't show up on party…
Browse files Browse the repository at this point in the history
… creation
  • Loading branch information
bohare committed Jan 24, 2017
1 parent 2c0f545 commit f0cc4ad
Show file tree
Hide file tree
Showing 30 changed files with 1,158 additions and 361 deletions.
103 changes: 103 additions & 0 deletions cadasta/core/form_mixins.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
from django.forms import Form, ModelForm

from jsonattrs.forms import form_field_from_name
from django.contrib.contenttypes.models import ContentType
from tutelary.models import Role

from .mixins import SchemaSelectorMixin


class SuperUserCheck:

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._su_role = None
Expand All @@ -12,3 +19,99 @@ def is_superuser(self, user):

return any([isinstance(pol, Role) and pol == self.su_role
for pol in user.assigned_policies()])


class AttributeFormMixin(SchemaSelectorMixin):
def create_model_fields(self, field_prefix, attribute_map, new_item=False):
for selector, attributes in attribute_map.items():
for name, attr in attributes.items():
fieldname = '{}::{}::{}'.format(
field_prefix, selector.lower(), name)
atype = attr.attr_type
args = {'label': attr.long_name, 'required': attr.required}
field = form_field_from_name(atype.form_field)
if not new_item:
self.set_initial(args, attr.name, attr)
if atype.form_field in ['ChoiceField', 'MultipleChoiceField']:
if (attr.choice_labels is not None and
attr.choice_labels != []):
chs = list(zip(attr.choices, attr.choice_labels))
else:
# chs = list(map(lambda c: (c, c), attr.choices))
chs = [(c, c) for c in attr.choices]
args['choices'] = chs
if atype.form_field == 'BooleanField':
args['required'] = attr.required
if len(attr.default) > 0:
self.set_default(args, attr, boolean=True)
else:
if attr.required and new_item:
args['required'] = True
if len(attr.default) > 0 and len(
args.get('initial', '')) == 0:
self.set_default(args, attr)
self.fields[fieldname] = field(**args)

def set_default(self, args, attr, boolean=False):
if len(attr.default) > 0:
if boolean:
args['initial'] = (attr.default != 'False')
else:
args['initial'] = attr.default

def set_initial(self, args, name, attr):
if hasattr(self, 'instance'):
attrvals = getattr(self.instance, self.attributes_field)
if name in attrvals:
if attr.attr_type.form_field == 'BooleanField':
args['initial'] = (
attrvals[name]
if isinstance(attrvals[name], bool)
else attrvals[name] != 'False'
)
else:
args['initial'] = attrvals.get(name, None)

def process_attributes(self, key, entity_type=''):
attributes = {}
for k, v in self.cleaned_data.items():
if k.startswith(key + '::'):
_, type, name = k.split('::')
if type in [entity_type.lower(), 'default']:
attributes[name] = v
if hasattr(self, 'instance'):
setattr(self.instance, self.attributes_field, attributes)
else:
return attributes


class AttributeForm(AttributeFormMixin, Form):
def add_attribute_fields(self, content_type):
label = '{}.{}'.format(content_type.app_label, content_type.model)
attributes = self.get_model_attributes(self.project, label)
new_item = self.data.get('new_item') == 'on'
self.create_model_fields(
content_type.model, attributes, new_item=new_item
)


class AttributeModelForm(AttributeFormMixin, ModelForm):
def add_attribute_fields(self):
content_type = ContentType.objects.get_for_model(self.Meta.model)
label = '{}.{}'.format(content_type.app_label, content_type.model)
attributes = self.get_model_attributes(self.project, label)
new_item = self.data.get('new_item') == 'on'
self.create_model_fields(
content_type.model, attributes, new_item=new_item
)

def save(self, *args, **kwargs):
entity_type = kwargs.get('entity_type', '')
project_id = kwargs.get('project_id', None)
instance = super().save(commit=False)
content_type = ContentType.objects.get_for_model(instance)
if self.attributes_field is not None:
self.process_attributes(content_type.model, entity_type)
if project_id is not None and hasattr(instance, 'project_id'):
setattr(instance, 'project_id', project_id)
return super().save()
71 changes: 68 additions & 3 deletions cadasta/core/mixins.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
from collections import OrderedDict

from django.conf import settings
from django.contrib import messages
from django.shortcuts import redirect
from django.contrib.contenttypes.models import ContentType
from django.core.urlresolvers import reverse
from django.shortcuts import redirect
from django.utils.translation import gettext as _

from jsonattrs.models import Schema, compose_schemas
from tutelary import mixins


class PermissionRequiredMixin(mixins.PermissionRequiredMixin):

def handle_no_permission(self):
msg = super().handle_no_permission()
messages.add_message(self.request, messages.WARNING,
Expand Down Expand Up @@ -52,10 +57,70 @@ def update_permissions(permission, obj=None):
def set_permissions(self, request, view=None):
if (hasattr(self, 'get_organization') and
self.get_organization().archived):
return False
return False
if (hasattr(self, 'get_project') and self.get_project().archived):
return False
if obj and self.get_object().archived:
return False
return permission
return set_permissions


class SchemaSelectorMixin():

def get_attributes(self, project):
content_type_to_selectors = self._get_content_types_to_selectors()
attributes_for_models = {}
for content_type, selector_fields in content_type_to_selectors.items():
label = '{}.{}'.format(content_type.app_label, content_type.model)
model = content_type.model_class()
choices = []
selectors = OrderedDict({})
attributes_for_models[label] = OrderedDict({})

for selector_field in selector_fields:
field = model._meta.get_field(selector_field.partition('.')[0])
if field.choices:
choices = [
(field.name, choice[0]) for choice in field.choices]
else:
selector = project
sf = selector_field.partition('.')[-1]
sf = sf.replace('.pk', '_id')
selector = getattr(selector, sf, None)
if selector:
selectors[sf] = selector

if selectors and not choices:
defaults = list(selectors.values())
schemas = Schema.objects.lookup(
content_type=content_type, selectors=defaults)
if schemas:
attributes, _, _ = compose_schemas(*schemas)
attributes_for_models[label]['DEFAULT'] = attributes

if selectors and choices:
for choice in choices:
conditionals = list(selectors.values()) + [choice[1]]
schemas = Schema.objects.lookup(
content_type=content_type,
selectors=conditionals)
if schemas:
key = '{value}'.format(value=choice[1])
attributes, _, _ = compose_schemas(*schemas)
attributes_for_models[label][key] = attributes

return attributes_for_models

def get_model_attributes(self, project, content_type):
attributes_for_models = self.get_attributes(project)
return attributes_for_models[content_type]

def _get_content_types_to_selectors(self):
content_type_to_selectors = dict()
for k, v in settings.JSONATTRS_SCHEMA_SELECTORS.items():
a, m = k.split('.')
content_type_to_selectors[
ContentType.objects.get(app_label=a, model=m)
] = v
return content_type_to_selectors
4 changes: 2 additions & 2 deletions cadasta/core/static/css/main.css

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

87 changes: 87 additions & 0 deletions cadasta/core/static/js/party_attrs.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
/* eslint-env jquery */

function enableGroup() {
$('.party-gr').removeClass('hidden');
$('.party-in').addClass('hidden');
$('.party-co').addClass('hidden');
$('.party-gr .form-control').prop('disabled', '');
$('.party-in .form-control').prop('disabled', 'disabled');
$('.party-co .form-control').prop('disabled', 'disabled');
}

function enableIndividual() {
$('.party-in').removeClass('hidden');
$('.party-gr').addClass('hidden');
$('.party-co').addClass('hidden');
$('.party-in .form-control').prop('disabled', '');
$('.party-gr .form-control').prop('disabled', 'disabled');
$('.party-co .form-control').prop('disabled', 'disabled');
}

function enableCorporation() {
$('.party-co').removeClass('hidden');
$('.party-gr').addClass('hidden');
$('.party-in').addClass('hidden');
$('.party-co .form-control').prop('disabled', '');
$('.party-gr .form-control').prop('disabled', 'disabled');
$('.party-in .form-control').prop('disabled', 'disabled');
}

function disableConditionals() {
$('.party-co').addClass('hidden');
$('.party-gr').addClass('hidden');
$('.party-in').addClass('hidden');
$('.party-co .form-control').prop('disabled', 'disabled');
$('.party-gr .form-control').prop('disabled', 'disabled');
$('.party-in .form-control').prop('disabled', 'disabled');
}

function toggleParsleyRequired(val) {
const typeChoices = ['in', 'gr', 'co'];
$.each(typeChoices, function(idx, choice) {
if (val === choice) {
$.each($(`.party-${val} .form-control`), function(idx, value) {
if (value.hasAttribute('data-parsley-required')) {
$(value).attr('data-parsley-required', true);
$(value).prop('required', 'required');
}
});
} else {
$.each($(`.party-${choice} .form-control`), function(idx, value) {
if (value.hasAttribute('data-parsley-required')) {
$(value).attr('data-parsley-required', false);
$(value).prop('required', '');
}
});
}
});
}

function toggleStates(val) {
if (val === 'in') {
enableIndividual();
toggleParsleyRequired(val);
}
if (val === 'gr') {
enableGroup();
toggleParsleyRequired(val);
}
if (val === 'co') {
enableCorporation();
toggleParsleyRequired(val);
}
if (val === '') {
disableConditionals();
}
}

$().ready(function() {
const val = $('.party-type').val().toLowerCase();
toggleStates(val);
});


$('select.party-type').on('change', function(e) {
const val = e.target.value.toLowerCase();
toggleStates(val);
});
6 changes: 4 additions & 2 deletions cadasta/core/static/js/rel_tenure.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
/* eslint-env jquery */

$('button#add-party').click(function() {
$('div#select-party').toggleClass('hidden');
$('div#new-item').toggleClass('hidden');
});

$('table#select-list tr').click(function(event) {
var target = $(event.target).closest('tr');
var relId = target.attr('data-id');
const target = $(event.target).closest('tr');
const relId = target.attr('data-id');
target.closest('tbody').find('tr.info').removeClass('info');
target.addClass('info');
$('input[name="id"]').val(relId);
Expand Down
Empty file.
40 changes: 40 additions & 0 deletions cadasta/core/templatetags/filters.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
from django import template
from django.forms import ChoiceField, FileField

register = template.Library()


@register.filter(name='field_value')
def field_value(field):
"""Return the value for this BoundField."""
if field.form.is_bound:
if isinstance(field.field, FileField) and field.data is None:
val = field.form.initial.get(field.name, field.field.initial)
else:
val = field.data
else:
val = field.form.initial.get(field.name, field.field.initial)
if callable(val):
val = val()
if val is None:
val = ''
return val


@register.filter(name='display_choice_verbose')
def display_choice_verbose(field):
"""Return the displayed value for this BoundField."""
if isinstance(field.field, ChoiceField):
value = field_value(field)
for (val, desc) in field.field.choices:
if val == value:
return desc


@register.filter(name='set_parsley_required')
def set_parsley_required(field):
if field.field.required:
field.field.widget.attrs = {
'data-parsley-required': 'true'
}
return field
Loading

0 comments on commit f0cc4ad

Please sign in to comment.