# coding=utf-8 from __future__ import absolute_import from __future__ import division from __future__ import print_function from __future__ import unicode_literals from django.contrib.contenttypes.models import ContentType from django.core.exceptions import ValidationError from django.db import transaction import gettext from django.utils.encoding import force_text _ = gettext.gettext __author__ = 'marcos' import collections from django import forms from rapid.widgets import RapidSelector, RapidRelationReadOnly, rapid_alternatives_widget from rapid.wrappers import FieldData, ModelData class RapidAlternativesField(forms.Field): 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') def for_model(request, model, default_relations=()): 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, RapidRelationReadOnly(f.related_model()))) 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'): widgets.append((f.bare_name(), RapidSelector(f))) # 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): 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 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) 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() self.save_m2m() return obj class Meta(object): model = form_model fields = '__all__' widgets = form_widgets return CForm