datawinners/project/questionnaire_fields.py
import re
from string import strip
from django.core.exceptions import ValidationError
from django.forms import ChoiceField, FloatField, TextInput
from django.utils.encoding import force_unicode
from django.utils.translation import ugettext
from django import forms
from django.utils.translation import ugettext_lazy as _
from datawinners.entity.fields import PhoneNumberField, DjangoDateField
from datawinners.entity.import_data import load_all_entities_of_type
from mangrove.form_model.validation import GeoCodeConstraint
from mangrove.form_model.form_model import LOCATION_TYPE_FIELD_NAME, FIRSTNAME_FIELD, NAME_FIELD, GEO_CODE_FIELD_NAME, MOBILE_NUMBER_FIELD, SHORT_CODE_FIELD
from mangrove.form_model.field import SelectField, HierarchyField, TelephoneNumberField, IntegerField, GeoCodeField, DateField
from mangrove.utils.types import is_empty
from datawinners.utils import translate, get_text_language_by_instruction
def _get_choice_label_prefix(entity):
return entity['mobile_number'] if entity['name'] == '' else entity['name']
def as_choices(entities):
return [(entity['short_code'], _get_choice_label_prefix(entity) + ' (' + entity['short_code'] + ')') for entity in entities]
class FormField(object):
def create(self, field):
try:
field_creation_map = {SelectField: SelectFormField,
TelephoneNumberField: PhoneNumberFormField,
IntegerField: IntegerFormField,
DateField: DateFormField,
}
return field_creation_map[type(field)]().create(field)
except KeyError:
return CharFormField().create(field)
class SelectFormField(object):
def create(self, field):
if field.single_select_flag:
for opt in field.options:
if opt['text'] == field.value:
field.value = opt['val']
return ChoiceField(choices=self._create_choices(field), required=field.is_required(),
label=field.label,
initial=field.value, help_text=field.instruction)
else:
field_values = []
if field.value is not None:
field_labels = field.value.split(',')
for opt in field.options:
if opt['text'] in field_labels:
field_values.append(opt['val'])
return forms.MultipleChoiceField(label=field.label, widget=forms.CheckboxSelectMultiple,
choices=self._create_choices(field),
initial=field_values, required=field.is_required(),
help_text=field.instruction)
def _create_choices(self, field):
choice_list = [('', '--None--')] if field.single_select_flag else []
choice_list.extend([(option['val'], option['text']) for option in field.options])
choices = tuple(choice_list)
return choices
class PhoneNumberFormField(object):
def create(self, field):
telephone_number_field = PhoneNumberField(label=field.label, initial=field.value, required=field.is_required(),
help_text=field.instruction)
telephone_number_field.widget.attrs["watermark"] = get_text_field_constraint_text(field)
telephone_number_field.widget.attrs['style'] = 'padding-top: 7px;'
if field.name == LOCATION_TYPE_FIELD_NAME and isinstance(field, HierarchyField):
telephone_number_field.widget.attrs['class'] = 'location_field'
return telephone_number_field
class TextInputForFloat(TextInput):
def _has_changed(self, initial, data):
if data is None:
data_value = 0
else:
data_value = data
if initial is None:
initial_value = 0
else:
initial_value = initial
try:
return float(initial_value) != float(data_value)
except ValueError:
return force_unicode(initial_value) != force_unicode(data_value)
class IntegerFormField(object):
def create(self, field):
constraints = self._get_number_constraints(field)
float_field = FloatField(label=field.label, initial=field.value,
required=field.is_required(), help_text=field.instruction, widget=TextInputForFloat,
**constraints)
float_field.widget.attrs["watermark"] = self._get_number_field_constraint_text(field)
float_field.widget.attrs['style'] = 'padding-top: 7px;'
return float_field
def _get_number_constraints(self, field):
constraints = {}
if not is_empty(field.constraints):
constraint = field.constraints[0]
if constraint.max is not None: constraints["max_value"] = float(constraint.max)
if constraint.min is not None: constraints["min_value"] = float(constraint.min)
return constraints
def _get_number_field_constraint_text(self, field):
max = min = None
if len(field.constraints) > 0:
constraint = field.constraints[0]
min = constraint.min
max = constraint.max
if min is not None and max is None:
constraint_text = _("Minimum %s") % min
return constraint_text
if min is None and max is not None:
constraint_text = _("Upto %s") % max
return constraint_text
elif min is not None and max is not None:
constraint_text = _("%s -- %s") % (min, max)
return constraint_text
return ""
class DateFormField(object):
def create(self, field):
format = field.DATE_DICTIONARY.get(field.date_format)
date_field = DjangoDateField(input_formats=(format,), label=field.label, initial=field.value,
required=field.is_required(), help_text=field.instruction)
date_field.widget.attrs["watermark"] = get_text_field_constraint_text(field)
date_field.widget.attrs['style'] = 'padding-top: 7px;'
return date_field
class GeoCodeValidator(object):
clean = lambda self, x: strip(x)
def __call__(self, value):
lat_long_string = self.clean(value)
lat_long = lat_long_string.replace(",", " ")
lat_long = re.sub(' +', ' ', lat_long).split(" ")
try:
if len(lat_long) != 2:
raise Exception
GeoCodeConstraint().validate(latitude=lat_long[0], longitude=lat_long[1])
except Exception:
raise ValidationError(_(
"Incorrect GPS format. The GPS coordinates must be in the following format: xx.xxxx,yy.yyyy. Example -18.8665,47.5315"))
return lat_long_string
class CharFormField(object):
def create(self, field):
constraints = self._get_chars_constraints(field)
validators = [GeoCodeValidator()] if type(field) == GeoCodeField else []
char_field = StrippedCharField(label=field.label, initial=field.value, required=field.is_required(),
help_text=_(field.instruction), validators=validators, **constraints)
char_field.widget.attrs["watermark"] = "xx.xxxx,yy.yyyy" if type(
field) == GeoCodeField else get_text_field_constraint_text(field)
char_field.widget.attrs['style'] = 'padding-top: 7px;'
char_field.widget.attrs['class'] = css_class(field)
return char_field
def _get_chars_constraints(self, field):
constraints = {}
if not is_empty(field.constraints):
constraint = field.constraints[0]
if constraint.max is not None: constraints["max_length"] = constraint.max
if constraint.min is not None: constraints["min_length"] = constraint.min
return constraints
class EntityField(object):
def __init__(self, dbm, project):
self.dbm = dbm
self.project = project
def create(self, subject_field, entity_type):
#reporter_entity_type = 'reporter'
#if self.project.is_on_type(reporter_entity_type):
# choice_fields = self._data_sender_choice_fields(subject_field)
#else:
choice_fields = self._subject_choice_fields(entity_type, subject_field)
return {subject_field.code: choice_fields}
def _build_subject_choice_data(self, subjects, key_list):
values = map(lambda x: x["cols"] + [x["short_code"]], subjects)
key_list.append('unique_id')
return [dict(zip(key_list, value_list)) for value_list in values]
def _get_choice_field(self, data_sender_choices, subject_field, help_text):
subject_choice_field = ChoiceField(required=subject_field.is_required(), choices=data_sender_choices,
label=subject_field.name,
initial=subject_field.value, help_text=help_text)
subject_choice_field.widget.attrs['class'] = 'subject_field'
return subject_choice_field
def get_value(self, subject):
return subject['name'] + ' (' + subject['short_code'] + ')'
def choice(self, subject):
return subject['unique_id'], self.get_value(subject)
def _data_sender_choice_fields(self, subject_field):
data_senders = self.project.get_data_senders(self.dbm)
data_sender_choices = as_choices(data_senders)
return self._get_choice_field(data_sender_choices, subject_field, help_text=subject_field.instruction)
def _subject_choice_fields(self, entity_type, subject_field):
subjects, fields, label = load_all_entities_of_type(self.dbm, type=entity_type)
subjects = self._build_subject_choice_data(subjects, fields)
language = get_text_language_by_instruction(subject_field.instruction)
instruction_for_subject_field = translate("Choose Subject from this list.", language=language, func=ugettext)
all_subject_choices = map(self.choice, subjects)
choice_fields = self._get_choice_field(all_subject_choices, subject_field,
help_text=instruction_for_subject_field)
return choice_fields
def get_text_field_constraint_text(field):
if not is_empty(field.constraints):
length_constraint = field.constraints[0]
min = length_constraint.min
max = length_constraint.max
if min is not None and max is None:
constraint_text = _("Minimum %s characters") % min
return constraint_text
if min is None and max is not None:
constraint_text = _("Upto %s characters") % max
return constraint_text
elif min is not None and max is not None:
constraint_text = _("Between %s -- %s characters") % (min, max)
return constraint_text
return ""
def css_class(field):
if field.is_entity_field:
return 'subject_field'
if field.name == LOCATION_TYPE_FIELD_NAME and isinstance(field, HierarchyField):
return 'location_field'
return None
class StrippedCharField(forms.CharField):
def __init__(self, max_length=None, min_length=None, strip=True, *args, **kwargs):
super(StrippedCharField, self).__init__(max_length, min_length, *args, **kwargs)
self.strip = strip
def clean(self, value):
if value and self.strip:
value = value.strip()
return super(StrippedCharField, self).clean(value)