helpers.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353
  1. from __future__ import unicode_literals
  2. from django import forms
  3. from django.contrib.admin.utils import (flatten_fieldsets, lookup_field,
  4. display_for_field, label_for_field, help_text_for_field)
  5. from django.contrib.admin.templatetags.admin_static import static
  6. from django.core.exceptions import ObjectDoesNotExist
  7. from django.db.models.fields.related import ManyToManyRel
  8. from django.forms.utils import flatatt
  9. from django.template.defaultfilters import capfirst, linebreaksbr
  10. from django.utils.encoding import force_text, smart_text
  11. from django.utils.html import conditional_escape, format_html
  12. from django.utils.safestring import mark_safe
  13. from django.utils import six
  14. from django.utils.translation import ugettext_lazy as _
  15. from django.conf import settings
  16. ACTION_CHECKBOX_NAME = '_selected_action'
  17. class ActionForm(forms.Form):
  18. action = forms.ChoiceField(label=_('Action:'))
  19. select_across = forms.BooleanField(label='', required=False, initial=0,
  20. widget=forms.HiddenInput({'class': 'select-across'}))
  21. checkbox = forms.CheckboxInput({'class': 'action-select'}, lambda value: False)
  22. class AdminForm(object):
  23. def __init__(self, form, fieldsets, prepopulated_fields, readonly_fields=None, model_admin=None):
  24. self.form, self.fieldsets = form, fieldsets
  25. self.prepopulated_fields = [{
  26. 'field': form[field_name],
  27. 'dependencies': [form[f] for f in dependencies]
  28. } for field_name, dependencies in prepopulated_fields.items()]
  29. self.model_admin = model_admin
  30. if readonly_fields is None:
  31. readonly_fields = ()
  32. self.readonly_fields = readonly_fields
  33. def __iter__(self):
  34. for name, options in self.fieldsets:
  35. yield Fieldset(
  36. self.form, name,
  37. readonly_fields=self.readonly_fields,
  38. model_admin=self.model_admin,
  39. **options
  40. )
  41. def _media(self):
  42. media = self.form.media
  43. for fs in self:
  44. media = media + fs.media
  45. return media
  46. media = property(_media)
  47. class Fieldset(object):
  48. def __init__(self, form, name=None, readonly_fields=(), fields=(), classes=(),
  49. description=None, model_admin=None):
  50. self.form = form
  51. self.name, self.fields = name, fields
  52. self.classes = ' '.join(classes)
  53. self.description = description
  54. self.model_admin = model_admin
  55. self.readonly_fields = readonly_fields
  56. def _media(self):
  57. if 'collapse' in self.classes:
  58. extra = '' if settings.DEBUG else '.min'
  59. js = ['jquery%s.js' % extra,
  60. 'jquery.init.js',
  61. 'collapse%s.js' % extra]
  62. return forms.Media(js=[static('admin/js/%s' % url) for url in js])
  63. return forms.Media()
  64. media = property(_media)
  65. def __iter__(self):
  66. for field in self.fields:
  67. yield Fieldline(self.form, field, self.readonly_fields, model_admin=self.model_admin)
  68. class Fieldline(object):
  69. def __init__(self, form, field, readonly_fields=None, model_admin=None):
  70. self.form = form # A django.forms.Form instance
  71. if not hasattr(field, "__iter__") or isinstance(field, six.text_type):
  72. self.fields = [field]
  73. else:
  74. self.fields = field
  75. self.has_visible_field = not all(field in self.form.fields and
  76. self.form.fields[field].widget.is_hidden
  77. for field in self.fields)
  78. self.model_admin = model_admin
  79. if readonly_fields is None:
  80. readonly_fields = ()
  81. self.readonly_fields = readonly_fields
  82. def __iter__(self):
  83. for i, field in enumerate(self.fields):
  84. if field in self.readonly_fields:
  85. yield AdminReadonlyField(self.form, field, is_first=(i == 0),
  86. model_admin=self.model_admin)
  87. else:
  88. yield AdminField(self.form, field, is_first=(i == 0))
  89. def errors(self):
  90. return mark_safe('\n'.join(self.form[f].errors.as_ul() for f in self.fields if f not in self.readonly_fields).strip('\n'))
  91. class AdminField(object):
  92. def __init__(self, form, field, is_first):
  93. self.field = form[field] # A django.forms.BoundField instance
  94. self.is_first = is_first # Whether this field is first on the line
  95. self.is_checkbox = isinstance(self.field.field.widget, forms.CheckboxInput)
  96. def label_tag(self):
  97. classes = []
  98. contents = conditional_escape(force_text(self.field.label))
  99. if self.is_checkbox:
  100. classes.append('vCheckboxLabel')
  101. if self.field.field.required:
  102. classes.append('required')
  103. if not self.is_first:
  104. classes.append('inline')
  105. attrs = {'class': ' '.join(classes)} if classes else {}
  106. # checkboxes should not have a label suffix as the checkbox appears
  107. # to the left of the label.
  108. return self.field.label_tag(contents=mark_safe(contents), attrs=attrs,
  109. label_suffix='' if self.is_checkbox else None)
  110. def errors(self):
  111. return mark_safe(self.field.errors.as_ul())
  112. class AdminReadonlyField(object):
  113. def __init__(self, form, field, is_first, model_admin=None):
  114. # Make self.field look a little bit like a field. This means that
  115. # {{ field.name }} must be a useful class name to identify the field.
  116. # For convenience, store other field-related data here too.
  117. if callable(field):
  118. class_name = field.__name__ if field.__name__ != '<lambda>' else ''
  119. else:
  120. class_name = field
  121. if form._meta.labels and class_name in form._meta.labels:
  122. label = form._meta.labels[class_name]
  123. else:
  124. label = label_for_field(field, form._meta.model, model_admin)
  125. if form._meta.help_texts and class_name in form._meta.help_texts:
  126. help_text = form._meta.help_texts[class_name]
  127. else:
  128. help_text = help_text_for_field(class_name, form._meta.model)
  129. self.field = {
  130. 'name': class_name,
  131. 'label': label,
  132. 'help_text': help_text,
  133. 'field': field,
  134. }
  135. self.form = form
  136. self.model_admin = model_admin
  137. self.is_first = is_first
  138. self.is_checkbox = False
  139. self.is_readonly = True
  140. def label_tag(self):
  141. attrs = {}
  142. if not self.is_first:
  143. attrs["class"] = "inline"
  144. label = self.field['label']
  145. return format_html('<label{0}>{1}:</label>',
  146. flatatt(attrs),
  147. capfirst(force_text(label)))
  148. def contents(self):
  149. from django.contrib.admin.templatetags.admin_list import _boolean_icon
  150. from django.contrib.admin.views.main import EMPTY_CHANGELIST_VALUE
  151. field, obj, model_admin = self.field['field'], self.form.instance, self.model_admin
  152. try:
  153. f, attr, value = lookup_field(field, obj, model_admin)
  154. except (AttributeError, ValueError, ObjectDoesNotExist):
  155. result_repr = EMPTY_CHANGELIST_VALUE
  156. else:
  157. if f is None:
  158. boolean = getattr(attr, "boolean", False)
  159. if boolean:
  160. result_repr = _boolean_icon(value)
  161. else:
  162. result_repr = smart_text(value)
  163. if getattr(attr, "allow_tags", False):
  164. result_repr = mark_safe(result_repr)
  165. else:
  166. result_repr = linebreaksbr(result_repr)
  167. else:
  168. if isinstance(f.rel, ManyToManyRel) and value is not None:
  169. result_repr = ", ".join(map(six.text_type, value.all()))
  170. else:
  171. result_repr = display_for_field(value, f)
  172. return conditional_escape(result_repr)
  173. class InlineAdminFormSet(object):
  174. """
  175. A wrapper around an inline formset for use in the admin system.
  176. """
  177. def __init__(self, inline, formset, fieldsets, prepopulated_fields=None,
  178. readonly_fields=None, model_admin=None):
  179. self.opts = inline
  180. self.formset = formset
  181. self.fieldsets = fieldsets
  182. self.model_admin = model_admin
  183. if readonly_fields is None:
  184. readonly_fields = ()
  185. self.readonly_fields = readonly_fields
  186. if prepopulated_fields is None:
  187. prepopulated_fields = {}
  188. self.prepopulated_fields = prepopulated_fields
  189. def __iter__(self):
  190. for form, original in zip(self.formset.initial_forms, self.formset.get_queryset()):
  191. view_on_site_url = self.opts.get_view_on_site_url(original)
  192. yield InlineAdminForm(self.formset, form, self.fieldsets,
  193. self.prepopulated_fields, original, self.readonly_fields,
  194. model_admin=self.opts, view_on_site_url=view_on_site_url)
  195. for form in self.formset.extra_forms:
  196. yield InlineAdminForm(self.formset, form, self.fieldsets,
  197. self.prepopulated_fields, None, self.readonly_fields,
  198. model_admin=self.opts)
  199. yield InlineAdminForm(self.formset, self.formset.empty_form,
  200. self.fieldsets, self.prepopulated_fields, None,
  201. self.readonly_fields, model_admin=self.opts)
  202. def fields(self):
  203. fk = getattr(self.formset, "fk", None)
  204. for i, field_name in enumerate(flatten_fieldsets(self.fieldsets)):
  205. if fk and fk.name == field_name:
  206. continue
  207. if field_name in self.readonly_fields:
  208. yield {
  209. 'label': label_for_field(field_name, self.opts.model, self.opts),
  210. 'widget': {
  211. 'is_hidden': False
  212. },
  213. 'required': False,
  214. 'help_text': help_text_for_field(field_name, self.opts.model),
  215. }
  216. else:
  217. yield self.formset.form.base_fields[field_name]
  218. def _media(self):
  219. media = self.opts.media + self.formset.media
  220. for fs in self:
  221. media = media + fs.media
  222. return media
  223. media = property(_media)
  224. class InlineAdminForm(AdminForm):
  225. """
  226. A wrapper around an inline form for use in the admin system.
  227. """
  228. def __init__(self, formset, form, fieldsets, prepopulated_fields, original,
  229. readonly_fields=None, model_admin=None, view_on_site_url=None):
  230. self.formset = formset
  231. self.model_admin = model_admin
  232. self.original = original
  233. if original is not None:
  234. # Since this module gets imported in the application's root package,
  235. # it cannot import models from other applications at the module level.
  236. from django.contrib.contenttypes.models import ContentType
  237. self.original_content_type_id = ContentType.objects.get_for_model(original).pk
  238. self.show_url = original and view_on_site_url is not None
  239. self.absolute_url = view_on_site_url
  240. super(InlineAdminForm, self).__init__(form, fieldsets, prepopulated_fields,
  241. readonly_fields, model_admin)
  242. def __iter__(self):
  243. for name, options in self.fieldsets:
  244. yield InlineFieldset(self.formset, self.form, name,
  245. self.readonly_fields, model_admin=self.model_admin, **options)
  246. def needs_explicit_pk_field(self):
  247. # Auto fields are editable (oddly), so need to check for auto or non-editable pk
  248. if self.form._meta.model._meta.has_auto_field or not self.form._meta.model._meta.pk.editable:
  249. return True
  250. # Also search any parents for an auto field. (The pk info is propagated to child
  251. # models so that does not need to be checked in parents.)
  252. for parent in self.form._meta.model._meta.get_parent_list():
  253. if parent._meta.has_auto_field:
  254. return True
  255. return False
  256. def field_count(self):
  257. # tabular.html uses this function for colspan value.
  258. num_of_fields = 0
  259. if self.has_auto_field():
  260. num_of_fields += 1
  261. num_of_fields += len(self.fieldsets[0][1]["fields"])
  262. if self.formset.can_order:
  263. num_of_fields += 1
  264. if self.formset.can_delete:
  265. num_of_fields += 1
  266. return num_of_fields
  267. def pk_field(self):
  268. return AdminField(self.form, self.formset._pk_field.name, False)
  269. def fk_field(self):
  270. fk = getattr(self.formset, "fk", None)
  271. if fk:
  272. return AdminField(self.form, fk.name, False)
  273. else:
  274. return ""
  275. def deletion_field(self):
  276. from django.forms.formsets import DELETION_FIELD_NAME
  277. return AdminField(self.form, DELETION_FIELD_NAME, False)
  278. def ordering_field(self):
  279. from django.forms.formsets import ORDERING_FIELD_NAME
  280. return AdminField(self.form, ORDERING_FIELD_NAME, False)
  281. class InlineFieldset(Fieldset):
  282. def __init__(self, formset, *args, **kwargs):
  283. self.formset = formset
  284. super(InlineFieldset, self).__init__(*args, **kwargs)
  285. def __iter__(self):
  286. fk = getattr(self.formset, "fk", None)
  287. for field in self.fields:
  288. if fk and fk.name == field:
  289. continue
  290. yield Fieldline(self.form, field, self.readonly_fields,
  291. model_admin=self.model_admin)
  292. class AdminErrorList(forms.utils.ErrorList):
  293. """
  294. Stores all errors for the form/formsets in an add/change stage view.
  295. """
  296. def __init__(self, form, inline_formsets):
  297. super(AdminErrorList, self).__init__()
  298. if form.is_bound:
  299. self.extend(list(six.itervalues(form.errors)))
  300. for inline_formset in inline_formsets:
  301. self.extend(inline_formset.non_form_errors())
  302. for errors_in_inline_form in inline_formset.errors:
  303. self.extend(list(six.itervalues(errors_in_inline_form)))