text: """
  数据即时编辑
  ============
  
  功能
  ----
  
  该插件可以在列表页中即时编辑某字段的值, 使用 Ajax 技术, 无需提交或刷新页面即可完成数据的修改, 对于需要频繁修改的字段(如: 状态)相当有用.
  
  截图
  ----
  
  .. image:: /images/plugins/editable.png
  
  使用
  ----
  
  使用该插件主要设置 OptionClass 的 ``list_editable`` 属性. ``list_editable`` 属性设置哪些字段需要即时修改功能. 示例如下::
  
      class MyModelAdmin(object):
  
          list_editable = ['price', 'status', ...]
  
  """
  
  from django import template
  from django.core.exceptions import PermissionDenied, ObjectDoesNotExist
  from django.db import models, transaction
  from django import forms
  from django.forms.models import modelform_factory
  from django.forms import Media
  from django.http import Http404, HttpResponse
  from django.utils.encoding import force_text, smart_text
  from django.utils.html import escape, conditional_escape
  from django.utils.safestring import mark_safe
  from django.utils.translation import ugettext as _
  from xadmin.plugins.ajax import JsonErrorDict
  from xadmin.sites import site
  from xadmin.util import lookup_field, display_for_field, label_for_field, unquote, boolean_icon
  from xadmin.views import BaseAdminPlugin, ModelFormAdminView, ListAdminView
  from xadmin.views.base import csrf_protect_m, filter_hook
  from xadmin.views.edit import ModelFormAdminUtil
  from xadmin.views.list import EMPTY_CHANGELIST_VALUE
  from xadmin.layout import FormHelper
  
  
  class EditablePlugin(BaseAdminPlugin):
      list_editable = []
  
      def __init__(self, admin_view):
          super(EditablePlugin, self).__init__(admin_view)
          self.editable_need_fields = {}
  
      def init_request(self, *args, **kwargs):
          active = bool(self.request.method == 'GET' and self.admin_view.has_change_permission() and self.list_editable)
          if active:
              self.model_form = self.get_model_view(ModelFormAdminUtil, self.model).form_obj
          return active
  
      def result_item(self, item, obj, field_name, row):
          if self.list_editable and item.field and item.field.editable and (field_name in self.list_editable):
              pk = getattr(obj, obj._meta.pk.attname)
              field_label = label_for_field(field_name, obj,
                                            model_admin=self.admin_view,
                                            return_attr=False
                                            )
  
              item.wraps.insert(0, '<span class="editable-field">%s</span>')
              item.btns.append((
                                       '<a class="editable-handler" title="%s" data-editable-field="%s" data-editable-loadurl="%s">' +
                                       '<i class="fa fa-edit"></i></a>') %
                               (_(u"Enter %s") % field_label, field_name,
                                self.admin_view.model_admin_url('patch', pk) + '?fields=' + field_name))
  
              if field_name not in self.editable_need_fields:
                  self.editable_need_fields[field_name] = item.field
          return item
  
      # Media
      def get_media(self, media):
          if self.editable_need_fields:
  
              try:
                  m = self.model_form.media
              except:
                  m = Media()
              media = media + m + \
                      self.vendor(
                          'xadmin.plugin.editable.js', 'xadmin.widget.editable.css')
          return media
  
  
  class EditPatchView(ModelFormAdminView, ListAdminView):
  
      def init_request(self, object_id, *args, **kwargs):
          self.org_obj = self.get_object(unquote(object_id))
  
          # 因为自定义的表单在这里也无效，但会影响编辑字段，我们用默认代替自定义
          self.form = forms.ModelForm
  
          # For list view get new field display html
          self.pk_attname = self.opts.pk.attname
  
          if not self.has_change_permission(self.org_obj):
              raise PermissionDenied
  
          if self.org_obj is None:
              raise Http404(_('%(name)s object with primary key %(key)r does not exist.') %
                            {'name': force_text(self.opts.verbose_name), 'key': escape(object_id)})
  
      def get_new_field_html(self, f):
          result = self.result_item(self.org_obj, f, {'is_display_first':
                                                          False, 'object': self.org_obj})
          return mark_safe(result.text) if result.allow_tags else conditional_escape(result.text)
  
      def _get_new_field_html(self, field_name):
          try:
              f, attr, value = lookup_field(field_name, self.org_obj, self)
          except (AttributeError, ObjectDoesNotExist):
              return EMPTY_CHANGELIST_VALUE
          else:
              allow_tags = False
              if f is None:
                  allow_tags = getattr(attr, 'allow_tags', False)
                  boolean = getattr(attr, 'boolean', False)
                  if boolean:
                      allow_tags = True
                      text = boolean_icon(value)
                  else:
                      text = smart_text(value)
              else:
                  if isinstance(f.rel, models.ManyToOneRel):
                      field_val = getattr(self.org_obj, f.name)
                      if field_val is None:
                          text = EMPTY_CHANGELIST_VALUE
                      else:
                          text = field_val
                  else:
                      text = display_for_field(value, f)
              return mark_safe(text) if allow_tags else conditional_escape(text)
  
      @filter_hook
      def get(self, request, object_id):
          model_fields = [f.name for f in self.opts.fields]
          fields = [f for f in request.GET['fields'].split(',') if f in model_fields]
          defaults = {
              "form": self.form,
              "fields": fields,
              "formfield_callback": self.formfield_for_dbfield,
          }
          form_class = modelform_factory(self.model, **defaults)
          form = form_class(instance=self.org_obj)
  
          helper = FormHelper()
          helper.form_tag = False
          helper.include_media = False
          form.helper = helper
  
          s = '{% load i18n crispy_forms_tags %}<form method="post" action="{{action_url}}">{% crispy form %}' + \
              '<button type="submit" class="btn btn-success btn-block btn-sm">{% trans "Apply" %}</button></form>'
          t = template.Template(s)
          c = template.Context({'form': form, 'action_url': self.model_admin_url('patch', self.org_obj.pk)})
  
          return HttpResponse(t.render(c))
  
      @filter_hook
      @csrf_protect_m
      @transaction.atomic
      def post(self, request, object_id):
          model_fields = [f.name for f in self.opts.fields]
          fields = [f for f in request.POST.keys() if f in model_fields]
          defaults = {
              "form": self.form,
              "fields": fields,
              "formfield_callback": self.formfield_for_dbfield,
          }
          form_class = modelform_factory(self.model, **defaults)
          form = form_class(
              instance=self.org_obj, data=request.POST, files=request.FILES)
  
          result = {}
          if form.is_valid():
              form.save(commit=True)
              result['result'] = 'success'
              result['new_data'] = form.cleaned_data
              result['new_html'] = dict(
                  [(f, self.get_new_field_html(f)) for f in fields])
          else:
              result['result'] = 'error'
              result['errors'] = JsonErrorDict(form.errors, form).as_json()
  
          return self.render_response(result)
  
  
  site.register_plugin(EditablePlugin, ListAdminView)
  site.register_modelview(r'^(.+)/patch/$', EditPatchView, name='%s_%s_patch')
  