rapidforms.py 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235
  1. # coding=utf-8
  2. from __future__ import absolute_import
  3. from __future__ import division
  4. from __future__ import print_function
  5. from __future__ import unicode_literals
  6. import collections
  7. from django import forms
  8. from django.contrib.contenttypes.models import ContentType
  9. from django.core.exceptions import ValidationError
  10. from django.db import transaction
  11. from django.utils.encoding import force_text
  12. from rapid.widgets import RapidSelector, RapidRelationReadOnly, rapid_alternatives_widget, rapid_dependent_widget
  13. from rapid.wrappers import FieldData, ModelData
  14. import gettext
  15. _ = gettext.gettext
  16. class RapidAlternativesField(forms.Field):
  17. """
  18. Form field for AlternativeData.
  19. """
  20. def __init__(self, field_name, alternatives, selector_name, form, request, instance=None, *args, **kwargs):
  21. model = None
  22. if instance:
  23. model = getattr(instance, selector_name)
  24. if form.base_fields[selector_name].initial:
  25. model = ContentType.objects.get_for_id(form.base_fields[selector_name].initial)
  26. alt = []
  27. for a in alternatives:
  28. md = ModelData(a)
  29. ft = for_model(request, a)
  30. prefix = field_name + '_' + str(md.content_type().pk)
  31. selected = ContentType.objects.get_for_model(a) == model
  32. inst = getattr(instance, field_name) if selected else None
  33. if request.method == 'POST':
  34. fm = ft(request.POST, request.FILES, prefix=prefix, instance=inst)
  35. else:
  36. fm = ft(prefix=prefix, instance=inst)
  37. alt.append((md.content_type().pk, (md, fm, selected)))
  38. alt = dict(alt)
  39. kwargs['widget'] = rapid_alternatives_widget(alt, selector_name)
  40. # noinspection PyArgumentList
  41. super(RapidAlternativesField, self).__init__(field_name, *args, **kwargs)
  42. def to_python(self, value):
  43. if value is None:
  44. return None
  45. if value.is_valid():
  46. obj = value.save(commit=False)
  47. obj.save_m2m = value.save_m2m
  48. return obj
  49. raise ValidationError(_("Invalid value"), code='invalid')
  50. class RapidDependentField(forms.Field):
  51. """
  52. Form fields for dependent instances.
  53. This field will be displayed for reverse relations where the
  54. foreign key of this model is required. That is, for related models
  55. that can not exist unless when related to this one.
  56. """
  57. def __init__(self, field, originator, model, request, instance=None, multiple=False, *args, **kwargs):
  58. self.multiple = multiple
  59. md = ModelData(model)
  60. prefix = field.bare_name() + '_' + str(md.content_type().pk)
  61. if multiple:
  62. ftt = for_model(request, model, [(field.related_field().bare_name(), originator)])
  63. ft = forms.modelformset_factory(model, form=ftt)
  64. else:
  65. ft = for_model(request, model, [(field.related_field().bare_name(), originator)])
  66. if request.method == 'POST':
  67. if multiple:
  68. fm = ft(request.POST, request.FILES, prefix=prefix, queryset=instance)
  69. else:
  70. fm = ft(request.POST, request.FILES, prefix=prefix, instance=instance)
  71. else:
  72. if multiple:
  73. fm = ft(prefix=prefix, queryset=instance)
  74. else:
  75. fm = ft(instance=instance, prefix=prefix)
  76. kwargs['widget'] = rapid_dependent_widget(md, fm, bool(instance), multiple)
  77. # noinspection PyArgumentList
  78. super(RapidDependentField, self).__init__(field, *args, **kwargs)
  79. self.required = False
  80. def to_python(self, value):
  81. if self.multiple:
  82. (f, ii) = value
  83. if f is None: return None
  84. if f.is_valid():
  85. class ValList:
  86. def __init__(self, vals):
  87. self.vals = vals
  88. def save_m2m(self):
  89. for v in self.vals:
  90. v.save_m2m()
  91. def __iter__(self):
  92. return self.vals.__iter__()
  93. objs = [x.save(commit=False) for x in f.forms]
  94. return ValList([objs[i] for i in ii])
  95. else:
  96. if value is None: return None
  97. if value.is_valid():
  98. obj = value.save(commit=False)
  99. obj.save_m2m = value.save_m2m
  100. return obj
  101. raise ValidationError(_("Invalid value"), code='invalid')
  102. def for_model(request, model, default_relations=()):
  103. default_relations = list(default_relations)
  104. default_relations_request = request.GET.get('default')
  105. widgets = []
  106. default_relations_fields = []
  107. if default_relations_request:
  108. default_relations_fields = default_relations_request.split(",")
  109. default_relations += ((x, int(y)) for (x, y) in (f.split(":") for f in default_relations_fields))
  110. default_relations_fields = [x for x, y in default_relations]
  111. for (x, y) in default_relations:
  112. f = FieldData(getattr(model, x).field, request)
  113. widgets.append((x, RapidRelationReadOnly(f.related_model().model)))
  114. default_relations_fields.append(x)
  115. for f in ModelData(model).local_fields():
  116. if f.is_relation() and force_text(f.bare_name()) not in default_relations_fields:
  117. if f.related_model().has_permission(request, 'select'):
  118. widgets.append((f.bare_name(), RapidSelector(f)))
  119. # ModelForm.Meta has attributes with the same names, thus I'll rename them
  120. form_model = model
  121. form_widgets = dict(widgets)
  122. # noinspection PyTypeChecker
  123. class CForm(forms.ModelForm):
  124. def __init__(self, *args, **kwargs):
  125. # Sets alternative data:
  126. initial = kwargs.get('initial', {})
  127. for (k, v) in default_relations:
  128. initial[k] = v
  129. if initial:
  130. kwargs['initial'] = initial
  131. instance = kwargs.get('instance')
  132. for n, fd in ModelData(model).rapid_alternative_data():
  133. ct = ModelData(model).field_by_name(fd.ct_field).field
  134. fk = ModelData(model).field_by_name(fd.fk_field).field
  135. # noinspection PyTypeChecker
  136. fl = RapidAlternativesField(n, ct.alternatives, ct.name, self, request, instance)
  137. # noinspection PyArgumentList
  138. type(self.__class__).__setattr__(self.__class__, n, fl)
  139. nd = collections.OrderedDict()
  140. for k, v in self.__class__.base_fields.items():
  141. if k == fk.name:
  142. nd[n] = fl
  143. else:
  144. nd[k] = v
  145. self.__class__.base_fields = nd
  146. # Sets fields for reverse relations:
  147. for f in ModelData(model).related_fields():
  148. if f.is_existential_dependency():
  149. md = f.related_model().model
  150. ft = {f.related_field().bare_name(): instance}
  151. dt = md.objects.filter(**ft)
  152. if f.is_multiple():
  153. fl = RapidDependentField(f, instance, md, request, dt, True)
  154. else:
  155. o = dt[0] if dt else None
  156. fl = RapidDependentField(f, instance, md, request,
  157. o, False)
  158. self.__class__.base_fields[f.bare_name()] = fl
  159. super(CForm, self).__init__(*args, **kwargs)
  160. @transaction.atomic
  161. def save(self, commit=True):
  162. if not commit:
  163. return super(CForm, self).save(commit)
  164. else:
  165. obj = super(CForm, self).save(commit=False)
  166. # Saving alternative data:
  167. for n, fd in ModelData(model).rapid_alternative_data():
  168. if self.instance:
  169. old_t = getattr(self.instance, fd.ct_field)
  170. new_t = getattr(obj, fd.ct_field)
  171. if old_t != new_t:
  172. getattr(self.instance, fd.bare_name).delete()
  173. fob = self.cleaned_data[n]
  174. fob.save()
  175. if hasattr(fob, 'save_m2m'):
  176. fob.save_m2m()
  177. setattr(obj, fd.fk_field, fob.pk)
  178. obj.save()
  179. # Saving reverse relations:
  180. for f in ModelData(model).related_fields():
  181. if f.is_existential_dependency():
  182. fob = self.cleaned_data[f.bare_name()]
  183. ft = {f.related_field().bare_name(): obj}
  184. if fob:
  185. if f.is_multiple():
  186. ex = {'pk__in': [ob.pk for ob in fob if ob.pk]}
  187. f.related_model().model.objects.filter(**ft).exclude(**ex).delete()
  188. for ob in fob:
  189. setattr(ob, f.related_field().bare_name(), obj)
  190. ob.save()
  191. if hasattr(ob, 'save_m2m'):
  192. ob.save_m2m()
  193. else:
  194. ex = {'pk__exact': fob.pk}
  195. f.related_model().model.objects.filter(**ft).exclude(**ex).delete()
  196. setattr(fob, f.related_field().bare_name(), obj)
  197. fob.save()
  198. if hasattr(fob, 'save_m2m'):
  199. fob.save_m2m()
  200. else:
  201. f.related_model().model.objects.filter(**ft).delete()
  202. self.save_m2m()
  203. return obj
  204. class Meta(object):
  205. model = form_model
  206. fields = '__all__'
  207. widgets = form_widgets
  208. return CForm