detail.py 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283
  1. from __future__ import absolute_import
  2. import copy
  3. from crispy_forms.utils import TEMPLATE_PACK
  4. from django import forms
  5. from django.contrib.contenttypes.models import ContentType
  6. from django.core.exceptions import PermissionDenied, ObjectDoesNotExist
  7. from django.db import models
  8. from django.forms.models import modelform_factory
  9. from django.http import Http404
  10. from django.template import loader
  11. from django.template.response import TemplateResponse
  12. from django.utils import six
  13. from django.utils.encoding import force_text, smart_text
  14. from django.utils.html import escape
  15. from django.utils.safestring import mark_safe
  16. from django.utils.translation import ugettext as _
  17. from django.utils.html import conditional_escape
  18. from xadmin.layout import FormHelper, Layout, Fieldset, Container, Column, Field, Col, TabHolder
  19. from xadmin.util import unquote, lookup_field, display_for_field, boolean_icon, label_for_field
  20. from .base import ModelAdminView, filter_hook, csrf_protect_m
  21. # Text to display within change-list table cells if the value is blank.
  22. EMPTY_CHANGELIST_VALUE = _('Null')
  23. class ShowField(Field):
  24. template = "xadmin/layout/field_value.html"
  25. def __init__(self, callback, *args, **kwargs):
  26. super(ShowField, self).__init__(*args, **kwargs)
  27. self.results = [(field, callback(field)) for field in self.fields]
  28. def render(self, form, form_style, context, template_pack=TEMPLATE_PACK, extra_context=None, **kwargs):
  29. super(ShowField, self).render(form, form_style, context, template_pack, extra_context, **kwargs)
  30. if extra_context is None:
  31. extra_context = {}
  32. if hasattr(self, 'wrapper_class'):
  33. extra_context['wrapper_class'] = self.wrapper_class
  34. if self.attrs:
  35. if 'detail-class' in self.attrs:
  36. extra_context['input_class'] = self.attrs['detail-class']
  37. elif 'class' in self.attrs:
  38. extra_context['input_class'] = self.attrs['class']
  39. html = ''
  40. for field, result in self.results:
  41. extra_context['result'] = result
  42. if field in form.fields:
  43. if form.fields[field].widget != forms.HiddenInput:
  44. extra_context['field'] = form[field]
  45. html += loader.render_to_string(self.template, extra_context)
  46. else:
  47. extra_context['field'] = field
  48. html += loader.render_to_string(self.template, extra_context)
  49. return html
  50. class ResultField(object):
  51. def __init__(self, obj, field_name, admin_view=None):
  52. self.text = ' '
  53. self.wraps = []
  54. self.allow_tags = False
  55. self.obj = obj
  56. self.admin_view = admin_view
  57. self.field_name = field_name
  58. self.field = None
  59. self.attr = None
  60. self.label = None
  61. self.value = None
  62. self.init()
  63. def init(self):
  64. self.label = label_for_field(self.field_name, self.obj.__class__,
  65. model_admin=self.admin_view,
  66. return_attr=False
  67. )
  68. try:
  69. f, attr, value = lookup_field(
  70. self.field_name, self.obj, self.admin_view)
  71. except (AttributeError, ObjectDoesNotExist):
  72. self.text
  73. else:
  74. if f is None:
  75. self.allow_tags = getattr(attr, 'allow_tags', False)
  76. boolean = getattr(attr, 'boolean', False)
  77. if boolean:
  78. self.allow_tags = True
  79. self.text = boolean_icon(value)
  80. else:
  81. self.text = smart_text(value)
  82. else:
  83. if isinstance(f.remote_field, models.ManyToOneRel):
  84. self.text = getattr(self.obj, f.name)
  85. else:
  86. self.text = display_for_field(value, f)
  87. self.field = f
  88. self.attr = attr
  89. self.value = value
  90. @property
  91. def val(self):
  92. text = mark_safe(
  93. self.text) if self.allow_tags else conditional_escape(self.text)
  94. if force_text(text) == '' or text == 'None' or text == EMPTY_CHANGELIST_VALUE:
  95. text = mark_safe(
  96. '<span class="text-muted">%s</span>' % EMPTY_CHANGELIST_VALUE)
  97. for wrap in self.wraps:
  98. text = mark_safe(wrap % text)
  99. return text
  100. def replace_field_to_value(layout, cb):
  101. cls_str = str if six.PY3 else basestring
  102. for i, lo in enumerate(layout.fields):
  103. if isinstance(lo, Field) or issubclass(lo.__class__, Field):
  104. layout.fields[i] = ShowField(
  105. cb, *lo.fields, attrs=lo.attrs, wrapper_class=lo.wrapper_class)
  106. elif isinstance(lo, cls_str):
  107. layout.fields[i] = ShowField(cb, lo)
  108. elif hasattr(lo, 'get_field_names'):
  109. replace_field_to_value(lo, cb)
  110. class DetailAdminView(ModelAdminView):
  111. form = forms.ModelForm
  112. detail_layout = None
  113. detail_show_all = True
  114. detail_template = None
  115. form_layout = None
  116. def init_request(self, object_id, *args, **kwargs):
  117. self.obj = self.get_object(unquote(object_id))
  118. if not self.has_view_permission(self.obj):
  119. raise PermissionDenied
  120. if self.obj is None:
  121. raise Http404(
  122. _('%(name)s object with primary key %(key)r does not exist.') %
  123. {'name': force_text(self.opts.verbose_name), 'key': escape(object_id)})
  124. self.org_obj = self.obj
  125. @filter_hook
  126. def get_form_layout(self):
  127. layout = copy.deepcopy(self.detail_layout or self.form_layout)
  128. if layout is None:
  129. layout = Layout(Container(Col('full',
  130. Fieldset(
  131. "", *self.form_obj.fields.keys(),
  132. css_class="unsort no_title"), horizontal=True, span=12)
  133. ))
  134. elif type(layout) in (list, tuple) and len(layout) > 0:
  135. if isinstance(layout[0], Column):
  136. fs = layout
  137. elif isinstance(layout[0], (Fieldset, TabHolder)):
  138. fs = (Col('full', *layout, horizontal=True, span=12),)
  139. else:
  140. fs = (
  141. Col('full', Fieldset("", *layout, css_class="unsort no_title"), horizontal=True, span=12),)
  142. layout = Layout(Container(*fs))
  143. if self.detail_show_all:
  144. rendered_fields = [i[1] for i in layout.get_field_names()]
  145. container = layout[0].fields
  146. other_fieldset = Fieldset(_(u'Other Fields'), *[
  147. f for f in self.form_obj.fields.keys() if f not in rendered_fields])
  148. if len(other_fieldset.fields):
  149. if len(container) and isinstance(container[0], Column):
  150. container[0].fields.append(other_fieldset)
  151. else:
  152. container.append(other_fieldset)
  153. return layout
  154. @filter_hook
  155. def get_model_form(self, **kwargs):
  156. """
  157. Returns a Form class for use in the admin add view. This is used by
  158. add_view and change_view.
  159. """
  160. if self.exclude is None:
  161. exclude = []
  162. else:
  163. exclude = list(self.exclude)
  164. if self.exclude is None and hasattr(self.form, '_meta') and self.form._meta.exclude:
  165. # Take the custom ModelForm's Meta.exclude into account only if the
  166. # ModelAdmin doesn't define its own.
  167. exclude.extend(self.form._meta.exclude)
  168. # if exclude is an empty list we pass None to be consistant with the
  169. # default on modelform_factory
  170. exclude = exclude or None
  171. defaults = {
  172. "form": self.form,
  173. "fields": self.fields and list(self.fields) or '__all__',
  174. "exclude": exclude,
  175. }
  176. defaults.update(kwargs)
  177. return modelform_factory(self.model, **defaults)
  178. @filter_hook
  179. def get_form_helper(self):
  180. helper = FormHelper()
  181. helper.form_tag = False
  182. helper.include_media = False
  183. layout = self.get_form_layout()
  184. replace_field_to_value(layout, self.get_field_result)
  185. helper.add_layout(layout)
  186. cls_str = str if six.PY3 else basestring
  187. helper.filter(cls_str, max_level=20).wrap(ShowField, admin_view=self)
  188. return helper
  189. @csrf_protect_m
  190. @filter_hook
  191. def get(self, request, *args, **kwargs):
  192. form = self.get_model_form()
  193. self.form_obj = form(instance=self.obj)
  194. helper = self.get_form_helper()
  195. if helper:
  196. self.form_obj.helper = helper
  197. return self.get_response()
  198. @filter_hook
  199. def get_context(self):
  200. new_context = {
  201. 'title': _('%s Detail') % force_text(self.opts.verbose_name),
  202. 'form': self.form_obj,
  203. 'object': self.obj,
  204. 'has_change_permission': self.has_change_permission(self.obj),
  205. 'has_delete_permission': self.has_delete_permission(self.obj),
  206. 'content_type_id': ContentType.objects.get_for_model(self.model).id,
  207. }
  208. context = super(DetailAdminView, self).get_context()
  209. context.update(new_context)
  210. return context
  211. @filter_hook
  212. def get_breadcrumb(self):
  213. bcs = super(DetailAdminView, self).get_breadcrumb()
  214. item = {'title': force_text(self.obj)}
  215. if self.has_view_permission():
  216. item['url'] = self.model_admin_url('detail', self.obj.pk)
  217. bcs.append(item)
  218. return bcs
  219. @filter_hook
  220. def get_media(self):
  221. return super(DetailAdminView, self).get_media() + self.form_obj.media + \
  222. self.vendor('xadmin.page.form.js', 'xadmin.form.css')
  223. @filter_hook
  224. def get_field_result(self, field_name):
  225. return ResultField(self.obj, field_name, self)
  226. @filter_hook
  227. def get_response(self, *args, **kwargs):
  228. context = self.get_context()
  229. context.update(kwargs or {})
  230. self.request.current_app = self.admin_site.name
  231. response = TemplateResponse(self.request, self.detail_template or
  232. self.get_template_list('views/model_detail.html'),
  233. context)
  234. return response
  235. class DetailAdminUtil(DetailAdminView):
  236. def init_request(self, obj):
  237. self.obj = obj
  238. self.org_obj = obj