batch.py 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156
  1. import copy
  2. from django import forms
  3. from django.db import models
  4. from django.core.exceptions import PermissionDenied
  5. from django.forms.models import modelform_factory
  6. from django.template.response import TemplateResponse
  7. from django.utils.encoding import force_text
  8. from django.utils.safestring import mark_safe
  9. from django.utils.translation import ugettext as _, ugettext_lazy
  10. from xadmin.layout import FormHelper, Layout, Fieldset, Container, Col
  11. from xadmin.plugins.actions import BaseActionView, ACTION_CHECKBOX_NAME
  12. from xadmin.util import model_ngettext, vendor
  13. from xadmin.views.base import filter_hook
  14. from xadmin.views.edit import ModelFormAdminView
  15. BATCH_CHECKBOX_NAME = '_batch_change_fields'
  16. class ChangeFieldWidgetWrapper(forms.Widget):
  17. def __init__(self, widget):
  18. self.needs_multipart_form = widget.needs_multipart_form
  19. self.attrs = widget.attrs
  20. self.widget = widget
  21. def __deepcopy__(self, memo):
  22. obj = copy.copy(self)
  23. obj.widget = copy.deepcopy(self.widget, memo)
  24. obj.attrs = self.widget.attrs
  25. memo[id(self)] = obj
  26. return obj
  27. @property
  28. def media(self):
  29. media = self.widget.media + vendor('xadmin.plugin.batch.js')
  30. return media
  31. def render(self, name, value, attrs=None):
  32. output = []
  33. is_required = self.widget.is_required
  34. output.append(u'<label class="btn btn-info btn-xs">'
  35. '<input type="checkbox" class="batch-field-checkbox" name="%s" value="%s"%s/> %s</label>' %
  36. (BATCH_CHECKBOX_NAME, name, (is_required and ' checked="checked"' or ''), _('Change this field')))
  37. output.extend([('<div class="control-wrap" style="margin-top: 10px;%s" id="id_%s_wrap_container">' %
  38. ((not is_required and 'display: none;' or ''), name)),
  39. self.widget.render(name, value, attrs), '</div>'])
  40. return mark_safe(u''.join(output))
  41. def build_attrs(self, extra_attrs=None, **kwargs):
  42. "Helper function for building an attribute dictionary."
  43. self.attrs = self.widget.build_attrs(extra_attrs=None, **kwargs)
  44. return self.attrs
  45. def value_from_datadict(self, data, files, name):
  46. return self.widget.value_from_datadict(data, files, name)
  47. def id_for_label(self, id_):
  48. return self.widget.id_for_label(id_)
  49. class BatchChangeAction(BaseActionView):
  50. action_name = "change_selected"
  51. description = ugettext_lazy(
  52. u'Batch Change selected %(verbose_name_plural)s')
  53. batch_change_form_template = None
  54. model_perm = 'change'
  55. batch_fields = []
  56. def change_models(self, queryset, cleaned_data):
  57. n = queryset.count()
  58. data = {}
  59. fields = self.opts.fields + self.opts.many_to_many
  60. for f in fields:
  61. if not f.editable or isinstance(f, models.AutoField) \
  62. or not f.name in cleaned_data:
  63. continue
  64. data[f] = cleaned_data[f.name]
  65. if n:
  66. for obj in queryset:
  67. for f, v in data.items():
  68. f.save_form_data(obj, v)
  69. obj.save()
  70. self.message_user(_("Successfully change %(count)d %(items)s.") % {
  71. "count": n, "items": model_ngettext(self.opts, n)
  72. }, 'success')
  73. def get_change_form(self, is_post, fields):
  74. edit_view = self.get_model_view(ModelFormAdminView, self.model)
  75. def formfield_for_dbfield(db_field, **kwargs):
  76. formfield = edit_view.formfield_for_dbfield(db_field, required=is_post, **kwargs)
  77. formfield.widget = ChangeFieldWidgetWrapper(formfield.widget)
  78. return formfield
  79. defaults = {
  80. "form": edit_view.form,
  81. "fields": fields,
  82. "formfield_callback": formfield_for_dbfield,
  83. }
  84. return modelform_factory(self.model, **defaults)
  85. def do_action(self, queryset):
  86. if not self.has_change_permission():
  87. raise PermissionDenied
  88. change_fields = [f for f in self.request.POST.getlist(BATCH_CHECKBOX_NAME) if f in self.batch_fields]
  89. if change_fields and self.request.POST.get('post'):
  90. self.form_obj = self.get_change_form(True, change_fields)(
  91. data=self.request.POST, files=self.request.FILES)
  92. if self.form_obj.is_valid():
  93. self.change_models(queryset, self.form_obj.cleaned_data)
  94. return None
  95. else:
  96. self.form_obj = self.get_change_form(False, self.batch_fields)()
  97. helper = FormHelper()
  98. helper.form_tag = False
  99. helper.include_media = False
  100. helper.add_layout(Layout(Container(Col('full',
  101. Fieldset("", *self.form_obj.fields.keys(), css_class="unsort no_title"), horizontal=True, span=12)
  102. )))
  103. self.form_obj.helper = helper
  104. count = len(queryset)
  105. if count == 1:
  106. objects_name = force_text(self.opts.verbose_name)
  107. else:
  108. objects_name = force_text(self.opts.verbose_name_plural)
  109. context = self.get_context()
  110. context.update({
  111. "title": _("Batch change %s") % objects_name,
  112. 'objects_name': objects_name,
  113. 'form': self.form_obj,
  114. 'queryset': queryset,
  115. 'count': count,
  116. "opts": self.opts,
  117. "app_label": self.app_label,
  118. 'action_checkbox_name': ACTION_CHECKBOX_NAME,
  119. })
  120. return TemplateResponse(self.request, self.batch_change_form_template or
  121. self.get_template_list('views/batch_change_form.html'), context)
  122. @filter_hook
  123. def get_media(self):
  124. media = super(BatchChangeAction, self).get_media()
  125. media = media + self.form_obj.media + self.vendor(
  126. 'xadmin.page.form.js', 'xadmin.form.css')
  127. return media