# -*- 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'') 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.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()