from django import forms from django.contrib import admin from django.contrib.admin.widgets import ForeignKeyRawIdWidget from django.http import HttpResponseRedirect from django.shortcuts import render from django.utils.translation import ngettext, gettext_lazy as _ class AdminActionBase: action_name = None options_template_name = 'admin/action_forms/admin_action_base.html' title = None queryset_action_label = None action_button_label = None def __init__(self, action_name=None): if action_name: self.action_name = action_name def apply(self, queryset, form): raise NotImplementedError def get_message(self, count): raise NotImplementedError def get_failure_message(self, count, failure_count): raise NotImplementedError class BaseForm(forms.Form): _selected_action = forms.CharField(widget=forms.MultipleHiddenInput) def get_form_class(self, modeladmin, request, queryset): """ Example: class CustomForm(BaseForm) chosen_target = forms.ModelChoiceField( label=_("Choose target itembundle"), queryset=ItemBundle.objects.exclude(pk__in=queryset), widget=ForeignKeyRawIdWidget(modeladmin.model._meta.get_field('parent').rel, modeladmin.admin_site), empty_label=_("Root level"), required=False) return CustomForm """ raise NotImplementedError def __call__(self, modeladmin, request, queryset): form_class = self.get_form_class(modeladmin, request, queryset) form = None if 'apply' in request.POST: form = form_class(request.POST) if form.is_valid(): queryset_count = queryset.count() count = self.apply(queryset, form) failure_count = queryset_count - count if failure_count > 0: message = self.get_failure_message(form, count, failure_count) else: message = self.get_message(form, count) modeladmin.message_user(request, message) return HttpResponseRedirect(request.get_full_path()) if 'cancel' in request.POST: return HttpResponseRedirect(request.get_full_path()) if not form: form = form_class(initial={ '_selected_action': request.POST.getlist( admin.ACTION_CHECKBOX_NAME), }) return render(request, self.options_template_name, context={ 'action_name': self.action_name, 'title': self.title, 'queryset_action_label': self.queryset_action_label, 'action_button_label': self.action_button_label, 'queryset': queryset, 'action_form': form, 'opts': modeladmin.model._meta, }) class TargetActionBase(AdminActionBase): target_model = None related_field_name = None def get_form_class(self, modeladmin, request, queryset): class ChooseTargetForm(AdminActionBase.BaseForm): chosen_target = forms.ModelChoiceField( label=_("Choose {}".format(self.target_model._meta.verbose_name)), queryset=self.target_model.objects.exclude(pk__in=queryset), widget=ForeignKeyRawIdWidget( modeladmin.model._meta.get_field(self.related_field_name).rel, modeladmin.admin_site ), ) return ChooseTargetForm def get_target(self, form): return form.cleaned_data['chosen_target'] def get_message(self, form, count): chosen_target = form.cleaned_data['chosen_target'] target_name = chosen_target.name return ngettext( 'Successfully added %(count)d %(verbose_name)s to %(target)s.', 'Successfully added %(count)d %(verbose_name_plural)s to %(target)s.', count) % { 'count': count, 'verbose_name': self.target_model._meta.verbose_name, 'verbose_name_plural': self.target_model._meta.verbose_name_plural, 'target': target_name} def get_failure_message(self, form, count, failure_count): chosen_target = form.cleaned_data['chosen_target'] target_name = chosen_target.name return ngettext( 'Adding %(count)d %(verbose_name)s to %(target)s, %(failure_count)s failed or skipped.', 'Adding %(count)d %(verbose_name_plural)s to %(target)s, %(failure_count)s failed or skipped.', count) % { 'count': count, 'verbose_name': self.target_model._meta.verbose_name, 'verbose_name_plural': self.target_model._meta.verbose_name_plural, 'target': target_name, 'failure_count': failure_count} class ChangeForeignKeyAction(AdminActionBase): # Subclass:: # action_name = 'change_fieldname_action' # field_name_label = _("") # title = _("Change ") # queryset_action_label = _("For the following items the will be changed:") # action_button_label = _("Change ") def apply(self, queryset, form): raise NotImplementedError("apply must be implemented by the subclass.") # Subclass:: # new_value = form.cleaned_data['new_value'] # return change_fieldname_state(queryset, new_value) def get_value_choices_queryset(self, modeladmin, request, queryset): raise NotImplementedError("get_value_choices_queryset must be implemented by the subclass.") def get_form_class(self, modeladmin, request, queryset): class ChooseValueForm(AdminActionBase.BaseForm): new_value = forms.ModelChoiceField( label=_("Choose %(field_name)s" % {'field_name': self.field_name_label}), queryset=self.get_value_choices_queryset(modeladmin, request, queryset), required=True) return ChooseValueForm def get_message(self, form, count): new_value = form.cleaned_data['new_value'] return ngettext( 'Successfully changed %(field_name)s of %(count)d item to %(new_value)s.', 'Successfully changed %(field_name)s of %(count)d items to %(new_value)s.', count) % { 'field_name': self.field_name_label, 'count': count, 'new_value': new_value} def get_failure_message(self, form, count, failure_count): new_value = form.cleaned_data['new_value'] return ngettext( 'Successfully changed %(field_name)s of %(count)d item to %(new_value)s, %(failure_count)s failed or skipped.', 'Successfully changed %(field_name)s of %(count)d items to %(new_value)s, %(failure_count)s failed or skipped.', count) % { 'field_name': self.field_name_label, 'count': count, 'new_value': new_value, 'failure_count': failure_count}