web/lib/django/contrib/admin/sites.py
changeset 0 0d40e90630ef
child 29 cc9b7e14412b
equal deleted inserted replaced
-1:000000000000 0:0d40e90630ef
       
     1 import re
       
     2 from django import http, template
       
     3 from django.contrib.admin import ModelAdmin
       
     4 from django.contrib.admin import actions
       
     5 from django.contrib.auth import authenticate, login
       
     6 from django.db.models.base import ModelBase
       
     7 from django.core.exceptions import ImproperlyConfigured
       
     8 from django.core.urlresolvers import reverse
       
     9 from django.shortcuts import render_to_response
       
    10 from django.utils.functional import update_wrapper
       
    11 from django.utils.safestring import mark_safe
       
    12 from django.utils.text import capfirst
       
    13 from django.utils.translation import ugettext_lazy, ugettext as _
       
    14 from django.views.decorators.cache import never_cache
       
    15 from django.conf import settings
       
    16 try:
       
    17     set
       
    18 except NameError:
       
    19     from sets import Set as set     # Python 2.3 fallback
       
    20 
       
    21 ERROR_MESSAGE = ugettext_lazy("Please enter a correct username and password. Note that both fields are case-sensitive.")
       
    22 LOGIN_FORM_KEY = 'this_is_the_login_form'
       
    23 
       
    24 class AlreadyRegistered(Exception):
       
    25     pass
       
    26 
       
    27 class NotRegistered(Exception):
       
    28     pass
       
    29 
       
    30 class AdminSite(object):
       
    31     """
       
    32     An AdminSite object encapsulates an instance of the Django admin application, ready
       
    33     to be hooked in to your URLconf. Models are registered with the AdminSite using the
       
    34     register() method, and the root() method can then be used as a Django view function
       
    35     that presents a full admin interface for the collection of registered models.
       
    36     """
       
    37 
       
    38     index_template = None
       
    39     login_template = None
       
    40     app_index_template = None
       
    41 
       
    42     def __init__(self, name=None, app_name='admin'):
       
    43         self._registry = {} # model_class class -> admin_class instance
       
    44         self.root_path = None
       
    45         if name is None:
       
    46             self.name = 'admin'
       
    47         else:
       
    48             self.name = name
       
    49         self.app_name = app_name
       
    50         self._actions = {'delete_selected': actions.delete_selected}
       
    51         self._global_actions = self._actions.copy()
       
    52 
       
    53     def register(self, model_or_iterable, admin_class=None, **options):
       
    54         """
       
    55         Registers the given model(s) with the given admin class.
       
    56 
       
    57         The model(s) should be Model classes, not instances.
       
    58 
       
    59         If an admin class isn't given, it will use ModelAdmin (the default
       
    60         admin options). If keyword arguments are given -- e.g., list_display --
       
    61         they'll be applied as options to the admin class.
       
    62 
       
    63         If a model is already registered, this will raise AlreadyRegistered.
       
    64         """
       
    65         if not admin_class:
       
    66             admin_class = ModelAdmin
       
    67 
       
    68         # Don't import the humongous validation code unless required
       
    69         if admin_class and settings.DEBUG:
       
    70             from django.contrib.admin.validation import validate
       
    71         else:
       
    72             validate = lambda model, adminclass: None
       
    73 
       
    74         if isinstance(model_or_iterable, ModelBase):
       
    75             model_or_iterable = [model_or_iterable]
       
    76         for model in model_or_iterable:
       
    77             if model in self._registry:
       
    78                 raise AlreadyRegistered('The model %s is already registered' % model.__name__)
       
    79 
       
    80             # If we got **options then dynamically construct a subclass of
       
    81             # admin_class with those **options.
       
    82             if options:
       
    83                 # For reasons I don't quite understand, without a __module__
       
    84                 # the created class appears to "live" in the wrong place,
       
    85                 # which causes issues later on.
       
    86                 options['__module__'] = __name__
       
    87                 admin_class = type("%sAdmin" % model.__name__, (admin_class,), options)
       
    88 
       
    89             # Validate (which might be a no-op)
       
    90             validate(admin_class, model)
       
    91 
       
    92             # Instantiate the admin class to save in the registry
       
    93             self._registry[model] = admin_class(model, self)
       
    94 
       
    95     def unregister(self, model_or_iterable):
       
    96         """
       
    97         Unregisters the given model(s).
       
    98 
       
    99         If a model isn't already registered, this will raise NotRegistered.
       
   100         """
       
   101         if isinstance(model_or_iterable, ModelBase):
       
   102             model_or_iterable = [model_or_iterable]
       
   103         for model in model_or_iterable:
       
   104             if model not in self._registry:
       
   105                 raise NotRegistered('The model %s is not registered' % model.__name__)
       
   106             del self._registry[model]
       
   107 
       
   108     def add_action(self, action, name=None):
       
   109         """
       
   110         Register an action to be available globally.
       
   111         """
       
   112         name = name or action.__name__
       
   113         self._actions[name] = action
       
   114         self._global_actions[name] = action
       
   115 
       
   116     def disable_action(self, name):
       
   117         """
       
   118         Disable a globally-registered action. Raises KeyError for invalid names.
       
   119         """
       
   120         del self._actions[name]
       
   121 
       
   122     def get_action(self, name):
       
   123         """
       
   124         Explicitally get a registered global action wheather it's enabled or
       
   125         not. Raises KeyError for invalid names.
       
   126         """
       
   127         return self._global_actions[name]
       
   128 
       
   129     def actions(self):
       
   130         """
       
   131         Get all the enabled actions as an iterable of (name, func).
       
   132         """
       
   133         return self._actions.iteritems()
       
   134     actions = property(actions)
       
   135 
       
   136     def has_permission(self, request):
       
   137         """
       
   138         Returns True if the given HttpRequest has permission to view
       
   139         *at least one* page in the admin site.
       
   140         """
       
   141         return request.user.is_authenticated() and request.user.is_staff
       
   142 
       
   143     def check_dependencies(self):
       
   144         """
       
   145         Check that all things needed to run the admin have been correctly installed.
       
   146 
       
   147         The default implementation checks that LogEntry, ContentType and the
       
   148         auth context processor are installed.
       
   149         """
       
   150         from django.contrib.admin.models import LogEntry
       
   151         from django.contrib.contenttypes.models import ContentType
       
   152 
       
   153         if not LogEntry._meta.installed:
       
   154             raise ImproperlyConfigured("Put 'django.contrib.admin' in your INSTALLED_APPS setting in order to use the admin application.")
       
   155         if not ContentType._meta.installed:
       
   156             raise ImproperlyConfigured("Put 'django.contrib.contenttypes' in your INSTALLED_APPS setting in order to use the admin application.")
       
   157         if 'django.core.context_processors.auth' not in settings.TEMPLATE_CONTEXT_PROCESSORS:
       
   158             raise ImproperlyConfigured("Put 'django.core.context_processors.auth' in your TEMPLATE_CONTEXT_PROCESSORS setting in order to use the admin application.")
       
   159 
       
   160     def admin_view(self, view, cacheable=False):
       
   161         """
       
   162         Decorator to create an admin view attached to this ``AdminSite``. This
       
   163         wraps the view and provides permission checking by calling
       
   164         ``self.has_permission``.
       
   165 
       
   166         You'll want to use this from within ``AdminSite.get_urls()``:
       
   167 
       
   168             class MyAdminSite(AdminSite):
       
   169 
       
   170                 def get_urls(self):
       
   171                     from django.conf.urls.defaults import patterns, url
       
   172 
       
   173                     urls = super(MyAdminSite, self).get_urls()
       
   174                     urls += patterns('',
       
   175                         url(r'^my_view/$', self.admin_view(some_view))
       
   176                     )
       
   177                     return urls
       
   178 
       
   179         By default, admin_views are marked non-cacheable using the
       
   180         ``never_cache`` decorator. If the view can be safely cached, set
       
   181         cacheable=True.
       
   182         """
       
   183         def inner(request, *args, **kwargs):
       
   184             if not self.has_permission(request):
       
   185                 return self.login(request)
       
   186             return view(request, *args, **kwargs)
       
   187         if not cacheable:
       
   188             inner = never_cache(inner)
       
   189         return update_wrapper(inner, view)
       
   190 
       
   191     def get_urls(self):
       
   192         from django.conf.urls.defaults import patterns, url, include
       
   193 
       
   194         def wrap(view, cacheable=False):
       
   195             def wrapper(*args, **kwargs):
       
   196                 return self.admin_view(view, cacheable)(*args, **kwargs)
       
   197             return update_wrapper(wrapper, view)
       
   198 
       
   199         # Admin-site-wide views.
       
   200         urlpatterns = patterns('',
       
   201             url(r'^$',
       
   202                 wrap(self.index),
       
   203                 name='index'),
       
   204             url(r'^logout/$',
       
   205                 wrap(self.logout),
       
   206                 name='logout'),
       
   207             url(r'^password_change/$',
       
   208                 wrap(self.password_change, cacheable=True),
       
   209                 name='password_change'),
       
   210             url(r'^password_change/done/$',
       
   211                 wrap(self.password_change_done, cacheable=True),
       
   212                 name='password_change_done'),
       
   213             url(r'^jsi18n/$',
       
   214                 wrap(self.i18n_javascript, cacheable=True),
       
   215                 name='jsi18n'),
       
   216             url(r'^r/(?P<content_type_id>\d+)/(?P<object_id>.+)/$',
       
   217                 'django.views.defaults.shortcut'),
       
   218             url(r'^(?P<app_label>\w+)/$',
       
   219                 wrap(self.app_index),
       
   220                 name='app_list')
       
   221         )
       
   222 
       
   223         # Add in each model's views.
       
   224         for model, model_admin in self._registry.iteritems():
       
   225             urlpatterns += patterns('',
       
   226                 url(r'^%s/%s/' % (model._meta.app_label, model._meta.module_name),
       
   227                     include(model_admin.urls))
       
   228             )
       
   229         return urlpatterns
       
   230 
       
   231     def urls(self):
       
   232         return self.get_urls(), self.app_name, self.name
       
   233     urls = property(urls)
       
   234 
       
   235     def password_change(self, request):
       
   236         """
       
   237         Handles the "change password" task -- both form display and validation.
       
   238         """
       
   239         from django.contrib.auth.views import password_change
       
   240         if self.root_path is not None:
       
   241             url = '%spassword_change/done/' % self.root_path
       
   242         else:
       
   243             url = reverse('admin:password_change_done', current_app=self.name)
       
   244         return password_change(request, post_change_redirect=url)
       
   245 
       
   246     def password_change_done(self, request):
       
   247         """
       
   248         Displays the "success" page after a password change.
       
   249         """
       
   250         from django.contrib.auth.views import password_change_done
       
   251         return password_change_done(request)
       
   252 
       
   253     def i18n_javascript(self, request):
       
   254         """
       
   255         Displays the i18n JavaScript that the Django admin requires.
       
   256 
       
   257         This takes into account the USE_I18N setting. If it's set to False, the
       
   258         generated JavaScript will be leaner and faster.
       
   259         """
       
   260         if settings.USE_I18N:
       
   261             from django.views.i18n import javascript_catalog
       
   262         else:
       
   263             from django.views.i18n import null_javascript_catalog as javascript_catalog
       
   264         return javascript_catalog(request, packages='django.conf')
       
   265 
       
   266     def logout(self, request):
       
   267         """
       
   268         Logs out the user for the given HttpRequest.
       
   269 
       
   270         This should *not* assume the user is already logged in.
       
   271         """
       
   272         from django.contrib.auth.views import logout
       
   273         return logout(request)
       
   274     logout = never_cache(logout)
       
   275 
       
   276     def login(self, request):
       
   277         """
       
   278         Displays the login form for the given HttpRequest.
       
   279         """
       
   280         from django.contrib.auth.models import User
       
   281 
       
   282         # If this isn't already the login page, display it.
       
   283         if not request.POST.has_key(LOGIN_FORM_KEY):
       
   284             if request.POST:
       
   285                 message = _("Please log in again, because your session has expired.")
       
   286             else:
       
   287                 message = ""
       
   288             return self.display_login_form(request, message)
       
   289 
       
   290         # Check that the user accepts cookies.
       
   291         if not request.session.test_cookie_worked():
       
   292             message = _("Looks like your browser isn't configured to accept cookies. Please enable cookies, reload this page, and try again.")
       
   293             return self.display_login_form(request, message)
       
   294         else:
       
   295             request.session.delete_test_cookie()
       
   296 
       
   297         # Check the password.
       
   298         username = request.POST.get('username', None)
       
   299         password = request.POST.get('password', None)
       
   300         user = authenticate(username=username, password=password)
       
   301         if user is None:
       
   302             message = ERROR_MESSAGE
       
   303             if username is not None and u'@' in username:
       
   304                 # Mistakenly entered e-mail address instead of username? Look it up.
       
   305                 try:
       
   306                     user = User.objects.get(email=username)
       
   307                 except (User.DoesNotExist, User.MultipleObjectsReturned):
       
   308                     message = _("Usernames cannot contain the '@' character.")
       
   309                 else:
       
   310                     if user.check_password(password):
       
   311                         message = _("Your e-mail address is not your username."
       
   312                                     " Try '%s' instead.") % user.username
       
   313                     else:
       
   314                         message = _("Usernames cannot contain the '@' character.")
       
   315             return self.display_login_form(request, message)
       
   316 
       
   317         # The user data is correct; log in the user in and continue.
       
   318         else:
       
   319             if user.is_active and user.is_staff:
       
   320                 login(request, user)
       
   321                 return http.HttpResponseRedirect(request.get_full_path())
       
   322             else:
       
   323                 return self.display_login_form(request, ERROR_MESSAGE)
       
   324     login = never_cache(login)
       
   325 
       
   326     def index(self, request, extra_context=None):
       
   327         """
       
   328         Displays the main admin index page, which lists all of the installed
       
   329         apps that have been registered in this site.
       
   330         """
       
   331         app_dict = {}
       
   332         user = request.user
       
   333         for model, model_admin in self._registry.items():
       
   334             app_label = model._meta.app_label
       
   335             has_module_perms = user.has_module_perms(app_label)
       
   336 
       
   337             if has_module_perms:
       
   338                 perms = model_admin.get_model_perms(request)
       
   339 
       
   340                 # Check whether user has any perm for this module.
       
   341                 # If so, add the module to the model_list.
       
   342                 if True in perms.values():
       
   343                     model_dict = {
       
   344                         'name': capfirst(model._meta.verbose_name_plural),
       
   345                         'admin_url': mark_safe('%s/%s/' % (app_label, model.__name__.lower())),
       
   346                         'perms': perms,
       
   347                     }
       
   348                     if app_label in app_dict:
       
   349                         app_dict[app_label]['models'].append(model_dict)
       
   350                     else:
       
   351                         app_dict[app_label] = {
       
   352                             'name': app_label.title(),
       
   353                             'app_url': app_label + '/',
       
   354                             'has_module_perms': has_module_perms,
       
   355                             'models': [model_dict],
       
   356                         }
       
   357 
       
   358         # Sort the apps alphabetically.
       
   359         app_list = app_dict.values()
       
   360         app_list.sort(lambda x, y: cmp(x['name'], y['name']))
       
   361 
       
   362         # Sort the models alphabetically within each app.
       
   363         for app in app_list:
       
   364             app['models'].sort(lambda x, y: cmp(x['name'], y['name']))
       
   365 
       
   366         context = {
       
   367             'title': _('Site administration'),
       
   368             'app_list': app_list,
       
   369             'root_path': self.root_path,
       
   370         }
       
   371         context.update(extra_context or {})
       
   372         context_instance = template.RequestContext(request, current_app=self.name)
       
   373         return render_to_response(self.index_template or 'admin/index.html', context,
       
   374             context_instance=context_instance
       
   375         )
       
   376     index = never_cache(index)
       
   377 
       
   378     def display_login_form(self, request, error_message='', extra_context=None):
       
   379         request.session.set_test_cookie()
       
   380         context = {
       
   381             'title': _('Log in'),
       
   382             'app_path': request.get_full_path(),
       
   383             'error_message': error_message,
       
   384             'root_path': self.root_path,
       
   385         }
       
   386         context.update(extra_context or {})
       
   387         context_instance = template.RequestContext(request, current_app=self.name)
       
   388         return render_to_response(self.login_template or 'admin/login.html', context,
       
   389             context_instance=context_instance
       
   390         )
       
   391 
       
   392     def app_index(self, request, app_label, extra_context=None):
       
   393         user = request.user
       
   394         has_module_perms = user.has_module_perms(app_label)
       
   395         app_dict = {}
       
   396         for model, model_admin in self._registry.items():
       
   397             if app_label == model._meta.app_label:
       
   398                 if has_module_perms:
       
   399                     perms = model_admin.get_model_perms(request)
       
   400 
       
   401                     # Check whether user has any perm for this module.
       
   402                     # If so, add the module to the model_list.
       
   403                     if True in perms.values():
       
   404                         model_dict = {
       
   405                             'name': capfirst(model._meta.verbose_name_plural),
       
   406                             'admin_url': '%s/' % model.__name__.lower(),
       
   407                             'perms': perms,
       
   408                         }
       
   409                         if app_dict:
       
   410                             app_dict['models'].append(model_dict),
       
   411                         else:
       
   412                             # First time around, now that we know there's
       
   413                             # something to display, add in the necessary meta
       
   414                             # information.
       
   415                             app_dict = {
       
   416                                 'name': app_label.title(),
       
   417                                 'app_url': '',
       
   418                                 'has_module_perms': has_module_perms,
       
   419                                 'models': [model_dict],
       
   420                             }
       
   421         if not app_dict:
       
   422             raise http.Http404('The requested admin page does not exist.')
       
   423         # Sort the models alphabetically within each app.
       
   424         app_dict['models'].sort(lambda x, y: cmp(x['name'], y['name']))
       
   425         context = {
       
   426             'title': _('%s administration') % capfirst(app_label),
       
   427             'app_list': [app_dict],
       
   428             'root_path': self.root_path,
       
   429         }
       
   430         context.update(extra_context or {})
       
   431         context_instance = template.RequestContext(request, current_app=self.name)
       
   432         return render_to_response(self.app_index_template or ('admin/%s/app_index.html' % app_label,
       
   433             'admin/app_index.html'), context,
       
   434             context_instance=context_instance
       
   435         )
       
   436 
       
   437     def root(self, request, url):
       
   438         """
       
   439         DEPRECATED. This function is the old way of handling URL resolution, and
       
   440         is deprecated in favor of real URL resolution -- see ``get_urls()``.
       
   441 
       
   442         This function still exists for backwards-compatibility; it will be
       
   443         removed in Django 1.3.
       
   444         """
       
   445         import warnings
       
   446         warnings.warn(
       
   447             "AdminSite.root() is deprecated; use include(admin.site.urls) instead.",
       
   448             PendingDeprecationWarning
       
   449         )
       
   450 
       
   451         #
       
   452         # Again, remember that the following only exists for
       
   453         # backwards-compatibility. Any new URLs, changes to existing URLs, or
       
   454         # whatever need to be done up in get_urls(), above!
       
   455         #
       
   456 
       
   457         if request.method == 'GET' and not request.path.endswith('/'):
       
   458             return http.HttpResponseRedirect(request.path + '/')
       
   459 
       
   460         if settings.DEBUG:
       
   461             self.check_dependencies()
       
   462 
       
   463         # Figure out the admin base URL path and stash it for later use
       
   464         self.root_path = re.sub(re.escape(url) + '$', '', request.path)
       
   465 
       
   466         url = url.rstrip('/') # Trim trailing slash, if it exists.
       
   467 
       
   468         # The 'logout' view doesn't require that the person is logged in.
       
   469         if url == 'logout':
       
   470             return self.logout(request)
       
   471 
       
   472         # Check permission to continue or display login form.
       
   473         if not self.has_permission(request):
       
   474             return self.login(request)
       
   475 
       
   476         if url == '':
       
   477             return self.index(request)
       
   478         elif url == 'password_change':
       
   479             return self.password_change(request)
       
   480         elif url == 'password_change/done':
       
   481             return self.password_change_done(request)
       
   482         elif url == 'jsi18n':
       
   483             return self.i18n_javascript(request)
       
   484         # URLs starting with 'r/' are for the "View on site" links.
       
   485         elif url.startswith('r/'):
       
   486             from django.contrib.contenttypes.views import shortcut
       
   487             return shortcut(request, *url.split('/')[1:])
       
   488         else:
       
   489             if '/' in url:
       
   490                 return self.model_page(request, *url.split('/', 2))
       
   491             else:
       
   492                 return self.app_index(request, url)
       
   493 
       
   494         raise http.Http404('The requested admin page does not exist.')
       
   495 
       
   496     def model_page(self, request, app_label, model_name, rest_of_url=None):
       
   497         """
       
   498         DEPRECATED. This is the old way of handling a model view on the admin
       
   499         site; the new views should use get_urls(), above.
       
   500         """
       
   501         from django.db import models
       
   502         model = models.get_model(app_label, model_name)
       
   503         if model is None:
       
   504             raise http.Http404("App %r, model %r, not found." % (app_label, model_name))
       
   505         try:
       
   506             admin_obj = self._registry[model]
       
   507         except KeyError:
       
   508             raise http.Http404("This model exists but has not been registered with the admin site.")
       
   509         return admin_obj(request, rest_of_url)
       
   510     model_page = never_cache(model_page)
       
   511 
       
   512 # This global object represents the default admin site, for the common case.
       
   513 # You can instantiate AdminSite in your own code to create a custom admin site.
       
   514 site = AdminSite()