2008-08-02 13:56:57 +08:00
import re
2008-07-19 07:54:34 +08:00
from django import http , template
from django . contrib . admin import ModelAdmin
from django . contrib . auth import authenticate , login
from django . db . models . base import ModelBase
2008-08-10 07:40:57 +08:00
from django . core . exceptions import ImproperlyConfigured
2008-07-19 07:54:34 +08:00
from django . shortcuts import render_to_response
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 . safestring import mark_safe
from django . utils . text import capfirst
from django . utils . translation import ugettext_lazy , ugettext as _
from django . views . decorators . cache import never_cache
from django . conf import settings
ERROR_MESSAGE = ugettext_lazy ( " Please enter a correct username and password. Note that both fields are case-sensitive. " )
LOGIN_FORM_KEY = ' this_is_the_login_form '
class AlreadyRegistered ( Exception ) :
pass
class NotRegistered ( Exception ) :
pass
class AdminSite ( object ) :
"""
An AdminSite object encapsulates an instance of the Django admin application , ready
2009-04-01 00:07:07 +08:00
to be hooked in to your URLconf . Models are registered with the AdminSite using the
2008-07-19 07:54:34 +08:00
register ( ) method , and the root ( ) method can then be used as a Django view function
that presents a full admin interface for the collection of registered models .
"""
2009-03-24 04:22:56 +08:00
2008-07-19 07:54:34 +08:00
index_template = None
login_template = None
2008-08-24 00:27:12 +08:00
app_index_template = None
2009-03-24 04:22:56 +08:00
2009-01-15 04:22:25 +08:00
def __init__ ( self , name = None ) :
2008-07-19 07:54:34 +08:00
self . _registry = { } # model_class class -> admin_class instance
2009-01-15 04:22:25 +08:00
# TODO Root path is used to calculate urls under the old root() method
# in order to maintain backwards compatibility we are leaving that in
# so root_path isn't needed, not sure what to do about this.
self . root_path = ' admin/ '
if name is None :
name = ' '
else :
name + = ' _ '
self . name = name
2009-03-24 04:22:56 +08:00
self . actions = [ ]
2008-07-19 07:54:34 +08:00
def register ( self , model_or_iterable , admin_class = None , * * options ) :
"""
Registers the given model ( s ) with the given admin class .
2009-03-24 04:22:56 +08:00
2008-07-19 07:54:34 +08:00
The model ( s ) should be Model classes , not instances .
2009-03-24 04:22:56 +08:00
2008-07-19 07:54:34 +08:00
If an admin class isn ' t given, it will use ModelAdmin (the default
admin options ) . If keyword arguments are given - - e . g . , list_display - -
they ' ll be applied as options to the admin class.
2009-03-24 04:22:56 +08:00
2008-07-19 07:54:34 +08:00
If a model is already registered , this will raise AlreadyRegistered .
"""
2009-03-18 04:40:01 +08:00
if not admin_class :
admin_class = ModelAdmin
2008-07-24 02:58:06 +08:00
# Don't import the humongous validation code unless required
if admin_class and settings . DEBUG :
2008-07-19 07:54:34 +08:00
from django . contrib . admin . validation import validate
2008-07-24 02:58:06 +08:00
else :
validate = lambda model , adminclass : None
2009-03-24 04:22:56 +08:00
2008-07-19 07:54:34 +08:00
if isinstance ( model_or_iterable , ModelBase ) :
model_or_iterable = [ model_or_iterable ]
for model in model_or_iterable :
if model in self . _registry :
raise AlreadyRegistered ( ' The model %s is already registered ' % model . __name__ )
2009-03-24 04:22:56 +08:00
2008-07-24 02:58:06 +08:00
# If we got **options then dynamically construct a subclass of
# admin_class with those **options.
if options :
# For reasons I don't quite understand, without a __module__
# the created class appears to "live" in the wrong place,
# which causes issues later on.
options [ ' __module__ ' ] = __name__
admin_class = type ( " %s Admin " % model . __name__ , ( admin_class , ) , options )
2009-03-24 04:22:56 +08:00
2008-07-24 02:58:06 +08:00
# Validate (which might be a no-op)
validate ( admin_class , model )
2009-03-24 04:22:56 +08:00
2008-07-24 02:58:06 +08:00
# Instantiate the admin class to save in the registry
2008-07-19 07:54:34 +08:00
self . _registry [ model ] = admin_class ( model , self )
2009-03-24 04:22:56 +08:00
2008-07-19 07:54:34 +08:00
def unregister ( self , model_or_iterable ) :
"""
Unregisters the given model ( s ) .
2009-03-24 04:22:56 +08:00
2008-07-19 07:54:34 +08:00
If a model isn ' t already registered, this will raise NotRegistered.
"""
if isinstance ( model_or_iterable , ModelBase ) :
model_or_iterable = [ model_or_iterable ]
for model in model_or_iterable :
if model not in self . _registry :
raise NotRegistered ( ' The model %s is not registered ' % model . __name__ )
del self . _registry [ model ]
2009-03-24 04:22:56 +08:00
def add_action ( self , action ) :
if not callable ( action ) :
raise TypeError ( " You can only register callable actions through an admin site " )
self . actions . append ( action )
2008-07-19 07:54:34 +08:00
def has_permission ( self , request ) :
"""
Returns True if the given HttpRequest has permission to view
* at least one * page in the admin site .
"""
return request . user . is_authenticated ( ) and request . user . is_staff
2009-03-24 04:22:56 +08:00
2008-08-10 20:41:42 +08:00
def check_dependencies ( self ) :
2008-08-10 07:40:57 +08:00
"""
Check that all things needed to run the admin have been correctly installed .
2009-03-24 04:22:56 +08:00
2008-08-10 07:40:57 +08:00
The default implementation checks that LogEntry , ContentType and the
auth context processor are installed .
"""
from django . contrib . admin . models import LogEntry
2008-08-10 07:56:34 +08:00
from django . contrib . contenttypes . models import ContentType
2009-03-24 04:22:56 +08:00
2008-08-10 07:40:57 +08:00
if not LogEntry . _meta . installed :
raise ImproperlyConfigured ( " Put ' django.contrib.admin ' in your INSTALLED_APPS setting in order to use the admin application. " )
if not ContentType . _meta . installed :
raise ImproperlyConfigured ( " Put ' django.contrib.contenttypes ' in your INSTALLED_APPS setting in order to use the admin application. " )
if ' django.core.context_processors.auth ' not in settings . TEMPLATE_CONTEXT_PROCESSORS :
raise ImproperlyConfigured ( " Put ' django.core.context_processors.auth ' in your TEMPLATE_CONTEXT_PROCESSORS setting in order to use the admin application. " )
2009-03-24 04:22:56 +08:00
2009-01-15 04:22:25 +08:00
def admin_view ( self , view ) :
2008-08-02 13:56:57 +08:00
"""
2009-01-15 04:22:25 +08:00
Decorator to create an " admin view attached to this ``AdminSite``. This
wraps the view and provides permission checking by calling
` ` self . has_permission ` ` .
2009-03-24 04:22:56 +08:00
2009-01-15 04:22:25 +08:00
You ' ll want to use this from within ``AdminSite.get_urls()``:
2009-03-24 04:22:56 +08:00
2009-01-15 04:22:25 +08:00
class MyAdminSite ( AdminSite ) :
2009-03-24 04:22:56 +08:00
2009-01-15 04:22:25 +08:00
def get_urls ( self ) :
from django . conf . urls . defaults import patterns , url
2009-03-24 04:22:56 +08:00
2009-01-15 04:22:25 +08:00
urls = super ( MyAdminSite , self ) . get_urls ( )
urls + = patterns ( ' ' ,
url ( r ' ^my_view/$ ' , self . protected_view ( some_view ) )
)
return urls
2008-07-19 07:54:34 +08:00
"""
2009-01-15 04:22:25 +08:00
def inner ( request , * args , * * kwargs ) :
if not self . has_permission ( request ) :
return self . login ( request )
return view ( request , * args , * * kwargs )
return update_wrapper ( inner , view )
2009-03-24 04:22:56 +08:00
2009-01-15 04:22:25 +08:00
def get_urls ( self ) :
from django . conf . urls . defaults import patterns , url , include
2009-03-24 04:22:56 +08:00
2009-01-15 04:22:25 +08:00
def wrap ( view ) :
def wrapper ( * args , * * kwargs ) :
return self . admin_view ( view ) ( * args , * * kwargs )
return update_wrapper ( wrapper , view )
2009-03-24 04:22:56 +08:00
2009-01-15 04:22:25 +08:00
# Admin-site-wide views.
urlpatterns = patterns ( ' ' ,
url ( r ' ^$ ' ,
wrap ( self . index ) ,
name = ' %s admin_index ' % self . name ) ,
url ( r ' ^logout/$ ' ,
wrap ( self . logout ) ,
name = ' %s admin_logout ' ) ,
url ( r ' ^password_change/$ ' ,
wrap ( self . password_change ) ,
name = ' %s admin_password_change ' % self . name ) ,
url ( r ' ^password_change/done/$ ' ,
wrap ( self . password_change_done ) ,
name = ' %s admin_password_change_done ' % self . name ) ,
url ( r ' ^jsi18n/$ ' ,
wrap ( self . i18n_javascript ) ,
name = ' %s admin_jsi18n ' % self . name ) ,
url ( r ' ^r/(?P<content_type_id> \ d+)/(?P<object_id>.+)/$ ' ,
' django.views.defaults.shortcut ' ) ,
url ( r ' ^(?P<app_label> \ w+)/$ ' ,
wrap ( self . app_index ) ,
name = ' %s admin_app_list ' % self . name ) ,
)
2009-03-24 04:22:56 +08:00
2009-01-15 04:22:25 +08:00
# Add in each model's views.
for model , model_admin in self . _registry . iteritems ( ) :
urlpatterns + = patterns ( ' ' ,
url ( r ' ^ %s / %s / ' % ( model . _meta . app_label , model . _meta . module_name ) ,
include ( model_admin . urls ) )
)
return urlpatterns
2009-03-24 04:22:56 +08:00
2009-01-15 04:22:25 +08:00
def urls ( self ) :
return self . get_urls ( )
urls = property ( urls )
2009-03-24 04:22:56 +08:00
2008-07-19 07:54:34 +08:00
def password_change ( self , request ) :
"""
Handles the " change password " task - - both form display and validation .
"""
from django . contrib . auth . views import password_change
2008-08-23 11:26:01 +08:00
return password_change ( request ,
2008-08-25 06:45:35 +08:00
post_change_redirect = ' %s password_change/done/ ' % self . root_path )
2009-03-24 04:22:56 +08:00
2008-07-19 07:54:34 +08:00
def password_change_done ( self , request ) :
"""
Displays the " success " page after a password change .
"""
from django . contrib . auth . views import password_change_done
return password_change_done ( request )
2009-03-24 04:22:56 +08:00
2008-07-19 07:54:34 +08:00
def i18n_javascript ( self , request ) :
"""
Displays the i18n JavaScript that the Django admin requires .
2009-03-24 04:22:56 +08:00
2008-07-19 07:54:34 +08:00
This takes into account the USE_I18N setting . If it ' s set to False, the
generated JavaScript will be leaner and faster .
"""
if settings . USE_I18N :
from django . views . i18n import javascript_catalog
else :
from django . views . i18n import null_javascript_catalog as javascript_catalog
return javascript_catalog ( request , packages = ' django.conf ' )
2009-03-24 04:22:56 +08:00
2008-07-19 07:54:34 +08:00
def logout ( self , request ) :
"""
Logs out the user for the given HttpRequest .
2009-03-24 04:22:56 +08:00
2008-07-19 07:54:34 +08:00
This should * not * assume the user is already logged in .
"""
from django . contrib . auth . views import logout
return logout ( request )
logout = never_cache ( logout )
2009-03-24 04:22:56 +08:00
2008-07-19 07:54:34 +08:00
def login ( self , request ) :
"""
Displays the login form for the given HttpRequest .
"""
from django . contrib . auth . models import User
2009-03-24 04:22:56 +08:00
2008-07-19 07:54:34 +08:00
# If this isn't already the login page, display it.
if not request . POST . has_key ( LOGIN_FORM_KEY ) :
if request . POST :
2008-09-03 05:10:00 +08:00
message = _ ( " Please log in again, because your session has expired. " )
2008-07-19 07:54:34 +08:00
else :
message = " "
return self . display_login_form ( request , message )
2009-03-24 04:22:56 +08:00
2008-07-19 07:54:34 +08:00
# Check that the user accepts cookies.
if not request . session . test_cookie_worked ( ) :
message = _ ( " Looks like your browser isn ' t configured to accept cookies. Please enable cookies, reload this page, and try again. " )
return self . display_login_form ( request , message )
2008-08-24 14:34:18 +08:00
else :
request . session . delete_test_cookie ( )
2009-03-24 04:22:56 +08:00
2008-07-19 07:54:34 +08:00
# Check the password.
username = request . POST . get ( ' username ' , None )
password = request . POST . get ( ' password ' , None )
user = authenticate ( username = username , password = password )
if user is None :
message = ERROR_MESSAGE
if u ' @ ' in username :
# Mistakenly entered e-mail address instead of username? Look it up.
try :
user = User . objects . get ( email = username )
except ( User . DoesNotExist , User . MultipleObjectsReturned ) :
message = _ ( " Usernames cannot contain the ' @ ' character. " )
else :
if user . check_password ( password ) :
message = _ ( " Your e-mail address is not your username. "
" Try ' %s ' instead. " ) % user . username
else :
message = _ ( " Usernames cannot contain the ' @ ' character. " )
return self . display_login_form ( request , message )
2009-03-24 04:22:56 +08:00
2008-07-19 07:54:34 +08:00
# The user data is correct; log in the user in and continue.
else :
if user . is_active and user . is_staff :
login ( request , user )
2008-09-03 05:10:00 +08:00
return http . HttpResponseRedirect ( request . get_full_path ( ) )
2008-07-19 07:54:34 +08:00
else :
return self . display_login_form ( request , ERROR_MESSAGE )
login = never_cache ( login )
2009-03-24 04:22:56 +08:00
2008-07-19 07:54:34 +08:00
def index ( self , request , extra_context = None ) :
"""
Displays the main admin index page , which lists all of the installed
apps that have been registered in this site .
"""
app_dict = { }
user = request . user
for model , model_admin in self . _registry . items ( ) :
app_label = model . _meta . app_label
has_module_perms = user . has_module_perms ( app_label )
2009-03-24 04:22:56 +08:00
2008-07-19 07:54:34 +08:00
if has_module_perms :
perms = {
' add ' : model_admin . has_add_permission ( request ) ,
' change ' : model_admin . has_change_permission ( request ) ,
' delete ' : model_admin . has_delete_permission ( request ) ,
}
2009-03-24 04:22:56 +08:00
2008-07-19 07:54:34 +08:00
# Check whether user has any perm for this module.
# If so, add the module to the model_list.
if True in perms . values ( ) :
model_dict = {
' name ' : capfirst ( model . _meta . verbose_name_plural ) ,
' admin_url ' : mark_safe ( ' %s / %s / ' % ( app_label , model . __name__ . lower ( ) ) ) ,
' perms ' : perms ,
}
if app_label in app_dict :
app_dict [ app_label ] [ ' models ' ] . append ( model_dict )
else :
app_dict [ app_label ] = {
' name ' : app_label . title ( ) ,
2008-09-16 14:01:47 +08:00
' app_url ' : app_label + ' / ' ,
2008-07-19 07:54:34 +08:00
' has_module_perms ' : has_module_perms ,
' models ' : [ model_dict ] ,
}
2009-03-24 04:22:56 +08:00
2008-07-19 07:54:34 +08:00
# Sort the apps alphabetically.
app_list = app_dict . values ( )
app_list . sort ( lambda x , y : cmp ( x [ ' name ' ] , y [ ' name ' ] ) )
2009-03-24 04:22:56 +08:00
2008-07-19 07:54:34 +08:00
# Sort the models alphabetically within each app.
for app in app_list :
app [ ' models ' ] . sort ( lambda x , y : cmp ( x [ ' name ' ] , y [ ' name ' ] ) )
2009-03-24 04:22:56 +08:00
2008-07-19 07:54:34 +08:00
context = {
' title ' : _ ( ' Site administration ' ) ,
' app_list ' : app_list ,
' root_path ' : self . root_path ,
}
context . update ( extra_context or { } )
2008-08-02 13:56:57 +08:00
return render_to_response ( self . index_template or ' admin/index.html ' , context ,
2008-07-19 07:54:34 +08:00
context_instance = template . RequestContext ( request )
)
index = never_cache ( index )
2009-03-24 04:22:56 +08:00
2008-07-19 07:54:34 +08:00
def display_login_form ( self , request , error_message = ' ' , extra_context = None ) :
request . session . set_test_cookie ( )
context = {
' title ' : _ ( ' Log in ' ) ,
2008-08-10 01:35:19 +08:00
' app_path ' : request . get_full_path ( ) ,
2008-07-19 07:54:34 +08:00
' error_message ' : error_message ,
' root_path ' : self . root_path ,
}
context . update ( extra_context or { } )
return render_to_response ( self . login_template or ' admin/login.html ' , context ,
context_instance = template . RequestContext ( request )
)
2009-03-24 04:22:56 +08:00
2008-08-25 11:55:47 +08:00
def app_index ( self , request , app_label , extra_context = None ) :
2008-08-23 12:00:15 +08:00
user = request . user
has_module_perms = user . has_module_perms ( app_label )
app_dict = { }
for model , model_admin in self . _registry . items ( ) :
if app_label == model . _meta . app_label :
if has_module_perms :
perms = {
' add ' : user . has_perm ( " %s . %s " % ( app_label , model . _meta . get_add_permission ( ) ) ) ,
' change ' : user . has_perm ( " %s . %s " % ( app_label , model . _meta . get_change_permission ( ) ) ) ,
' delete ' : user . has_perm ( " %s . %s " % ( app_label , model . _meta . get_delete_permission ( ) ) ) ,
}
# Check whether user has any perm for this module.
# If so, add the module to the model_list.
if True in perms . values ( ) :
model_dict = {
' name ' : capfirst ( model . _meta . verbose_name_plural ) ,
' admin_url ' : ' %s / ' % model . __name__ . lower ( ) ,
' perms ' : perms ,
}
2008-08-27 13:22:25 +08:00
if app_dict :
app_dict [ ' models ' ] . append ( model_dict ) ,
else :
# First time around, now that we know there's
# something to display, add in the necessary meta
# information.
app_dict = {
' name ' : app_label . title ( ) ,
' app_url ' : ' ' ,
' has_module_perms ' : has_module_perms ,
' models ' : [ model_dict ] ,
}
if not app_dict :
raise http . Http404 ( ' The requested admin page does not exist. ' )
2008-08-23 12:00:15 +08:00
# Sort the models alphabetically within each app.
app_dict [ ' models ' ] . sort ( lambda x , y : cmp ( x [ ' name ' ] , y [ ' name ' ] ) )
2008-08-25 11:55:47 +08:00
context = {
2008-08-29 04:17:31 +08:00
' title ' : _ ( ' %s administration ' ) % capfirst ( app_label ) ,
2008-08-27 15:27:09 +08:00
' app_list ' : [ app_dict ] ,
' root_path ' : self . root_path ,
2008-08-25 11:55:47 +08:00
}
context . update ( extra_context or { } )
2009-04-01 22:13:59 +08:00
return render_to_response ( self . app_index_template or ( ' admin/ %s /app_index.html ' % app_label ,
' admin/app_index.html ' ) , context ,
2008-08-25 11:55:47 +08:00
context_instance = template . RequestContext ( request )
)
2009-03-24 04:22:56 +08:00
2009-01-15 04:22:25 +08:00
def root ( self , request , url ) :
"""
DEPRECATED . This function is the old way of handling URL resolution , and
is deprecated in favor of real URL resolution - - see ` ` get_urls ( ) ` ` .
2009-03-24 04:22:56 +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 .
"""
import warnings
warnings . warn (
2009-03-24 04:22:56 +08:00
" AdminSite.root() is deprecated; use include(admin.site.urls) instead. " ,
2009-01-15 04:22:25 +08:00
PendingDeprecationWarning
)
2009-03-24 04:22:56 +08:00
2009-01-15 04:22:25 +08:00
#
# Again, remember that the following 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-24 04:22:56 +08:00
2009-01-15 04:22:25 +08:00
if request . method == ' GET ' and not request . path . endswith ( ' / ' ) :
return http . HttpResponseRedirect ( request . path + ' / ' )
2009-03-24 04:22:56 +08:00
2009-01-15 04:22:25 +08:00
if settings . DEBUG :
self . check_dependencies ( )
2009-03-24 04:22:56 +08:00
2009-01-15 04:22:25 +08:00
# Figure out the admin base URL path and stash it for later use
self . root_path = re . sub ( re . escape ( url ) + ' $ ' , ' ' , request . path )
2009-03-24 04:22:56 +08:00
2009-01-15 04:22:25 +08:00
url = url . rstrip ( ' / ' ) # Trim trailing slash, if it exists.
2009-03-24 04:22:56 +08:00
2009-01-15 04:22:25 +08:00
# The 'logout' view doesn't require that the person is logged in.
if url == ' logout ' :
return self . logout ( request )
2009-03-24 04:22:56 +08:00
2009-01-15 04:22:25 +08:00
# Check permission to continue or display login form.
if not self . has_permission ( request ) :
return self . login ( request )
2009-03-24 04:22:56 +08:00
2009-01-15 04:22:25 +08:00
if url == ' ' :
return self . index ( request )
elif url == ' password_change ' :
return self . password_change ( request )
elif url == ' password_change/done ' :
return self . password_change_done ( request )
elif url == ' jsi18n ' :
return self . i18n_javascript ( request )
# URLs starting with 'r/' are for the "View on site" links.
elif url . startswith ( ' r/ ' ) :
from django . contrib . contenttypes . views import shortcut
return shortcut ( request , * url . split ( ' / ' ) [ 1 : ] )
else :
if ' / ' in url :
return self . model_page ( request , * url . split ( ' / ' , 2 ) )
else :
return self . app_index ( request , url )
2009-03-24 04:22:56 +08:00
2009-01-15 04:22:25 +08:00
raise http . Http404 ( ' The requested admin page does not exist. ' )
2009-03-24 04:22:56 +08:00
2009-01-15 04:22:25 +08:00
def model_page ( self , request , app_label , model_name , rest_of_url = None ) :
"""
DEPRECATED . This is the old way of handling a model view on the admin
site ; the new views should use get_urls ( ) , above .
"""
from django . db import models
model = models . get_model ( app_label , model_name )
if model is None :
raise http . Http404 ( " App %r , model %r , not found. " % ( app_label , model_name ) )
try :
admin_obj = self . _registry [ model ]
except KeyError :
raise http . Http404 ( " This model exists but has not been registered with the admin site. " )
return admin_obj ( request , rest_of_url )
2009-03-24 04:22:56 +08:00
model_page = never_cache ( model_page )
2008-07-19 07:54:34 +08:00
# This global object represents the default admin site, for the common case.
# You can instantiate AdminSite in your own code to create a custom admin site.
site = AdminSite ( )