diff options
Diffstat (limited to 'lib/python2.7/site-packages/django/contrib/admin/options.py')
-rw-r--r-- | lib/python2.7/site-packages/django/contrib/admin/options.py | 1718 |
1 files changed, 0 insertions, 1718 deletions
diff --git a/lib/python2.7/site-packages/django/contrib/admin/options.py b/lib/python2.7/site-packages/django/contrib/admin/options.py deleted file mode 100644 index 9dddcb0..0000000 --- a/lib/python2.7/site-packages/django/contrib/admin/options.py +++ /dev/null @@ -1,1718 +0,0 @@ -import copy -import operator -from functools import partial, reduce, update_wrapper - -from django import forms -from django.conf import settings -from django.contrib import messages -from django.contrib.admin import widgets, helpers -from django.contrib.admin.util import (unquote, flatten_fieldsets, get_deleted_objects, - model_format_dict, NestedObjects, lookup_needs_distinct) -from django.contrib.admin import validation -from django.contrib.admin.templatetags.admin_static import static -from django.contrib.admin.templatetags.admin_urls import add_preserved_filters -from django.contrib.auth import get_permission_codename -from django.contrib.contenttypes.models import ContentType -from django.core.exceptions import PermissionDenied, ValidationError, FieldError -from django.core.paginator import Paginator -from django.core.urlresolvers import reverse -from django.db import models, transaction, router -from django.db.models.constants import LOOKUP_SEP -from django.db.models.related import RelatedObject -from django.db.models.fields import BLANK_CHOICE_DASH, FieldDoesNotExist -from django.db.models.sql.constants import QUERY_TERMS -from django.forms.formsets import all_valid, DELETION_FIELD_NAME -from django.forms.models import (modelform_factory, modelformset_factory, - inlineformset_factory, BaseInlineFormSet, modelform_defines_fields) -from django.http import Http404, HttpResponseRedirect -from django.http.response import HttpResponseBase -from django.shortcuts import get_object_or_404 -from django.template.response import SimpleTemplateResponse, TemplateResponse -from django.utils.decorators import method_decorator -from django.utils.datastructures import SortedDict -from django.utils.html import escape, escapejs -from django.utils.safestring import mark_safe -from django.utils import six -from django.utils.deprecation import RenameMethodsBase -from django.utils.http import urlencode -from django.utils.text import capfirst, get_text_list -from django.utils.translation import ugettext as _ -from django.utils.translation import ungettext -from django.utils.encoding import force_text -from django.views.decorators.csrf import csrf_protect - - -IS_POPUP_VAR = '_popup' - -HORIZONTAL, VERTICAL = 1, 2 -# returns the <ul> class for a given radio_admin field -get_ul_class = lambda x: 'radiolist%s' % (' inline' if x == HORIZONTAL else '') - - -class IncorrectLookupParameters(Exception): - pass - -# Defaults for formfield_overrides. ModelAdmin subclasses can change this -# by adding to ModelAdmin.formfield_overrides. - -FORMFIELD_FOR_DBFIELD_DEFAULTS = { - models.DateTimeField: { - 'form_class': forms.SplitDateTimeField, - 'widget': widgets.AdminSplitDateTime - }, - models.DateField: {'widget': widgets.AdminDateWidget}, - models.TimeField: {'widget': widgets.AdminTimeWidget}, - models.TextField: {'widget': widgets.AdminTextareaWidget}, - models.URLField: {'widget': widgets.AdminURLFieldWidget}, - models.IntegerField: {'widget': widgets.AdminIntegerFieldWidget}, - models.BigIntegerField: {'widget': widgets.AdminBigIntegerFieldWidget}, - models.CharField: {'widget': widgets.AdminTextInputWidget}, - models.ImageField: {'widget': widgets.AdminFileWidget}, - models.FileField: {'widget': widgets.AdminFileWidget}, - models.EmailField: {'widget': widgets.AdminEmailInputWidget}, -} - -csrf_protect_m = method_decorator(csrf_protect) - - -class RenameBaseModelAdminMethods(forms.MediaDefiningClass, RenameMethodsBase): - renamed_methods = ( - ('queryset', 'get_queryset', PendingDeprecationWarning), - ) - - -class BaseModelAdmin(six.with_metaclass(RenameBaseModelAdminMethods)): - """Functionality common to both ModelAdmin and InlineAdmin.""" - - raw_id_fields = () - fields = None - exclude = None - fieldsets = None - form = forms.ModelForm - filter_vertical = () - filter_horizontal = () - radio_fields = {} - prepopulated_fields = {} - formfield_overrides = {} - readonly_fields = () - ordering = None - - # validation - validator_class = validation.BaseValidator - - @classmethod - def validate(cls, model): - validator = cls.validator_class() - validator.validate(cls, model) - - def __init__(self): - overrides = FORMFIELD_FOR_DBFIELD_DEFAULTS.copy() - overrides.update(self.formfield_overrides) - self.formfield_overrides = 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 - # 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 - # OneToOneField with parent_link=True or a M2M intermediary. - if formfield and db_field.name not in self.raw_id_fields: - related_modeladmin = self.admin_site._registry.get( - db_field.rel.to) - can_add_related = bool(related_modeladmin and - related_modeladmin.has_add_permission(request)) - formfield.widget = widgets.RelatedFieldWidgetWrapper( - formfield.widget, db_field.rel, self.admin_site, - can_add_related=can_add_related) - - return formfield - - # If we've got overrides for the formfield defined, use 'em. **kwargs - # passed to formfield_for_dbfield override the defaults. - for klass in db_field.__class__.mro(): - if klass in self.formfield_overrides: - kwargs = dict(copy.deepcopy(self.formfield_overrides[klass]), **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. - """ - # If the field is named as a radio_field, use a RadioSelect - if db_field.name in self.radio_fields: - # Avoid stomping on custom widget/choices arguments. - if 'widget' not in kwargs: - kwargs['widget'] = widgets.AdminRadioSelect(attrs={ - 'class': get_ul_class(self.radio_fields[db_field.name]), - }) - if 'choices' not in kwargs: - kwargs['choices'] = db_field.get_choices( - include_blank=db_field.blank, - blank_choice=[('', _('None'))] - ) - return db_field.formfield(**kwargs) - - def get_field_queryset(self, db, db_field, request): - """ - If the ModelAdmin specifies ordering, the queryset should respect that - ordering. Otherwise don't specify the queryset, let the field decide - (returns None in that case). - """ - related_admin = self.admin_site._registry.get(db_field.rel.to, None) - if related_admin is not None: - ordering = related_admin.get_ordering(request) - if ordering is not None and ordering != (): - return db_field.rel.to._default_manager.using(db).order_by(*ordering).complex_filter(db_field.rel.limit_choices_to) - return None - - def formfield_for_foreignkey(self, db_field, request=None, **kwargs): - """ - Get a form Field for a ForeignKey. - """ - db = kwargs.get('using') - if db_field.name in self.raw_id_fields: - kwargs['widget'] = widgets.ForeignKeyRawIdWidget(db_field.rel, - self.admin_site, using=db) - elif db_field.name in self.radio_fields: - kwargs['widget'] = widgets.AdminRadioSelect(attrs={ - 'class': get_ul_class(self.radio_fields[db_field.name]), - }) - kwargs['empty_label'] = _('None') if db_field.blank else None - - if not 'queryset' in kwargs: - queryset = self.get_field_queryset(db, db_field, request) - if queryset is not None: - kwargs['queryset'] = queryset - - return db_field.formfield(**kwargs) - - def formfield_for_manytomany(self, db_field, request=None, **kwargs): - """ - Get a form Field for a ManyToManyField. - """ - # If it uses an intermediary model that isn't auto created, don't show - # a field in admin. - if not db_field.rel.through._meta.auto_created: - return None - db = kwargs.get('using') - - if db_field.name in self.raw_id_fields: - kwargs['widget'] = widgets.ManyToManyRawIdWidget(db_field.rel, - self.admin_site, using=db) - 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)) - - if not 'queryset' in kwargs: - queryset = self.get_field_queryset(db, db_field, request) - if queryset is not None: - kwargs['queryset'] = queryset - - return db_field.formfield(**kwargs) - - def _declared_fieldsets(self): - if self.fieldsets: - return self.fieldsets - elif self.fields: - return [(None, {'fields': self.fields})] - return None - declared_fieldsets = property(_declared_fieldsets) - - def get_ordering(self, request): - """ - Hook for specifying field ordering. - """ - return self.ordering or () # otherwise we might try to *None, which is bad ;) - - def get_readonly_fields(self, request, obj=None): - """ - Hook for specifying custom readonly fields. - """ - return self.readonly_fields - - def get_prepopulated_fields(self, request, obj=None): - """ - Hook for specifying custom prepopulated fields. - """ - return self.prepopulated_fields - - def get_queryset(self, request): - """ - Returns a QuerySet of all model instances that can be edited by the - admin site. This is used by changelist_view. - """ - qs = self.model._default_manager.get_queryset() - # TODO: this should be handled by some parameter to the ChangeList. - ordering = self.get_ordering(request) - if ordering: - qs = qs.order_by(*ordering) - return qs - - def lookup_allowed(self, lookup, value): - model = self.model - # Check FKey lookups that are allowed, so that popups produced by - # ForeignKeyRawIdWidget, on the basis of ForeignKey.limit_choices_to, - # are allowed to work. - for l in model._meta.related_fkey_lookups: - for k, v in widgets.url_params_from_lookup_dict(l).items(): - if k == lookup and v == value: - return True - - parts = lookup.split(LOOKUP_SEP) - - # Last term in lookup is a query term (__exact, __startswith etc) - # This term can be ignored. - if len(parts) > 1 and parts[-1] in QUERY_TERMS: - parts.pop() - - # Special case -- foo__id__exact and foo__id queries are implied - # if foo has been specifically included in the lookup list; so - # drop __id if it is the last part. However, first we need to find - # the pk attribute name. - rel_name = None - for part in parts[:-1]: - try: - field, _, _, _ = model._meta.get_field_by_name(part) - except FieldDoesNotExist: - # Lookups on non-existent fields are ok, since they're ignored - # later. - return True - if hasattr(field, 'rel'): - if field.rel is None: - # This property or relation doesn't exist, but it's allowed - # since it's ignored in ChangeList.get_filters(). - return True - model = field.rel.to - rel_name = field.rel.get_related_field().name - elif isinstance(field, RelatedObject): - model = field.model - rel_name = model._meta.pk.name - else: - rel_name = None - if rel_name and len(parts) > 1 and parts[-1] == rel_name: - parts.pop() - - if len(parts) == 1: - return True - clean_lookup = LOOKUP_SEP.join(parts) - return clean_lookup in self.list_filter or clean_lookup == self.date_hierarchy - - def has_add_permission(self, request): - """ - Returns True if the given request has permission to add an object. - Can be overridden by the user in subclasses. - """ - opts = self.opts - codename = get_permission_codename('add', opts) - return request.user.has_perm("%s.%s" % (opts.app_label, codename)) - - def has_change_permission(self, request, obj=None): - """ - Returns True if the given request has permission to change the given - Django model instance, the default implementation doesn't examine the - `obj` parameter. - - Can be overridden by the user in subclasses. In such case it should - return True if the given request has permission to change the `obj` - 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 - codename = get_permission_codename('change', opts) - return request.user.has_perm("%s.%s" % (opts.app_label, codename)) - - def has_delete_permission(self, request, obj=None): - """ - Returns True if the given request has permission to change the given - Django model instance, the default implementation doesn't examine the - `obj` parameter. - - Can be overridden by the user in subclasses. In such case it should - return True if the given request has permission to delete the `obj` - 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 - codename = get_permission_codename('delete', opts) - return request.user.has_perm("%s.%s" % (opts.app_label, codename)) - - -class ModelAdmin(BaseModelAdmin): - "Encapsulates all admin options and functionality for a given model." - - list_display = ('__str__',) - list_display_links = () - list_filter = () - list_select_related = False - list_per_page = 100 - list_max_show_all = 200 - list_editable = () - search_fields = () - date_hierarchy = None - save_as = False - save_on_top = False - paginator = Paginator - preserve_filters = True - inlines = [] - - # Custom templates (designed to be over-ridden in subclasses) - add_form_template = None - change_form_template = None - change_list_template = None - delete_confirmation_template = None - delete_selected_confirmation_template = None - object_history_template = None - - # Actions - actions = [] - action_form = helpers.ActionForm - actions_on_top = True - actions_on_bottom = False - actions_selection_counter = True - - # validation - validator_class = validation.ModelAdminValidator - - def __init__(self, model, admin_site): - self.model = model - self.opts = model._meta - self.admin_site = admin_site - super(ModelAdmin, self).__init__() - - def get_inline_instances(self, request, obj=None): - inline_instances = [] - for inline_class in self.inlines: - inline = inline_class(self.model, self.admin_site) - if request: - if not (inline.has_add_permission(request) or - inline.has_change_permission(request, obj) or - inline.has_delete_permission(request, obj)): - continue - if not inline.has_add_permission(request): - inline.max_num = 0 - inline_instances.append(inline) - - return inline_instances - - def get_urls(self): - from django.conf.urls 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.model._meta.app_label, self.model._meta.model_name - - urlpatterns = patterns('', - url(r'^$', - wrap(self.changelist_view), - name='%s_%s_changelist' % info), - url(r'^add/$', - wrap(self.add_view), - name='%s_%s_add' % info), - url(r'^(.+)/history/$', - wrap(self.history_view), - name='%s_%s_history' % info), - url(r'^(.+)/delete/$', - wrap(self.delete_view), - name='%s_%s_delete' % info), - url(r'^(.+)/$', - wrap(self.change_view), - name='%s_%s_change' % info), - ) - return urlpatterns - - def urls(self): - return self.get_urls() - urls = property(urls) - - @property - def media(self): - extra = '' if settings.DEBUG else '.min' - js = [ - 'core.js', - 'admin/RelatedObjectLookups.js', - 'jquery%s.js' % extra, - 'jquery.init.js' - ] - if self.actions is not None: - js.append('actions%s.js' % extra) - if self.prepopulated_fields: - js.extend(['urlify.js', 'prepopulate%s.js' % extra]) - return forms.Media(js=[static('admin/js/%s' % url) for url in js]) - - def get_model_perms(self, request): - """ - Returns a dict of all perms for this model. This dict has the keys - ``add``, ``change``, and ``delete`` mapping to the True/False for each - of those actions. - """ - return { - 'add': self.has_add_permission(request), - 'change': self.has_change_permission(request), - 'delete': self.has_delete_permission(request), - } - - 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, fields=None) - fields = list(form.base_fields) + list(self.get_readonly_fields(request, obj)) - return [(None, {'fields': fields})] - - def get_form(self, request, obj=None, **kwargs): - """ - Returns a Form class for use in the admin add view. This is used by - add_view and change_view. - """ - if 'fields' in kwargs: - fields = kwargs.pop('fields') - else: - fields = flatten_fieldsets(self.get_fieldsets(request, obj)) - if self.exclude is None: - exclude = [] - else: - exclude = list(self.exclude) - exclude.extend(self.get_readonly_fields(request, obj)) - if self.exclude is None and hasattr(self.form, '_meta') and self.form._meta.exclude: - # Take the custom ModelForm's Meta.exclude into account only if the - # ModelAdmin doesn't define its own. - exclude.extend(self.form._meta.exclude) - # if exclude is an empty list we pass None to be consistent with the - # default on modelform_factory - exclude = exclude or None - defaults = { - "form": self.form, - "fields": fields, - "exclude": exclude, - "formfield_callback": partial(self.formfield_for_dbfield, request=request), - } - defaults.update(kwargs) - - if defaults['fields'] is None and not modelform_defines_fields(defaults['form']): - defaults['fields'] = forms.ALL_FIELDS - - try: - return modelform_factory(self.model, **defaults) - except FieldError as e: - raise FieldError('%s. Check fields/fieldsets/exclude attributes of class %s.' - % (e, self.__class__.__name__)) - - def get_changelist(self, request, **kwargs): - """ - Returns the ChangeList class for use on the changelist page. - """ - from django.contrib.admin.views.main import ChangeList - return ChangeList - - def get_object(self, request, object_id): - """ - Returns an instance matching the primary key provided. ``None`` is - returned if no match is found (or the object_id failed validation - against the primary key field). - """ - queryset = self.get_queryset(request) - model = queryset.model - try: - object_id = model._meta.pk.to_python(object_id) - return queryset.get(pk=object_id) - except (model.DoesNotExist, ValidationError, ValueError): - return None - - def get_changelist_form(self, request, **kwargs): - """ - Returns a Form class for use in the Formset on the changelist page. - """ - defaults = { - "formfield_callback": partial(self.formfield_for_dbfield, request=request), - } - defaults.update(kwargs) - if (defaults.get('fields') is None - and not modelform_defines_fields(defaults.get('form'))): - defaults['fields'] = forms.ALL_FIELDS - - 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 - is used. - """ - defaults = { - "formfield_callback": partial(self.formfield_for_dbfield, request=request), - } - defaults.update(kwargs) - 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.get_inline_instances(request, obj): - yield inline.get_formset(request, obj) - - def get_paginator(self, request, queryset, per_page, orphans=0, allow_empty_first_page=True): - return self.paginator(queryset, per_page, orphans, allow_empty_first_page) - - 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 - LogEntry.objects.log_action( - user_id=request.user.pk, - content_type_id=ContentType.objects.get_for_model(object).pk, - object_id=object.pk, - object_repr=force_text(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 - LogEntry.objects.log_action( - user_id=request.user.pk, - content_type_id=ContentType.objects.get_for_model(object).pk, - object_id=object.pk, - object_repr=force_text(object), - action_flag=CHANGE, - change_message=message - ) - - def log_deletion(self, request, object, object_repr): - """ - Log that an object will be deleted. Note that this method is called - before the deletion. - - The default implementation creates an admin LogEntry object. - """ - from django.contrib.admin.models import LogEntry, DELETION - LogEntry.objects.log_action( - user_id=request.user.pk, - content_type_id=ContentType.objects.get_for_model(self.model).pk, - object_id=object.pk, - object_repr=object_repr, - action_flag=DELETION - ) - - def action_checkbox(self, obj): - """ - A list_display column containing a checkbox widget. - """ - return helpers.checkbox.render(helpers.ACTION_CHECKBOX_NAME, force_text(obj.pk)) - action_checkbox.short_description = mark_safe('<input type="checkbox" id="action-toggle" />') - action_checkbox.allow_tags = True - - def get_actions(self, request): - """ - Return a dictionary mapping the names of all actions for this - ModelAdmin to a tuple of (callable, name, description) for each action. - """ - # If self.actions is explicitly set to None that means that we don't - # want *any* actions enabled on this page. - from django.contrib.admin.views.main import _is_changelist_popup - if self.actions is None or _is_changelist_popup(request): - return SortedDict() - - actions = [] - - # Gather actions from the admin site first - for (name, func) in self.admin_site.actions: - description = getattr(func, 'short_description', name.replace('_', ' ')) - actions.append((func, name, description)) - - # Then gather them from the model admin and all parent classes, - # starting with self and working back up. - for klass in self.__class__.mro()[::-1]: - class_actions = getattr(klass, 'actions', []) - # Avoid trying to iterate over None - if not class_actions: - continue - actions.extend([self.get_action(action) for action in class_actions]) - - # get_action might have returned None, so filter any of those out. - actions = filter(None, actions) - - # Convert the actions into a SortedDict keyed by name. - actions = SortedDict([ - (name, (func, name, desc)) - for func, name, desc in actions - ]) - - return actions - - def get_action_choices(self, request, default_choices=BLANK_CHOICE_DASH): - """ - Return a list of choices for use in a form object. Each choice is a - tuple (name, description). - """ - choices = [] + default_choices - for func, name, description in six.itervalues(self.get_actions(request)): - choice = (name, description % model_format_dict(self.opts)) - choices.append(choice) - return choices - - def get_action(self, action): - """ - Return a given action from a parameter, which can either be a callable, - or the name of a method on the ModelAdmin. Return is a tuple of - (callable, name, description). - """ - # If the action is a callable, just use it. - if callable(action): - func = action - action = action.__name__ - - # Next, look for a method. Grab it off self.__class__ to get an unbound - # method instead of a bound one; this ensures that the calling - # conventions are the same for functions and methods. - elif hasattr(self.__class__, action): - func = getattr(self.__class__, action) - - # Finally, look for a named method on the admin site - else: - try: - func = self.admin_site.get_action(action) - except KeyError: - return None - - if hasattr(func, 'short_description'): - description = func.short_description - else: - description = capfirst(action.replace('_', ' ')) - return func, action, description - - def get_list_display(self, request): - """ - Return a sequence containing the fields to be displayed on the - changelist. - """ - return self.list_display - - def get_list_display_links(self, request, list_display): - """ - Return a sequence containing the fields to be displayed as links - on the changelist. The list_display parameter is the list of fields - returned by get_list_display(). - """ - if self.list_display_links or not list_display: - return self.list_display_links - else: - # Use only the first item in list_display as link - return list(list_display)[:1] - - def get_list_filter(self, request): - """ - Returns a sequence containing the fields to be displayed as filters in - the right sidebar of the changelist page. - """ - return self.list_filter - - def get_search_results(self, request, queryset, search_term): - """ - Returns a tuple containing a queryset to implement the search, - and a boolean indicating if the results may contain duplicates. - """ - # Apply keyword searches. - def construct_search(field_name): - if field_name.startswith('^'): - return "%s__istartswith" % field_name[1:] - elif field_name.startswith('='): - return "%s__iexact" % field_name[1:] - elif field_name.startswith('@'): - return "%s__search" % field_name[1:] - else: - return "%s__icontains" % field_name - - use_distinct = False - if self.search_fields and search_term: - orm_lookups = [construct_search(str(search_field)) - for search_field in self.search_fields] - for bit in search_term.split(): - or_queries = [models.Q(**{orm_lookup: bit}) - for orm_lookup in orm_lookups] - queryset = queryset.filter(reduce(operator.or_, or_queries)) - if not use_distinct: - for search_spec in orm_lookups: - if lookup_needs_distinct(self.opts, search_spec): - use_distinct = True - break - - return queryset, use_distinct - - def get_preserved_filters(self, request): - """ - Returns the preserved filters querystring. - """ - match = request.resolver_match - if self.preserve_filters and match: - opts = self.model._meta - current_url = '%s:%s' % (match.app_name, match.url_name) - changelist_url = 'admin:%s_%s_changelist' % (opts.app_label, opts.model_name) - if current_url == changelist_url: - preserved_filters = request.GET.urlencode() - else: - preserved_filters = request.GET.get('_changelist_filters') - - if preserved_filters: - return urlencode({'_changelist_filters': preserved_filters}) - return '' - - def construct_change_message(self, request, form, formsets): - """ - Construct a change message from a changed object. - """ - 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: - change_message.append(_('Added %(name)s "%(object)s".') - % {'name': force_text(added_object._meta.verbose_name), - 'object': force_text(added_object)}) - for changed_object, changed_fields in formset.changed_objects: - change_message.append(_('Changed %(list)s for %(name)s "%(object)s".') - % {'list': get_text_list(changed_fields, _('and')), - 'name': force_text(changed_object._meta.verbose_name), - 'object': force_text(changed_object)}) - for deleted_object in formset.deleted_objects: - change_message.append(_('Deleted %(name)s "%(object)s".') - % {'name': force_text(deleted_object._meta.verbose_name), - 'object': force_text(deleted_object)}) - change_message = ' '.join(change_message) - return change_message or _('No fields changed.') - - def message_user(self, request, message, level=messages.INFO, extra_tags='', - fail_silently=False): - """ - Send a message to the user. The default implementation - posts a message using the django.contrib.messages backend. - - Exposes almost the same API as messages.add_message(), but accepts the - positional arguments in a different order to maintain backwards - compatibility. For convenience, it accepts the `level` argument as - a string rather than the usual level number. - """ - - if not isinstance(level, int): - # attempt to get the level if passed a string - try: - level = getattr(messages.constants, level.upper()) - except AttributeError: - levels = messages.constants.DEFAULT_TAGS.values() - levels_repr = ', '.join('`%s`' % l for l in levels) - raise ValueError('Bad message level string: `%s`. ' - 'Possible values are: %s' % (level, levels_repr)) - - messages.add_message(request, level, message, extra_tags=extra_tags, - fail_silently=fail_silently) - - 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 delete_model(self, request, obj): - """ - Given a model instance delete it from the database. - """ - obj.delete() - - def save_formset(self, request, form, formset, change): - """ - Given an inline formset save it to the database. - """ - formset.save() - - def save_related(self, request, form, formsets, change): - """ - Given the ``HttpRequest``, the parent ``ModelForm`` instance, the - list of inline formsets and a boolean value based on whether the - parent is being added or changed, save the related objects to the - database. Note that at this point save_form() and save_model() have - already been called. - """ - form.save_m2m() - for formset in formsets: - self.save_formset(request, form, formset, change=change) - - def render_change_form(self, request, context, add=False, change=False, form_url='', obj=None): - opts = self.model._meta - app_label = opts.app_label - preserved_filters = self.get_preserved_filters(request) - form_url = add_preserved_filters({'preserved_filters': preserved_filters, 'opts': opts}, form_url) - context.update({ - 'add': add, - 'change': change, - 'has_add_permission': self.has_add_permission(request), - 'has_change_permission': self.has_change_permission(request, obj), - 'has_delete_permission': self.has_delete_permission(request, obj), - 'has_file_field': True, # FIXME - this should check if form or formsets have a FileField, - 'has_absolute_url': hasattr(self.model, 'get_absolute_url'), - 'form_url': form_url, - 'opts': opts, - 'content_type_id': ContentType.objects.get_for_model(self.model).id, - 'save_as': self.save_as, - 'save_on_top': self.save_on_top, - }) - if add and self.add_form_template is not None: - form_template = self.add_form_template - else: - form_template = self.change_form_template - - return TemplateResponse(request, form_template or [ - "admin/%s/%s/change_form.html" % (app_label, opts.model_name), - "admin/%s/change_form.html" % app_label, - "admin/change_form.html" - ], context, current_app=self.admin_site.name) - - def response_add(self, request, obj, post_url_continue=None): - """ - Determines the HttpResponse for the add_view stage. - """ - opts = obj._meta - pk_value = obj._get_pk_val() - preserved_filters = self.get_preserved_filters(request) - - msg_dict = {'name': force_text(opts.verbose_name), 'obj': force_text(obj)} - # Here, we distinguish between different save types by checking for - # the presence of keys in request.POST. - if IS_POPUP_VAR in request.POST: - return SimpleTemplateResponse('admin/popup_response.html', { - 'pk_value': escape(pk_value), - 'obj': escapejs(obj) - }) - - elif "_continue" in request.POST: - msg = _('The %(name)s "%(obj)s" was added successfully. You may edit it again below.') % msg_dict - self.message_user(request, msg, messages.SUCCESS) - if post_url_continue is None: - post_url_continue = reverse('admin:%s_%s_change' % - (opts.app_label, opts.model_name), - args=(pk_value,), - current_app=self.admin_site.name) - post_url_continue = add_preserved_filters({'preserved_filters': preserved_filters, 'opts': opts}, post_url_continue) - return HttpResponseRedirect(post_url_continue) - - elif "_addanother" in request.POST: - msg = _('The %(name)s "%(obj)s" was added successfully. You may add another %(name)s below.') % msg_dict - self.message_user(request, msg, messages.SUCCESS) - redirect_url = request.path - redirect_url = add_preserved_filters({'preserved_filters': preserved_filters, 'opts': opts}, redirect_url) - return HttpResponseRedirect(redirect_url) - - else: - msg = _('The %(name)s "%(obj)s" was added successfully.') % msg_dict - self.message_user(request, msg, messages.SUCCESS) - return self.response_post_save_add(request, obj) - - def response_change(self, request, obj): - """ - Determines the HttpResponse for the change_view stage. - """ - opts = self.model._meta - pk_value = obj._get_pk_val() - preserved_filters = self.get_preserved_filters(request) - - msg_dict = {'name': force_text(opts.verbose_name), 'obj': force_text(obj)} - if "_continue" in request.POST: - msg = _('The %(name)s "%(obj)s" was changed successfully. You may edit it again below.') % msg_dict - self.message_user(request, msg, messages.SUCCESS) - redirect_url = request.path - redirect_url = add_preserved_filters({'preserved_filters': preserved_filters, 'opts': opts}, redirect_url) - return HttpResponseRedirect(redirect_url) - - elif "_saveasnew" in request.POST: - msg = _('The %(name)s "%(obj)s" was added successfully. You may edit it again below.') % msg_dict - self.message_user(request, msg, messages.SUCCESS) - redirect_url = reverse('admin:%s_%s_change' % - (opts.app_label, opts.model_name), - args=(pk_value,), - current_app=self.admin_site.name) - redirect_url = add_preserved_filters({'preserved_filters': preserved_filters, 'opts': opts}, redirect_url) - return HttpResponseRedirect(redirect_url) - - elif "_addanother" in request.POST: - msg = _('The %(name)s "%(obj)s" was changed successfully. You may add another %(name)s below.') % msg_dict - self.message_user(request, msg, messages.SUCCESS) - redirect_url = reverse('admin:%s_%s_add' % - (opts.app_label, opts.model_name), - current_app=self.admin_site.name) - redirect_url = add_preserved_filters({'preserved_filters': preserved_filters, 'opts': opts}, redirect_url) - return HttpResponseRedirect(redirect_url) - - else: - msg = _('The %(name)s "%(obj)s" was changed successfully.') % msg_dict - self.message_user(request, msg, messages.SUCCESS) - return self.response_post_save_change(request, obj) - - def response_post_save_add(self, request, obj): - """ - Figure out where to redirect after the 'Save' button has been pressed - when adding a new object. - """ - opts = self.model._meta - if self.has_change_permission(request, None): - post_url = reverse('admin:%s_%s_changelist' % - (opts.app_label, opts.model_name), - current_app=self.admin_site.name) - preserved_filters = self.get_preserved_filters(request) - post_url = add_preserved_filters({'preserved_filters': preserved_filters, 'opts': opts}, post_url) - else: - post_url = reverse('admin:index', - current_app=self.admin_site.name) - return HttpResponseRedirect(post_url) - - def response_post_save_change(self, request, obj): - """ - Figure out where to redirect after the 'Save' button has been pressed - when editing an existing object. - """ - opts = self.model._meta - - if self.has_change_permission(request, None): - post_url = reverse('admin:%s_%s_changelist' % - (opts.app_label, opts.model_name), - current_app=self.admin_site.name) - preserved_filters = self.get_preserved_filters(request) - post_url = add_preserved_filters({'preserved_filters': preserved_filters, 'opts': opts}, post_url) - else: - post_url = reverse('admin:index', - current_app=self.admin_site.name) - return HttpResponseRedirect(post_url) - - def response_action(self, request, queryset): - """ - Handle an admin action. This is called if a request is POSTed to the - changelist; it returns an HttpResponse if the action was handled, and - None otherwise. - """ - - # There can be multiple action forms on the page (at the top - # and bottom of the change list, for example). Get the action - # whose button was pushed. - try: - action_index = int(request.POST.get('index', 0)) - except ValueError: - action_index = 0 - - # Construct the action form. - data = request.POST.copy() - data.pop(helpers.ACTION_CHECKBOX_NAME, None) - data.pop("index", None) - - # Use the action whose button was pushed - try: - data.update({'action': data.getlist('action')[action_index]}) - except IndexError: - # If we didn't get an action from the chosen form that's invalid - # POST data, so by deleting action it'll fail the validation check - # below. So no need to do anything here - pass - - action_form = self.action_form(data, auto_id=None) - action_form.fields['action'].choices = self.get_action_choices(request) - - # If the form's valid we can handle the action. - if action_form.is_valid(): - action = action_form.cleaned_data['action'] - select_across = action_form.cleaned_data['select_across'] - func = self.get_actions(request)[action][0] - - # Get the list of selected PKs. If nothing's selected, we can't - # perform an action on it, so bail. Except we want to perform - # the action explicitly on all objects. - selected = request.POST.getlist(helpers.ACTION_CHECKBOX_NAME) - if not selected and not select_across: - # Reminder that something needs to be selected or nothing will happen - msg = _("Items must be selected in order to perform " - "actions on them. No items have been changed.") - self.message_user(request, msg, messages.WARNING) - return None - - if not select_across: - # Perform the action only on the selected objects - queryset = queryset.filter(pk__in=selected) - - response = func(self, request, queryset) - - # Actions may return an HttpResponse-like object, which will be - # used as the response from the POST. If not, we'll be a good - # little HTTP citizen and redirect back to the changelist page. - if isinstance(response, HttpResponseBase): - return response - else: - return HttpResponseRedirect(request.get_full_path()) - else: - msg = _("No action selected.") - self.message_user(request, msg, messages.WARNING) - return None - - @csrf_protect_m - @transaction.atomic - 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 = [] - inline_instances = self.get_inline_instances(request, None) - if request.method == 'POST': - form = ModelForm(request.POST, request.FILES) - if form.is_valid(): - new_object = self.save_form(request, form, change=False) - form_validated = True - else: - form_validated = False - new_object = self.model() - prefixes = {} - for FormSet, inline in zip(self.get_formsets(request), inline_instances): - prefix = FormSet.get_default_prefix() - prefixes[prefix] = prefixes.get(prefix, 0) + 1 - if prefixes[prefix] != 1 or not prefix: - prefix = "%s-%s" % (prefix, prefixes[prefix]) - formset = FormSet(data=request.POST, files=request.FILES, - instance=new_object, - save_as_new="_saveasnew" in request.POST, - prefix=prefix, queryset=inline.get_queryset(request)) - formsets.append(formset) - if all_valid(formsets) and form_validated: - self.save_model(request, new_object, form, False) - self.save_related(request, form, formsets, False) - self.log_addition(request, new_object) - return self.response_add(request, new_object) - else: - # Prepare the dict of initial data from the request. - # We have to special-case M2Ms as a list of comma-separated PKs. - initial = dict(request.GET.items()) - for k in initial: - try: - f = opts.get_field(k) - except models.FieldDoesNotExist: - continue - if isinstance(f, models.ManyToManyField): - initial[k] = initial[k].split(",") - form = ModelForm(initial=initial) - prefixes = {} - for FormSet, inline in zip(self.get_formsets(request), inline_instances): - prefix = FormSet.get_default_prefix() - prefixes[prefix] = prefixes.get(prefix, 0) + 1 - if prefixes[prefix] != 1 or not prefix: - prefix = "%s-%s" % (prefix, prefixes[prefix]) - formset = FormSet(instance=self.model(), prefix=prefix, - queryset=inline.get_queryset(request)) - formsets.append(formset) - - adminForm = helpers.AdminForm(form, list(self.get_fieldsets(request)), - self.get_prepopulated_fields(request), - self.get_readonly_fields(request), - model_admin=self) - media = self.media + adminForm.media - - inline_admin_formsets = [] - for inline, formset in zip(inline_instances, formsets): - fieldsets = list(inline.get_fieldsets(request)) - readonly = list(inline.get_readonly_fields(request)) - prepopulated = dict(inline.get_prepopulated_fields(request)) - inline_admin_formset = helpers.InlineAdminFormSet(inline, formset, - fieldsets, prepopulated, readonly, model_admin=self) - inline_admin_formsets.append(inline_admin_formset) - media = media + inline_admin_formset.media - - context = { - 'title': _('Add %s') % force_text(opts.verbose_name), - 'adminform': adminForm, - 'is_popup': IS_POPUP_VAR in request.REQUEST, - 'media': media, - 'inline_admin_formsets': inline_admin_formsets, - 'errors': helpers.AdminErrorList(form, formsets), - 'app_label': opts.app_label, - 'preserved_filters': self.get_preserved_filters(request), - } - context.update(extra_context or {}) - return self.render_change_form(request, context, form_url=form_url, add=True) - - @csrf_protect_m - @transaction.atomic - def change_view(self, request, object_id, form_url='', extra_context=None): - "The 'change' admin view for this model." - model = self.model - opts = model._meta - - obj = self.get_object(request, unquote(object_id)) - - 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_text(opts.verbose_name), 'key': escape(object_id)}) - - if request.method == 'POST' and "_saveasnew" in request.POST: - return self.add_view(request, form_url=reverse('admin:%s_%s_add' % - (opts.app_label, opts.model_name), - current_app=self.admin_site.name)) - - ModelForm = self.get_form(request, obj) - formsets = [] - inline_instances = self.get_inline_instances(request, obj) - if request.method == 'POST': - form = ModelForm(request.POST, request.FILES, instance=obj) - if form.is_valid(): - form_validated = True - new_object = self.save_form(request, form, change=True) - else: - form_validated = False - new_object = obj - prefixes = {} - for FormSet, inline in zip(self.get_formsets(request, new_object), inline_instances): - prefix = FormSet.get_default_prefix() - prefixes[prefix] = prefixes.get(prefix, 0) + 1 - if prefixes[prefix] != 1 or not prefix: - prefix = "%s-%s" % (prefix, prefixes[prefix]) - formset = FormSet(request.POST, request.FILES, - instance=new_object, prefix=prefix, - queryset=inline.get_queryset(request)) - - formsets.append(formset) - - if all_valid(formsets) and form_validated: - self.save_model(request, new_object, form, True) - self.save_related(request, form, formsets, 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 = {} - for FormSet, inline in zip(self.get_formsets(request, obj), inline_instances): - prefix = FormSet.get_default_prefix() - prefixes[prefix] = prefixes.get(prefix, 0) + 1 - if prefixes[prefix] != 1 or not prefix: - prefix = "%s-%s" % (prefix, prefixes[prefix]) - formset = FormSet(instance=obj, prefix=prefix, - queryset=inline.get_queryset(request)) - formsets.append(formset) - - adminForm = helpers.AdminForm(form, self.get_fieldsets(request, obj), - self.get_prepopulated_fields(request, obj), - self.get_readonly_fields(request, obj), - model_admin=self) - media = self.media + adminForm.media - - inline_admin_formsets = [] - for inline, formset in zip(inline_instances, formsets): - fieldsets = list(inline.get_fieldsets(request, obj)) - readonly = list(inline.get_readonly_fields(request, obj)) - prepopulated = dict(inline.get_prepopulated_fields(request, obj)) - inline_admin_formset = helpers.InlineAdminFormSet(inline, formset, - fieldsets, prepopulated, readonly, model_admin=self) - inline_admin_formsets.append(inline_admin_formset) - media = media + inline_admin_formset.media - - context = { - 'title': _('Change %s') % force_text(opts.verbose_name), - 'adminform': adminForm, - 'object_id': object_id, - 'original': obj, - 'is_popup': IS_POPUP_VAR in request.REQUEST, - 'media': media, - 'inline_admin_formsets': inline_admin_formsets, - 'errors': helpers.AdminErrorList(form, formsets), - 'app_label': opts.app_label, - 'preserved_filters': self.get_preserved_filters(request), - } - context.update(extra_context or {}) - return self.render_change_form(request, context, change=True, obj=obj, form_url=form_url) - - @csrf_protect_m - def changelist_view(self, request, extra_context=None): - """ - The 'change list' admin view for this model. - """ - from django.contrib.admin.views.main import ERROR_FLAG - opts = self.model._meta - app_label = opts.app_label - if not self.has_change_permission(request, None): - raise PermissionDenied - - list_display = self.get_list_display(request) - list_display_links = self.get_list_display_links(request, list_display) - list_filter = self.get_list_filter(request) - - # Check actions to see if any are available on this changelist - actions = self.get_actions(request) - if actions: - # Add the action checkboxes if there are any actions available. - list_display = ['action_checkbox'] + list(list_display) - - ChangeList = self.get_changelist(request) - try: - cl = ChangeList(request, self.model, list_display, - list_display_links, list_filter, self.date_hierarchy, - self.search_fields, self.list_select_related, - self.list_per_page, self.list_max_show_all, self.list_editable, - self) - except IncorrectLookupParameters: - # Wacky lookup parameters were given, so redirect to the main - # changelist page, without parameters, and pass an 'invalid=1' - # parameter via the query string. If wacky parameters were given - # and the 'invalid=1' parameter was already in the query string, - # something is screwed up with the database, so display an error - # page. - if ERROR_FLAG in request.GET.keys(): - return SimpleTemplateResponse('admin/invalid_setup.html', { - 'title': _('Database error'), - }) - return HttpResponseRedirect(request.path + '?' + ERROR_FLAG + '=1') - - # If the request was POSTed, this might be a bulk action or a bulk - # edit. Try to look up an action or confirmation first, but if this - # isn't an action the POST will fall through to the bulk edit check, - # below. - action_failed = False - selected = request.POST.getlist(helpers.ACTION_CHECKBOX_NAME) - - # Actions with no confirmation - if (actions and request.method == 'POST' and - 'index' in request.POST and '_save' not in request.POST): - if selected: - response = self.response_action(request, queryset=cl.get_queryset(request)) - if response: - return response - else: - action_failed = True - else: - msg = _("Items must be selected in order to perform " - "actions on them. No items have been changed.") - self.message_user(request, msg, messages.WARNING) - action_failed = True - - # Actions with confirmation - if (actions and request.method == 'POST' and - helpers.ACTION_CHECKBOX_NAME in request.POST and - 'index' not in request.POST and '_save' not in request.POST): - if selected: - response = self.response_action(request, queryset=cl.get_queryset(request)) - if response: - return response - else: - action_failed = True - - # 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 cl.list_editable and - '_save' in request.POST and not action_failed): - FormSet = self.get_changelist_formset(request) - formset = cl.formset = FormSet(request.POST, request.FILES, queryset=cl.result_list) - if formset.is_valid(): - changecount = 0 - for form in formset.forms: - if form.has_changed(): - obj = self.save_form(request, form, change=True) - self.save_model(request, obj, form, change=True) - self.save_related(request, form, formsets=[], change=True) - change_msg = self.construct_change_message(request, form, None) - self.log_change(request, obj, change_msg) - changecount += 1 - - if changecount: - if changecount == 1: - name = force_text(opts.verbose_name) - else: - name = force_text(opts.verbose_name_plural) - msg = ungettext("%(count)s %(name)s was changed successfully.", - "%(count)s %(name)s were changed successfully.", - changecount) % {'count': changecount, - 'name': name, - 'obj': force_text(obj)} - self.message_user(request, msg, messages.SUCCESS) - - return HttpResponseRedirect(request.get_full_path()) - - # Handle GET -- construct a formset for display. - elif cl.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 - else: - media = self.media - - # Build the action form and populate it with available actions. - if actions: - action_form = self.action_form(auto_id=None) - action_form.fields['action'].choices = self.get_action_choices(request) - else: - action_form = None - - selection_note_all = ungettext('%(total_count)s selected', - 'All %(total_count)s selected', cl.result_count) - - context = { - 'module_name': force_text(opts.verbose_name_plural), - 'selection_note': _('0 of %(cnt)s selected') % {'cnt': len(cl.result_list)}, - 'selection_note_all': selection_note_all % {'total_count': cl.result_count}, - 'title': cl.title, - 'is_popup': cl.is_popup, - 'cl': cl, - 'media': media, - 'has_add_permission': self.has_add_permission(request), - 'opts': cl.opts, - 'app_label': app_label, - 'action_form': action_form, - 'actions_on_top': self.actions_on_top, - 'actions_on_bottom': self.actions_on_bottom, - 'actions_selection_counter': self.actions_selection_counter, - 'preserved_filters': self.get_preserved_filters(request), - } - context.update(extra_context or {}) - - return TemplateResponse(request, self.change_list_template or [ - 'admin/%s/%s/change_list.html' % (app_label, opts.model_name), - 'admin/%s/change_list.html' % app_label, - 'admin/change_list.html' - ], context, current_app=self.admin_site.name) - - @csrf_protect_m - @transaction.atomic - 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 - - obj = self.get_object(request, unquote(object_id)) - - 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_text(opts.verbose_name), 'key': escape(object_id)} - ) - - using = router.db_for_write(self.model) - - # Populate deleted_objects, a data structure of all related objects that - # will also be deleted. - (deleted_objects, perms_needed, protected) = get_deleted_objects( - [obj], opts, request.user, self.admin_site, using) - - if request.POST: # The user has already confirmed the deletion. - if perms_needed: - raise PermissionDenied - obj_display = force_text(obj) - self.log_deletion(request, obj, obj_display) - self.delete_model(request, obj) - - self.message_user(request, _( - 'The %(name)s "%(obj)s" was deleted successfully.') % { - 'name': force_text(opts.verbose_name), - 'obj': force_text(obj_display)}, - messages.SUCCESS) - - if self.has_change_permission(request, None): - post_url = reverse('admin:%s_%s_changelist' % - (opts.app_label, opts.model_name), - current_app=self.admin_site.name) - preserved_filters = self.get_preserved_filters(request) - post_url = add_preserved_filters( - {'preserved_filters': preserved_filters, 'opts': opts}, post_url - ) - else: - post_url = reverse('admin:index', - current_app=self.admin_site.name) - return HttpResponseRedirect(post_url) - - object_name = force_text(opts.verbose_name) - - if perms_needed or protected: - title = _("Cannot delete %(name)s") % {"name": object_name} - else: - title = _("Are you sure?") - - context = { - "title": title, - "object_name": object_name, - "object": obj, - "deleted_objects": deleted_objects, - "perms_lacking": perms_needed, - "protected": protected, - "opts": opts, - "app_label": app_label, - 'preserved_filters': self.get_preserved_filters(request), - } - context.update(extra_context or {}) - - return TemplateResponse(request, self.delete_confirmation_template or [ - "admin/%s/%s/delete_confirmation.html" % (app_label, opts.model_name), - "admin/%s/delete_confirmation.html" % app_label, - "admin/delete_confirmation.html" - ], context, current_app=self.admin_site.name) - - def history_view(self, request, object_id, extra_context=None): - "The 'history' admin view for this model." - from django.contrib.admin.models import LogEntry - # First check if the user can see this history. - model = self.model - obj = get_object_or_404(model, pk=unquote(object_id)) - - if not self.has_change_permission(request, obj): - raise PermissionDenied - - # Then get the history for this object. - opts = model._meta - app_label = opts.app_label - action_list = LogEntry.objects.filter( - object_id=unquote(object_id), - content_type__id__exact=ContentType.objects.get_for_model(model).id - ).select_related().order_by('action_time') - - context = { - 'title': _('Change history: %s') % force_text(obj), - 'action_list': action_list, - 'module_name': capfirst(force_text(opts.verbose_name_plural)), - 'object': obj, - 'app_label': app_label, - 'opts': opts, - 'preserved_filters': self.get_preserved_filters(request), - } - context.update(extra_context or {}) - return TemplateResponse(request, self.object_history_template or [ - "admin/%s/%s/object_history.html" % (app_label, opts.model_name), - "admin/%s/object_history.html" % app_label, - "admin/object_history.html" - ], context, current_app=self.admin_site.name) - - -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. - """ - model = None - fk_name = None - formset = BaseInlineFormSet - extra = 3 - max_num = None - template = None - verbose_name = None - verbose_name_plural = None - can_delete = True - - # validation - validator_class = validation.InlineValidator - - def __init__(self, parent_model, admin_site): - self.admin_site = admin_site - self.parent_model = parent_model - self.opts = self.model._meta - super(InlineModelAdmin, self).__init__() - if self.verbose_name is None: - 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 - - @property - def media(self): - extra = '' if settings.DEBUG else '.min' - js = ['jquery%s.js' % extra, 'jquery.init.js', 'inlines%s.js' % extra] - if self.prepopulated_fields: - js.extend(['urlify.js', 'prepopulate%s.js' % extra]) - if self.filter_vertical or self.filter_horizontal: - js.extend(['SelectBox.js', 'SelectFilter2.js']) - return forms.Media(js=[static('admin/js/%s' % url) for url in js]) - - def get_extra(self, request, obj=None, **kwargs): - """Hook for customizing the number of extra inline forms.""" - return self.extra - - def get_max_num(self, request, obj=None, **kwargs): - """Hook for customizing the max number of extra inline forms.""" - return self.max_num - - def get_formset(self, request, obj=None, **kwargs): - """Returns a BaseInlineFormSet class for use in admin add/change views.""" - if 'fields' in kwargs: - fields = kwargs.pop('fields') - else: - fields = flatten_fieldsets(self.get_fieldsets(request, obj)) - if self.exclude is None: - exclude = [] - else: - exclude = list(self.exclude) - exclude.extend(self.get_readonly_fields(request, obj)) - if self.exclude is None and hasattr(self.form, '_meta') and self.form._meta.exclude: - # Take the custom ModelForm's Meta.exclude into account only if the - # InlineModelAdmin doesn't define its own. - exclude.extend(self.form._meta.exclude) - # if exclude is an empty list we use None, since that's the actual - # default - exclude = exclude or None - can_delete = self.can_delete and self.has_delete_permission(request, obj) - defaults = { - "form": self.form, - "formset": self.formset, - "fk_name": self.fk_name, - "fields": fields, - "exclude": exclude, - "formfield_callback": partial(self.formfield_for_dbfield, request=request), - "extra": self.get_extra(request, obj, **kwargs), - "max_num": self.get_max_num(request, obj, **kwargs), - "can_delete": can_delete, - } - - defaults.update(kwargs) - base_model_form = defaults['form'] - - class DeleteProtectedModelForm(base_model_form): - def hand_clean_DELETE(self): - """ - We don't validate the 'DELETE' field itself because on - templates it's not rendered using the field information, but - just using a generic "deletion_field" of the InlineModelAdmin. - """ - if self.cleaned_data.get(DELETION_FIELD_NAME, False): - using = router.db_for_write(self._meta.model) - collector = NestedObjects(using=using) - collector.collect([self.instance]) - if collector.protected: - objs = [] - for p in collector.protected: - objs.append( - # Translators: Model verbose name and instance representation, suitable to be an item in a list - _('%(class_name)s %(instance)s') % { - 'class_name': p._meta.verbose_name, - 'instance': p} - ) - params = {'class_name': self._meta.model._meta.verbose_name, - 'instance': self.instance, - 'related_objects': get_text_list(objs, _('and'))} - msg = _("Deleting %(class_name)s %(instance)s would require " - "deleting the following protected related objects: " - "%(related_objects)s") - raise ValidationError(msg, code='deleting_protected', params=params) - - def is_valid(self): - result = super(DeleteProtectedModelForm, self).is_valid() - self.hand_clean_DELETE() - return result - - defaults['form'] = DeleteProtectedModelForm - - if defaults['fields'] is None and not modelform_defines_fields(defaults['form']): - defaults['fields'] = forms.ALL_FIELDS - - 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, obj, fields=None).form - fields = list(form.base_fields) + list(self.get_readonly_fields(request, obj)) - return [(None, {'fields': fields})] - - def get_queryset(self, request): - queryset = super(InlineModelAdmin, self).get_queryset(request) - if not self.has_change_permission(request): - queryset = queryset.none() - return queryset - - def has_add_permission(self, request): - if self.opts.auto_created: - # We're checking the rights to an auto-created intermediate model, - # which doesn't have its own individual permissions. The user needs - # to have the change permission for the related model in order to - # be able to do anything with the intermediate model. - return self.has_change_permission(request) - return super(InlineModelAdmin, self).has_add_permission(request) - - def has_change_permission(self, request, obj=None): - opts = self.opts - if opts.auto_created: - # The model was auto-created as intermediary for a - # ManyToMany-relationship, find the target model - for field in opts.fields: - if field.rel and field.rel.to != self.parent_model: - opts = field.rel.to._meta - break - codename = get_permission_codename('change', opts) - return request.user.has_perm("%s.%s" % (opts.app_label, codename)) - - def has_delete_permission(self, request, obj=None): - if self.opts.auto_created: - # We're checking the rights to an auto-created intermediate model, - # which doesn't have its own individual permissions. The user needs - # to have the change permission for the related model in order to - # be able to do anything with the intermediate model. - return self.has_change_permission(request, obj) - return super(InlineModelAdmin, self).has_delete_permission(request, obj) - - -class StackedInline(InlineModelAdmin): - template = 'admin/edit_inline/stacked.html' - - -class TabularInline(InlineModelAdmin): - template = 'admin/edit_inline/tabular.html' |