diff --git a/django/contrib/admin/options.py b/django/contrib/admin/options.py index c79523c2ff..69f52aadad 100644 --- a/django/contrib/admin/options.py +++ b/django/contrib/admin/options.py @@ -32,11 +32,11 @@ class IncorrectLookupParameters(Exception): # Defaults for formfield_overrides. ModelAdmin subclasses can change this # by adding to ModelAdmin.formfield_overrides. - + FORMFIELD_FOR_DBFIELD_DEFAULTS = { - models.DateTimeField: { + models.DateTimeField: { 'form_class': forms.SplitDateTimeField, - 'widget': widgets.AdminSplitDateTime + 'widget': widgets.AdminSplitDateTime }, models.DateField: {'widget': widgets.AdminDateWidget}, models.TimeField: {'widget': widgets.AdminTimeWidget}, @@ -51,7 +51,7 @@ FORMFIELD_FOR_DBFIELD_DEFAULTS = { class BaseModelAdmin(object): """Functionality common to both ModelAdmin and InlineAdmin.""" - + raw_id_fields = () fields = None exclude = None @@ -62,57 +62,57 @@ class BaseModelAdmin(object): radio_fields = {} prepopulated_fields = {} formfield_overrides = {} - + def __init__(self): self.formfield_overrides = dict(FORMFIELD_FOR_DBFIELD_DEFAULTS, **self.formfield_overrides) - + def formfield_for_dbfield(self, db_field, **kwargs): """ Hook for specifying the form Field instance for a given database Field instance. - + If kwargs are given, they're passed to the form Field's constructor. """ request = kwargs.pop("request", None) - + # If the field specifies choices, we don't need to look for special # admin widgets - we just need to use a select widget of some kind. if db_field.choices: return self.formfield_for_choice_field(db_field, request, **kwargs) - + # ForeignKey or ManyToManyFields if isinstance(db_field, (models.ForeignKey, models.ManyToManyField)): # Combine the field kwargs with any options for formfield_overrides. - # Make sure the passed in **kwargs override anything in + # Make sure the passed in **kwargs override anything in # formfield_overrides because **kwargs is more specific, and should # always win. if db_field.__class__ in self.formfield_overrides: kwargs = dict(self.formfield_overrides[db_field.__class__], **kwargs) - + # Get the correct formfield. if isinstance(db_field, models.ForeignKey): formfield = self.formfield_for_foreignkey(db_field, request, **kwargs) elif isinstance(db_field, models.ManyToManyField): formfield = self.formfield_for_manytomany(db_field, request, **kwargs) - + # For non-raw_id fields, wrap the widget with a wrapper that adds # extra HTML -- the "add other" interface -- to the end of the - # rendered output. formfield can be None if it came from a + # rendered output. formfield can be None if it came from a # OneToOneField with parent_link=True or a M2M intermediary. if formfield and db_field.name not in self.raw_id_fields: formfield.widget = widgets.RelatedFieldWidgetWrapper(formfield.widget, db_field.rel, self.admin_site) return formfield - + # If we've got overrides for the formfield defined, use 'em. **kwargs # passed to formfield_for_dbfield override the defaults. if db_field.__class__ in self.formfield_overrides: kwargs = dict(self.formfield_overrides[db_field.__class__], **kwargs) return db_field.formfield(**kwargs) - + # For any other type of field, just call its formfield() method. return db_field.formfield(**kwargs) - + def formfield_for_choice_field(self, db_field, request=None, **kwargs): """ Get a form Field for a database Field that has declared choices. @@ -130,7 +130,7 @@ class BaseModelAdmin(object): blank_choice=[('', _('None'))] ) return db_field.formfield(**kwargs) - + def formfield_for_foreignkey(self, db_field, request=None, **kwargs): """ Get a form Field for a ForeignKey. @@ -142,7 +142,7 @@ class BaseModelAdmin(object): 'class': get_ul_class(self.radio_fields[db_field.name]), }) kwargs['empty_label'] = db_field.blank and _('None') or None - + return db_field.formfield(**kwargs) def formfield_for_manytomany(self, db_field, request=None, **kwargs): @@ -152,15 +152,15 @@ class BaseModelAdmin(object): # If it uses an intermediary model, don't show field in admin. if db_field.rel.through is not None: return None - + if db_field.name in self.raw_id_fields: kwargs['widget'] = widgets.ManyToManyRawIdWidget(db_field.rel) kwargs['help_text'] = '' elif db_field.name in (list(self.filter_vertical) + list(self.filter_horizontal)): kwargs['widget'] = widgets.FilteredSelectMultiple(db_field.verbose_name, (db_field.name in self.filter_vertical)) - + return db_field.formfield(**kwargs) - + def _declared_fieldsets(self): if self.fieldsets: return self.fieldsets @@ -172,7 +172,7 @@ class BaseModelAdmin(object): class ModelAdmin(BaseModelAdmin): "Encapsulates all admin options and functionality for a given model." __metaclass__ = forms.MediaDefiningClass - + list_display = ('__str__',) list_display_links = () list_filter = () @@ -185,13 +185,13 @@ class ModelAdmin(BaseModelAdmin): save_on_top = False ordering = None inlines = [] - + # Custom templates (designed to be over-ridden in subclasses) change_form_template = None change_list_template = None delete_confirmation_template = None object_history_template = None - + def __init__(self, model, admin_site): self.model = model self.opts = model._meta @@ -201,17 +201,17 @@ class ModelAdmin(BaseModelAdmin): inline_instance = inline_class(self.model, self.admin_site) self.inline_instances.append(inline_instance) super(ModelAdmin, self).__init__() - + def get_urls(self): from django.conf.urls.defaults import patterns, url - + def wrap(view): def wrapper(*args, **kwargs): return self.admin_site.admin_view(view)(*args, **kwargs) return update_wrapper(wrapper, view) - + info = self.admin_site.name, self.model._meta.app_label, self.model._meta.module_name - + urlpatterns = patterns('', url(r'^$', wrap(self.changelist_view), @@ -230,50 +230,50 @@ class ModelAdmin(BaseModelAdmin): name='%sadmin_%s_%s_change' % info), ) return urlpatterns - + def urls(self): return self.get_urls() urls = property(urls) - + def _media(self): from django.conf import settings - + js = ['js/core.js', 'js/admin/RelatedObjectLookups.js'] if self.prepopulated_fields: js.append('js/urlify.js') if self.opts.get_ordered_objects(): js.extend(['js/getElementsBySelector.js', 'js/dom-drag.js' , 'js/admin/ordering.js']) - + return forms.Media(js=['%s%s' % (settings.ADMIN_MEDIA_PREFIX, url) for url in js]) media = property(_media) - + def has_add_permission(self, request): "Returns True if the given request has permission to add an object." opts = self.opts return request.user.has_perm(opts.app_label + '.' + opts.get_add_permission()) - + def has_change_permission(self, request, obj=None): """ Returns True if the given request has permission to change the given Django model instance. - + If `obj` is None, this should return True if the given request has permission to change *any* object of the given type. """ opts = self.opts return request.user.has_perm(opts.app_label + '.' + opts.get_change_permission()) - + def has_delete_permission(self, request, obj=None): """ Returns True if the given request has permission to change the given Django model instance. - + If `obj` is None, this should return True if the given request has permission to delete *any* object of the given type. """ opts = self.opts return request.user.has_perm(opts.app_label + '.' + opts.get_delete_permission()) - + def queryset(self, request): """ Returns a QuerySet of all model instances that can be edited by the @@ -285,14 +285,14 @@ class ModelAdmin(BaseModelAdmin): if ordering: qs = qs.order_by(*ordering) return qs - + def get_fieldsets(self, request, obj=None): "Hook for specifying fieldsets for the add form." if self.declared_fieldsets: return self.declared_fieldsets form = self.get_form(request, obj) return [(None, {'fields': form.base_fields.keys()})] - + def get_form(self, request, obj=None, **kwargs): """ Returns a Form class for use in the admin add view. This is used by @@ -314,7 +314,7 @@ class ModelAdmin(BaseModelAdmin): } defaults.update(kwargs) return modelform_factory(self.model, **defaults) - + def get_changelist_form(self, request, **kwargs): """ Returns a Form class for use in the Formset on the changelist page. @@ -324,28 +324,28 @@ class ModelAdmin(BaseModelAdmin): } defaults.update(kwargs) return modelform_factory(self.model, **defaults) - + def get_changelist_formset(self, request, **kwargs): """ - Returns a FormSet class for use on the changelist page if list_editable + Returns a FormSet class for use on the changelist page if list_editable is used. """ defaults = { "formfield_callback": curry(self.formfield_for_dbfield, request=request), } defaults.update(kwargs) - return modelformset_factory(self.model, - self.get_changelist_form(request), extra=0, + return modelformset_factory(self.model, + self.get_changelist_form(request), extra=0, fields=self.list_editable, **defaults) - + def get_formsets(self, request, obj=None): for inline in self.inline_instances: yield inline.get_formset(request, obj) - + def log_addition(self, request, object): """ Log that an object has been successfully added. - + The default implementation creates an admin LogEntry object. """ from django.contrib.admin.models import LogEntry, ADDITION @@ -356,11 +356,11 @@ class ModelAdmin(BaseModelAdmin): object_repr = force_unicode(object), action_flag = ADDITION ) - + def log_change(self, request, object, message): """ Log that an object has been successfully changed. - + The default implementation creates an admin LogEntry object. """ from django.contrib.admin.models import LogEntry, CHANGE @@ -372,13 +372,13 @@ class ModelAdmin(BaseModelAdmin): action_flag = CHANGE, change_message = message ) - + def log_deletion(self, request, object, object_repr): """ Log that an object has been successfully deleted. Note that since the object is deleted, it might no longer be safe to call *any* methods on the object, hence this method getting object_repr. - + The default implementation creates an admin LogEntry object. """ from django.contrib.admin.models import LogEntry, DELETION @@ -389,8 +389,8 @@ class ModelAdmin(BaseModelAdmin): object_repr = object_repr, action_flag = DELETION ) - - + + def construct_change_message(self, request, form, formsets): """ Construct a change message from a changed object. @@ -398,7 +398,7 @@ class ModelAdmin(BaseModelAdmin): change_message = [] if form.changed_data: change_message.append(_('Changed %s.') % get_text_list(form.changed_data, _('and'))) - + if formsets: for formset in formsets: for added_object in formset.new_objects: @@ -416,33 +416,33 @@ class ModelAdmin(BaseModelAdmin): 'object': force_unicode(deleted_object)}) change_message = ' '.join(change_message) return change_message or _('No fields changed.') - + def message_user(self, request, message): """ Send a message to the user. The default implementation posts a message using the auth Message object. """ request.user.message_set.create(message=message) - + def save_form(self, request, form, change): """ Given a ModelForm return an unsaved instance. ``change`` is True if the object is being changed, and False if it's being added. """ return form.save(commit=False) - + def save_model(self, request, obj, form, change): """ Given a model instance save it to the database. """ obj.save() - + def save_formset(self, request, form, formset, change): """ Given an inline formset save it to the database. """ formset.save() - + def render_change_form(self, request, context, add=False, change=False, form_url='', obj=None): opts = self.model._meta app_label = opts.app_label @@ -468,14 +468,14 @@ class ModelAdmin(BaseModelAdmin): "admin/%s/change_form.html" % app_label, "admin/change_form.html" ], context, context_instance=template.RequestContext(request)) - + def response_add(self, request, obj, post_url_continue='../%s/'): """ Determines the HttpResponse for the add_view stage. """ opts = obj._meta pk_value = obj._get_pk_val() - + msg = _('The %(name)s "%(obj)s" was added successfully.') % {'name': force_unicode(opts.verbose_name), 'obj': force_unicode(obj)} # Here, we distinguish between different save types by checking for # the presence of keys in request.POST. @@ -484,7 +484,7 @@ class ModelAdmin(BaseModelAdmin): if request.POST.has_key("_popup"): post_url_continue += "?_popup=1" return HttpResponseRedirect(post_url_continue % pk_value) - + if request.POST.has_key("_popup"): return HttpResponse('' % \ # escape() calls force_unicode. @@ -494,7 +494,7 @@ class ModelAdmin(BaseModelAdmin): return HttpResponseRedirect(request.path) else: self.message_user(request, msg) - + # Figure out where to redirect. If the user has change permission, # redirect to the change-list page for this object. Otherwise, # redirect to the admin index. @@ -503,14 +503,14 @@ class ModelAdmin(BaseModelAdmin): else: post_url = '../../../' return HttpResponseRedirect(post_url) - + def response_change(self, request, obj): """ Determines the HttpResponse for the change_view stage. """ opts = obj._meta pk_value = obj._get_pk_val() - + msg = _('The %(name)s "%(obj)s" was changed successfully.') % {'name': force_unicode(opts.verbose_name), 'obj': force_unicode(obj)} if request.POST.has_key("_continue"): self.message_user(request, msg + ' ' + _("You may edit it again below.")) @@ -528,15 +528,15 @@ class ModelAdmin(BaseModelAdmin): else: self.message_user(request, msg) return HttpResponseRedirect("../") - + def add_view(self, request, form_url='', extra_context=None): "The 'add' admin view for this model." model = self.model opts = model._meta - + if not self.has_add_permission(request): raise PermissionDenied - + ModelForm = self.get_form(request) formsets = [] if request.method == 'POST': @@ -563,7 +563,7 @@ class ModelAdmin(BaseModelAdmin): form.save_m2m() for formset in formsets: self.save_formset(request, form, formset, change=False) - + self.log_addition(request, new_object) return self.response_add(request, new_object) else: @@ -586,17 +586,17 @@ class ModelAdmin(BaseModelAdmin): prefix = "%s-%s" % (prefix, prefixes[prefix]) formset = FormSet(instance=self.model(), prefix=prefix) formsets.append(formset) - + adminForm = helpers.AdminForm(form, list(self.get_fieldsets(request)), self.prepopulated_fields) media = self.media + adminForm.media - + inline_admin_formsets = [] for inline, formset in zip(self.inline_instances, formsets): fieldsets = list(inline.get_fieldsets(request)) inline_admin_formset = helpers.InlineAdminFormSet(inline, formset, fieldsets) inline_admin_formsets.append(inline_admin_formset) media = media + inline_admin_formset.media - + context = { 'title': _('Add %s') % force_unicode(opts.verbose_name), 'adminform': adminForm, @@ -611,12 +611,12 @@ class ModelAdmin(BaseModelAdmin): context.update(extra_context or {}) return self.render_change_form(request, context, add=True) add_view = transaction.commit_on_success(add_view) - + def change_view(self, request, object_id, extra_context=None): "The 'change' admin view for this model." model = self.model opts = model._meta - + try: obj = model._default_manager.get(pk=unquote(object_id)) except model.DoesNotExist: @@ -624,16 +624,16 @@ class ModelAdmin(BaseModelAdmin): # permissions yet. We don't want an unauthenticated user to be able # to determine whether a given object exists. obj = None - + if not self.has_change_permission(request, obj): raise PermissionDenied - + if obj is None: raise Http404(_('%(name)s object with primary key %(key)r does not exist.') % {'name': force_unicode(opts.verbose_name), 'key': escape(object_id)}) - + if request.method == 'POST' and request.POST.has_key("_saveasnew"): return self.add_view(request, form_url='../../add/') - + ModelForm = self.get_form(request, obj) formsets = [] if request.method == 'POST': @@ -653,17 +653,17 @@ class ModelAdmin(BaseModelAdmin): formset = FormSet(request.POST, request.FILES, instance=new_object, prefix=prefix) formsets.append(formset) - + if all_valid(formsets) and form_validated: self.save_model(request, new_object, form, change=True) form.save_m2m() for formset in formsets: self.save_formset(request, form, formset, change=True) - + change_message = self.construct_change_message(request, form, formsets) self.log_change(request, new_object, change_message) return self.response_change(request, new_object) - + else: form = ModelForm(instance=obj) prefixes = {} @@ -674,17 +674,17 @@ class ModelAdmin(BaseModelAdmin): prefix = "%s-%s" % (prefix, prefixes[prefix]) formset = FormSet(instance=obj, prefix=prefix) formsets.append(formset) - + adminForm = helpers.AdminForm(form, self.get_fieldsets(request, obj), self.prepopulated_fields) media = self.media + adminForm.media - + inline_admin_formsets = [] for inline, formset in zip(self.inline_instances, formsets): fieldsets = list(inline.get_fieldsets(request, obj)) inline_admin_formset = helpers.InlineAdminFormSet(inline, formset, fieldsets) inline_admin_formsets.append(inline_admin_formset) media = media + inline_admin_formset.media - + context = { 'title': _('Change %s') % force_unicode(opts.verbose_name), 'adminform': adminForm, @@ -700,7 +700,7 @@ class ModelAdmin(BaseModelAdmin): context.update(extra_context or {}) return self.render_change_form(request, context, change=True, obj=obj) change_view = transaction.commit_on_success(change_view) - + def changelist_view(self, request, extra_context=None): "The 'change list' admin view for this model." from django.contrib.admin.views.main import ChangeList, ERROR_FLAG @@ -720,12 +720,12 @@ class ModelAdmin(BaseModelAdmin): if ERROR_FLAG in request.GET.keys(): return render_to_response('admin/invalid_setup.html', {'title': _('Database error')}) return HttpResponseRedirect(request.path + '?' + ERROR_FLAG + '=1') - + # If we're allowing changelist editing, we need to construct a formset # for the changelist given all the fields to be edited. Then we'll # use the formset to validate/process POSTed data. formset = cl.formset = None - + # Handle POSTed bulk-edit data. if request.method == "POST" and self.list_editable: FormSet = self.get_changelist_formset(request) @@ -740,23 +740,26 @@ class ModelAdmin(BaseModelAdmin): change_msg = self.construct_change_message(request, form, None) self.log_change(request, obj, change_msg) changecount += 1 - + if changecount: - msg = ngettext("%(count)s %(singular)s was changed successfully.", - "%(count)s %(plural)s were changed successfully.", + if changecount == 1: + name = force_unicode(opts.verbose_name) + else: + name = force_unicode(opts.verbose_name_plural) + msg = ngettext("%(count)s %(name)s was changed successfully.", + "%(count)s %(name)s were changed successfully.", changecount) % {'count': changecount, - 'singular': force_unicode(opts.verbose_name), - 'plural': force_unicode(opts.verbose_name_plural), + 'name': name, 'obj': force_unicode(obj)} self.message_user(request, msg) - + return HttpResponseRedirect(request.get_full_path()) - + # Handle GET -- construct a formset for display. elif self.list_editable: FormSet = self.get_changelist_formset(request) formset = cl.formset = FormSet(queryset=cl.result_list) - + # Build the list of media to be used by the formset. if formset: media = self.media + formset.media @@ -778,12 +781,12 @@ class ModelAdmin(BaseModelAdmin): 'admin/%s/change_list.html' % app_label, 'admin/change_list.html' ], context, context_instance=template.RequestContext(request)) - + def delete_view(self, request, object_id, extra_context=None): "The 'delete' admin view for this model." opts = self.model._meta app_label = opts.app_label - + try: obj = self.model._default_manager.get(pk=unquote(object_id)) except self.model.DoesNotExist: @@ -791,32 +794,32 @@ class ModelAdmin(BaseModelAdmin): # permissions yet. We don't want an unauthenticated user to be able # to determine whether a given object exists. obj = None - + if not self.has_delete_permission(request, obj): raise PermissionDenied - + if obj is None: raise Http404(_('%(name)s object with primary key %(key)r does not exist.') % {'name': force_unicode(opts.verbose_name), 'key': escape(object_id)}) - + # Populate deleted_objects, a data structure of all related objects that # will also be deleted. deleted_objects = [mark_safe(u'%s: %s' % (escape(force_unicode(capfirst(opts.verbose_name))), object_id, escape(obj))), []] perms_needed = set() get_deleted_objects(deleted_objects, perms_needed, request.user, obj, opts, 1, self.admin_site) - + if request.POST: # The user has already confirmed the deletion. if perms_needed: raise PermissionDenied obj_display = force_unicode(obj) obj.delete() - + self.log_deletion(request, obj, obj_display) self.message_user(request, _('The %(name)s "%(obj)s" was deleted successfully.') % {'name': force_unicode(opts.verbose_name), 'obj': force_unicode(obj_display)}) - + if not self.has_change_permission(request, None): return HttpResponseRedirect("../../../../") return HttpResponseRedirect("../../") - + context = { "title": _("Are you sure?"), "object_name": force_unicode(opts.verbose_name), @@ -833,7 +836,7 @@ class ModelAdmin(BaseModelAdmin): "admin/%s/delete_confirmation.html" % app_label, "admin/delete_confirmation.html" ], context, context_instance=template.RequestContext(request)) - + def history_view(self, request, object_id, extra_context=None): "The 'history' admin view for this model." from django.contrib.admin.models import LogEntry @@ -869,11 +872,11 @@ class ModelAdmin(BaseModelAdmin): DEPRECATED: this is the old way of URL resolution, replaced by ``get_urls()``. This only called by AdminSite.root(), which is also deprecated. - + Again, remember that the following code only exists for backwards-compatibility. Any new URLs, changes to existing URLs, or whatever need to be done up in get_urls(), above! - + This function still exists for backwards-compatibility; it will be removed in Django 1.3. """ @@ -892,7 +895,7 @@ class ModelAdmin(BaseModelAdmin): class InlineModelAdmin(BaseModelAdmin): """ Options for inline editing of ``model`` instances. - + Provide ``name`` to specify the attribute name of the ``ForeignKey`` from ``model`` to its parent. This is required if ``model`` has more than one ``ForeignKey`` to its parent. @@ -905,7 +908,7 @@ class InlineModelAdmin(BaseModelAdmin): template = None verbose_name = None verbose_name_plural = None - + def __init__(self, parent_model, admin_site): self.admin_site = admin_site self.parent_model = parent_model @@ -915,7 +918,7 @@ class InlineModelAdmin(BaseModelAdmin): self.verbose_name = self.model._meta.verbose_name if self.verbose_name_plural is None: self.verbose_name_plural = self.model._meta.verbose_name_plural - + def _media(self): from django.conf import settings js = [] @@ -925,7 +928,7 @@ class InlineModelAdmin(BaseModelAdmin): js.extend(['js/SelectBox.js' , 'js/SelectFilter2.js']) return forms.Media(js=['%s%s' % (settings.ADMIN_MEDIA_PREFIX, url) for url in js]) media = property(_media) - + def get_formset(self, request, obj=None, **kwargs): """Returns a BaseInlineFormSet class for use in admin add/change views.""" if self.declared_fieldsets: @@ -948,13 +951,13 @@ class InlineModelAdmin(BaseModelAdmin): } defaults.update(kwargs) return inlineformset_factory(self.parent_model, self.model, **defaults) - + def get_fieldsets(self, request, obj=None): if self.declared_fieldsets: return self.declared_fieldsets form = self.get_formset(request).form return [(None, {'fields': form.base_fields.keys()})] - + class StackedInline(InlineModelAdmin): template = 'admin/edit_inline/stacked.html'