def mass_update(modeladmin, request, queryset): # noqa """ mass update queryset """ def not_required(field, **kwargs): """ force all fields as not required""" kwargs['required'] = False return field.formfield(**kwargs) def _doit(): errors = {} updated = 0 for record in queryset: for field_name, value_or_func in list(form.cleaned_data.items()): if callable(value_or_func): old_value = getattr(record, field_name) setattr(record, field_name, value_or_func(old_value)) else: setattr(record, field_name, value_or_func) if clean: record.clean() record.save() updated += 1 if updated: messages.info(request, _("Updated %s records") % updated) if len(errors): messages.error(request, "%s records not updated due errors" % len(errors)) adminaction_end.send(sender=modeladmin.model, action='mass_update', request=request, queryset=queryset, modeladmin=modeladmin, form=form, errors=errors, updated=updated) opts = modeladmin.model._meta perm = "{0}.{1}".format( opts.app_label.lower(), get_permission_codename('adminactions_massupdate', opts)) if not request.user.has_perm(perm): messages.error( request, _('Sorry you do not have rights to execute this action')) return try: adminaction_requested.send(sender=modeladmin.model, action='mass_update', request=request, queryset=queryset, modeladmin=modeladmin) except ActionInterrupted as e: messages.error(request, str(e)) return # Allows to specified a custom mass update Form in the ModelAdmin mass_update_form = getattr(modeladmin, 'mass_update_form', MassUpdateForm) MForm = modelform_factory(modeladmin.model, form=mass_update_form, exclude=('pk', ), formfield_callback=not_required) grouped = defaultdict(lambda: []) selected_fields = [] initial = { '_selected_action': request.POST.getlist(helpers.ACTION_CHECKBOX_NAME), 'select_across': request.POST.get('select_across') == '1', 'action': 'mass_update' } if 'apply' in request.POST: form = MForm(request.POST) if form.is_valid(): try: adminaction_start.send(sender=modeladmin.model, action='mass_update', request=request, queryset=queryset, modeladmin=modeladmin, form=form) except ActionInterrupted as e: messages.error(request, str(e)) return HttpResponseRedirect(request.get_full_path()) # need_transaction = form.cleaned_data.get('_unique_transaction', False) validate = form.cleaned_data.get('_validate', False) clean = form.cleaned_data.get('_clean', False) if validate: with compat.atomic(): _doit() else: values = {} for field_name, value in list(form.cleaned_data.items()): if isinstance(form.fields[field_name], ModelMultipleChoiceField): messages.error( request, "Unable no mass update ManyToManyField without 'validate'" ) return HttpResponseRedirect(request.get_full_path()) elif callable(value): messages.error( request, "Unable no mass update using operators without 'validate'" ) return HttpResponseRedirect(request.get_full_path()) elif field_name not in [ '_selected_action', '_validate', 'select_across', 'action', '_unique_transaction', '_clean' ]: values[field_name] = value queryset.update(**values) return HttpResponseRedirect(request.get_full_path()) else: initial.update({'action': 'mass_update', '_validate': 1}) # form = MForm(initial=initial) prefill_with = request.POST.get('prefill-with', None) prefill_instance = None try: # Gets the instance directly from the queryset for data security prefill_instance = queryset.get(pk=prefill_with) except ObjectDoesNotExist: pass form = MForm(initial=initial, instance=prefill_instance) for el in queryset.all()[:10]: for f in modeladmin.model._meta.fields: if f.name not in form._no_sample_for: if hasattr(f, 'flatchoices') and f.flatchoices: grouped[f.name] = list( dict(getattr(f, 'flatchoices')).values()) elif hasattr(f, 'choices') and f.choices: grouped[f.name] = list( dict(getattr(f, 'choices')).values()) elif isinstance(f, df.BooleanField): grouped[f.name] = [True, False] else: value = getattr(el, f.name) if value is not None and value not in grouped[f.name]: grouped[f.name].append(value) initial[f.name] = initial.get(f.name, value) adminForm = helpers.AdminForm(form, modeladmin.get_fieldsets(request), {}, [], model_admin=modeladmin) media = modeladmin.media + adminForm.media dthandler = lambda obj: obj.isoformat() if isinstance(obj, datetime.date ) else str(obj) tpl = 'adminactions/mass_update.html' ctx = { 'adminform': adminForm, 'form': form, 'title': u"Mass update %s" % smart_text(modeladmin.opts.verbose_name_plural), 'grouped': grouped, 'fieldvalues': json.dumps(grouped, default=dthandler), 'change': True, 'selected_fields': selected_fields, 'is_popup': False, 'save_as': False, 'has_delete_permission': False, 'has_add_permission': False, 'has_change_permission': True, 'opts': modeladmin.model._meta, 'app_label': modeladmin.model._meta.app_label, # 'action': 'mass_update', # 'select_across': request.POST.get('select_across')=='1', 'media': mark_safe(media), 'selection': queryset } return render_to_response(tpl, RequestContext(request, ctx))
def merge(master, other, fields=None, commit=False, m2m=None, related=None): # noqa """ Merge 'other' into master. `fields` is a list of fieldnames that must be readed from ``other`` to put into master. If ``fields`` is None ``master`` will get all the ``other`` values except primary_key. Finally ``other`` will be deleted and master will be preserved @param master: Model instance @param other: Model instance @param fields: list of fieldnames to merge @param m2m: list of m2m fields to merge. If empty will be removed @param related: list of related fieldnames to merge. If empty will be removed @return: """ fields = fields or [f.name for f in master._meta.fields] all_m2m = {} all_related = {} if related == ALL_FIELDS: related = [rel.get_accessor_name() for rel in compat.get_all_related_objects(master)] # for rel in master._meta.get_all_related_objects(False, False, False)] if m2m == ALL_FIELDS: m2m = [field.name for field in master._meta.many_to_many] if m2m and not commit: raise ValueError('Cannot save related with `commit=False`') with compat.atomic(): result = clone_instance(master) for fieldname in fields: f = get_field_by_path(master, fieldname) if f and not f.primary_key: setattr(result, fieldname, getattr(other, fieldname)) if m2m: for fieldname in set(m2m): all_m2m[fieldname] = [] field_object = get_field_by_path(master, fieldname) if not isinstance(field_object, ManyToManyField): raise ValueError('{0} is not a ManyToManyField field'.format(fieldname)) source_m2m = getattr(other, field_object.name) for r in source_m2m.all(): all_m2m[fieldname].append(r) if related: for name in set(related): related_object = get_field_by_path(master, name) all_related[name] = [] if related_object and isinstance(related_object.field, OneToOneField): try: accessor = getattr(other, name) all_related[name] = [(related_object.field.name, accessor)] except ObjectDoesNotExist: pass else: accessor = getattr(other, name, None) if accessor: rel_fieldname = list(accessor.core_filters.keys())[0].split('__')[0] for r in accessor.all(): all_related[name].append((rel_fieldname, r)) if commit: for name, elements in list(all_related.items()): for rel_fieldname, element in elements: setattr(element, rel_fieldname, master) element.save() other.delete() result.save() for fieldname, elements in list(all_m2m.items()): dest_m2m = getattr(result, fieldname) for element in elements: dest_m2m.add(element) return result
def mass_update(modeladmin, request, queryset): """ mass update queryset """ def not_required(field, **kwargs): """ force all fields as not required""" kwargs['required'] = False return field.formfield(**kwargs) opts = modeladmin.model._meta perm = "{0}.{1}".format(opts.app_label.lower(), get_permission_codename('adminactions_massupdate', opts)) if not request.user.has_perm(perm): messages.error(request, _('Sorry you do not have rights to execute this action')) return try: adminaction_requested.send(sender=modeladmin.model, action='mass_update', request=request, queryset=queryset, modeladmin=modeladmin) except ActionInterrupted as e: messages.error(request, str(e)) return # Allows to specified a custom mass update Form in the ModelAdmin mass_update_form = getattr(modeladmin, 'mass_update_form', MassUpdateForm) MForm = modelform_factory(modeladmin.model, form=mass_update_form, exclude=('pk',), formfield_callback=not_required) grouped = defaultdict(lambda: []) selected_fields = [] initial = {'_selected_action': request.POST.getlist(helpers.ACTION_CHECKBOX_NAME), 'select_across': request.POST.get('select_across') == '1', 'action': 'mass_update'} if 'apply' in request.POST: form = MForm(request.POST) if form.is_valid(): try: adminaction_start.send(sender=modeladmin.model, action='mass_update', request=request, queryset=queryset, modeladmin=modeladmin, form=form) except ActionInterrupted as e: messages.error(request, str(e)) return HttpResponseRedirect(request.get_full_path()) need_transaction = form.cleaned_data.get('_unique_transaction', False) validate = form.cleaned_data.get('_validate', False) clean = form.cleaned_data.get('_clean', False) if validate: def _doit(): errors = {} updated = 0 for record in queryset: for field_name, value_or_func in form.cleaned_data.items(): if callable(value_or_func): old_value = getattr(record, field_name) setattr(record, field_name, value_or_func(old_value)) else: setattr(record, field_name, value_or_func) if clean: record.clean() record.save() updated += 1 if updated: messages.info(request, _("Updated %s records") % updated) if len(errors): messages.error(request, "%s records not updated due errors" % len(errors)) adminaction_end.send(sender=modeladmin.model, action='mass_update', request=request, queryset=queryset, modeladmin=modeladmin, form=form, errors=errors, updated=updated) with compat.atomic(): _doit() else: values = {} for field_name, value in form.cleaned_data.items(): if isinstance(form.fields[field_name], ModelMultipleChoiceField): messages.error(request, "Unable no mass update ManyToManyField without 'validate'") return HttpResponseRedirect(request.get_full_path()) elif callable(value): messages.error(request, "Unable no mass update using operators without 'validate'") return HttpResponseRedirect(request.get_full_path()) elif field_name not in ['_selected_action', '_validate', 'select_across', 'action']: values[field_name] = value queryset.update(**values) return HttpResponseRedirect(request.get_full_path()) else: initial.update({'action': 'mass_update', '_validate': 1}) # form = MForm(initial=initial) prefill_with = request.POST.get('prefill-with', None) prefill_instance = None try: # Gets the instance directly from the queryset for data security prefill_instance = queryset.get(pk=prefill_with) except ObjectDoesNotExist: pass form = MForm(initial=initial, instance=prefill_instance) for el in queryset.all()[:10]: for f in modeladmin.model._meta.fields: if f.name not in form._no_sample_for: if hasattr(f, 'flatchoices') and f.flatchoices: grouped[f.name] = dict(getattr(f, 'flatchoices')).values() elif hasattr(f, 'choices') and f.choices: grouped[f.name] = dict(getattr(f, 'choices')).values() elif isinstance(f, df.BooleanField): grouped[f.name] = [True, False] else: value = getattr(el, f.name) if value is not None and value not in grouped[f.name]: grouped[f.name].append(value) initial[f.name] = initial.get(f.name, value) adminForm = helpers.AdminForm(form, modeladmin.get_fieldsets(request), {}, [], model_admin=modeladmin) media = modeladmin.media + adminForm.media dthandler = lambda obj: obj.isoformat() if isinstance(obj, datetime.date) else str(obj) tpl = 'adminactions/mass_update.html' ctx = {'adminform': adminForm, 'form': form, 'title': u"Mass update %s" % force_unicode(modeladmin.opts.verbose_name_plural), 'grouped': grouped, 'fieldvalues': json.dumps(grouped, default=dthandler), 'change': True, 'selected_fields': selected_fields, 'is_popup': False, 'save_as': False, 'has_delete_permission': False, 'has_add_permission': False, 'has_change_permission': True, 'opts': modeladmin.model._meta, 'app_label': modeladmin.model._meta.app_label, # 'action': 'mass_update', # 'select_across': request.POST.get('select_across')=='1', 'media': mark_safe(media), 'selection': queryset} return render_to_response(tpl, RequestContext(request, ctx))