app/django/contrib/admin/sites.py
changeset 323 ff1a9aa48cfd
equal deleted inserted replaced
322:6641e941ef1e 323:ff1a9aa48cfd
       
     1 import base64
       
     2 import re
       
     3 from django import http, template
       
     4 from django.contrib.admin import ModelAdmin
       
     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.shortcuts import render_to_response
       
     9 from django.utils.safestring import mark_safe
       
    10 from django.utils.text import capfirst
       
    11 from django.utils.translation import ugettext_lazy, ugettext as _
       
    12 from django.views.decorators.cache import never_cache
       
    13 from django.conf import settings
       
    14 from django.utils.hashcompat import md5_constructor
       
    15 
       
    16 ERROR_MESSAGE = ugettext_lazy("Please enter a correct username and password. Note that both fields are case-sensitive.")
       
    17 LOGIN_FORM_KEY = 'this_is_the_login_form'
       
    18 
       
    19 class AlreadyRegistered(Exception):
       
    20     pass
       
    21 
       
    22 class NotRegistered(Exception):
       
    23     pass
       
    24 
       
    25 class AdminSite(object):
       
    26     """
       
    27     An AdminSite object encapsulates an instance of the Django admin application, ready
       
    28     to be hooked in to your URLConf. Models are registered with the AdminSite using the
       
    29     register() method, and the root() method can then be used as a Django view function
       
    30     that presents a full admin interface for the collection of registered models.
       
    31     """
       
    32 
       
    33     index_template = None
       
    34     login_template = None
       
    35     app_index_template = None
       
    36 
       
    37     def __init__(self):
       
    38         self._registry = {} # model_class class -> admin_class instance
       
    39 
       
    40     def register(self, model_or_iterable, admin_class=None, **options):
       
    41         """
       
    42         Registers the given model(s) with the given admin class.
       
    43 
       
    44         The model(s) should be Model classes, not instances.
       
    45 
       
    46         If an admin class isn't given, it will use ModelAdmin (the default
       
    47         admin options). If keyword arguments are given -- e.g., list_display --
       
    48         they'll be applied as options to the admin class.
       
    49 
       
    50         If a model is already registered, this will raise AlreadyRegistered.
       
    51         """
       
    52         # Don't import the humongous validation code unless required
       
    53         if admin_class and settings.DEBUG:
       
    54             from django.contrib.admin.validation import validate
       
    55         else:
       
    56             validate = lambda model, adminclass: None
       
    57 
       
    58         if not admin_class:
       
    59             admin_class = ModelAdmin
       
    60         if isinstance(model_or_iterable, ModelBase):
       
    61             model_or_iterable = [model_or_iterable]
       
    62         for model in model_or_iterable:
       
    63             if model in self._registry:
       
    64                 raise AlreadyRegistered('The model %s is already registered' % model.__name__)
       
    65 
       
    66             # If we got **options then dynamically construct a subclass of
       
    67             # admin_class with those **options.
       
    68             if options:
       
    69                 # For reasons I don't quite understand, without a __module__
       
    70                 # the created class appears to "live" in the wrong place,
       
    71                 # which causes issues later on.
       
    72                 options['__module__'] = __name__
       
    73                 admin_class = type("%sAdmin" % model.__name__, (admin_class,), options)
       
    74 
       
    75             # Validate (which might be a no-op)
       
    76             validate(admin_class, model)
       
    77 
       
    78             # Instantiate the admin class to save in the registry
       
    79             self._registry[model] = admin_class(model, self)
       
    80 
       
    81     def unregister(self, model_or_iterable):
       
    82         """
       
    83         Unregisters the given model(s).
       
    84 
       
    85         If a model isn't already registered, this will raise NotRegistered.
       
    86         """
       
    87         if isinstance(model_or_iterable, ModelBase):
       
    88             model_or_iterable = [model_or_iterable]
       
    89         for model in model_or_iterable:
       
    90             if model not in self._registry:
       
    91                 raise NotRegistered('The model %s is not registered' % model.__name__)
       
    92             del self._registry[model]
       
    93 
       
    94     def has_permission(self, request):
       
    95         """
       
    96         Returns True if the given HttpRequest has permission to view
       
    97         *at least one* page in the admin site.
       
    98         """
       
    99         return request.user.is_authenticated() and request.user.is_staff
       
   100 
       
   101     def check_dependencies(self):
       
   102         """
       
   103         Check that all things needed to run the admin have been correctly installed.
       
   104 
       
   105         The default implementation checks that LogEntry, ContentType and the
       
   106         auth context processor are installed.
       
   107         """
       
   108         from django.contrib.admin.models import LogEntry
       
   109         from django.contrib.contenttypes.models import ContentType
       
   110 
       
   111         if not LogEntry._meta.installed:
       
   112             raise ImproperlyConfigured("Put 'django.contrib.admin' in your INSTALLED_APPS setting in order to use the admin application.")
       
   113         if not ContentType._meta.installed:
       
   114             raise ImproperlyConfigured("Put 'django.contrib.contenttypes' in your INSTALLED_APPS setting in order to use the admin application.")
       
   115         if 'django.core.context_processors.auth' not in settings.TEMPLATE_CONTEXT_PROCESSORS:
       
   116             raise ImproperlyConfigured("Put 'django.core.context_processors.auth' in your TEMPLATE_CONTEXT_PROCESSORS setting in order to use the admin application.")
       
   117 
       
   118     def root(self, request, url):
       
   119         """
       
   120         Handles main URL routing for the admin app.
       
   121 
       
   122         `url` is the remainder of the URL -- e.g. 'comments/comment/'.
       
   123         """
       
   124         if request.method == 'GET' and not request.path.endswith('/'):
       
   125             return http.HttpResponseRedirect(request.path + '/')
       
   126 
       
   127         if settings.DEBUG:
       
   128             self.check_dependencies()
       
   129 
       
   130         # Figure out the admin base URL path and stash it for later use
       
   131         self.root_path = re.sub(re.escape(url) + '$', '', request.path)
       
   132 
       
   133         url = url.rstrip('/') # Trim trailing slash, if it exists.
       
   134 
       
   135         # The 'logout' view doesn't require that the person is logged in.
       
   136         if url == 'logout':
       
   137             return self.logout(request)
       
   138 
       
   139         # Check permission to continue or display login form.
       
   140         if not self.has_permission(request):
       
   141             return self.login(request)
       
   142 
       
   143         if url == '':
       
   144             return self.index(request)
       
   145         elif url == 'password_change':
       
   146             return self.password_change(request)
       
   147         elif url == 'password_change/done':
       
   148             return self.password_change_done(request)
       
   149         elif url == 'jsi18n':
       
   150             return self.i18n_javascript(request)
       
   151         # URLs starting with 'r/' are for the "View on site" links.
       
   152         elif url.startswith('r/'):
       
   153             from django.contrib.contenttypes.views import shortcut
       
   154             return shortcut(request, *url.split('/')[1:])
       
   155         else:
       
   156             if '/' in url:
       
   157                 return self.model_page(request, *url.split('/', 2))
       
   158             else:
       
   159                 return self.app_index(request, url)
       
   160 
       
   161         raise http.Http404('The requested admin page does not exist.')
       
   162 
       
   163     def model_page(self, request, app_label, model_name, rest_of_url=None):
       
   164         """
       
   165         Handles the model-specific functionality of the admin site, delegating
       
   166         to the appropriate ModelAdmin class.
       
   167         """
       
   168         from django.db import models
       
   169         model = models.get_model(app_label, model_name)
       
   170         if model is None:
       
   171             raise http.Http404("App %r, model %r, not found." % (app_label, model_name))
       
   172         try:
       
   173             admin_obj = self._registry[model]
       
   174         except KeyError:
       
   175             raise http.Http404("This model exists but has not been registered with the admin site.")
       
   176         return admin_obj(request, rest_of_url)
       
   177     model_page = never_cache(model_page)
       
   178 
       
   179     def password_change(self, request):
       
   180         """
       
   181         Handles the "change password" task -- both form display and validation.
       
   182         """
       
   183         from django.contrib.auth.views import password_change
       
   184         return password_change(request,
       
   185             post_change_redirect='%spassword_change/done/' % self.root_path)
       
   186 
       
   187     def password_change_done(self, request):
       
   188         """
       
   189         Displays the "success" page after a password change.
       
   190         """
       
   191         from django.contrib.auth.views import password_change_done
       
   192         return password_change_done(request)
       
   193 
       
   194     def i18n_javascript(self, request):
       
   195         """
       
   196         Displays the i18n JavaScript that the Django admin requires.
       
   197 
       
   198         This takes into account the USE_I18N setting. If it's set to False, the
       
   199         generated JavaScript will be leaner and faster.
       
   200         """
       
   201         if settings.USE_I18N:
       
   202             from django.views.i18n import javascript_catalog
       
   203         else:
       
   204             from django.views.i18n import null_javascript_catalog as javascript_catalog
       
   205         return javascript_catalog(request, packages='django.conf')
       
   206 
       
   207     def logout(self, request):
       
   208         """
       
   209         Logs out the user for the given HttpRequest.
       
   210 
       
   211         This should *not* assume the user is already logged in.
       
   212         """
       
   213         from django.contrib.auth.views import logout
       
   214         return logout(request)
       
   215     logout = never_cache(logout)
       
   216 
       
   217     def login(self, request):
       
   218         """
       
   219         Displays the login form for the given HttpRequest.
       
   220         """
       
   221         from django.contrib.auth.models import User
       
   222 
       
   223         # If this isn't already the login page, display it.
       
   224         if not request.POST.has_key(LOGIN_FORM_KEY):
       
   225             if request.POST:
       
   226                 message = _("Please log in again, because your session has expired.")
       
   227             else:
       
   228                 message = ""
       
   229             return self.display_login_form(request, message)
       
   230 
       
   231         # Check that the user accepts cookies.
       
   232         if not request.session.test_cookie_worked():
       
   233             message = _("Looks like your browser isn't configured to accept cookies. Please enable cookies, reload this page, and try again.")
       
   234             return self.display_login_form(request, message)
       
   235         else:
       
   236             request.session.delete_test_cookie()
       
   237 
       
   238         # Check the password.
       
   239         username = request.POST.get('username', None)
       
   240         password = request.POST.get('password', None)
       
   241         user = authenticate(username=username, password=password)
       
   242         if user is None:
       
   243             message = ERROR_MESSAGE
       
   244             if u'@' in username:
       
   245                 # Mistakenly entered e-mail address instead of username? Look it up.
       
   246                 try:
       
   247                     user = User.objects.get(email=username)
       
   248                 except (User.DoesNotExist, User.MultipleObjectsReturned):
       
   249                     message = _("Usernames cannot contain the '@' character.")
       
   250                 else:
       
   251                     if user.check_password(password):
       
   252                         message = _("Your e-mail address is not your username."
       
   253                                     " Try '%s' instead.") % user.username
       
   254                     else:
       
   255                         message = _("Usernames cannot contain the '@' character.")
       
   256             return self.display_login_form(request, message)
       
   257 
       
   258         # The user data is correct; log in the user in and continue.
       
   259         else:
       
   260             if user.is_active and user.is_staff:
       
   261                 login(request, user)
       
   262                 return http.HttpResponseRedirect(request.get_full_path())
       
   263             else:
       
   264                 return self.display_login_form(request, ERROR_MESSAGE)
       
   265     login = never_cache(login)
       
   266 
       
   267     def index(self, request, extra_context=None):
       
   268         """
       
   269         Displays the main admin index page, which lists all of the installed
       
   270         apps that have been registered in this site.
       
   271         """
       
   272         app_dict = {}
       
   273         user = request.user
       
   274         for model, model_admin in self._registry.items():
       
   275             app_label = model._meta.app_label
       
   276             has_module_perms = user.has_module_perms(app_label)
       
   277 
       
   278             if has_module_perms:
       
   279                 perms = {
       
   280                     'add': model_admin.has_add_permission(request),
       
   281                     'change': model_admin.has_change_permission(request),
       
   282                     'delete': model_admin.has_delete_permission(request),
       
   283                 }
       
   284 
       
   285                 # Check whether user has any perm for this module.
       
   286                 # If so, add the module to the model_list.
       
   287                 if True in perms.values():
       
   288                     model_dict = {
       
   289                         'name': capfirst(model._meta.verbose_name_plural),
       
   290                         'admin_url': mark_safe('%s/%s/' % (app_label, model.__name__.lower())),
       
   291                         'perms': perms,
       
   292                     }
       
   293                     if app_label in app_dict:
       
   294                         app_dict[app_label]['models'].append(model_dict)
       
   295                     else:
       
   296                         app_dict[app_label] = {
       
   297                             'name': app_label.title(),
       
   298                             'app_url': app_label + '/',
       
   299                             'has_module_perms': has_module_perms,
       
   300                             'models': [model_dict],
       
   301                         }
       
   302 
       
   303         # Sort the apps alphabetically.
       
   304         app_list = app_dict.values()
       
   305         app_list.sort(lambda x, y: cmp(x['name'], y['name']))
       
   306 
       
   307         # Sort the models alphabetically within each app.
       
   308         for app in app_list:
       
   309             app['models'].sort(lambda x, y: cmp(x['name'], y['name']))
       
   310 
       
   311         context = {
       
   312             'title': _('Site administration'),
       
   313             'app_list': app_list,
       
   314             'root_path': self.root_path,
       
   315         }
       
   316         context.update(extra_context or {})
       
   317         return render_to_response(self.index_template or 'admin/index.html', context,
       
   318             context_instance=template.RequestContext(request)
       
   319         )
       
   320     index = never_cache(index)
       
   321 
       
   322     def display_login_form(self, request, error_message='', extra_context=None):
       
   323         request.session.set_test_cookie()
       
   324         context = {
       
   325             'title': _('Log in'),
       
   326             'app_path': request.get_full_path(),
       
   327             'error_message': error_message,
       
   328             'root_path': self.root_path,
       
   329         }
       
   330         context.update(extra_context or {})
       
   331         return render_to_response(self.login_template or 'admin/login.html', context,
       
   332             context_instance=template.RequestContext(request)
       
   333         )
       
   334 
       
   335     def app_index(self, request, app_label, extra_context=None):
       
   336         user = request.user
       
   337         has_module_perms = user.has_module_perms(app_label)
       
   338         app_dict = {}
       
   339         for model, model_admin in self._registry.items():
       
   340             if app_label == model._meta.app_label:
       
   341                 if has_module_perms:
       
   342                     perms = {
       
   343                         'add': user.has_perm("%s.%s" % (app_label, model._meta.get_add_permission())),
       
   344                         'change': user.has_perm("%s.%s" % (app_label, model._meta.get_change_permission())),
       
   345                         'delete': user.has_perm("%s.%s" % (app_label, model._meta.get_delete_permission())),
       
   346                     }
       
   347                     # Check whether user has any perm for this module.
       
   348                     # If so, add the module to the model_list.
       
   349                     if True in perms.values():
       
   350                         model_dict = {
       
   351                             'name': capfirst(model._meta.verbose_name_plural),
       
   352                             'admin_url': '%s/' % model.__name__.lower(),
       
   353                             'perms': perms,
       
   354                         }
       
   355                         if app_dict:
       
   356                             app_dict['models'].append(model_dict),
       
   357                         else:
       
   358                             # First time around, now that we know there's
       
   359                             # something to display, add in the necessary meta
       
   360                             # information.
       
   361                             app_dict = {
       
   362                                 'name': app_label.title(),
       
   363                                 'app_url': '',
       
   364                                 'has_module_perms': has_module_perms,
       
   365                                 'models': [model_dict],
       
   366                             }
       
   367         if not app_dict:
       
   368             raise http.Http404('The requested admin page does not exist.')
       
   369         # Sort the models alphabetically within each app.
       
   370         app_dict['models'].sort(lambda x, y: cmp(x['name'], y['name']))
       
   371         context = {
       
   372             'title': _('%s administration') % capfirst(app_label),
       
   373             'app_list': [app_dict],
       
   374             'root_path': self.root_path,
       
   375         }
       
   376         context.update(extra_context or {})
       
   377         return render_to_response(self.app_index_template or 'admin/app_index.html', context,
       
   378             context_instance=template.RequestContext(request)
       
   379         )
       
   380 
       
   381 # This global object represents the default admin site, for the common case.
       
   382 # You can instantiate AdminSite in your own code to create a custom admin site.
       
   383 site = AdminSite()