# coding=utf-8 from __future__ import absolute_import from __future__ import division from __future__ import print_function from __future__ import unicode_literals import collections from django import forms from django.db import models from django.conf import settings from django.contrib.contenttypes.models import ContentType from django.core.exceptions import ValidationError from django.db import transaction from django.utils.encoding import force_text from datetimewidget import widgets as dtwidgets from rapid.widgets import RapidSelector, rapid_alternatives_widget, rapid_dependent_widget, \ getRapidReadonlyRelation from rapid.wrappers import FieldData, ModelData import gettext _ = gettext.gettext class RapidAlternativesField(forms.Field): """ Form field for AlternativeData. """ def __init__(self, field_name, alternatives, selector_name, form, request, instance=None, *args, **kwargs): model = None if instance: model = getattr(instance, selector_name) if form.base_fields[selector_name].initial: model = ContentType.objects.get_for_id(form.base_fields[selector_name].initial) alt = [] for a in alternatives: md = ModelData(a) ft = for_model(request, a) prefix = field_name + '_' + str(md.content_type().pk) selected = ContentType.objects.get_for_model(a) == model inst = getattr(instance, field_name) if selected else None if request.method == 'POST': fm = ft(request.POST, request.FILES, prefix=prefix, instance=inst) else: fm = ft(prefix=prefix, instance=inst) alt.append((md.content_type().pk, (md, fm, selected))) alt = dict(alt) kwargs['widget'] = rapid_alternatives_widget(alt, selector_name) # noinspection PyArgumentList super(RapidAlternativesField, self).__init__(field_name, *args, **kwargs) def to_python(self, value): if value is None: return None if value.is_valid(): obj = value.save(commit=False) obj.save_m2m = value.save_m2m return obj raise ValidationError(_("Invalid value"), code='invalid') class RapidDependentField(forms.Field): """ Form fields for dependent instances. This field will be displayed for reverse relations where the foreign key of this model is required. That is, for related models that can not exist unless when related to this one. """ def __init__(self, field, originator, model, request, instance=None, multiple=False, *args, **kwargs): self.multiple = multiple md = ModelData(model) prefix = field.bare_name() + '_' + str(md.content_type().pk) if multiple: ftt = for_model(request, model, [(field.related_field().bare_name(), originator)]) ft = forms.modelformset_factory(model, form=ftt) else: ft = for_model(request, model, [(field.related_field().bare_name(), originator)]) if request.method == 'POST': if multiple: fm = ft(request.POST, request.FILES, prefix=prefix, queryset=instance) else: fm = ft(request.POST, request.FILES, prefix=prefix, instance=instance) else: if multiple: fm = ft(prefix=prefix, queryset=instance) else: fm = ft(instance=instance, prefix=prefix) kwargs['widget'] = rapid_dependent_widget(md, fm, bool(instance), multiple) # noinspection PyArgumentList super(RapidDependentField, self).__init__(field, *args, **kwargs) self.required = False def to_python(self, value): if self.multiple: (f, ii) = value if f is None: return None if f.is_valid(): class ValList: def __init__(self, vals): self.vals = vals def save_m2m(self): for v in self.vals: v.save_m2m() def __iter__(self): return self.vals.__iter__() objs = [x.save(commit=False) for x in f.forms] return ValList([objs[i] for i in ii]) else: if value is None: return None if value.is_valid(): obj = value.save(commit=False) obj.save_m2m = value.save_m2m return obj raise ValidationError(_("Invalid value"), code='invalid') def for_model(request, model, default_relations=()): hasDtWidgets = 'datetimewidget' in settings.INSTALLED_APPS default_relations = list(default_relations) default_relations_request = request.GET.get('default') widgets = [] default_relations_fields = [] if default_relations_request: default_relations_fields = default_relations_request.split(",") default_relations += ((x, int(y)) for (x, y) in (f.split(":") for f in default_relations_fields)) default_relations_fields = [x for x, y in default_relations] for (x, y) in default_relations: f = FieldData(getattr(model, x).field, request) widgets.append((x, getRapidReadonlyRelation(f.related_model().model, y))) default_relations_fields.append(x) for f in ModelData(model).local_fields(): if f.is_relation(): if force_text(f.bare_name()) not in default_relations_fields and \ f.related_model().has_permission(request, 'select'): widgets.append((f.bare_name(), RapidSelector(f))) elif force_text(f.bare_name()) in default_relations_fields: pass if type(f.field) == models.DateField: widgets.append((f.bare_name(), dtwidgets.DateWidget(usel10n = True, bootstrap_version=3))) if type(f.field) == models.DateTimeField: widgets.append((f.bare_name(), dtwidgets.DateTimeWidget(usel10n = True, bootstrap_version=3))) if type(f.field) == models.TimeField: widgets.append((f.bare_name(), dtwidgets.TimeWidget(usel10n = True, bootstrap_version=3))) #if force_text(model._meta.db_table) == 'testproject_singledepontest1': # import pdb; pdb.set_trace() # ModelForm.Meta has attributes with the same names, thus I'll rename them form_model = model form_widgets = dict(widgets) # noinspection PyTypeChecker class CForm(forms.ModelForm): def __init__(self, *args, **kwargs): # Sets alternative data: initial = kwargs.get('initial', {}) for (k, v) in default_relations: initial[k] = v if initial: kwargs['initial'] = initial instance = kwargs.get('instance') for n, fd in ModelData(model).rapid_alternative_data(): ct = ModelData(model).field_by_name(fd.ct_field).field fk = ModelData(model).field_by_name(fd.fk_field).field # noinspection PyTypeChecker fl = RapidAlternativesField(n, ct.alternatives, ct.name, self, request, instance) # noinspection PyArgumentList type(self.__class__).__setattr__(self.__class__, n, fl) nd = collections.OrderedDict() for k, v in self.__class__.base_fields.items(): if k == fk.name: nd[n] = fl else: nd[k] = v self.__class__.base_fields = nd # Sets fields for reverse relations: for f in ModelData(model).related_fields(): if f.is_existential_dependency(): md = f.related_model().model ft = {f.related_field().bare_name(): instance} dt = md.objects.filter(**ft) if f.is_multiple(): fl = RapidDependentField(f, instance, md, request, dt, True) else: o = dt[0] if dt else None fl = RapidDependentField(f, instance, md, request, o, False) self.__class__.base_fields[f.bare_name()] = fl super(CForm, self).__init__(*args, **kwargs) @transaction.atomic def save(self, commit=True): if not commit: return super(CForm, self).save(commit) else: obj = super(CForm, self).save(commit=False) # Saving alternative data: for n, fd in ModelData(model).rapid_alternative_data(): if self.instance: old_t = getattr(self.instance, fd.ct_field) new_t = getattr(obj, fd.ct_field) if old_t != new_t: getattr(self.instance, fd.bare_name).delete() fob = self.cleaned_data[n] fob.save() if hasattr(fob, 'save_m2m'): fob.save_m2m() setattr(obj, fd.fk_field, fob.pk) obj.save() # Saving reverse relations: for f in ModelData(model).related_fields(): if f.is_existential_dependency(): fob = self.cleaned_data[f.bare_name()] ft = {f.related_field().bare_name(): obj} if fob: if f.is_multiple(): ex = {'pk__in': [ob.pk for ob in fob if ob.pk]} f.related_model().model.objects.filter(**ft).exclude(**ex).delete() for ob in fob: setattr(ob, f.related_field().bare_name(), obj) ob.save() if hasattr(ob, 'save_m2m'): ob.save_m2m() else: ex = {'pk__exact': fob.pk} f.related_model().model.objects.filter(**ft).exclude(**ex).delete() setattr(fob, f.related_field().bare_name(), obj) fob.save() if hasattr(fob, 'save_m2m'): fob.save_m2m() else: f.related_model().model.objects.filter(**ft).delete() self.save_m2m() return obj class Meta(object): model = form_model fields = '__all__' widgets = form_widgets return CForm