# -*- coding: utf-8 -*-
# YOU HAVE TO SET POST_VALIDATION_MODE TO True or False in settings.py
from django.conf import settings
try:
from django import newforms as forms
from django.newforms.util import ErrorList, ValidationError, flatatt, StrAndUnicode, smart_unicode
except:
from django import forms
from django.forms.util import ErrorList, ValidationError, flatatt, StrAndUnicode, smart_unicode
from types import *
from django.utils.translation import gettext_lazy
from itertools import chain
from django.utils.translation import gettext as _
from django.pimentech.utils import change_dict_for_creation
try:
set # Only available in Python 2.4+
except NameError:
from sets import Set as set # Python 2.3 fallback
from django import VERSION
from django.utils import simplejson
if VERSION[0] == 0 and int(VERSION[1]) <= 96:
CLEAN = 'clean_data'
else:
CLEAN = 'cleaned_data'
def desunicode(data):
if type(data) == UnicodeType:
return data.encode(settings.DEFAULT_CHARSET)
return data
class PopupInput(forms.TextInput):
label = None
def __init__(self, popup_url, *args, **kwargs):
self.popup_url = popup_url
super(PopupInput, self).__init__(*args, **kwargs)
def render(self, name, value, attrs=None):
return u'' \
''% {
'name' : name,
'value' : value,
'label' : self.label,
'popup_url' : self.popup_url }
class SelectMultipleNoEscape(forms.SelectMultiple):
def render(self, name, value, attrs=None, choices=()):
if value is None: value = []
final_attrs = self.build_attrs(attrs, name=name)
output = [u'')
return u'\n'.join(output)
class CheckboxSelectMultipleNoEscape(forms.CheckboxSelectMultiple):
def render(self, name, value, attrs=None, choices=()):
if value is None: value = []
has_id = attrs and attrs.has_key('id')
final_attrs = self.build_attrs(attrs, name=name)
output = [u'
']
str_values = set([smart_unicode(v) for v in value]) # Normalize to strings.
options = list(self.choices) + list(choices)
def asc(a, b):
x = a[1].lower()
y = b[1].lower()
if x>y:
return 1
elif x==y:
return 0
else: # x' % (rendered_cb, smart_unicode(option_label)))
output.append(u'
')
return u'\n'.join(output)
class ForeignKeyField(forms.IntegerField):
def __init__(self, popup_url, *args, **kwargs):
self.widget = PopupInput(popup_url)
super(ForeignKeyField, self).__init__(*args, **kwargs)
def clean(self, value):
"""
Validates that int() can be called on the input. Returns the result
of int(). Returns None for empty values.
"""
super(ForeignKeyField, self).clean(value)
if self.required and value == '0':
raise ValidationError(_(u'This field is required.'))
return value
class NNChoiceField(forms.MultipleChoiceField):
nn = True
def __init__(self, FormClass, NNClass, RefClass, query_choices=None, query_initial=None, choices=None,
required=True, widget=SelectMultipleNoEscape, label=None, help_text=None):
self.FormClass = FormClass
self.form_table = FormClass._meta.db_table
self.NNClass = NNClass
self.nn_table = NNClass._meta.db_table
self.RefClass = RefClass
self.ref_table = RefClass._meta.db_table
self.query_choices = query_choices or {}
self.query_initial = query_initial or {}
choices = choices or [ (object._get_pk_val(), _(str(object))) for object in self.RefClass.objects.filter(**self.query_choices) ]
super(NNChoiceField, self).__init__(choices, required, widget, label, help_text)
def get_initial(self, dataform, instance):
#instance = dataform.instance_ref or dataform.instance_base
self.query_initial[self.form_table] = instance._get_pk_val()
self._initial_values = [ getattr(object, self.ref_table+'_id') for object in self.NNClass.objects.filter(**self.query_initial) ]
return self._initial_values
def _modified(self, values):
form = set()
initial = set()
for value in values:
form.add(int(value))
for value in self._initial_values:
initial.add(int(value))
self.modified = form != initial
return self.modified
def delete(self, instance):
#idee : filtrer aussi en fonction des id de RefClass qui satisfont query_choices.
#Car le champs ne porte que sur ces entrees la de la nn.
args = { self.form_table : instance._get_pk_val() }
for obj in self.NNClass.objects.filter(**args):
obj.delete()
def save(self, instance, ids):
id_ref = instance._get_pk_val()
for id in ids:
args = {
self.ref_table +'_id' : id,
self.form_table+'_id' : id_ref
}
instance = self.NNClass(**args)
instance.save()
def set_help_text(self, dataform):
if dataform.instance_ref is None:
self.help_text = ''
else:
instance = dataform.instance_base
self.get_initial(dataform, instance)
help_text = '
'
self.query_initial[self.form_table] = instance._get_pk_val()
ref_values = [ getattr(object, self.ref_table+'_id') \
for object in self.NNClass.objects.filter(**self.query_initial) ]
for id, name in self.choices:
if id in self._initial_values and id not in ref_values:
help_text += '
-%s
' % gettext_lazy(name)
elif id not in self._initial_values and id in ref_values:
help_text += '
+%s
' % gettext_lazy(name)
help_text += '
'
self.help_text = help_text
class DataForm(forms.Form):
fixed_columns_ref = { }
def __init__(self, instance, ref_value=None,
ref_field='ref_object', ref_dataform=None, constraints = None,
data=None, auto_id='id_%s', prefix=None):
self.instance_base = instance
self.ref_field = ref_field
self.ref_value = ref_value
self.ref_dataform = ref_dataform
self.instance_ref = None
self.constraints = constraints or {}
Class = instance.__class__
forms.Form.__init__(self, data=data, auto_id=auto_id, prefix=prefix)
if self.ref_dataform and self.ref_dataform.instance_ref:
self.ref_value = ref_dataform.instance_ref._get_pk_val()
if self.ref_value:
self.constraints[self.ref_field] = self.ref_value
try:
self.instance_ref = Class.objects.get(**self.constraints)
except Class.DoesNotExist:
self.instance_ref = None
self.initial = self._initial(settings.POST_VALIDATION_MODE and self.instance_base or self.instance_ref or self.instance_base)
self.fill_help_text()
def update_from_form(self, instance):
"Update instance with form values"
from django.db import models
if not self.is_valid():
raise ValueError("The %s could not be saved because the data didn't validate." \
% instance.__class__._meta.object_name)
cleaned_data = getattr(self, CLEAN)
for key, field in self.fields.items():
if hasattr(field, 'nn'):
continue
else:
f = self.get_instance_field(key)
if f:
if not f.editable or isinstance(f, models.AutoField):
continue
if f.get_internal_type() == 'ForeignKey':
setattr(instance, key + '_id', int(cleaned_data[key]))
else:
setattr(instance, key, self.cast_form_field(key))
else:
setattr(instance, key, self.cast_form_field(key))
def copy(self, instance):
dict = instance.__dict__.copy()
del dict[instance._meta.pk.column]
for key in dict.keys():
try:
if instance._meta.get_field(key).unique:
#cle unique, on supprime de la copie
# TODO : un truc plus intelligent
del dict[key]
except:
pass
if key[0] == '_':
del dict[key]
return instance.__class__(**dict)
def save(self, instance):
instance.save()
# cause when 'field_id' is modified, we have to regenerate 'field' object :
return instance.__class__.objects.get(
**{instance._meta.pk.column : instance._get_pk_val() })
def save_nn(self, instance):
"caca"
for key, form_field in self.fields.items():
if hasattr(form_field, 'nn'):
form_field.delete(instance)
form_field.save(instance, getattr(self, CLEAN)[key])
def delete(self, instance):
if instance:
instance.delete()
def modify(self):
if self.modified:
if not self.instance_ref:
new_instance = self.copy(self.instance_base)
if self.ref_value:
setattr(new_instance, self._ref_field_column(), self.ref_value)
else:
self.ref_dataform.modified = True
# if one node is modified, we copy all the branch
self.ref_dataform.modify()
setattr(new_instance, self._ref_field_column(), self.ref_dataform.instance_ref._get_pk_val())
for key, value in self.fixed_columns_ref.items():
setattr(new_instance, key, value)
self.instance_ref = self.save(new_instance)
for key, form_field in self.fields.items():
instance_field = self.get_instance_field(key)
if hasattr(form_field, 'nn'):
for object in form_field.NNClass.objects.filter(**form_field.query_initial):
dict = object.__dict__.copy()
del dict[object._meta.pk.column]
dict[form_field.form_table+'_id'] = self.instance_ref._get_pk_val()
object_ref = object.__class__(**dict)
object_ref.save()
# YOU HAVE TO SET POST_VALIDATION_MODE TO True or False in settings.py
if settings.POST_VALIDATION_MODE:
self.update_from_form(self.instance_base)
self.instance_base = self.save(self.instance_base)
self.save_nn(self.instance_base)
else:
self.update_from_form(self.instance_ref)
self.instance_ref = self.save(self.instance_ref)
self.save_nn(self.instance_ref)
self.fill_help_text()
return self.instance_ref
def validate(self):
"Fill instance base with form and delete instance ref"
self.update_from_form(self.instance_base)
self.save(self.instance_base)
self.save_nn(self.instance_base)
self.delete(self.instance_ref)
self.instance_ref = None
self.fill_help_text()
def delete_ref(self):
"Delete ref_value and rebuild form"
if self.ref_value:
self.delete(self.instance_ref)
self.instance_ref = None
self.initial = self._initial(self.instance_base)
self.fill_help_text()
# self.__init__(self.instance_base, None,
# self.ref_field, self.ref_dataform,
# None, self.auto_id, self.prefix)
def unvalidate(self):
"Deprecated"
self.delete_ref()
def _initial(self, instance):
#instance = self.instance_ref or self.instance_base
dic = {}
for key, form_field in self.fields.items():
instance_field = self.get_instance_field(key)
if hasattr(form_field, 'nn'):
dic[key] = form_field.get_initial(self, instance)
else:
if instance_field:
if instance_field.get_internal_type() == 'ForeignKey':
dic[key] = getattr(instance, key + '_id')
else:
dic[key] = getattr(instance, key)
return dic
def _help_text(self, instance, instance_field, key):
help_text = ''
if self.instance_ref is not None:
if instance == self.instance_ref:
instance_help = self.instance_base
else:
instance_help = self.instance_ref
if instance_field.get_internal_type() == 'ForeignKey':
valid_value = getattr(instance, key + '_id')
if getattr(instance_help, key + '_id') != valid_value:
help_text = str(getattr(instance, key))
else:
valid_value = getattr(instance, key)
if desunicode(getattr(instance_help, key)) != desunicode(valid_value):
if valid_value:
help_text = str(valid_value)
return help_text or ''
def fill_help_text(self):
instance = settings.POST_VALIDATION_MODE and self.instance_ref or self.instance_base
instance_label = (instance == self.instance_ref) and self.instance_base or self.instance_ref or self.instance_base
for key, form_field in self.fields.items():
if hasattr(form_field, 'nn'):
form_field.set_help_text(self)
else:
if form_field.__class__.__name__ == 'ForeignKeyField':
form_field.label = str(getattr(instance_label, key))
form_field.widget.label = str(getattr(instance_label, key))
form_field.help_text = self._help_text(instance, self.get_instance_field(key), key)
def get_instance_field(self, key):
for field in self.instance_base.__class__._meta.fields:
if field.name == key:
return field
return None
def cast_form_field(self, key):
form_value = getattr(self, CLEAN)[key]
instance_field = self.get_instance_field(key)
if instance_field:
instance_type = instance_field.get_internal_type()
if instance_type == 'FloatField':
form_value = form_value and float(form_value) or None
elif instance_type in ('ForeignKeyField', 'IntegerField'):
form_value = int(form_value)
elif type(form_value) == UnicodeType:
form_value = form_value.encode(settings.DEFAULT_CHARSET)
return form_value
def _modified(self):
for key, form_field in self.fields.items():
if hasattr(form_field, 'nn'):
if form_field._modified(getattr(self, CLEAN)[key]):
self.modified = True
return True
else:
instance_field = self.get_instance_field(key)
if instance_field and instance_field.get_internal_type() == 'ForeignKey':
instance_value = getattr(self.instance_base, key+'_id')
else:
instance_value = getattr(self.instance_base, key)
form_value = self.cast_form_field(key)
if instance_value != form_value:
self.modified = True
return True
self.modified = False
return False
def is_valid(self):
if not super(DataForm, self).is_valid():
return False
self.modified = self._modified()
return True
def _ref_field_column(self):
if self.ref_field in ('ref_object', 'object_id'):
return self.ref_field
return self.ref_field + '_id'
class DataFormList(list):
""" list of dataforms. Adding rows to the database must be taken care of outside (ajax for example).
if less than min_entries database entries are found, we fill with empty entries."""
def __init__(self, ModelClass, fixe, Form, label, fixed_columns_ref, data, min_entries=0):
self.fixe = fixe
self.label = label
self.ModelClass = ModelClass
self.data = data
self.querylist = list(self.ModelClass.objects.filter(**fixe).order_by('id'))
fixe_for_creation = change_dict_for_creation(self.ModelClass, self.fixe.copy())
while len(self.querylist) < min_entries:
self.querylist.append(self.ModelClass.objects.create(**fixe_for_creation))
for instance in self.querylist:
form = Form(instance,
ref_value=instance.id,
ref_field='object_id',
data=data,
prefix='%s'%instance.id)
#beware : specific !
form.fixed_columns_ref = fixed_columns_ref
self.append(form)
json = {}
json.update(fixe)
self.json = simplejson.dumps(json, ensure_ascii=False)
def is_valid(self):
valid_overall = True
for form in self:
this_is_valid = form.is_valid()
valid_overall = valid_overall and this_is_valid
#return reduce(et_logique, [ form.is_valid() for form in self ] )
return valid_overall
def modify(self):
for form in self:
form.modify()
def delete_ref(self):
for form in self:
form.delete_ref()
def validate(self):
for form in self:
form.validate()