PageRenderTime 219ms CodeModel.GetById 31ms app.highlight 152ms RepoModel.GetById 18ms app.codeStats 1ms

/django/contrib/admin/options.py

https://code.google.com/p/mango-py/
Python | 1363 lines | 1131 code | 86 blank | 146 comment | 163 complexity | 83266c79b4cba8e524074ac86a97f947 MD5 | raw file

Large files files are truncated, but you can click here to view the full file

   1from django import forms, template
   2from django.forms.formsets import all_valid
   3from django.forms.models import (modelform_factory, modelformset_factory,
   4    inlineformset_factory, BaseInlineFormSet)
   5from django.contrib.contenttypes.models import ContentType
   6from django.contrib.admin import widgets, helpers
   7from django.contrib.admin.util import unquote, flatten_fieldsets, get_deleted_objects, model_format_dict
   8from django.contrib import messages
   9from django.views.decorators.csrf import csrf_protect
  10from django.core.exceptions import PermissionDenied, ValidationError
  11from django.core.paginator import Paginator
  12from django.db import models, transaction, router
  13from django.db.models.related import RelatedObject
  14from django.db.models.fields import BLANK_CHOICE_DASH, FieldDoesNotExist
  15from django.db.models.sql.constants import LOOKUP_SEP, QUERY_TERMS
  16from django.http import Http404, HttpResponse, HttpResponseRedirect
  17from django.shortcuts import get_object_or_404, render_to_response
  18from django.utils.decorators import method_decorator
  19from django.utils.datastructures import SortedDict
  20from django.utils.functional import update_wrapper
  21from django.utils.html import escape, escapejs
  22from django.utils.safestring import mark_safe
  23from django.utils.functional import curry
  24from django.utils.text import capfirst, get_text_list
  25from django.utils.translation import ugettext as _
  26from django.utils.translation import ungettext
  27from django.utils.encoding import force_unicode
  28
  29HORIZONTAL, VERTICAL = 1, 2
  30# returns the <ul> class for a given radio_admin field
  31get_ul_class = lambda x: 'radiolist%s' % ((x == HORIZONTAL) and ' inline' or '')
  32
  33class IncorrectLookupParameters(Exception):
  34    pass
  35
  36# Defaults for formfield_overrides. ModelAdmin subclasses can change this
  37# by adding to ModelAdmin.formfield_overrides.
  38
  39FORMFIELD_FOR_DBFIELD_DEFAULTS = {
  40    models.DateTimeField: {
  41        'form_class': forms.SplitDateTimeField,
  42        'widget': widgets.AdminSplitDateTime
  43    },
  44    models.DateField:       {'widget': widgets.AdminDateWidget},
  45    models.TimeField:       {'widget': widgets.AdminTimeWidget},
  46    models.TextField:       {'widget': widgets.AdminTextareaWidget},
  47    models.URLField:        {'widget': widgets.AdminURLFieldWidget},
  48    models.IntegerField:    {'widget': widgets.AdminIntegerFieldWidget},
  49    models.BigIntegerField: {'widget': widgets.AdminIntegerFieldWidget},
  50    models.CharField:       {'widget': widgets.AdminTextInputWidget},
  51    models.ImageField:      {'widget': widgets.AdminFileWidget},
  52    models.FileField:       {'widget': widgets.AdminFileWidget},
  53}
  54
  55csrf_protect_m = method_decorator(csrf_protect)
  56
  57class InlineAdminField(object):
  58    def __init__(self, name):
  59        self.name = name
  60        self.is_inline = True
  61        self.empty_widget = True
  62        self.widget = None
  63        #self.required = True
  64        self.help_text = ''
  65
  66class BaseModelAdmin(object):
  67    """Functionality common to both ModelAdmin and InlineAdmin."""
  68    __metaclass__ = forms.MediaDefiningClass
  69
  70    raw_id_fields = ()
  71    fields = None
  72    exclude = None
  73    fieldsets = None
  74    form = forms.ModelForm
  75    filter_vertical = ()
  76    filter_horizontal = ()
  77    radio_fields = {}
  78    prepopulated_fields = {}
  79    formfield_overrides = {}
  80    readonly_fields = ()
  81    ordering = None
  82
  83    def __init__(self):
  84        overrides = FORMFIELD_FOR_DBFIELD_DEFAULTS.copy()
  85        overrides.update(self.formfield_overrides)
  86        self.formfield_overrides = overrides
  87
  88    def formfield_for_dbfield(self, db_field, **kwargs):
  89        """
  90        Hook for specifying the form Field instance for a given database Field
  91        instance.
  92
  93        If kwargs are given, they're passed to the form Field's constructor.
  94        """
  95        request = kwargs.pop("request", None)
  96
  97        # If the field specifies choices, we don't need to look for special
  98        # admin widgets - we just need to use a select widget of some kind.
  99        if db_field.choices:
 100            return self.formfield_for_choice_field(db_field, request, **kwargs)
 101
 102        # ForeignKey or ManyToManyFields
 103        if isinstance(db_field, (models.ForeignKey, models.ManyToManyField)):
 104            # Combine the field kwargs with any options for formfield_overrides.
 105            # Make sure the passed in **kwargs override anything in
 106            # formfield_overrides because **kwargs is more specific, and should
 107            # always win.
 108            if db_field.__class__ in self.formfield_overrides:
 109                kwargs = dict(self.formfield_overrides[db_field.__class__], **kwargs)
 110
 111            # Get the correct formfield.
 112            if isinstance(db_field, models.ForeignKey):
 113                formfield = self.formfield_for_foreignkey(db_field, request, **kwargs)
 114            elif isinstance(db_field, models.ManyToManyField):
 115                formfield = self.formfield_for_manytomany(db_field, request, **kwargs)
 116
 117            # For non-raw_id fields, wrap the widget with a wrapper that adds
 118            # extra HTML -- the "add other" interface -- to the end of the
 119            # rendered output. formfield can be None if it came from a
 120            # OneToOneField with parent_link=True or a M2M intermediary.
 121            if formfield and db_field.name not in self.raw_id_fields:
 122                related_modeladmin = self.admin_site._registry.get(
 123                                                            db_field.rel.to)
 124                can_add_related = bool(related_modeladmin and
 125                            related_modeladmin.has_add_permission(request))
 126                formfield.widget = widgets.RelatedFieldWidgetWrapper(
 127                            formfield.widget, db_field.rel, self.admin_site,
 128                            can_add_related=can_add_related)
 129
 130            return formfield
 131
 132        # If we've got overrides for the formfield defined, use 'em. **kwargs
 133        # passed to formfield_for_dbfield override the defaults.
 134        for klass in db_field.__class__.mro():
 135            if klass in self.formfield_overrides:
 136                kwargs = dict(self.formfield_overrides[klass], **kwargs)
 137                return db_field.formfield(**kwargs)
 138
 139        # For any other type of field, just call its formfield() method.
 140        return db_field.formfield(**kwargs)
 141
 142    def formfield_for_choice_field(self, db_field, request=None, **kwargs):
 143        """
 144        Get a form Field for a database Field that has declared choices.
 145        """
 146        # If the field is named as a radio_field, use a RadioSelect
 147        if db_field.name in self.radio_fields:
 148            # Avoid stomping on custom widget/choices arguments.
 149            if 'widget' not in kwargs:
 150                kwargs['widget'] = widgets.AdminRadioSelect(attrs={
 151                    'class': get_ul_class(self.radio_fields[db_field.name]),
 152                })
 153            if 'choices' not in kwargs:
 154                kwargs['choices'] = db_field.get_choices(
 155                    include_blank = db_field.blank,
 156                    blank_choice=[('', _('None'))]
 157                )
 158        return db_field.formfield(**kwargs)
 159
 160    def formfield_for_foreignkey(self, db_field, request=None, **kwargs):
 161        """
 162        Get a form Field for a ForeignKey.
 163        """
 164        db = kwargs.get('using')
 165        if db_field.name in self.raw_id_fields:
 166            kwargs['widget'] = widgets.ForeignKeyRawIdWidget(db_field.rel, using=db)
 167        elif db_field.name in self.radio_fields:
 168            kwargs['widget'] = widgets.AdminRadioSelect(attrs={
 169                'class': get_ul_class(self.radio_fields[db_field.name]),
 170            })
 171            kwargs['empty_label'] = db_field.blank and _('None') or None
 172
 173        return db_field.formfield(**kwargs)
 174
 175    def formfield_for_manytomany(self, db_field, request=None, **kwargs):
 176        """
 177        Get a form Field for a ManyToManyField.
 178        """
 179        # If it uses an intermediary model that isn't auto created, don't show
 180        # a field in admin.
 181        if not db_field.rel.through._meta.auto_created:
 182            return None
 183        db = kwargs.get('using')
 184
 185        if db_field.name in self.raw_id_fields:
 186            kwargs['widget'] = widgets.ManyToManyRawIdWidget(db_field.rel, using=db)
 187            kwargs['help_text'] = ''
 188        elif db_field.name in (list(self.filter_vertical) + list(self.filter_horizontal)):
 189            kwargs['widget'] = widgets.FilteredSelectMultiple(db_field.verbose_name, (db_field.name in self.filter_vertical))
 190
 191        return db_field.formfield(**kwargs)
 192
 193    def _declared_fieldsets(self):
 194        if self.fieldsets:
 195            return self.fieldsets
 196        elif self.fields:
 197            return [(None, {'fields': self.fields})]
 198        return None
 199    declared_fieldsets = property(_declared_fieldsets)
 200
 201    def get_readonly_fields(self, request, obj=None):
 202        return self.readonly_fields
 203
 204    def queryset(self, request):
 205        """
 206        Returns a QuerySet of all model instances that can be edited by the
 207        admin site. This is used by changelist_view.
 208        """
 209        qs = self.model._default_manager.get_query_set()
 210        # TODO: this should be handled by some parameter to the ChangeList.
 211        ordering = self.ordering or () # otherwise we might try to *None, which is bad ;)
 212        if ordering:
 213            qs = qs.order_by(*ordering)
 214        return qs
 215
 216    def lookup_allowed(self, lookup, value):
 217        model = self.model
 218        # Check FKey lookups that are allowed, so that popups produced by
 219        # ForeignKeyRawIdWidget, on the basis of ForeignKey.limit_choices_to,
 220        # are allowed to work.
 221        for l in model._meta.related_fkey_lookups:
 222            for k, v in widgets.url_params_from_lookup_dict(l).items():
 223                if k == lookup and v == value:
 224                    return True
 225
 226        parts = lookup.split(LOOKUP_SEP)
 227
 228        # Last term in lookup is a query term (__exact, __startswith etc)
 229        # This term can be ignored.
 230        if len(parts) > 1 and parts[-1] in QUERY_TERMS:
 231            parts.pop()
 232
 233        # Special case -- foo__id__exact and foo__id queries are implied
 234        # if foo has been specificially included in the lookup list; so
 235        # drop __id if it is the last part. However, first we need to find
 236        # the pk attribute name.
 237        pk_attr_name = None
 238        for part in parts[:-1]:
 239            field, _, _, _ = model._meta.get_field_by_name(part)
 240            if hasattr(field, 'rel'):
 241                model = field.rel.to
 242                pk_attr_name = model._meta.pk.name
 243            elif isinstance(field, RelatedObject):
 244                model = field.model
 245                pk_attr_name = model._meta.pk.name
 246            else:
 247                pk_attr_name = None
 248        if pk_attr_name and len(parts) > 1 and parts[-1] == pk_attr_name:
 249            parts.pop()
 250
 251        try:
 252            self.model._meta.get_field_by_name(parts[0])
 253        except FieldDoesNotExist:
 254            # Lookups on non-existants fields are ok, since they're ignored
 255            # later.
 256            return True
 257        else:
 258            if len(parts) == 1:
 259                return True
 260            clean_lookup = LOOKUP_SEP.join(parts)
 261            return clean_lookup in self.list_filter or clean_lookup == self.date_hierarchy
 262
 263class ModelAdmin(BaseModelAdmin):
 264    "Encapsulates all admin options and functionality for a given model."
 265
 266    list_display = ('__str__',)
 267    list_display_links = ()
 268    list_filter = ()
 269    list_select_related = False
 270    list_per_page = 100
 271    list_editable = ()
 272    search_fields = ()
 273    date_hierarchy = None
 274    save_as = False
 275    save_on_top = False
 276    paginator = Paginator
 277    inlines = []
 278
 279    # Custom templates (designed to be over-ridden in subclasses)
 280    add_form_template = None
 281    change_form_template = None
 282    change_list_template = None
 283    delete_confirmation_template = None
 284    delete_selected_confirmation_template = None
 285    object_history_template = None
 286
 287    # Actions
 288    actions = []
 289    action_form = helpers.ActionForm
 290    actions_on_top = True
 291    actions_on_bottom = False
 292    actions_selection_counter = True
 293
 294    def __init__(self, model, admin_site):
 295        self.model = model
 296        self.opts = model._meta
 297        self.admin_site = admin_site
 298        self.inline_instances = []
 299        for inline_class in self.inlines:
 300            inline_instance = inline_class(self.model, self.admin_site)
 301            self.inline_instances.append(inline_instance)
 302        if 'action_checkbox' not in self.list_display and self.actions is not None:
 303            self.list_display = ['action_checkbox'] +  list(self.list_display)
 304        if not self.list_display_links:
 305            for name in self.list_display:
 306                if name != 'action_checkbox':
 307                    self.list_display_links = [name]
 308                    break
 309        super(ModelAdmin, self).__init__()
 310
 311    def get_urls(self):
 312        from django.conf.urls.defaults import patterns, url
 313
 314        def wrap(view):
 315            def wrapper(*args, **kwargs):
 316                return self.admin_site.admin_view(view)(*args, **kwargs)
 317            return update_wrapper(wrapper, view)
 318
 319        info = self.model._meta.app_label, self.model._meta.module_name
 320
 321        urlpatterns = patterns('',
 322            url(r'^$',
 323                wrap(self.changelist_view),
 324                name='%s_%s_changelist' % info),
 325            url(r'^add/$',
 326                wrap(self.add_view),
 327                name='%s_%s_add' % info),
 328            url(r'^(.+)/history/$',
 329                wrap(self.history_view),
 330                name='%s_%s_history' % info),
 331            url(r'^(.+)/delete/$',
 332                wrap(self.delete_view),
 333                name='%s_%s_delete' % info),
 334            url(r'^(.+)/$',
 335                wrap(self.change_view),
 336                name='%s_%s_change' % info),
 337        )
 338        return urlpatterns
 339
 340    def urls(self):
 341        return self.get_urls()
 342    urls = property(urls)
 343
 344    def _media(self):
 345        from django.conf import settings
 346
 347        js = ['js/core.js', 'js/admin/RelatedObjectLookups.js',
 348              'js/jquery.min.js', 'js/jquery.init.js']
 349        if self.actions is not None:
 350            js.extend(['js/actions.min.js'])
 351        if self.prepopulated_fields:
 352            js.append('js/urlify.js')
 353            js.append('js/prepopulate.min.js')
 354        if self.opts.get_ordered_objects():
 355            js.extend(['js/getElementsBySelector.js', 'js/dom-drag.js' , 'js/admin/ordering.js'])
 356
 357        return forms.Media(js=['%s%s' % (settings.ADMIN_MEDIA_PREFIX, url) for url in js])
 358    media = property(_media)
 359
 360    def has_add_permission(self, request):
 361        """
 362        Returns True if the given request has permission to add an object.
 363        Can be overriden by the user in subclasses.
 364        """
 365        opts = self.opts
 366        return request.user.has_perm(opts.app_label + '.' + opts.get_add_permission())
 367
 368    def has_change_permission(self, request, obj=None):
 369        """
 370        Returns True if the given request has permission to change the given
 371        Django model instance, the default implementation doesn't examine the
 372        `obj` parameter.
 373
 374        Can be overriden by the user in subclasses. In such case it should
 375        return True if the given request has permission to change the `obj`
 376        model instance. If `obj` is None, this should return True if the given
 377        request has permission to change *any* object of the given type.
 378        """
 379        opts = self.opts
 380        return request.user.has_perm(opts.app_label + '.' + opts.get_change_permission())
 381
 382    def has_delete_permission(self, request, obj=None):
 383        """
 384        Returns True if the given request has permission to change the given
 385        Django model instance, the default implementation doesn't examine the
 386        `obj` parameter.
 387
 388        Can be overriden by the user in subclasses. In such case it should
 389        return True if the given request has permission to delete the `obj`
 390        model instance. If `obj` is None, this should return True if the given
 391        request has permission to delete *any* object of the given type.
 392        """
 393        opts = self.opts
 394        return request.user.has_perm(opts.app_label + '.' + opts.get_delete_permission())
 395
 396    def get_model_perms(self, request):
 397        """
 398        Returns a dict of all perms for this model. This dict has the keys
 399        ``add``, ``change``, and ``delete`` mapping to the True/False for each
 400        of those actions.
 401        """
 402        return {
 403            'add': self.has_add_permission(request),
 404            'change': self.has_change_permission(request),
 405            'delete': self.has_delete_permission(request),
 406        }
 407
 408    def get_fieldsets(self, request, obj=None):
 409        "Hook for specifying fieldsets for the add form."
 410        if self.declared_fieldsets:
 411            return self.declared_fieldsets
 412        form = self.get_form(request, obj)
 413        fields = form.base_fields.keys() + list(self.get_readonly_fields(request, obj))
 414        return [(None, {'fields': fields})]
 415
 416    def get_form(self, request, obj=None, **kwargs):
 417        """
 418        Returns a Form class for use in the admin add view. This is used by
 419        add_view and change_view.
 420        """
 421        if self.declared_fieldsets:
 422            fields = flatten_fieldsets(self.declared_fieldsets)
 423        else:
 424            fields = None
 425        if self.exclude is None:
 426            exclude = []
 427        else:
 428            exclude = list(self.exclude)
 429        exclude.extend(kwargs.get("exclude", []))
 430        exclude.extend(self.get_readonly_fields(request, obj))
 431        # if exclude is an empty list we pass None to be consistant with the
 432        # default on modelform_factory
 433        exclude = exclude or None
 434        defaults = {
 435            "form": self.form,
 436            "fields": fields,
 437            "exclude": exclude,
 438            "formfield_callback": curry(self.formfield_for_dbfield, request=request),
 439        }
 440        defaults.update(kwargs)
 441        form = modelform_factory(self.model, **defaults)
 442        possible_inlines = [name for name in form.base_fields if form.base_fields[name] is None]
 443        inlines = [name for name in possible_inlines if name in [inl.__name__ for inl in self.inlines]]
 444        for inl in inlines:
 445            form.base_fields[inl] = InlineAdminField(inl)
 446        return form
 447
 448    def get_changelist(self, request, **kwargs):
 449        """
 450        Returns the ChangeList class for use on the changelist page.
 451        """
 452        from django.contrib.admin.views.main import ChangeList
 453        return ChangeList
 454
 455    def get_object(self, request, object_id):
 456        """
 457        Returns an instance matching the primary key provided. ``None``  is
 458        returned if no match is found (or the object_id failed validation
 459        against the primary key field).
 460        """
 461        queryset = self.queryset(request)
 462        model = queryset.model
 463        try:
 464            object_id = model._meta.pk.to_python(object_id)
 465            return queryset.get(pk=object_id)
 466        except (model.DoesNotExist, ValidationError):
 467            return None
 468
 469    def get_changelist_form(self, request, **kwargs):
 470        """
 471        Returns a Form class for use in the Formset on the changelist page.
 472        """
 473        defaults = {
 474            "formfield_callback": curry(self.formfield_for_dbfield, request=request),
 475        }
 476        defaults.update(kwargs)
 477        return modelform_factory(self.model, **defaults)
 478
 479    def get_changelist_formset(self, request, **kwargs):
 480        """
 481        Returns a FormSet class for use on the changelist page if list_editable
 482        is used.
 483        """
 484        defaults = {
 485            "formfield_callback": curry(self.formfield_for_dbfield, request=request),
 486        }
 487        defaults.update(kwargs)
 488        return modelformset_factory(self.model,
 489            self.get_changelist_form(request), extra=0,
 490            fields=self.list_editable, **defaults)
 491
 492    def get_formsets(self, request, obj=None):
 493        for inline in self.inline_instances:
 494            yield inline.get_formset(request, obj)
 495
 496    def get_paginator(self, request, queryset, per_page, orphans=0, allow_empty_first_page=True):
 497        return self.paginator(queryset, per_page, orphans, allow_empty_first_page)
 498
 499    def log_addition(self, request, object):
 500        """
 501        Log that an object has been successfully added.
 502
 503        The default implementation creates an admin LogEntry object.
 504        """
 505        from django.contrib.admin.models import LogEntry, ADDITION
 506        LogEntry.objects.log_action(
 507            user_id         = request.user.pk,
 508            content_type_id = ContentType.objects.get_for_model(object).pk,
 509            object_id       = object.pk,
 510            object_repr     = force_unicode(object),
 511            action_flag     = ADDITION
 512        )
 513
 514    def log_change(self, request, object, message):
 515        """
 516        Log that an object has been successfully changed.
 517
 518        The default implementation creates an admin LogEntry object.
 519        """
 520        from django.contrib.admin.models import LogEntry, CHANGE
 521        LogEntry.objects.log_action(
 522            user_id         = request.user.pk,
 523            content_type_id = ContentType.objects.get_for_model(object).pk,
 524            object_id       = object.pk,
 525            object_repr     = force_unicode(object),
 526            action_flag     = CHANGE,
 527            change_message  = message
 528        )
 529
 530    def log_deletion(self, request, object, object_repr):
 531        """
 532        Log that an object will be deleted. Note that this method is called
 533        before the deletion.
 534
 535        The default implementation creates an admin LogEntry object.
 536        """
 537        from django.contrib.admin.models import LogEntry, DELETION
 538        LogEntry.objects.log_action(
 539            user_id         = request.user.id,
 540            content_type_id = ContentType.objects.get_for_model(self.model).pk,
 541            object_id       = object.pk,
 542            object_repr     = object_repr,
 543            action_flag     = DELETION
 544        )
 545
 546    def action_checkbox(self, obj):
 547        """
 548        A list_display column containing a checkbox widget.
 549        """
 550        return helpers.checkbox.render(helpers.ACTION_CHECKBOX_NAME, force_unicode(obj.pk))
 551    action_checkbox.short_description = mark_safe('<input type="checkbox" id="action-toggle" />')
 552    action_checkbox.allow_tags = True
 553
 554    def get_actions(self, request):
 555        """
 556        Return a dictionary mapping the names of all actions for this
 557        ModelAdmin to a tuple of (callable, name, description) for each action.
 558        """
 559        # If self.actions is explicitally set to None that means that we don't
 560        # want *any* actions enabled on this page.
 561        from django.contrib.admin.views.main import IS_POPUP_VAR
 562        if self.actions is None or IS_POPUP_VAR in request.GET:
 563            return SortedDict()
 564
 565        actions = []
 566
 567        # Gather actions from the admin site first
 568        for (name, func) in self.admin_site.actions:
 569            description = getattr(func, 'short_description', name.replace('_', ' '))
 570            actions.append((func, name, description))
 571
 572        # Then gather them from the model admin and all parent classes,
 573        # starting with self and working back up.
 574        for klass in self.__class__.mro()[::-1]:
 575            class_actions = getattr(klass, 'actions', [])
 576            # Avoid trying to iterate over None
 577            if not class_actions:
 578                continue
 579            actions.extend([self.get_action(action) for action in class_actions])
 580
 581        # get_action might have returned None, so filter any of those out.
 582        actions = filter(None, actions)
 583
 584        # Convert the actions into a SortedDict keyed by name
 585        # and sorted by description.
 586        actions.sort(key=lambda k: k[2].lower())
 587        actions = SortedDict([
 588            (name, (func, name, desc))
 589            for func, name, desc in actions
 590        ])
 591
 592        return actions
 593
 594    def get_action_choices(self, request, default_choices=BLANK_CHOICE_DASH):
 595        """
 596        Return a list of choices for use in a form object.  Each choice is a
 597        tuple (name, description).
 598        """
 599        choices = [] + default_choices
 600        for func, name, description in self.get_actions(request).itervalues():
 601            choice = (name, description % model_format_dict(self.opts))
 602            choices.append(choice)
 603        return choices
 604
 605    def get_action(self, action):
 606        """
 607        Return a given action from a parameter, which can either be a callable,
 608        or the name of a method on the ModelAdmin.  Return is a tuple of
 609        (callable, name, description).
 610        """
 611        # If the action is a callable, just use it.
 612        if callable(action):
 613            func = action
 614            action = action.__name__
 615
 616        # Next, look for a method. Grab it off self.__class__ to get an unbound
 617        # method instead of a bound one; this ensures that the calling
 618        # conventions are the same for functions and methods.
 619        elif hasattr(self.__class__, action):
 620            func = getattr(self.__class__, action)
 621
 622        # Finally, look for a named method on the admin site
 623        else:
 624            try:
 625                func = self.admin_site.get_action(action)
 626            except KeyError:
 627                return None
 628
 629        if hasattr(func, 'short_description'):
 630            description = func.short_description
 631        else:
 632            description = capfirst(action.replace('_', ' '))
 633        return func, action, description
 634
 635    def construct_change_message(self, request, form, formsets):
 636        """
 637        Construct a change message from a changed object.
 638        """
 639        change_message = []
 640        if form.changed_data:
 641            change_message.append(_('Changed %s.') % get_text_list(form.changed_data, _('and')))
 642
 643        if formsets:
 644            for formset in formsets:
 645                for added_object in formset.new_objects:
 646                    change_message.append(_('Added %(name)s "%(object)s".')
 647                                          % {'name': force_unicode(added_object._meta.verbose_name),
 648                                             'object': force_unicode(added_object)})
 649                for changed_object, changed_fields in formset.changed_objects:
 650                    change_message.append(_('Changed %(list)s for %(name)s "%(object)s".')
 651                                          % {'list': get_text_list(changed_fields, _('and')),
 652                                             'name': force_unicode(changed_object._meta.verbose_name),
 653                                             'object': force_unicode(changed_object)})
 654                for deleted_object in formset.deleted_objects:
 655                    change_message.append(_('Deleted %(name)s "%(object)s".')
 656                                          % {'name': force_unicode(deleted_object._meta.verbose_name),
 657                                             'object': force_unicode(deleted_object)})
 658        change_message = ' '.join(change_message)
 659        return change_message or _('No fields changed.')
 660
 661    def message_user(self, request, message):
 662        """
 663        Send a message to the user. The default implementation
 664        posts a message using the django.contrib.messages backend.
 665        """
 666        messages.info(request, message)
 667
 668    def save_form(self, request, form, change):
 669        """
 670        Given a ModelForm return an unsaved instance. ``change`` is True if
 671        the object is being changed, and False if it's being added.
 672        """
 673        return form.save(commit=False)
 674
 675    def save_model(self, request, obj, form, change):
 676        """
 677        Given a model instance save it to the database.
 678        """
 679        obj.save()
 680
 681    def delete_model(self, request, obj):
 682        """
 683        Given a model instance delete it from the database.
 684        """
 685        obj.delete()
 686
 687    def save_formset(self, request, form, formset, change):
 688        """
 689        Given an inline formset save it to the database.
 690        """
 691        formset.save()
 692
 693    def render_change_form(self, request, context, add=False, change=False, form_url='', obj=None):
 694        opts = self.model._meta
 695        app_label = opts.app_label
 696        ordered_objects = opts.get_ordered_objects()
 697        context.update({
 698            'add': add,
 699            'change': change,
 700            'has_add_permission': self.has_add_permission(request),
 701            'has_change_permission': self.has_change_permission(request, obj),
 702            'has_delete_permission': self.has_delete_permission(request, obj),
 703            'has_file_field': True, # FIXME - this should check if form or formsets have a FileField,
 704            'has_absolute_url': hasattr(self.model, 'get_absolute_url'),
 705            'ordered_objects': ordered_objects,
 706            'form_url': mark_safe(form_url),
 707            'opts': opts,
 708            'content_type_id': ContentType.objects.get_for_model(self.model).id,
 709            'save_as': self.save_as,
 710            'save_on_top': self.save_on_top,
 711            'root_path': self.admin_site.root_path,
 712        })
 713        if add and self.add_form_template is not None:
 714            form_template = self.add_form_template
 715        else:
 716            form_template = self.change_form_template
 717        context_instance = template.RequestContext(request, current_app=self.admin_site.name)
 718        return render_to_response(form_template or [
 719            "admin/%s/%s/change_form.html" % (app_label, opts.object_name.lower()),
 720            "admin/%s/change_form.html" % app_label,
 721            "admin/change_form.html"
 722        ], context, context_instance=context_instance)
 723
 724    def response_add(self, request, obj, post_url_continue='../%s/'):
 725        """
 726        Determines the HttpResponse for the add_view stage.
 727        """
 728        opts = obj._meta
 729        pk_value = obj._get_pk_val()
 730
 731        msg = _('The %(name)s "%(obj)s" was added successfully.') % {'name': force_unicode(opts.verbose_name), 'obj': force_unicode(obj)}
 732        # Here, we distinguish between different save types by checking for
 733        # the presence of keys in request.POST.
 734        if "_continue" in request.POST:
 735            self.message_user(request, msg + ' ' + _("You may edit it again below."))
 736            if "_popup" in request.POST:
 737                post_url_continue += "?_popup=1"
 738            return HttpResponseRedirect(post_url_continue % pk_value)
 739
 740        if "_popup" in request.POST:
 741            return HttpResponse('<script type="text/javascript">opener.dismissAddAnotherPopup(window, "%s", "%s");</script>' % \
 742                # escape() calls force_unicode.
 743                (escape(pk_value), escapejs(obj)))
 744        elif "_addanother" in request.POST:
 745            self.message_user(request, msg + ' ' + (_("You may add another %s below.") % force_unicode(opts.verbose_name)))
 746            return HttpResponseRedirect(request.path)
 747        else:
 748            self.message_user(request, msg)
 749
 750            # Figure out where to redirect. If the user has change permission,
 751            # redirect to the change-list page for this object. Otherwise,
 752            # redirect to the admin index.
 753            if self.has_change_permission(request, None):
 754                post_url = '../'
 755            else:
 756                post_url = '../../../'
 757            return HttpResponseRedirect(post_url)
 758
 759    def response_change(self, request, obj):
 760        """
 761        Determines the HttpResponse for the change_view stage.
 762        """
 763        opts = obj._meta
 764
 765        # Handle proxy models automatically created by .only() or .defer()
 766        verbose_name = opts.verbose_name
 767        if obj._deferred:
 768            opts_ = opts.proxy_for_model._meta
 769            verbose_name = opts_.verbose_name
 770
 771        pk_value = obj._get_pk_val()
 772
 773        msg = _('The %(name)s "%(obj)s" was changed successfully.') % {'name': force_unicode(verbose_name), 'obj': force_unicode(obj)}
 774        if "_continue" in request.POST:
 775            self.message_user(request, msg + ' ' + _("You may edit it again below."))
 776            if "_popup" in request.REQUEST:
 777                return HttpResponseRedirect(request.path + "?_popup=1")
 778            else:
 779                return HttpResponseRedirect(request.path)
 780        elif "_saveasnew" in request.POST:
 781            msg = _('The %(name)s "%(obj)s" was added successfully. You may edit it again below.') % {'name': force_unicode(verbose_name), 'obj': obj}
 782            self.message_user(request, msg)
 783            return HttpResponseRedirect("../%s/" % pk_value)
 784        elif "_addanother" in request.POST:
 785            self.message_user(request, msg + ' ' + (_("You may add another %s below.") % force_unicode(verbose_name)))
 786            return HttpResponseRedirect("../add/")
 787        else:
 788            self.message_user(request, msg)
 789            # Figure out where to redirect. If the user has change permission,
 790            # redirect to the change-list page for this object. Otherwise,
 791            # redirect to the admin index.
 792            if self.has_change_permission(request, None):
 793                return HttpResponseRedirect('../')
 794            else:
 795                return HttpResponseRedirect('../../../')
 796
 797    def response_action(self, request, queryset):
 798        """
 799        Handle an admin action. This is called if a request is POSTed to the
 800        changelist; it returns an HttpResponse if the action was handled, and
 801        None otherwise.
 802        """
 803
 804        # There can be multiple action forms on the page (at the top
 805        # and bottom of the change list, for example). Get the action
 806        # whose button was pushed.
 807        try:
 808            action_index = int(request.POST.get('index', 0))
 809        except ValueError:
 810            action_index = 0
 811
 812        # Construct the action form.
 813        data = request.POST.copy()
 814        data.pop(helpers.ACTION_CHECKBOX_NAME, None)
 815        data.pop("index", None)
 816
 817        # Use the action whose button was pushed
 818        try:
 819            data.update({'action': data.getlist('action')[action_index]})
 820        except IndexError:
 821            # If we didn't get an action from the chosen form that's invalid
 822            # POST data, so by deleting action it'll fail the validation check
 823            # below. So no need to do anything here
 824            pass
 825
 826        action_form = self.action_form(data, auto_id=None)
 827        action_form.fields['action'].choices = self.get_action_choices(request)
 828
 829        # If the form's valid we can handle the action.
 830        if action_form.is_valid():
 831            action = action_form.cleaned_data['action']
 832            select_across = action_form.cleaned_data['select_across']
 833            func, name, description = self.get_actions(request)[action]
 834
 835            # Get the list of selected PKs. If nothing's selected, we can't
 836            # perform an action on it, so bail. Except we want to perform
 837            # the action explicitly on all objects.
 838            selected = request.POST.getlist(helpers.ACTION_CHECKBOX_NAME)
 839            if not selected and not select_across:
 840                # Reminder that something needs to be selected or nothing will happen
 841                msg = _("Items must be selected in order to perform "
 842                        "actions on them. No items have been changed.")
 843                self.message_user(request, msg)
 844                return None
 845
 846            if not select_across:
 847                # Perform the action only on the selected objects
 848                queryset = queryset.filter(pk__in=selected)
 849
 850            response = func(self, request, queryset)
 851
 852            # Actions may return an HttpResponse, which will be used as the
 853            # response from the POST. If not, we'll be a good little HTTP
 854            # citizen and redirect back to the changelist page.
 855            if isinstance(response, HttpResponse):
 856                return response
 857            else:
 858                return HttpResponseRedirect(request.get_full_path())
 859        else:
 860            msg = _("No action selected.")
 861            self.message_user(request, msg)
 862            return None
 863
 864    @csrf_protect_m
 865    @transaction.commit_on_success
 866    def add_view(self, request, form_url='', extra_context=None):
 867        "The 'add' admin view for this model."
 868        model = self.model
 869        opts = model._meta
 870
 871        if not self.has_add_permission(request):
 872            raise PermissionDenied
 873
 874        ModelForm = self.get_form(request)
 875        formsets = []
 876        if request.method == 'POST':
 877            form = ModelForm(request.POST, request.FILES)
 878            if form.is_valid():
 879                new_object = self.save_form(request, form, change=False)
 880                form_validated = True
 881            else:
 882                form_validated = False
 883                new_object = self.model()
 884            prefixes = {}
 885            for FormSet, inline in zip(self.get_formsets(request), self.inline_instances):
 886                prefix = FormSet.get_default_prefix()
 887                prefixes[prefix] = prefixes.get(prefix, 0) + 1
 888                if prefixes[prefix] != 1:
 889                    prefix = "%s-%s" % (prefix, prefixes[prefix])
 890                formset = FormSet(data=request.POST, files=request.FILES,
 891                                  instance=new_object,
 892                                  save_as_new="_saveasnew" in request.POST,
 893                                  prefix=prefix, queryset=inline.queryset(request))
 894                formsets.append(formset)
 895            if all_valid(formsets) and form_validated:  
 896                self.save_model(request, new_object, form, change=False)
 897                form.save_m2m()
 898                for formset in formsets:
 899                    self.save_formset(request, form, formset, change=False)
 900
 901                self.log_addition(request, new_object)
 902                return self.response_add(request, new_object)
 903        else:
 904            # Prepare the dict of initial data from the request.
 905            # We have to special-case M2Ms as a list of comma-separated PKs.
 906            initial = dict(request.GET.items())
 907            for k in initial:
 908                try:
 909                    f = opts.get_field(k)
 910                except models.FieldDoesNotExist:
 911                    continue
 912                if isinstance(f, models.ManyToManyField):
 913                    initial[k] = initial[k].split(",")
 914            form = ModelForm(initial=initial)
 915            prefixes = {}
 916            for FormSet, inline in zip(self.get_formsets(request),
 917                                       self.inline_instances):
 918                prefix = FormSet.get_default_prefix()
 919                prefixes[prefix] = prefixes.get(prefix, 0) + 1
 920                if prefixes[prefix] != 1:
 921                    prefix = "%s-%s" % (prefix, prefixes[prefix])
 922                formset = FormSet(instance=self.model(), prefix=prefix,
 923                                  queryset=inline.queryset(request))
 924                formsets.append(formset)
 925
 926        adminForm = helpers.AdminForm(form, list(self.get_fieldsets(request)),
 927            self.prepopulated_fields, self.get_readonly_fields(request),
 928            model_admin=self)
 929        media = self.media + adminForm.media
 930
 931        inline_admin_formsets = []
 932        for inline, formset in zip(self.inline_instances, formsets):
 933            fieldsets = list(inline.get_fieldsets(request))
 934            readonly = list(inline.get_readonly_fields(request))
 935            inline_admin_formset = helpers.InlineAdminFormSet(inline, formset,
 936                fieldsets, readonly, model_admin=self)
 937            inline_admin_formsets.append(inline_admin_formset)
 938            media = media + inline_admin_formset.media
 939
 940        context = {
 941            'title': _('Add %s') % force_unicode(opts.verbose_name),
 942            'adminform': adminForm,
 943            'is_popup': "_popup" in request.REQUEST,
 944            'show_delete': False,
 945            'media': mark_safe(media),
 946            'inline_admin_formsets': inline_admin_formsets,
 947            'errors': helpers.AdminErrorList(form, formsets),
 948            'root_path': self.admin_site.root_path,
 949            'app_label': opts.app_label,
 950        }
 951        context.update(extra_context or {})
 952        return self.render_change_form(request, context, form_url=form_url, add=True)
 953
 954    @csrf_protect_m
 955    @transaction.commit_on_success
 956    def change_view(self, request, object_id, extra_context=None):
 957        "The 'change' admin view for this model."
 958        model = self.model
 959        opts = model._meta
 960
 961        obj = self.get_object(request, unquote(object_id))
 962
 963        if not self.has_change_permission(request, obj):
 964            raise PermissionDenied
 965
 966        if obj is None:
 967            raise Http404(_('%(name)s object with primary key %(key)r does not exist.') % {'name': force_unicode(opts.verbose_name), 'key': escape(object_id)})
 968
 969        if request.method == 'POST' and "_saveasnew" in request.POST:
 970            return self.add_view(request, form_url='../add/')
 971
 972        ModelForm = self.get_form(request, obj)
 973        formsets = []
 974        if request.method == 'POST':
 975            form = ModelForm(request.POST, request.FILES, instance=obj)
 976            if form.is_valid():
 977                form_validated = True
 978                new_object = self.save_form(request, form, change=True)
 979            else:
 980                form_validated = False
 981                new_object = obj
 982            prefixes = {}
 983            for FormSet, inline in zip(self.get_formsets(request, new_object),
 984                                       self.inline_instances):
 985                prefix = FormSet.get_default_prefix()
 986                prefixes[prefix] = prefixes.get(prefix, 0) + 1
 987                if prefixes[prefix] != 1:
 988                    prefix = "%s-%s" % (prefix, prefixes[prefix])
 989                formset = FormSet(request.POST, request.FILES,
 990                                  instance=new_object, prefix=prefix,
 991                                  queryset=inline.queryset(request))
 992
 993                formsets.append(formset)
 994
 995            if all_valid(formsets) and form_validated:
 996                self.save_model(request, new_object, form, change=True)
 997                form.save_m2m()
 998                for formset in formsets:
 999                    self.save_formset(request, form, formset, change=True)
1000
1001                change_message = self.construct_change_message(request, form, formsets)
1002                self.log_change(request, new_object, change_message)
1003                return self.response_change(request, new_object)
1004
1005        else:
1006            form = ModelForm(instance=obj)
1007            prefixes = {}
1008            for FormSet, inline in zip(self.get_formsets(request, obj), self.inline_instances):
1009                prefix = FormSet.get_default_prefix()
1010                prefixes[prefix] = prefixes.get(prefix, 0) + 1
1011                if prefixes[prefix] != 1:
1012                    prefix = "%s-%s" % (prefix, prefixes[prefix])
1013                formset = FormSet(instance=obj, prefix=prefix,
1014                                  queryset=inline.queryset(request))
1015                formsets.append(formset)
1016
1017        adminForm = helpers.AdminForm(form, self.get_fieldsets(request, obj),
1018            self.prepopulated_fields, self.get_readonly_fields(request, obj),
1019            model_admin=self)
1020        media = self.media + adminForm.media
1021
1022        inline_admin_formsets = []
1023        for inline, formset in zip(self.inline_instances, formsets):
1024            fieldsets = list(inline.get_fieldsets(request, obj))
1025            readonly = list(inline.get_readonly_fields(request, obj))
1026            inline_admin_formset = helpers.InlineAdminFormSet(inline, formset,
1027                fieldsets, readonly, model_admin=self)
1028            inline_admin_formsets.append(inline_admin_formset)
1029            media = media + inline_admin_formset.media
1030
1031        context = {
1032            'title': _('Change %s') % force_unicode(opts.verbose_name),
1033            'adminform': adminForm,
1034            'object_id': object_id,
1035            'original': obj,
1036            'is_popup': "_popup" in request.REQUEST,
1037            'media': mark_safe(media),
1038            'inline_admin_formsets': inline_admin_formsets,
1039            'errors': helpers.AdminErrorList(form, formsets),
1040            'root_path': self.admin_site.root_path,
1041            'app_label': opts.app_label,
1042        }
1043        context.update(extra_context or {})
1044        return self.render_change_form(request, context, change=True, obj=obj)
1045
1046    @csrf_protect_m
1047    def changelist_view(self, request, extra_context=None):
1048        "The 'change list' admin view for this model."
1049        from django.contrib.admin.views.main import ERROR_FLAG
1050        opts = self.model._meta
1051        app_label = opts.app_label
1052        if not self.has_change_permission(request, None):
1053            raise PermissionDenied
1054
1055        # Check actions to see if any are available on this changelist
1056        actions = self.get_actions(request)
1057
1058        # Remove action checkboxes if there aren't any actions available.
1059        list_display = list(self.list_display)
1060        if not actions:
1061            try:
1062                list_display.remove('action_checkbox')
1063            except ValueError:
1064                pass
1065
1066        ChangeList = self.get_changelist(request)
1067        try:
1068            cl = ChangeList(request, self.model, list_display, self.list_display_links,
1069                self.list_filter, self.date_hierarchy, self.search_fields,
1070                self.list_select_related, self.list_per_page, self.list_editable, self)
1071        except IncorrectLookupParameters:
1072            # Wacky lookup parameters were given, so redirect to the main
1073            # changelist page, without parameters, and pass an 'invalid=1'
1074            # parameter via the query string. If wacky parameters were given
1075            # and the 'invalid=1' parameter was already in the query string,
1076            # something is screwed up with the database, so display an error
1077            # page.
1078            if ERROR_FLAG in request.GET.keys():
1079                return render_to_response('admin/invalid_setup.html', {'title': _('Database error')})
1080            return HttpResponseRedirect(request.path + '?' + ERROR_FLAG + '=1')
1081
1082        # If the request was POSTed, this might be a bulk action or a bulk
1083        # edit. Try to look up an action or confirmation first, but if this
1084        # isn't an action the POST will fall through to the bulk edit check,
1085        # below.
1086        action_failed = False
1087        selected = request.POST.getlist(helpers.ACTION_CHECKBOX_NAME)
1088
1089        # Actions with no confirmation
1090        if (actions and request.method == 'POST' and
1091                'index' in request.POST and '_save' not in request.POST):
1092            if selected:
1093                response = self.response_action(request, queryset=cl.get_query_set())
1094                if response:
1095                    return response
1096                else:
1097                    action_failed = True
1098            else:
1099                msg = _("Items must be selected in order to perform "
1100                        "actions on them. No items have been changed.")
1101                self.message_user(request, msg)
1102                action_failed = True
1103
1104        # Actions with confirmation
1105        if (actions and request.method == 'POST' and
1106                helpers.ACTION_CHECKBOX_NAME in request.POST and
1107                'index' not in request.POST and '_save' not in request.POST):
1108            if selected:
1109                response = self.response_action(request, queryset=cl.get_query_set())
1110                if response:
1111                    return response
1112                else:
1113                    action_failed = True
1114
1115        # If we're allowing changelist editing, we need to construct a formset
1116        # for the changelist given all the fields to be edited. Then we'll
1117        # use the formset to validate/process POSTed data.
1118        formset = cl.formset = None
1119
1120        # Handle POSTed bulk-edit data.
1121        if (request.method == "POST" and cl.list_editable and
1122                '_save' in request.POST and not action_failed):
1123            FormSet = self.get_changelist_formset(request)
1124            formset = cl.formset = FormSet(request.POST, request.FILES, queryset=cl.result_list)
1125            if formset.is_valid():
1126                changecount = 0
1127                for form in formset.forms:
1128                    if form.has_changed():
1129                        obj = self.save_form(request, form, change=True)
1130                        self.save_model(request, obj, form, change=True)
1131                        form.save_m2m()
1132                        change_msg = self.construct_change_message(request, form, None)
1133                        self.log_change(request, obj, change_msg)
1134                        changecount += 1
1135
1136                if changecount:
1137                    if changecount == 1:
1138                        name = force_unicode(opts.verbose_name)
1139                    else:
1140                        name = force_unicode(opts.verbose_name_plural)
1141                    msg = ungettext("%(count)s %(name)s was changed successfully.",
1142                                    "%(count)s %(name)s were changed successfully.",
1143                                    changecount) % {'count': changecount,
1144                                                    'name': name,
1145                                                    'obj': force_unicode(obj)}
1146                    self.message_user(request, msg)
1147
1148                return HttpResponseRedirect(request.get_full_path())
1149
1150        # Handle GET -- construct a formset for display.
1151        elif cl.list_editable:
1152            FormSet = self.get_changelist_formset(request)
1153            formset = cl.formset = FormSet(queryset=cl.result_list)
1154
1155        # Build the list of media to be used by the formset.
1156        if formset:
1157          

Large files files are truncated, but you can click here to view the full file