|
@@ -13,7 +13,7 @@ from django.core.exceptions import ValidationError
|
|
|
from django.db import transaction
|
|
|
from django.utils.encoding import force_text
|
|
|
|
|
|
-from rapid.widgets import RapidSelector, RapidRelationReadOnly, rapid_alternatives_widget
|
|
|
+from rapid.widgets import RapidSelector, RapidRelationReadOnly, rapid_alternatives_widget, rapid_dependent_widget
|
|
|
from rapid.wrappers import FieldData, ModelData
|
|
|
|
|
|
import gettext
|
|
@@ -21,6 +21,9 @@ _ = 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:
|
|
@@ -54,6 +57,62 @@ class RapidAlternativesField(forms.Field):
|
|
|
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=()):
|
|
|
default_relations = list(default_relations)
|
|
|
default_relations_request = request.GET.get('default')
|
|
@@ -63,9 +122,10 @@ def for_model(request, model, default_relations=()):
|
|
|
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, RapidRelationReadOnly(f.related_model())))
|
|
|
+ for (x, y) in default_relations:
|
|
|
+ f = FieldData(getattr(model, x).field, request)
|
|
|
+ widgets.append((x, RapidRelationReadOnly(f.related_model().model)))
|
|
|
+ default_relations_fields.append(x)
|
|
|
for f in ModelData(model).local_fields():
|
|
|
if f.is_relation() and force_text(f.bare_name()) not in default_relations_fields:
|
|
|
if f.related_model().has_permission(request, 'select'):
|
|
@@ -77,6 +137,8 @@ def for_model(request, model, default_relations=()):
|
|
|
# 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
|
|
@@ -97,6 +159,21 @@ def for_model(request, model, default_relations=()):
|
|
|
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
|
|
@@ -105,6 +182,8 @@ def for_model(request, model, default_relations=()):
|
|
|
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)
|
|
@@ -116,7 +195,34 @@ def for_model(request, model, default_relations=()):
|
|
|
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
|
|
|
|
|
@@ -126,3 +232,4 @@ def for_model(request, model, default_relations=()):
|
|
|
widgets = form_widgets
|
|
|
|
|
|
return CForm
|
|
|
+
|