rapidforms.py 11 KB

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