web/lib/django/contrib/admin/sites.py
changeset 38 77b6da96e6f1
equal deleted inserted replaced
37:8d941af65caf 38:77b6da96e6f1
       
     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.views.decorators.csrf import csrf_protect
       
     7 from django.db.models.base import ModelBase
       
     8 from django.core.exceptions import ImproperlyConfigured
       
     9 from django.core.urlresolvers import reverse
       
    10 from django.shortcuts import render_to_response
       
    11 from django.utils.functional import update_wrapper
       
    12 from django.utils.safestring import mark_safe
       
    13 from django.utils.text import capfirst
       
    14 from django.utils.translation import ugettext_lazy, ugettext as _
       
    15 from django.views.decorators.cache import never_cache
       
    16 from django.conf import settings
       
    17 
       
    18 ERROR_MESSAGE = ugettext_lazy("Please enter a correct username and password. Note that both fields are case-sensitive.")
       
    19 LOGIN_FORM_KEY = 'this_is_the_login_form'
       
    20 
       
    21 class AlreadyRegistered(Exception):
       
    22     pass
       
    23 
       
    24 class NotRegistered(Exception):
       
    25     pass
       
    26 
       
    27 class AdminSite(object):
       
    28     """
       
    29     An AdminSite object encapsulates an instance of the Django admin application, ready
       
    30     to be hooked in to your URLconf. Models are registered with the AdminSite using the
       
    31     register() method, and the root() method can then be used as a Django view function
       
    32     that presents a full admin interface for the collection of registered models.
       
    33     """
       
    34 
       
    35     index_template = None
       
    36     app_index_template = None
       
    37     login_template = None
       
    38     logout_template = None
       
    39     password_change_template = None
       
    40     password_change_done_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_active 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 "
       
   155                 "INSTALLED_APPS setting in order to use the admin application.")
       
   156         if not ContentType._meta.installed:
       
   157             raise ImproperlyConfigured("Put 'django.contrib.contenttypes' in "
       
   158                 "your INSTALLED_APPS setting in order to use the admin application.")
       
   159         if not ('django.contrib.auth.context_processors.auth' in settings.TEMPLATE_CONTEXT_PROCESSORS or
       
   160             'django.core.context_processors.auth' in settings.TEMPLATE_CONTEXT_PROCESSORS):
       
   161             raise ImproperlyConfigured("Put 'django.contrib.auth.context_processors.auth' "
       
   162                 "in your TEMPLATE_CONTEXT_PROCESSORS setting in order to use the admin application.")
       
   163 
       
   164     def admin_view(self, view, cacheable=False):
       
   165         """
       
   166         Decorator to create an admin view attached to this ``AdminSite``. This
       
   167         wraps the view and provides permission checking by calling
       
   168         ``self.has_permission``.
       
   169 
       
   170         You'll want to use this from within ``AdminSite.get_urls()``:
       
   171 
       
   172             class MyAdminSite(AdminSite):
       
   173 
       
   174                 def get_urls(self):
       
   175                     from django.conf.urls.defaults import patterns, url
       
   176 
       
   177                     urls = super(MyAdminSite, self).get_urls()
       
   178                     urls += patterns('',
       
   179                         url(r'^my_view/$', self.admin_view(some_view))
       
   180                     )
       
   181                     return urls
       
   182 
       
   183         By default, admin_views are marked non-cacheable using the
       
   184         ``never_cache`` decorator. If the view can be safely cached, set
       
   185         cacheable=True.
       
   186         """
       
   187         def inner(request, *args, **kwargs):
       
   188             if not self.has_permission(request):
       
   189                 return self.login(request)
       
   190             return view(request, *args, **kwargs)
       
   191         if not cacheable:
       
   192             inner = never_cache(inner)
       
   193         # We add csrf_protect here so this function can be used as a utility
       
   194         # function for any view, without having to repeat 'csrf_protect'.
       
   195         if not getattr(view, 'csrf_exempt', False):
       
   196             inner = csrf_protect(inner)
       
   197         return update_wrapper(inner, view)
       
   198 
       
   199     def get_urls(self):
       
   200         from django.conf.urls.defaults import patterns, url, include
       
   201 
       
   202         if settings.DEBUG:
       
   203             self.check_dependencies()
       
   204 
       
   205         def wrap(view, cacheable=False):
       
   206             def wrapper(*args, **kwargs):
       
   207                 return self.admin_view(view, cacheable)(*args, **kwargs)
       
   208             return update_wrapper(wrapper, view)
       
   209 
       
   210         # Admin-site-wide views.
       
   211         urlpatterns = patterns('',
       
   212             url(r'^$',
       
   213                 wrap(self.index),
       
   214                 name='index'),
       
   215             url(r'^logout/$',
       
   216                 wrap(self.logout),
       
   217                 name='logout'),
       
   218             url(r'^password_change/$',
       
   219                 wrap(self.password_change, cacheable=True),
       
   220                 name='password_change'),
       
   221             url(r'^password_change/done/$',
       
   222                 wrap(self.password_change_done, cacheable=True),
       
   223                 name='password_change_done'),
       
   224             url(r'^jsi18n/$',
       
   225                 wrap(self.i18n_javascript, cacheable=True),
       
   226                 name='jsi18n'),
       
   227             url(r'^r/(?P<content_type_id>\d+)/(?P<object_id>.+)/$',
       
   228                 'django.views.defaults.shortcut'),
       
   229             url(r'^(?P<app_label>\w+)/$',
       
   230                 wrap(self.app_index),
       
   231                 name='app_list')
       
   232         )
       
   233 
       
   234         # Add in each model's views.
       
   235         for model, model_admin in self._registry.iteritems():
       
   236             urlpatterns += patterns('',
       
   237                 url(r'^%s/%s/' % (model._meta.app_label, model._meta.module_name),
       
   238                     include(model_admin.urls))
       
   239             )
       
   240         return urlpatterns
       
   241 
       
   242     def urls(self):
       
   243         return self.get_urls(), self.app_name, self.name
       
   244     urls = property(urls)
       
   245 
       
   246     def password_change(self, request):
       
   247         """
       
   248         Handles the "change password" task -- both form display and validation.
       
   249         """
       
   250         from django.contrib.auth.views import password_change
       
   251         if self.root_path is not None:
       
   252             url = '%spassword_change/done/' % self.root_path
       
   253         else:
       
   254             url = reverse('admin:password_change_done', current_app=self.name)
       
   255         defaults = {
       
   256             'post_change_redirect': url
       
   257         }
       
   258         if self.password_change_template is not None:
       
   259             defaults['template_name'] = self.password_change_template
       
   260         return password_change(request, **defaults)
       
   261 
       
   262     def password_change_done(self, request):
       
   263         """
       
   264         Displays the "success" page after a password change.
       
   265         """
       
   266         from django.contrib.auth.views import password_change_done
       
   267         defaults = {}
       
   268         if self.password_change_done_template is not None:
       
   269             defaults['template_name'] = self.password_change_done_template
       
   270         return password_change_done(request, **defaults)
       
   271 
       
   272     def i18n_javascript(self, request):
       
   273         """
       
   274         Displays the i18n JavaScript that the Django admin requires.
       
   275 
       
   276         This takes into account the USE_I18N setting. If it's set to False, the
       
   277         generated JavaScript will be leaner and faster.
       
   278         """
       
   279         if settings.USE_I18N:
       
   280             from django.views.i18n import javascript_catalog
       
   281         else:
       
   282             from django.views.i18n import null_javascript_catalog as javascript_catalog
       
   283         return javascript_catalog(request, packages='django.conf')
       
   284 
       
   285     def logout(self, request):
       
   286         """
       
   287         Logs out the user for the given HttpRequest.
       
   288 
       
   289         This should *not* assume the user is already logged in.
       
   290         """
       
   291         from django.contrib.auth.views import logout
       
   292         defaults = {}
       
   293         if self.logout_template is not None:
       
   294             defaults['template_name'] = self.logout_template
       
   295         return logout(request, **defaults)
       
   296     logout = never_cache(logout)
       
   297 
       
   298     def login(self, request):
       
   299         """
       
   300         Displays the login form for the given HttpRequest.
       
   301         """
       
   302         from django.contrib.auth.models import User
       
   303 
       
   304         # If this isn't already the login page, display it.
       
   305         if not request.POST.has_key(LOGIN_FORM_KEY):
       
   306             if request.POST:
       
   307                 message = _("Please log in again, because your session has expired.")
       
   308             else:
       
   309                 message = ""
       
   310             return self.display_login_form(request, message)
       
   311 
       
   312         # Check that the user accepts cookies.
       
   313         if not request.session.test_cookie_worked():
       
   314             message = _("Looks like your browser isn't configured to accept cookies. Please enable cookies, reload this page, and try again.")
       
   315             return self.display_login_form(request, message)
       
   316         else:
       
   317             request.session.delete_test_cookie()
       
   318 
       
   319         # Check the password.
       
   320         username = request.POST.get('username', None)
       
   321         password = request.POST.get('password', None)
       
   322         user = authenticate(username=username, password=password)
       
   323         if user is None:
       
   324             message = ERROR_MESSAGE
       
   325             if username is not None and u'@' in username:
       
   326                 # Mistakenly entered e-mail address instead of username? Look it up.
       
   327                 try:
       
   328                     user = User.objects.get(email=username)
       
   329                 except (User.DoesNotExist, User.MultipleObjectsReturned):
       
   330                     message = _("Usernames cannot contain the '@' character.")
       
   331                 else:
       
   332                     if user.check_password(password):
       
   333                         message = _("Your e-mail address is not your username."
       
   334                                     " Try '%s' instead.") % user.username
       
   335                     else:
       
   336                         message = _("Usernames cannot contain the '@' character.")
       
   337             return self.display_login_form(request, message)
       
   338 
       
   339         # The user data is correct; log in the user in and continue.
       
   340         else:
       
   341             if user.is_active and user.is_staff:
       
   342                 login(request, user)
       
   343                 return http.HttpResponseRedirect(request.get_full_path())
       
   344             else:
       
   345                 return self.display_login_form(request, ERROR_MESSAGE)
       
   346     login = never_cache(login)
       
   347 
       
   348     def index(self, request, extra_context=None):
       
   349         """
       
   350         Displays the main admin index page, which lists all of the installed
       
   351         apps that have been registered in this site.
       
   352         """
       
   353         app_dict = {}
       
   354         user = request.user
       
   355         for model, model_admin in self._registry.items():
       
   356             app_label = model._meta.app_label
       
   357             has_module_perms = user.has_module_perms(app_label)
       
   358 
       
   359             if has_module_perms:
       
   360                 perms = model_admin.get_model_perms(request)
       
   361 
       
   362                 # Check whether user has any perm for this module.
       
   363                 # If so, add the module to the model_list.
       
   364                 if True in perms.values():
       
   365                     model_dict = {
       
   366                         'name': capfirst(model._meta.verbose_name_plural),
       
   367                         'admin_url': mark_safe('%s/%s/' % (app_label, model.__name__.lower())),
       
   368                         'perms': perms,
       
   369                     }
       
   370                     if app_label in app_dict:
       
   371                         app_dict[app_label]['models'].append(model_dict)
       
   372                     else:
       
   373                         app_dict[app_label] = {
       
   374                             'name': app_label.title(),
       
   375                             'app_url': app_label + '/',
       
   376                             'has_module_perms': has_module_perms,
       
   377                             'models': [model_dict],
       
   378                         }
       
   379 
       
   380         # Sort the apps alphabetically.
       
   381         app_list = app_dict.values()
       
   382         app_list.sort(lambda x, y: cmp(x['name'], y['name']))
       
   383 
       
   384         # Sort the models alphabetically within each app.
       
   385         for app in app_list:
       
   386             app['models'].sort(lambda x, y: cmp(x['name'], y['name']))
       
   387 
       
   388         context = {
       
   389             'title': _('Site administration'),
       
   390             'app_list': app_list,
       
   391             'root_path': self.root_path,
       
   392         }
       
   393         context.update(extra_context or {})
       
   394         context_instance = template.RequestContext(request, current_app=self.name)
       
   395         return render_to_response(self.index_template or 'admin/index.html', context,
       
   396             context_instance=context_instance
       
   397         )
       
   398     index = never_cache(index)
       
   399 
       
   400     def display_login_form(self, request, error_message='', extra_context=None):
       
   401         request.session.set_test_cookie()
       
   402         context = {
       
   403             'title': _('Log in'),
       
   404             'app_path': request.get_full_path(),
       
   405             'error_message': error_message,
       
   406             'root_path': self.root_path,
       
   407         }
       
   408         context.update(extra_context or {})
       
   409         context_instance = template.RequestContext(request, current_app=self.name)
       
   410         return render_to_response(self.login_template or 'admin/login.html', context,
       
   411             context_instance=context_instance
       
   412         )
       
   413 
       
   414     def app_index(self, request, app_label, extra_context=None):
       
   415         user = request.user
       
   416         has_module_perms = user.has_module_perms(app_label)
       
   417         app_dict = {}
       
   418         for model, model_admin in self._registry.items():
       
   419             if app_label == model._meta.app_label:
       
   420                 if has_module_perms:
       
   421                     perms = model_admin.get_model_perms(request)
       
   422 
       
   423                     # Check whether user has any perm for this module.
       
   424                     # If so, add the module to the model_list.
       
   425                     if True in perms.values():
       
   426                         model_dict = {
       
   427                             'name': capfirst(model._meta.verbose_name_plural),
       
   428                             'admin_url': '%s/' % model.__name__.lower(),
       
   429                             'perms': perms,
       
   430                         }
       
   431                         if app_dict:
       
   432                             app_dict['models'].append(model_dict),
       
   433                         else:
       
   434                             # First time around, now that we know there's
       
   435                             # something to display, add in the necessary meta
       
   436                             # information.
       
   437                             app_dict = {
       
   438                                 'name': app_label.title(),
       
   439                                 'app_url': '',
       
   440                                 'has_module_perms': has_module_perms,
       
   441                                 'models': [model_dict],
       
   442                             }
       
   443         if not app_dict:
       
   444             raise http.Http404('The requested admin page does not exist.')
       
   445         # Sort the models alphabetically within each app.
       
   446         app_dict['models'].sort(lambda x, y: cmp(x['name'], y['name']))
       
   447         context = {
       
   448             'title': _('%s administration') % capfirst(app_label),
       
   449             'app_list': [app_dict],
       
   450             'root_path': self.root_path,
       
   451         }
       
   452         context.update(extra_context or {})
       
   453         context_instance = template.RequestContext(request, current_app=self.name)
       
   454         return render_to_response(self.app_index_template or ('admin/%s/app_index.html' % app_label,
       
   455             'admin/app_index.html'), context,
       
   456             context_instance=context_instance
       
   457         )
       
   458 
       
   459     def root(self, request, url):
       
   460         """
       
   461         DEPRECATED. This function is the old way of handling URL resolution, and
       
   462         is deprecated in favor of real URL resolution -- see ``get_urls()``.
       
   463 
       
   464         This function still exists for backwards-compatibility; it will be
       
   465         removed in Django 1.3.
       
   466         """
       
   467         import warnings
       
   468         warnings.warn(
       
   469             "AdminSite.root() is deprecated; use include(admin.site.urls) instead.",
       
   470             DeprecationWarning
       
   471         )
       
   472 
       
   473         #
       
   474         # Again, remember that the following only exists for
       
   475         # backwards-compatibility. Any new URLs, changes to existing URLs, or
       
   476         # whatever need to be done up in get_urls(), above!
       
   477         #
       
   478 
       
   479         if request.method == 'GET' and not request.path.endswith('/'):
       
   480             return http.HttpResponseRedirect(request.path + '/')
       
   481 
       
   482         if settings.DEBUG:
       
   483             self.check_dependencies()
       
   484 
       
   485         # Figure out the admin base URL path and stash it for later use
       
   486         self.root_path = re.sub(re.escape(url) + '$', '', request.path)
       
   487 
       
   488         url = url.rstrip('/') # Trim trailing slash, if it exists.
       
   489 
       
   490         # The 'logout' view doesn't require that the person is logged in.
       
   491         if url == 'logout':
       
   492             return self.logout(request)
       
   493 
       
   494         # Check permission to continue or display login form.
       
   495         if not self.has_permission(request):
       
   496             return self.login(request)
       
   497 
       
   498         if url == '':
       
   499             return self.index(request)
       
   500         elif url == 'password_change':
       
   501             return self.password_change(request)
       
   502         elif url == 'password_change/done':
       
   503             return self.password_change_done(request)
       
   504         elif url == 'jsi18n':
       
   505             return self.i18n_javascript(request)
       
   506         # URLs starting with 'r/' are for the "View on site" links.
       
   507         elif url.startswith('r/'):
       
   508             from django.contrib.contenttypes.views import shortcut
       
   509             return shortcut(request, *url.split('/')[1:])
       
   510         else:
       
   511             if '/' in url:
       
   512                 return self.model_page(request, *url.split('/', 2))
       
   513             else:
       
   514                 return self.app_index(request, url)
       
   515 
       
   516         raise http.Http404('The requested admin page does not exist.')
       
   517 
       
   518     def model_page(self, request, app_label, model_name, rest_of_url=None):
       
   519         """
       
   520         DEPRECATED. This is the old way of handling a model view on the admin
       
   521         site; the new views should use get_urls(), above.
       
   522         """
       
   523         from django.db import models
       
   524         model = models.get_model(app_label, model_name)
       
   525         if model is None:
       
   526             raise http.Http404("App %r, model %r, not found." % (app_label, model_name))
       
   527         try:
       
   528             admin_obj = self._registry[model]
       
   529         except KeyError:
       
   530             raise http.Http404("This model exists but has not been registered with the admin site.")
       
   531         return admin_obj(request, rest_of_url)
       
   532     model_page = never_cache(model_page)
       
   533 
       
   534 # This global object represents the default admin site, for the common case.
       
   535 # You can instantiate AdminSite in your own code to create a custom admin site.
       
   536 site = AdminSite()