editable.py 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167
  1. from django import template
  2. from django.core.exceptions import PermissionDenied, ObjectDoesNotExist
  3. from django.db import models, transaction
  4. from django.forms.models import modelform_factory
  5. from django.forms import Media
  6. from django.http import Http404, HttpResponse
  7. from django.utils.encoding import force_text, smart_text
  8. from django.utils.html import escape, conditional_escape
  9. from django.utils.safestring import mark_safe
  10. from django.utils.translation import ugettext as _
  11. from xadmin.plugins.ajax import JsonErrorDict
  12. from xadmin.sites import site
  13. from xadmin.util import lookup_field, display_for_field, label_for_field, unquote, boolean_icon
  14. from xadmin.views import BaseAdminPlugin, ModelFormAdminView, ListAdminView
  15. from xadmin.views.base import csrf_protect_m, filter_hook
  16. from xadmin.views.edit import ModelFormAdminUtil
  17. from xadmin.views.list import EMPTY_CHANGELIST_VALUE
  18. from xadmin.layout import FormHelper
  19. class EditablePlugin(BaseAdminPlugin):
  20. list_editable = []
  21. def __init__(self, admin_view):
  22. super(EditablePlugin, self).__init__(admin_view)
  23. self.editable_need_fields = {}
  24. def init_request(self, *args, **kwargs):
  25. active = bool(self.request.method == 'GET' and self.admin_view.has_change_permission() and self.list_editable)
  26. if active:
  27. self.model_form = self.get_model_view(ModelFormAdminUtil, self.model).form_obj
  28. return active
  29. def result_item(self, item, obj, field_name, row):
  30. if self.list_editable and item.field and item.field.editable and (field_name in self.list_editable):
  31. pk = getattr(obj, obj._meta.pk.attname)
  32. field_label = label_for_field(field_name, obj,
  33. model_admin=self.admin_view,
  34. return_attr=False
  35. )
  36. item.wraps.insert(0, '<span class="editable-field">%s</span>')
  37. item.btns.append((
  38. '<a class="editable-handler" title="%s" data-editable-field="%s" data-editable-loadurl="%s">' +
  39. '<i class="fa fa-edit"></i></a>') %
  40. (_(u"Enter %s") % field_label, field_name, self.admin_view.model_admin_url('patch', pk) + '?fields=' + field_name))
  41. if field_name not in self.editable_need_fields:
  42. self.editable_need_fields[field_name] = item.field
  43. return item
  44. # Media
  45. def get_media(self, media):
  46. if self.editable_need_fields:
  47. try:
  48. m = self.model_form.media
  49. except:
  50. m = Media()
  51. media = media + m +\
  52. self.vendor(
  53. 'xadmin.plugin.editable.js', 'xadmin.widget.editable.css')
  54. return media
  55. class EditPatchView(ModelFormAdminView, ListAdminView):
  56. def init_request(self, object_id, *args, **kwargs):
  57. self.org_obj = self.get_object(unquote(object_id))
  58. # For list view get new field display html
  59. self.pk_attname = self.opts.pk.attname
  60. if not self.has_change_permission(self.org_obj):
  61. raise PermissionDenied
  62. if self.org_obj is None:
  63. raise Http404(_('%(name)s object with primary key %(key)r does not exist.') %
  64. {'name': force_text(self.opts.verbose_name), 'key': escape(object_id)})
  65. def get_new_field_html(self, f):
  66. result = self.result_item(self.org_obj, f, {'is_display_first':
  67. False, 'object': self.org_obj})
  68. return mark_safe(result.text) if result.allow_tags else conditional_escape(result.text)
  69. def _get_new_field_html(self, field_name):
  70. try:
  71. f, attr, value = lookup_field(field_name, self.org_obj, self)
  72. except (AttributeError, ObjectDoesNotExist):
  73. return EMPTY_CHANGELIST_VALUE
  74. else:
  75. allow_tags = False
  76. if f is None:
  77. allow_tags = getattr(attr, 'allow_tags', False)
  78. boolean = getattr(attr, 'boolean', False)
  79. if boolean:
  80. allow_tags = True
  81. text = boolean_icon(value)
  82. else:
  83. text = smart_text(value)
  84. else:
  85. if isinstance(f.rel, models.ManyToOneRel):
  86. field_val = getattr(self.org_obj, f.name)
  87. if field_val is None:
  88. text = EMPTY_CHANGELIST_VALUE
  89. else:
  90. text = field_val
  91. else:
  92. text = display_for_field(value, f)
  93. return mark_safe(text) if allow_tags else conditional_escape(text)
  94. @filter_hook
  95. def get(self, request, object_id):
  96. model_fields = [f.name for f in self.opts.fields]
  97. fields = [f for f in request.GET['fields'].split(',') if f in model_fields]
  98. defaults = {
  99. "form": self.form,
  100. "fields": fields,
  101. "formfield_callback": self.formfield_for_dbfield,
  102. }
  103. form_class = modelform_factory(self.model, **defaults)
  104. form = form_class(instance=self.org_obj)
  105. helper = FormHelper()
  106. helper.form_tag = False
  107. helper.include_media = False
  108. form.helper = helper
  109. s = '{% load i18n crispy_forms_tags %}<form method="post" action="{{action_url}}">{% crispy form %}' + \
  110. '<button type="submit" class="btn btn-success btn-block btn-sm">{% trans "Apply" %}</button></form>'
  111. t = template.Template(s)
  112. c = template.Context({'form': form, 'action_url': self.model_admin_url('patch', self.org_obj.pk)})
  113. return HttpResponse(t.render(c))
  114. @filter_hook
  115. @csrf_protect_m
  116. @transaction.atomic
  117. def post(self, request, object_id):
  118. model_fields = [f.name for f in self.opts.fields]
  119. fields = [f for f in request.POST.keys() if f in model_fields]
  120. defaults = {
  121. "form": self.form,
  122. "fields": fields,
  123. "formfield_callback": self.formfield_for_dbfield,
  124. }
  125. form_class = modelform_factory(self.model, **defaults)
  126. form = form_class(
  127. instance=self.org_obj, data=request.POST, files=request.FILES)
  128. result = {}
  129. if form.is_valid():
  130. form.save(commit=True)
  131. result['result'] = 'success'
  132. result['new_data'] = form.cleaned_data
  133. result['new_html'] = dict(
  134. [(f, self.get_new_field_html(f)) for f in fields])
  135. else:
  136. result['result'] = 'error'
  137. result['errors'] = JsonErrorDict(form.errors, form).as_json()
  138. return self.render_response(result)
  139. site.register_plugin(EditablePlugin, ListAdminView)
  140. site.register_modelview(r'^(.+)/patch/$', EditPatchView, name='%s_%s_patch')