2008-07-22 11:24:09 +08:00
from django import forms , template
2008-07-19 09:22:26 +08:00
from django . forms . formsets import all_valid
2009-03-18 04:51:47 +08:00
from django . forms . models import modelform_factory , modelformset_factory , inlineformset_factory
2008-08-09 04:27:48 +08:00
from django . forms . models import BaseInlineFormSet
2008-07-19 07:54:34 +08:00
from django . contrib . contenttypes . models import ContentType
from django . contrib . admin import widgets
2008-08-18 23:49:58 +08:00
from django . contrib . admin import helpers
2009-03-24 04:22:56 +08:00
from django . contrib . admin . util import unquote , flatten_fieldsets , get_deleted_objects , model_ngettext , model_format_dict
2008-08-10 07:40:57 +08:00
from django . core . exceptions import PermissionDenied
2008-07-19 07:54:34 +08:00
from django . db import models , transaction
2009-03-24 04:22:56 +08:00
from django . db . models . fields import BLANK_CHOICE_DASH
2008-07-19 07:54:34 +08:00
from django . http import Http404 , HttpResponse , HttpResponseRedirect
from django . shortcuts import get_object_or_404 , render_to_response
2009-04-07 04:23:33 +08:00
from django . utils . datastructures import SortedDict
2009-01-15 04:22:25 +08:00
from django . utils . functional import update_wrapper
2008-07-19 07:54:34 +08:00
from django . utils . html import escape
from django . utils . safestring import mark_safe
2009-01-16 23:32:31 +08:00
from django . utils . functional import curry
2008-07-19 07:54:34 +08:00
from django . utils . text import capfirst , get_text_list
from django . utils . translation import ugettext as _
2009-03-24 04:22:56 +08:00
from django . utils . translation import ngettext , ugettext_lazy
2008-07-19 07:54:34 +08:00
from django . utils . encoding import force_unicode
2008-08-02 13:29:17 +08:00
try :
set
except NameError :
from sets import Set as set # Python 2.3 fallback
2008-07-19 07:54:34 +08:00
HORIZONTAL , VERTICAL = 1 , 2
# returns the <ul> class for a given radio_admin field
get_ul_class = lambda x : ' radiolist %s ' % ( ( x == HORIZONTAL ) and ' inline ' or ' ' )
class IncorrectLookupParameters ( Exception ) :
pass
2009-01-16 23:32:31 +08:00
# Defaults for formfield_overrides. ModelAdmin subclasses can change this
# by adding to ModelAdmin.formfield_overrides.
2009-03-18 17:19:24 +08:00
2009-01-16 23:32:31 +08:00
FORMFIELD_FOR_DBFIELD_DEFAULTS = {
2009-03-18 17:19:24 +08:00
models . DateTimeField : {
2009-01-16 23:32:31 +08:00
' form_class ' : forms . SplitDateTimeField ,
2009-03-18 17:19:24 +08:00
' widget ' : widgets . AdminSplitDateTime
2009-01-16 23:32:31 +08:00
} ,
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 . CharField : { ' widget ' : widgets . AdminTextInputWidget } ,
models . ImageField : { ' widget ' : widgets . AdminFileWidget } ,
models . FileField : { ' widget ' : widgets . AdminFileWidget } ,
}
2008-07-19 07:54:34 +08:00
class BaseModelAdmin ( object ) :
""" Functionality common to both ModelAdmin and InlineAdmin. """
2009-03-18 17:19:24 +08:00
2008-07-19 07:54:34 +08:00
raw_id_fields = ( )
fields = None
2008-09-03 01:26:24 +08:00
exclude = None
2008-07-19 07:54:34 +08:00
fieldsets = None
form = forms . ModelForm
filter_vertical = ( )
filter_horizontal = ( )
radio_fields = { }
prepopulated_fields = { }
2009-01-16 23:32:31 +08:00
formfield_overrides = { }
2009-03-18 17:19:24 +08:00
2009-01-16 23:32:31 +08:00
def __init__ ( self ) :
self . formfield_overrides = dict ( FORMFIELD_FOR_DBFIELD_DEFAULTS , * * self . formfield_overrides )
2009-03-18 17:19:24 +08:00
2008-07-19 07:54:34 +08:00
def formfield_for_dbfield ( self , db_field , * * kwargs ) :
"""
Hook for specifying the form Field instance for a given database Field
instance .
2009-03-18 17:19:24 +08:00
2008-07-19 07:54:34 +08:00
If kwargs are given , they ' re passed to the form Field ' s constructor .
"""
2009-01-16 23:32:31 +08:00
request = kwargs . pop ( " request " , None )
2009-03-18 17:19:24 +08:00
2008-07-30 19:41:04 +08:00
# 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 :
2009-01-16 23:32:31 +08:00
return self . formfield_for_choice_field ( db_field , request , * * kwargs )
2009-03-18 17:19:24 +08:00
2009-01-16 23:32:31 +08:00
# ForeignKey or ManyToManyFields
if isinstance ( db_field , ( models . ForeignKey , models . ManyToManyField ) ) :
# Combine the field kwargs with any options for formfield_overrides.
2009-03-18 17:19:24 +08:00
# Make sure the passed in **kwargs override anything in
2009-01-16 23:32:31 +08:00
# 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 )
2009-03-18 17:19:24 +08:00
2009-01-16 23:32:31 +08:00
# 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 )
2009-03-18 17:19:24 +08:00
2009-01-16 23:32:31 +08:00
# For non-raw_id fields, wrap the widget with a wrapper that adds
# extra HTML -- the "add other" interface -- to the end of the
2009-03-18 17:19:24 +08:00
# rendered output. formfield can be None if it came from a
2009-01-16 23:32:31 +08:00
# OneToOneField with parent_link=True or a M2M intermediary.
if formfield and db_field . name not in self . raw_id_fields :
formfield . widget = widgets . RelatedFieldWidgetWrapper ( formfield . widget , db_field . rel , self . admin_site )
return formfield
2009-03-18 17:19:24 +08:00
2009-01-16 23:32:31 +08:00
# If we've got overrides for the formfield defined, use 'em. **kwargs
# passed to formfield_for_dbfield override the defaults.
2009-04-09 04:25:56 +08:00
for klass in db_field . __class__ . mro ( ) :
if klass in self . formfield_overrides :
kwargs = dict ( self . formfield_overrides [ klass ] , * * kwargs )
return db_field . formfield ( * * kwargs )
2009-03-18 17:19:24 +08:00
2009-01-16 23:32:31 +08:00
# For any other type of field, just call its formfield() method.
return db_field . formfield ( * * kwargs )
2009-03-18 17:19:24 +08:00
2009-01-16 23:32:31 +08:00
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 :
2008-09-01 04:11:28 +08:00
kwargs [ ' widget ' ] = widgets . AdminRadioSelect ( attrs = {
' class ' : get_ul_class ( self . radio_fields [ db_field . name ] ) ,
} )
2009-01-16 23:32:31 +08:00
if ' choices ' not in kwargs :
2008-09-01 04:11:28 +08:00
kwargs [ ' choices ' ] = db_field . get_choices (
include_blank = db_field . blank ,
blank_choice = [ ( ' ' , _ ( ' None ' ) ) ]
2008-07-30 19:41:04 +08:00
)
2009-01-16 23:32:31 +08:00
return db_field . formfield ( * * kwargs )
2009-03-18 17:19:24 +08:00
2009-01-16 23:32:31 +08:00
def formfield_for_foreignkey ( self , db_field , request = None , * * kwargs ) :
"""
Get a form Field for a ForeignKey .
"""
if db_field . name in self . raw_id_fields :
kwargs [ ' widget ' ] = widgets . ForeignKeyRawIdWidget ( db_field . rel )
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 ' ] = db_field . blank and _ ( ' None ' ) or None
2009-03-18 17:19:24 +08:00
2009-01-16 23:32:31 +08:00
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, don't show field in admin.
if db_field . rel . through is not None :
return None
2009-03-18 17:19:24 +08:00
2009-01-16 23:32:31 +08:00
if db_field . name in self . raw_id_fields :
kwargs [ ' widget ' ] = widgets . ManyToManyRawIdWidget ( db_field . rel )
kwargs [ ' help_text ' ] = ' '
elif db_field . name in ( list ( self . filter_vertical ) + list ( self . filter_horizontal ) ) :
kwargs [ ' widget ' ] = widgets . FilteredSelectMultiple ( db_field . verbose_name , ( db_field . name in self . filter_vertical ) )
2009-03-18 17:19:24 +08:00
2008-07-19 07:54:34 +08:00
return db_field . formfield ( * * kwargs )
2009-03-18 17:19:24 +08:00
2008-07-19 07:54:34 +08:00
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 )
class ModelAdmin ( BaseModelAdmin ) :
" Encapsulates all admin options and functionality for a given model. "
__metaclass__ = forms . MediaDefiningClass
2009-03-18 17:19:24 +08:00
2008-07-19 07:54:34 +08:00
list_display = ( ' __str__ ' , )
list_display_links = ( )
list_filter = ( )
list_select_related = False
list_per_page = 100
2009-03-18 04:51:47 +08:00
list_editable = ( )
2008-07-19 07:54:34 +08:00
search_fields = ( )
date_hierarchy = None
save_as = False
save_on_top = False
ordering = None
inlines = [ ]
2009-03-18 17:19:24 +08:00
2008-07-19 07:54:34 +08:00
# Custom templates (designed to be over-ridden in subclasses)
change_form_template = None
change_list_template = None
delete_confirmation_template = None
object_history_template = None
2009-03-18 17:19:24 +08:00
2009-03-24 04:22:56 +08:00
# Actions
2009-04-07 04:23:33 +08:00
actions = [ ]
2009-03-24 04:22:56 +08:00
action_form = helpers . ActionForm
actions_on_top = True
actions_on_bottom = False
2008-07-19 07:54:34 +08:00
def __init__ ( self , model , admin_site ) :
self . model = model
self . opts = model . _meta
self . admin_site = admin_site
self . inline_instances = [ ]
for inline_class in self . inlines :
inline_instance = inline_class ( self . model , self . admin_site )
self . inline_instances . append ( inline_instance )
2009-04-07 04:23:33 +08:00
if ' action_checkbox ' not in self . list_display and self . actions is not None :
2009-03-24 04:22:56 +08:00
self . list_display = [ ' action_checkbox ' ] + list ( self . list_display )
if not self . list_display_links :
for name in self . list_display :
if name != ' action_checkbox ' :
self . list_display_links = [ name ]
break
2008-07-19 07:54:34 +08:00
super ( ModelAdmin , self ) . __init__ ( )
2009-03-18 17:19:24 +08:00
2009-01-15 04:22:25 +08:00
def get_urls ( self ) :
from django . conf . urls . defaults import patterns , url
2009-03-18 17:19:24 +08:00
2009-01-15 04:22:25 +08:00
def wrap ( view ) :
def wrapper ( * args , * * kwargs ) :
return self . admin_site . admin_view ( view ) ( * args , * * kwargs )
return update_wrapper ( wrapper , view )
2009-03-18 17:19:24 +08:00
2009-01-15 04:22:25 +08:00
info = self . admin_site . name , self . model . _meta . app_label , self . model . _meta . module_name
2009-03-18 17:19:24 +08:00
2009-01-15 04:22:25 +08:00
urlpatterns = patterns ( ' ' ,
url ( r ' ^$ ' ,
wrap ( self . changelist_view ) ,
name = ' %s admin_ %s _ %s _changelist ' % info ) ,
url ( r ' ^add/$ ' ,
wrap ( self . add_view ) ,
name = ' %s admin_ %s _ %s _add ' % info ) ,
url ( r ' ^(.+)/history/$ ' ,
wrap ( self . history_view ) ,
name = ' %s admin_ %s _ %s _history ' % info ) ,
url ( r ' ^(.+)/delete/$ ' ,
wrap ( self . delete_view ) ,
name = ' %s admin_ %s _ %s _delete ' % info ) ,
url ( r ' ^(.+)/$ ' ,
wrap ( self . change_view ) ,
name = ' %s admin_ %s _ %s _change ' % info ) ,
)
return urlpatterns
2009-03-18 17:19:24 +08:00
2009-01-15 04:22:25 +08:00
def urls ( self ) :
return self . get_urls ( )
urls = property ( urls )
2009-03-18 17:19:24 +08:00
2008-07-19 07:54:34 +08:00
def _media ( self ) :
from django . conf import settings
2009-03-18 17:19:24 +08:00
2008-07-19 07:54:34 +08:00
js = [ ' js/core.js ' , ' js/admin/RelatedObjectLookups.js ' ]
2009-04-07 04:23:33 +08:00
if self . actions is not None :
2009-03-24 04:22:56 +08:00
js . extend ( [ ' js/getElementsBySelector.js ' , ' js/actions.js ' ] )
2008-07-19 07:54:34 +08:00
if self . prepopulated_fields :
js . append ( ' js/urlify.js ' )
if self . opts . get_ordered_objects ( ) :
js . extend ( [ ' js/getElementsBySelector.js ' , ' js/dom-drag.js ' , ' js/admin/ordering.js ' ] )
2009-03-18 17:19:24 +08:00
2008-07-19 07:54:34 +08:00
return forms . Media ( js = [ ' %s %s ' % ( settings . ADMIN_MEDIA_PREFIX , url ) for url in js ] )
media = property ( _media )
2009-03-18 17:19:24 +08:00
2008-07-19 07:54:34 +08:00
def has_add_permission ( self , request ) :
" Returns True if the given request has permission to add an object. "
opts = self . opts
return request . user . has_perm ( opts . app_label + ' . ' + opts . get_add_permission ( ) )
2009-03-18 17:19:24 +08:00
2008-07-19 07:54:34 +08:00
def has_change_permission ( self , request , obj = None ) :
"""
Returns True if the given request has permission to change the given
Django model instance .
2009-03-18 17:19:24 +08:00
2008-07-19 07:54:34 +08:00
If ` obj ` is None , this should return True if the given request has
permission to change * any * object of the given type .
"""
opts = self . opts
return request . user . has_perm ( opts . app_label + ' . ' + opts . get_change_permission ( ) )
2009-03-18 17:19:24 +08:00
2008-07-19 07:54:34 +08:00
def has_delete_permission ( self , request , obj = None ) :
"""
Returns True if the given request has permission to change the given
Django model instance .
2009-03-18 17:19:24 +08:00
2008-07-19 07:54:34 +08:00
If ` obj ` is None , this should return True if the given request has
permission to delete * any * object of the given type .
"""
opts = self . opts
return request . user . has_perm ( opts . app_label + ' . ' + opts . get_delete_permission ( ) )
2009-03-18 17:19:24 +08:00
2009-04-09 03:47:46 +08:00
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 ) ,
}
2008-07-19 07:54:34 +08:00
def 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_query_set ( )
# TODO: this should be handled by some parameter to the ChangeList.
ordering = self . ordering or ( ) # otherwise we might try to *None, which is bad ;)
if ordering :
qs = qs . order_by ( * ordering )
return qs
2009-03-18 17:19:24 +08:00
2008-07-19 07:54:34 +08:00
def get_fieldsets ( self , request , obj = None ) :
" Hook for specifying fieldsets for the add form. "
if self . declared_fieldsets :
return self . declared_fieldsets
2008-08-23 13:05:21 +08:00
form = self . get_form ( request , obj )
2008-07-19 07:54:34 +08:00
return [ ( None , { ' fields ' : form . base_fields . keys ( ) } ) ]
2009-03-18 17:19:24 +08:00
2008-08-16 05:57:36 +08:00
def get_form ( self , request , obj = None , * * kwargs ) :
2008-07-19 07:54:34 +08:00
"""
Returns a Form class for use in the admin add view . This is used by
add_view and change_view .
"""
if self . declared_fieldsets :
fields = flatten_fieldsets ( self . declared_fieldsets )
else :
fields = None
2008-09-03 01:26:24 +08:00
if self . exclude is None :
exclude = [ ]
else :
2008-09-27 13:57:10 +08:00
exclude = list ( self . exclude )
2008-08-16 05:57:36 +08:00
defaults = {
" form " : self . form ,
" fields " : fields ,
2008-09-03 01:26:24 +08:00
" exclude " : exclude + kwargs . get ( " exclude " , [ ] ) ,
2009-01-16 23:32:31 +08:00
" formfield_callback " : curry ( self . formfield_for_dbfield , request = request ) ,
2008-08-16 05:57:36 +08:00
}
defaults . update ( kwargs )
return modelform_factory ( self . model , * * defaults )
2009-03-18 17:19:24 +08:00
2009-03-18 04:51:47 +08:00
def get_changelist_form ( self , request , * * kwargs ) :
"""
Returns a Form class for use in the Formset on the changelist page .
"""
defaults = {
" formfield_callback " : curry ( self . formfield_for_dbfield , request = request ) ,
}
defaults . update ( kwargs )
return modelform_factory ( self . model , * * defaults )
2009-03-18 17:19:24 +08:00
2009-03-18 04:51:47 +08:00
def get_changelist_formset ( self , request , * * kwargs ) :
"""
2009-03-18 17:19:24 +08:00
Returns a FormSet class for use on the changelist page if list_editable
2009-03-18 04:51:47 +08:00
is used .
"""
defaults = {
" formfield_callback " : curry ( self . formfield_for_dbfield , request = request ) ,
}
defaults . update ( kwargs )
2009-03-18 17:19:24 +08:00
return modelformset_factory ( self . model ,
self . get_changelist_form ( request ) , extra = 0 ,
2009-03-18 04:51:47 +08:00
fields = self . list_editable , * * defaults )
2009-03-18 17:19:24 +08:00
2008-07-19 07:54:34 +08:00
def get_formsets ( self , request , obj = None ) :
for inline in self . inline_instances :
yield inline . get_formset ( request , obj )
2009-03-18 17:19:24 +08:00
2008-08-10 00:45:15 +08:00
def log_addition ( self , request , object ) :
"""
2009-01-15 04:22:25 +08:00
Log that an object has been successfully added .
2009-03-18 17:19:24 +08:00
2008-08-10 00:45:15 +08:00
The default implementation creates an admin LogEntry object .
"""
from django . contrib . admin . models import LogEntry , ADDITION
LogEntry . objects . log_action (
2009-01-15 04:22:25 +08:00
user_id = request . user . pk ,
2008-08-10 00:45:15 +08:00
content_type_id = ContentType . objects . get_for_model ( object ) . pk ,
object_id = object . pk ,
2009-01-15 04:22:25 +08:00
object_repr = force_unicode ( object ) ,
2008-08-10 00:45:15 +08:00
action_flag = ADDITION
)
2009-03-18 17:19:24 +08:00
2008-08-10 00:45:15 +08:00
def log_change ( self , request , object , message ) :
"""
2009-01-15 04:22:25 +08:00
Log that an object has been successfully changed .
2009-03-18 17:19:24 +08:00
2008-08-10 00:45:15 +08:00
The default implementation creates an admin LogEntry object .
"""
from django . contrib . admin . models import LogEntry , CHANGE
LogEntry . objects . log_action (
2009-01-15 04:22:25 +08:00
user_id = request . user . pk ,
content_type_id = ContentType . objects . get_for_model ( object ) . pk ,
object_id = object . pk ,
object_repr = force_unicode ( object ) ,
action_flag = CHANGE ,
2008-08-10 00:45:15 +08:00
change_message = message
)
2009-03-18 17:19:24 +08:00
2008-08-10 00:45:15 +08:00
def log_deletion ( self , request , object , object_repr ) :
"""
Log that an object has been successfully deleted . Note that since the
object is deleted , it might no longer be safe to call * any * methods
on the object , hence this method getting object_repr .
2009-03-18 17:19:24 +08:00
2008-08-10 00:45:15 +08:00
The default implementation creates an admin LogEntry object .
"""
from django . contrib . admin . models import LogEntry , DELETION
LogEntry . objects . log_action (
2009-01-15 04:22:25 +08:00
user_id = request . user . id ,
content_type_id = ContentType . objects . get_for_model ( self . model ) . pk ,
object_id = object . pk ,
2008-08-10 00:45:15 +08:00
object_repr = object_repr ,
action_flag = DELETION
)
2009-03-18 17:19:24 +08:00
2009-03-24 04:22:56 +08:00
def action_checkbox ( self , obj ) :
"""
A list_display column containing a checkbox widget .
"""
return helpers . checkbox . render ( helpers . ACTION_CHECKBOX_NAME , force_unicode ( obj . pk ) )
action_checkbox . short_description = mark_safe ( ' <input type= " checkbox " id= " action-toggle " /> ' )
action_checkbox . allow_tags = True
2009-04-07 04:23:33 +08:00
def get_actions ( self , request ) :
2009-03-24 04:22:56 +08:00
"""
Return a dictionary mapping the names of all actions for this
ModelAdmin to a tuple of ( callable , name , description ) for each action .
"""
2009-04-07 04:23:33 +08:00
# If self.actions is explicitally set to None that means that we don't
# want *any* actions enabled on this page.
if self . actions is None :
return [ ]
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
# and sorted by description.
actions . sort ( lambda a , b : cmp ( a [ 2 ] . lower ( ) , b [ 2 ] . lower ( ) ) )
actions = SortedDict ( [
( name , ( func , name , desc ) )
for func , name , desc in actions
] )
2009-03-24 04:22:56 +08:00
return actions
2009-04-07 04:23:33 +08:00
def get_action_choices ( self , request , default_choices = BLANK_CHOICE_DASH ) :
2009-03-24 04:22:56 +08:00
"""
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 self . get_actions ( request ) . itervalues ( ) :
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 calable ,
or the name of a method on the ModelAdmin . Return is a tuple of
( callable , name , description ) .
"""
2009-04-07 04:23:33 +08:00
# If the action is a callable, just use it.
2009-03-24 04:22:56 +08:00
if callable ( action ) :
func = action
action = action . __name__
2009-04-07 04:23:33 +08:00
# 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
2009-03-24 04:22:56 +08:00
if hasattr ( func , ' short_description ' ) :
description = func . short_description
else :
description = capfirst ( action . replace ( ' _ ' , ' ' ) )
return func , action , description
2008-08-10 00:45:15 +08:00
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 ' ) ) )
2009-03-18 17:19:24 +08:00
2008-08-10 00:45:15 +08:00
if formsets :
for formset in formsets :
for added_object in formset . new_objects :
change_message . append ( _ ( ' Added %(name)s " %(object)s " . ' )
% { ' name ' : added_object . _meta . verbose_name ,
2008-11-11 01:13:41 +08:00
' object ' : force_unicode ( added_object ) } )
2008-08-10 00:45:15 +08:00
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 ' : changed_object . _meta . verbose_name ,
2008-11-11 01:13:41 +08:00
' object ' : force_unicode ( changed_object ) } )
2008-08-10 00:45:15 +08:00
for deleted_object in formset . deleted_objects :
change_message . append ( _ ( ' Deleted %(name)s " %(object)s " . ' )
% { ' name ' : deleted_object . _meta . verbose_name ,
2008-11-11 01:13:41 +08:00
' object ' : force_unicode ( deleted_object ) } )
2008-08-10 00:45:15 +08:00
change_message = ' ' . join ( change_message )
return change_message or _ ( ' No fields changed. ' )
2009-03-18 17:19:24 +08:00
2008-08-10 00:45:15 +08:00
def message_user ( self , request , message ) :
"""
2009-01-15 04:22:25 +08:00
Send a message to the user . The default implementation
2008-08-10 00:45:15 +08:00
posts a message using the auth Message object .
"""
request . user . message_set . create ( message = message )
2009-03-18 17:19:24 +08:00
2008-08-10 04:52:40 +08:00
def save_form ( self , request , form , change ) :
2008-08-10 01:12:48 +08:00
"""
2008-08-10 04:52:40 +08:00
Given a ModelForm return an unsaved instance . ` ` change ` ` is True if
the object is being changed , and False if it ' s being added.
2008-08-10 01:12:48 +08:00
"""
2008-08-10 04:52:40 +08:00
return form . save ( commit = False )
2009-03-18 17:19:24 +08:00
2008-08-12 01:20:47 +08:00
def save_model ( self , request , obj , form , change ) :
"""
Given a model instance save it to the database .
"""
obj . save ( )
2009-03-18 17:19:24 +08:00
2008-08-10 01:12:48 +08:00
def save_formset ( self , request , form , formset , change ) :
"""
2008-08-12 01:20:47 +08:00
Given an inline formset save it to the database .
2008-08-10 01:12:48 +08:00
"""
2008-08-12 01:20:47 +08:00
formset . save ( )
2009-03-18 17:19:24 +08:00
2008-08-10 04:52:40 +08:00
def render_change_form ( self , request , context , add = False , change = False , form_url = ' ' , obj = None ) :
opts = self . model . _meta
app_label = opts . app_label
ordered_objects = opts . get_ordered_objects ( )
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 ' ) ,
' ordered_objects ' : ordered_objects ,
' form_url ' : mark_safe ( 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 ,
' root_path ' : self . admin_site . root_path ,
} )
return render_to_response ( self . change_form_template or [
" admin/ %s / %s /change_form.html " % ( app_label , opts . object_name . lower ( ) ) ,
" admin/ %s /change_form.html " % app_label ,
" admin/change_form.html "
] , context , context_instance = template . RequestContext ( request ) )
2009-03-18 17:19:24 +08:00
2008-08-10 04:52:40 +08:00
def response_add ( self , request , obj , post_url_continue = ' ../ %s / ' ) :
2008-07-19 07:54:34 +08:00
"""
2008-08-10 04:52:40 +08:00
Determines the HttpResponse for the add_view stage .
2008-07-19 07:54:34 +08:00
"""
2008-08-10 04:52:40 +08:00
opts = obj . _meta
pk_value = obj . _get_pk_val ( )
2009-03-18 17:19:24 +08:00
2008-08-10 04:52:40 +08:00
msg = _ ( ' The %(name)s " %(obj)s " was added successfully. ' ) % { ' name ' : force_unicode ( opts . verbose_name ) , ' obj ' : force_unicode ( obj ) }
2008-07-19 07:54:34 +08:00
# Here, we distinguish between different save types by checking for
# the presence of keys in request.POST.
if request . POST . has_key ( " _continue " ) :
2008-08-10 00:45:15 +08:00
self . message_user ( request , msg + ' ' + _ ( " You may edit it again below. " ) )
2008-07-19 07:54:34 +08:00
if request . POST . has_key ( " _popup " ) :
post_url_continue + = " ?_popup=1 "
return HttpResponseRedirect ( post_url_continue % pk_value )
2009-03-18 17:19:24 +08:00
2008-07-19 07:54:34 +08:00
if request . POST . has_key ( " _popup " ) :
return HttpResponse ( ' <script type= " text/javascript " >opener.dismissAddAnotherPopup(window, " %s " , " %s " );</script> ' % \
# escape() calls force_unicode.
2008-08-10 04:52:40 +08:00
( escape ( pk_value ) , escape ( obj ) ) )
2008-07-19 07:54:34 +08:00
elif request . POST . has_key ( " _addanother " ) :
2008-08-10 00:45:15 +08:00
self . message_user ( request , msg + ' ' + ( _ ( " You may add another %s below. " ) % force_unicode ( opts . verbose_name ) ) )
2008-07-19 07:54:34 +08:00
return HttpResponseRedirect ( request . path )
else :
2008-08-10 00:45:15 +08:00
self . message_user ( request , msg )
2009-03-18 17:19:24 +08:00
2008-07-19 07:54:34 +08:00
# Figure out where to redirect. If the user has change permission,
# redirect to the change-list page for this object. Otherwise,
# redirect to the admin index.
if self . has_change_permission ( request , None ) :
post_url = ' ../ '
else :
post_url = ' ../../../ '
return HttpResponseRedirect ( post_url )
2009-03-18 17:19:24 +08:00
2008-08-10 04:52:40 +08:00
def response_change ( self , request , obj ) :
2008-07-19 07:54:34 +08:00
"""
2008-08-10 04:52:40 +08:00
Determines the HttpResponse for the change_view stage .
2008-07-19 07:54:34 +08:00
"""
2008-08-10 04:52:40 +08:00
opts = obj . _meta
pk_value = obj . _get_pk_val ( )
2009-03-18 17:19:24 +08:00
2008-08-10 04:52:40 +08:00
msg = _ ( ' The %(name)s " %(obj)s " was changed successfully. ' ) % { ' name ' : force_unicode ( opts . verbose_name ) , ' obj ' : force_unicode ( obj ) }
2008-07-19 07:54:34 +08:00
if request . POST . has_key ( " _continue " ) :
2008-08-10 00:45:15 +08:00
self . message_user ( request , msg + ' ' + _ ( " You may edit it again below. " ) )
2008-07-19 07:54:34 +08:00
if request . REQUEST . has_key ( ' _popup ' ) :
return HttpResponseRedirect ( request . path + " ?_popup=1 " )
else :
return HttpResponseRedirect ( request . path )
elif request . POST . has_key ( " _saveasnew " ) :
2008-08-10 04:52:40 +08:00
msg = _ ( ' The %(name)s " %(obj)s " was added successfully. You may edit it again below. ' ) % { ' name ' : force_unicode ( opts . verbose_name ) , ' obj ' : obj }
2008-08-10 00:45:15 +08:00
self . message_user ( request , msg )
2008-07-19 07:54:34 +08:00
return HttpResponseRedirect ( " ../ %s / " % pk_value )
elif request . POST . has_key ( " _addanother " ) :
2008-08-10 00:45:15 +08:00
self . message_user ( request , msg + ' ' + ( _ ( " You may add another %s below. " ) % force_unicode ( opts . verbose_name ) ) )
2008-07-19 07:54:34 +08:00
return HttpResponseRedirect ( " ../add/ " )
else :
2008-08-10 00:45:15 +08:00
self . message_user ( request , msg )
2008-07-19 07:54:34 +08:00
return HttpResponseRedirect ( " ../ " )
2009-03-18 17:19:24 +08:00
2009-03-24 04:22:56 +08:00
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 )
2009-04-07 04:23:33 +08:00
# 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
2009-03-24 04:22:56 +08:00
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 ' ]
func , name , description = self . get_actions ( request ) [ action ]
2009-03-24 16:22:23 +08:00
# Get the list of selected PKs. If nothing's selected, we can't
2009-03-24 04:22:56 +08:00
# perform an action on it, so bail.
selected = request . POST . getlist ( helpers . ACTION_CHECKBOX_NAME )
if not selected :
return None
2009-04-07 04:23:33 +08:00
response = func ( self , request , queryset . filter ( pk__in = selected ) )
2009-03-24 16:22:23 +08:00
2009-03-24 04:22:56 +08:00
# Actions may return an HttpResponse, 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 , HttpResponse ) :
return response
else :
return HttpResponseRedirect ( " . " )
2008-07-19 07:54:34 +08:00
def add_view ( self , request , form_url = ' ' , extra_context = None ) :
" The ' add ' admin view for this model. "
model = self . model
opts = model . _meta
2009-03-18 17:19:24 +08:00
2008-07-19 07:54:34 +08:00
if not self . has_add_permission ( request ) :
raise PermissionDenied
2009-03-18 17:19:24 +08:00
2008-07-19 07:54:34 +08:00
ModelForm = self . get_form ( request )
2008-08-10 04:52:40 +08:00
formsets = [ ]
2008-07-19 07:54:34 +08:00
if request . method == ' POST ' :
form = ModelForm ( request . POST , request . FILES )
2008-08-10 04:52:40 +08:00
if form . is_valid ( ) :
form_validated = True
new_object = self . save_form ( request , form , change = False )
else :
form_validated = False
new_object = self . model ( )
2009-03-10 19:19:26 +08:00
prefixes = { }
2008-07-19 07:54:34 +08:00
for FormSet in self . get_formsets ( request ) :
2009-03-10 19:19:26 +08:00
prefix = FormSet . get_default_prefix ( )
prefixes [ prefix ] = prefixes . get ( prefix , 0 ) + 1
if prefixes [ prefix ] != 1 :
prefix = " %s - %s " % ( prefix , prefixes [ prefix ] )
2008-08-10 04:52:40 +08:00
formset = FormSet ( data = request . POST , files = request . FILES ,
instance = new_object ,
2009-03-10 19:19:26 +08:00
save_as_new = request . POST . has_key ( " _saveasnew " ) ,
prefix = prefix )
2008-08-10 04:52:40 +08:00
formsets . append ( formset )
if all_valid ( formsets ) and form_validated :
2008-08-12 01:20:47 +08:00
self . save_model ( request , new_object , form , change = False )
2008-08-10 04:52:40 +08:00
form . save_m2m ( )
for formset in formsets :
2008-08-12 01:20:47 +08:00
self . save_formset ( request , form , formset , change = False )
2009-03-18 17:19:24 +08:00
2008-08-10 04:52:40 +08:00
self . log_addition ( request , new_object )
return self . response_add ( request , new_object )
2008-07-19 07:54:34 +08:00
else :
2008-08-30 00:09:29 +08:00
# 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 )
2008-08-30 03:50:22 +08:00
except models . FieldDoesNotExist :
2008-08-30 02:12:31 +08:00
continue
2008-08-30 00:09:29 +08:00
if isinstance ( f , models . ManyToManyField ) :
initial [ k ] = initial [ k ] . split ( " , " )
form = ModelForm ( initial = initial )
2009-03-10 19:19:26 +08:00
prefixes = { }
2008-07-19 07:54:34 +08:00
for FormSet in self . get_formsets ( request ) :
2009-03-10 19:19:26 +08:00
prefix = FormSet . get_default_prefix ( )
prefixes [ prefix ] = prefixes . get ( prefix , 0 ) + 1
if prefixes [ prefix ] != 1 :
prefix = " %s - %s " % ( prefix , prefixes [ prefix ] )
formset = FormSet ( instance = self . model ( ) , prefix = prefix )
2008-08-10 04:52:40 +08:00
formsets . append ( formset )
2009-03-18 17:19:24 +08:00
2008-08-18 23:49:58 +08:00
adminForm = helpers . AdminForm ( form , list ( self . get_fieldsets ( request ) ) , self . prepopulated_fields )
2008-07-19 07:54:34 +08:00
media = self . media + adminForm . media
2009-03-18 17:19:24 +08:00
2008-07-19 07:54:34 +08:00
inline_admin_formsets = [ ]
2008-08-10 04:52:40 +08:00
for inline , formset in zip ( self . inline_instances , formsets ) :
2008-07-19 07:54:34 +08:00
fieldsets = list ( inline . get_fieldsets ( request ) )
2008-08-18 23:49:58 +08:00
inline_admin_formset = helpers . InlineAdminFormSet ( inline , formset , fieldsets )
2008-07-19 07:54:34 +08:00
inline_admin_formsets . append ( inline_admin_formset )
2008-08-16 01:36:33 +08:00
media = media + inline_admin_formset . media
2009-03-18 17:19:24 +08:00
2008-07-19 07:54:34 +08:00
context = {
2008-07-30 08:52:01 +08:00
' title ' : _ ( ' Add %s ' ) % force_unicode ( opts . verbose_name ) ,
2008-07-19 07:54:34 +08:00
' adminform ' : adminForm ,
' is_popup ' : request . REQUEST . has_key ( ' _popup ' ) ,
' show_delete ' : False ,
' media ' : mark_safe ( media ) ,
' inline_admin_formsets ' : inline_admin_formsets ,
2008-08-18 23:49:58 +08:00
' errors ' : helpers . AdminErrorList ( form , formsets ) ,
2008-07-19 07:54:34 +08:00
' root_path ' : self . admin_site . root_path ,
2008-09-08 13:45:17 +08:00
' app_label ' : opts . app_label ,
2008-07-19 07:54:34 +08:00
}
context . update ( extra_context or { } )
return self . render_change_form ( request , context , add = True )
2008-08-10 04:52:40 +08:00
add_view = transaction . commit_on_success ( add_view )
2009-03-18 17:19:24 +08:00
2008-07-19 07:54:34 +08:00
def change_view ( self , request , object_id , extra_context = None ) :
" The ' change ' admin view for this model. "
model = self . model
opts = model . _meta
2009-03-18 17:19:24 +08:00
2008-07-19 07:54:34 +08:00
try :
2009-04-01 11:11:58 +08:00
obj = self . queryset ( request ) . get ( pk = unquote ( object_id ) )
2008-07-19 07:54:34 +08:00
except model . DoesNotExist :
# Don't raise Http404 just yet, because we haven't checked
# permissions yet. We don't want an unauthenticated user to be able
# to determine whether a given object exists.
obj = None
2009-03-18 17:19:24 +08:00
2008-07-19 07:54:34 +08:00
if not self . has_change_permission ( request , obj ) :
raise PermissionDenied
2009-03-18 17:19:24 +08:00
2008-07-19 07:54:34 +08:00
if obj is None :
2008-11-07 00:44:42 +08:00
raise Http404 ( _ ( ' %(name)s object with primary key %(key)r does not exist. ' ) % { ' name ' : force_unicode ( opts . verbose_name ) , ' key ' : escape ( object_id ) } )
2009-03-18 17:19:24 +08:00
2008-09-17 16:59:55 +08:00
if request . method == ' POST ' and request . POST . has_key ( " _saveasnew " ) :
2008-07-19 07:54:34 +08:00
return self . add_view ( request , form_url = ' ../../add/ ' )
2009-03-18 17:19:24 +08:00
2008-07-19 07:54:34 +08:00
ModelForm = self . get_form ( request , obj )
2008-08-10 04:52:40 +08:00
formsets = [ ]
2008-07-19 07:54:34 +08:00
if request . method == ' POST ' :
form = ModelForm ( request . POST , request . FILES , instance = obj )
2008-08-10 04:52:40 +08:00
if form . is_valid ( ) :
form_validated = True
new_object = self . save_form ( request , form , change = True )
else :
form_validated = False
new_object = obj
2009-03-10 19:19:26 +08:00
prefixes = { }
2008-08-10 04:52:40 +08:00
for FormSet in self . get_formsets ( request , new_object ) :
2009-03-10 19:19:26 +08:00
prefix = FormSet . get_default_prefix ( )
prefixes [ prefix ] = prefixes . get ( prefix , 0 ) + 1
if prefixes [ prefix ] != 1 :
prefix = " %s - %s " % ( prefix , prefixes [ prefix ] )
2008-08-10 04:52:40 +08:00
formset = FormSet ( request . POST , request . FILES ,
2009-03-10 19:19:26 +08:00
instance = new_object , prefix = prefix )
2008-08-10 04:52:40 +08:00
formsets . append ( formset )
2009-03-18 17:19:24 +08:00
2008-08-10 04:52:40 +08:00
if all_valid ( formsets ) and form_validated :
2008-08-12 01:20:47 +08:00
self . save_model ( request , new_object , form , change = True )
2008-08-10 04:52:40 +08:00
form . save_m2m ( )
for formset in formsets :
2008-08-12 01:20:47 +08:00
self . save_formset ( request , form , formset , change = True )
2009-03-18 17:19:24 +08:00
2008-08-10 04:52:40 +08:00
change_message = self . construct_change_message ( request , form , formsets )
self . log_change ( request , new_object , change_message )
return self . response_change ( request , new_object )
2009-03-18 17:19:24 +08:00
2008-07-19 07:54:34 +08:00
else :
form = ModelForm ( instance = obj )
2009-03-10 19:19:26 +08:00
prefixes = { }
2008-07-19 07:54:34 +08:00
for FormSet in self . get_formsets ( request , obj ) :
2009-03-10 19:19:26 +08:00
prefix = FormSet . get_default_prefix ( )
prefixes [ prefix ] = prefixes . get ( prefix , 0 ) + 1
if prefixes [ prefix ] != 1 :
prefix = " %s - %s " % ( prefix , prefixes [ prefix ] )
formset = FormSet ( instance = obj , prefix = prefix )
2008-08-10 04:52:40 +08:00
formsets . append ( formset )
2009-03-18 17:19:24 +08:00
2008-08-18 23:49:58 +08:00
adminForm = helpers . AdminForm ( form , self . get_fieldsets ( request , obj ) , self . prepopulated_fields )
2008-07-19 07:54:34 +08:00
media = self . media + adminForm . media
2009-03-18 17:19:24 +08:00
2008-07-19 07:54:34 +08:00
inline_admin_formsets = [ ]
2008-08-10 04:52:40 +08:00
for inline , formset in zip ( self . inline_instances , formsets ) :
2008-07-19 07:54:34 +08:00
fieldsets = list ( inline . get_fieldsets ( request , obj ) )
2008-08-18 23:49:58 +08:00
inline_admin_formset = helpers . InlineAdminFormSet ( inline , formset , fieldsets )
2008-07-19 07:54:34 +08:00
inline_admin_formsets . append ( inline_admin_formset )
2008-07-23 11:25:41 +08:00
media = media + inline_admin_formset . media
2009-03-18 17:19:24 +08:00
2008-07-19 07:54:34 +08:00
context = {
2008-07-30 08:52:01 +08:00
' title ' : _ ( ' Change %s ' ) % force_unicode ( opts . verbose_name ) ,
2008-07-19 07:54:34 +08:00
' adminform ' : adminForm ,
' object_id ' : object_id ,
' original ' : obj ,
' is_popup ' : request . REQUEST . has_key ( ' _popup ' ) ,
' media ' : mark_safe ( media ) ,
' inline_admin_formsets ' : inline_admin_formsets ,
2008-08-18 23:49:58 +08:00
' errors ' : helpers . AdminErrorList ( form , formsets ) ,
2008-07-19 07:54:34 +08:00
' root_path ' : self . admin_site . root_path ,
2008-09-08 13:45:17 +08:00
' app_label ' : opts . app_label ,
2008-07-19 07:54:34 +08:00
}
context . update ( extra_context or { } )
return self . render_change_form ( request , context , change = True , obj = obj )
2008-08-10 04:52:40 +08:00
change_view = transaction . commit_on_success ( change_view )
2009-03-18 17:19:24 +08:00
2008-07-19 07:54:34 +08:00
def changelist_view ( self , request , extra_context = None ) :
" The ' change list ' admin view for this model. "
from django . contrib . admin . views . main import ChangeList , ERROR_FLAG
opts = self . model . _meta
app_label = opts . app_label
if not self . has_change_permission ( request , None ) :
raise PermissionDenied
2009-04-07 04:23:33 +08:00
# Check actions to see if any are available on this changelist
actions = self . get_actions ( request )
# Remove action checkboxes if there aren't any actions available.
list_display = list ( self . list_display )
if not actions :
try :
list_display . remove ( ' action_checkbox ' )
except ValueError :
pass
2008-07-19 07:54:34 +08:00
try :
2009-04-07 04:23:33 +08:00
cl = ChangeList ( request , self . model , list_display , self . list_display_links , self . list_filter ,
2009-03-18 04:51:47 +08:00
self . date_hierarchy , self . search_fields , self . list_select_related , self . list_per_page , self . list_editable , self )
2008-07-19 07:54:34 +08:00
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 render_to_response ( ' admin/invalid_setup.html ' , { ' title ' : _ ( ' Database error ' ) } )
return HttpResponseRedirect ( request . path + ' ? ' + ERROR_FLAG + ' =1 ' )
2009-04-07 04:23:33 +08:00
2009-03-24 04:22:56 +08:00
# If the request was POSTed, this might be a bulk action or a bulk edit.
# Try to look up an action first, but if this isn't an action the POST
# will fall through to the bulk edit check, below.
2009-04-07 04:23:33 +08:00
if actions and request . method == ' POST ' :
2009-03-24 04:22:56 +08:00
response = self . response_action ( request , queryset = cl . get_query_set ( ) )
if response :
return response
2009-03-24 16:22:23 +08:00
2009-03-18 04:51:47 +08:00
# 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
2009-03-18 17:19:24 +08:00
2009-03-18 04:51:47 +08:00
# Handle POSTed bulk-edit data.
if request . method == " POST " and self . list_editable :
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 )
form . save_m2m ( )
change_msg = self . construct_change_message ( request , form , None )
self . log_change ( request , obj , change_msg )
changecount + = 1
2009-03-18 17:19:24 +08:00
2009-03-18 04:51:47 +08:00
if changecount :
2009-03-18 17:19:24 +08:00
if changecount == 1 :
name = force_unicode ( opts . verbose_name )
else :
name = force_unicode ( opts . verbose_name_plural )
msg = ngettext ( " %(count)s %(name)s was changed successfully. " ,
" %(count)s %(name)s were changed successfully. " ,
2009-03-18 04:51:47 +08:00
changecount ) % { ' count ' : changecount ,
2009-03-18 17:19:24 +08:00
' name ' : name ,
2009-03-18 04:51:47 +08:00
' obj ' : force_unicode ( obj ) }
self . message_user ( request , msg )
2009-03-18 17:19:24 +08:00
2009-03-18 04:51:47 +08:00
return HttpResponseRedirect ( request . get_full_path ( ) )
2009-03-18 17:19:24 +08:00
2009-03-18 04:51:47 +08:00
# Handle GET -- construct a formset for display.
elif self . list_editable :
FormSet = self . get_changelist_formset ( request )
formset = cl . formset = FormSet ( queryset = cl . result_list )
2009-03-18 17:19:24 +08:00
2009-03-18 04:51:47 +08:00
# Build the list of media to be used by the formset.
if formset :
media = self . media + formset . media
else :
2009-03-24 04:22:56 +08:00
media = self . media
2009-03-24 16:22:23 +08:00
2009-03-24 04:22:56 +08:00
# Build the action form and populate it with available actions.
2009-04-07 04:23:33 +08:00
if actions :
action_form = self . action_form ( auto_id = None )
action_form . fields [ ' action ' ] . choices = self . get_action_choices ( request )
else :
action_form = None
2009-03-18 04:51:47 +08:00
2008-07-19 07:54:34 +08:00
context = {
' title ' : cl . title ,
' is_popup ' : cl . is_popup ,
' cl ' : cl ,
2009-03-18 04:51:47 +08:00
' media ' : media ,
2008-07-19 07:54:34 +08:00
' has_add_permission ' : self . has_add_permission ( request ) ,
' root_path ' : self . admin_site . root_path ,
2008-08-23 12:00:15 +08:00
' app_label ' : app_label ,
2009-03-24 04:22:56 +08:00
' action_form ' : action_form ,
' actions_on_top ' : self . actions_on_top ,
' actions_on_bottom ' : self . actions_on_bottom ,
2008-07-19 07:54:34 +08:00
}
context . update ( extra_context or { } )
return render_to_response ( self . change_list_template or [
' admin/ %s / %s /change_list.html ' % ( app_label , opts . object_name . lower ( ) ) ,
' admin/ %s /change_list.html ' % app_label ,
' admin/change_list.html '
] , context , context_instance = template . RequestContext ( request ) )
2009-03-18 17:19:24 +08:00
2008-07-19 07:54:34 +08:00
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
2009-03-18 17:19:24 +08:00
2008-07-19 07:54:34 +08:00
try :
2009-04-01 11:11:58 +08:00
obj = self . queryset ( request ) . get ( pk = unquote ( object_id ) )
2008-07-19 07:54:34 +08:00
except self . model . DoesNotExist :
# Don't raise Http404 just yet, because we haven't checked
# permissions yet. We don't want an unauthenticated user to be able
# to determine whether a given object exists.
obj = None
2009-03-18 17:19:24 +08:00
2008-07-19 07:54:34 +08:00
if not self . has_delete_permission ( request , obj ) :
raise PermissionDenied
2009-03-18 17:19:24 +08:00
2008-07-19 07:54:34 +08:00
if obj is None :
2008-11-07 00:44:42 +08:00
raise Http404 ( _ ( ' %(name)s object with primary key %(key)r does not exist. ' ) % { ' name ' : force_unicode ( opts . verbose_name ) , ' key ' : escape ( object_id ) } )
2009-03-18 17:19:24 +08:00
2008-07-19 07:54:34 +08:00
# Populate deleted_objects, a data structure of all related objects that
# will also be deleted.
2009-01-15 04:22:25 +08:00
deleted_objects = [ mark_safe ( u ' %s : <a href= " ../../ %s / " > %s </a> ' % ( escape ( force_unicode ( capfirst ( opts . verbose_name ) ) ) , object_id , escape ( obj ) ) ) , [ ] ]
2008-08-02 13:29:17 +08:00
perms_needed = set ( )
2008-07-19 07:54:34 +08:00
get_deleted_objects ( deleted_objects , perms_needed , request . user , obj , opts , 1 , self . admin_site )
2009-03-18 17:19:24 +08:00
2008-07-19 07:54:34 +08:00
if request . POST : # The user has already confirmed the deletion.
if perms_needed :
raise PermissionDenied
2008-09-19 01:10:16 +08:00
obj_display = force_unicode ( obj )
2008-07-19 07:54:34 +08:00
obj . delete ( )
2009-03-18 17:19:24 +08:00
2008-08-10 00:45:15 +08:00
self . log_deletion ( request , obj , obj_display )
self . message_user ( request , _ ( ' The %(name)s " %(obj)s " was deleted successfully. ' ) % { ' name ' : force_unicode ( opts . verbose_name ) , ' obj ' : force_unicode ( obj_display ) } )
2009-03-18 17:19:24 +08:00
2008-07-19 07:54:34 +08:00
if not self . has_change_permission ( request , None ) :
return HttpResponseRedirect ( " ../../../../ " )
return HttpResponseRedirect ( " ../../ " )
2009-03-18 17:19:24 +08:00
2008-07-19 07:54:34 +08:00
context = {
" title " : _ ( " Are you sure? " ) ,
2008-07-30 08:52:01 +08:00
" object_name " : force_unicode ( opts . verbose_name ) ,
2008-07-19 07:54:34 +08:00
" object " : obj ,
" deleted_objects " : deleted_objects ,
" perms_lacking " : perms_needed ,
" opts " : opts ,
" root_path " : self . admin_site . root_path ,
2008-08-23 12:00:15 +08:00
" app_label " : app_label ,
2008-07-19 07:54:34 +08:00
}
context . update ( extra_context or { } )
return render_to_response ( self . delete_confirmation_template or [
" admin/ %s / %s /delete_confirmation.html " % ( app_label , opts . object_name . lower ( ) ) ,
" admin/ %s /delete_confirmation.html " % app_label ,
" admin/delete_confirmation.html "
] , context , context_instance = template . RequestContext ( request ) )
2009-03-18 17:19:24 +08:00
2008-07-19 07:54:34 +08:00
def history_view ( self , request , object_id , extra_context = None ) :
" The ' history ' admin view for this model. "
from django . contrib . admin . models import LogEntry
model = self . model
opts = model . _meta
2008-08-26 03:56:14 +08:00
app_label = opts . app_label
2008-07-19 07:54:34 +08:00
action_list = LogEntry . objects . filter (
object_id = object_id ,
content_type__id__exact = ContentType . objects . get_for_model ( model ) . id
) . select_related ( ) . order_by ( ' action_time ' )
# If no history was found, see whether this object even exists.
obj = get_object_or_404 ( model , pk = object_id )
context = {
' title ' : _ ( ' Change history: %s ' ) % force_unicode ( obj ) ,
' action_list ' : action_list ,
2008-07-30 08:52:01 +08:00
' module_name ' : capfirst ( force_unicode ( opts . verbose_name_plural ) ) ,
2008-07-19 07:54:34 +08:00
' object ' : obj ,
' root_path ' : self . admin_site . root_path ,
2008-08-26 03:56:14 +08:00
' app_label ' : app_label ,
2008-07-19 07:54:34 +08:00
}
context . update ( extra_context or { } )
return render_to_response ( self . object_history_template or [
2008-09-08 13:45:17 +08:00
" admin/ %s / %s /object_history.html " % ( app_label , opts . object_name . lower ( ) ) ,
" admin/ %s /object_history.html " % app_label ,
2008-07-19 07:54:34 +08:00
" admin/object_history.html "
] , context , context_instance = template . RequestContext ( request ) )
2009-01-15 04:22:25 +08:00
#
# DEPRECATED methods.
#
def __call__ ( self , request , url ) :
"""
DEPRECATED : this is the old way of URL resolution , replaced by
` ` get_urls ( ) ` ` . This only called by AdminSite . root ( ) , which is also
deprecated .
2009-03-18 17:19:24 +08:00
2009-01-15 04:22:25 +08:00
Again , remember that the following code only exists for
backwards - compatibility . Any new URLs , changes to existing URLs , or
whatever need to be done up in get_urls ( ) , above !
2009-03-18 17:19:24 +08:00
2009-01-15 04:22:25 +08:00
This function still exists for backwards - compatibility ; it will be
removed in Django 1.3 .
"""
# Delegate to the appropriate method, based on the URL.
if url is None :
return self . changelist_view ( request )
elif url == " add " :
return self . add_view ( request )
elif url . endswith ( ' /history ' ) :
return self . history_view ( request , unquote ( url [ : - 8 ] ) )
elif url . endswith ( ' /delete ' ) :
return self . delete_view ( request , unquote ( url [ : - 7 ] ) )
else :
return self . change_view ( request , unquote ( url ) )
2008-07-19 07:54:34 +08:00
class InlineModelAdmin ( BaseModelAdmin ) :
"""
Options for inline editing of ` ` model ` ` instances .
2009-03-18 17:19:24 +08:00
2008-07-19 07:54:34 +08:00
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
2008-08-09 04:27:48 +08:00
formset = BaseInlineFormSet
2008-07-19 07:54:34 +08:00
extra = 3
max_num = 0
template = None
verbose_name = None
verbose_name_plural = None
2009-03-18 17:19:24 +08:00
2008-07-19 07:54:34 +08:00
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
2009-03-18 17:19:24 +08:00
2008-08-16 01:38:39 +08:00
def _media ( self ) :
from django . conf import settings
js = [ ]
if self . prepopulated_fields :
js . append ( ' js/urlify.js ' )
2008-08-16 04:33:45 +08:00
if self . filter_vertical or self . filter_horizontal :
js . extend ( [ ' js/SelectBox.js ' , ' js/SelectFilter2.js ' ] )
2008-08-16 01:38:39 +08:00
return forms . Media ( js = [ ' %s %s ' % ( settings . ADMIN_MEDIA_PREFIX , url ) for url in js ] )
media = property ( _media )
2009-03-18 17:19:24 +08:00
2008-08-16 05:57:36 +08:00
def get_formset ( self , request , obj = None , * * kwargs ) :
2008-07-19 07:54:34 +08:00
""" Returns a BaseInlineFormSet class for use in admin add/change views. """
if self . declared_fieldsets :
fields = flatten_fieldsets ( self . declared_fieldsets )
else :
fields = None
2008-09-03 01:26:24 +08:00
if self . exclude is None :
exclude = [ ]
else :
2008-09-27 13:57:10 +08:00
exclude = list ( self . exclude )
2008-08-16 05:57:36 +08:00
defaults = {
" form " : self . form ,
" formset " : self . formset ,
" fk_name " : self . fk_name ,
" fields " : fields ,
2008-09-03 01:26:24 +08:00
" exclude " : exclude + kwargs . get ( " exclude " , [ ] ) ,
2009-01-16 23:32:31 +08:00
" formfield_callback " : curry ( self . formfield_for_dbfield , request = request ) ,
2008-08-16 05:57:36 +08:00
" extra " : self . extra ,
" max_num " : self . max_num ,
}
defaults . update ( kwargs )
return inlineformset_factory ( self . parent_model , self . model , * * defaults )
2009-03-18 17:19:24 +08:00
2008-07-19 07:54:34 +08:00
def get_fieldsets ( self , request , obj = None ) :
if self . declared_fieldsets :
return self . declared_fieldsets
form = self . get_formset ( request ) . form
return [ ( None , { ' fields ' : form . base_fields . keys ( ) } ) ]
2009-03-18 17:19:24 +08:00
2008-07-19 07:54:34 +08:00
class StackedInline ( InlineModelAdmin ) :
template = ' admin/edit_inline/stacked.html '
class TabularInline ( InlineModelAdmin ) :
template = ' admin/edit_inline/tabular.html '