rapidforms.py 10 KB

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