diff options
Diffstat (limited to 'parts/django/django/contrib/admin/sites.py')
-rw-r--r-- | parts/django/django/contrib/admin/sites.py | 534 |
1 files changed, 534 insertions, 0 deletions
diff --git a/parts/django/django/contrib/admin/sites.py b/parts/django/django/contrib/admin/sites.py new file mode 100644 index 0000000..1778ae4 --- /dev/null +++ b/parts/django/django/contrib/admin/sites.py @@ -0,0 +1,534 @@ +import re +from django import http, template +from django.contrib.admin import ModelAdmin +from django.contrib.admin import actions +from django.contrib.auth import authenticate, login +from django.views.decorators.csrf import csrf_protect +from django.db.models.base import ModelBase +from django.core.exceptions import ImproperlyConfigured +from django.core.urlresolvers import reverse +from django.shortcuts import render_to_response +from django.utils.functional import update_wrapper +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 + to be hooked in to your URLconf. Models are registered with the AdminSite using the + 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. + """ + + index_template = None + app_index_template = None + login_template = None + logout_template = None + password_change_template = None + password_change_done_template = None + + def __init__(self, name=None, app_name='admin'): + self._registry = {} # model_class class -> admin_class instance + self.root_path = None + if name is None: + self.name = 'admin' + else: + self.name = name + self.app_name = app_name + self._actions = {'delete_selected': actions.delete_selected} + self._global_actions = self._actions.copy() + + def register(self, model_or_iterable, admin_class=None, **options): + """ + Registers the given model(s) with the given admin class. + + The model(s) should be Model classes, not instances. + + 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. + + If a model is already registered, this will raise AlreadyRegistered. + """ + if not admin_class: + admin_class = ModelAdmin + + # Don't import the humongous validation code unless required + if admin_class and settings.DEBUG: + from django.contrib.admin.validation import validate + else: + validate = lambda model, adminclass: None + + 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__) + + # 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("%sAdmin" % model.__name__, (admin_class,), options) + + # Validate (which might be a no-op) + validate(admin_class, model) + + # Instantiate the admin class to save in the registry + self._registry[model] = admin_class(model, self) + + def unregister(self, model_or_iterable): + """ + Unregisters the given model(s). + + 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] + + def add_action(self, action, name=None): + """ + Register an action to be available globally. + """ + name = name or action.__name__ + self._actions[name] = action + self._global_actions[name] = action + + def disable_action(self, name): + """ + Disable a globally-registered action. Raises KeyError for invalid names. + """ + del self._actions[name] + + def get_action(self, name): + """ + Explicitally get a registered global action wheather it's enabled or + not. Raises KeyError for invalid names. + """ + return self._global_actions[name] + + def actions(self): + """ + Get all the enabled actions as an iterable of (name, func). + """ + return self._actions.iteritems() + actions = property(actions) + + 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_active and request.user.is_staff + + def check_dependencies(self): + """ + Check that all things needed to run the admin have been correctly installed. + + The default implementation checks that LogEntry, ContentType and the + auth context processor are installed. + """ + from django.contrib.admin.models import LogEntry + from django.contrib.contenttypes.models import ContentType + + 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 not ('django.contrib.auth.context_processors.auth' in settings.TEMPLATE_CONTEXT_PROCESSORS or + 'django.core.context_processors.auth' in settings.TEMPLATE_CONTEXT_PROCESSORS): + raise ImproperlyConfigured("Put 'django.contrib.auth.context_processors.auth' " + "in your TEMPLATE_CONTEXT_PROCESSORS setting in order to use the admin application.") + + def admin_view(self, view, cacheable=False): + """ + Decorator to create an admin view attached to this ``AdminSite``. This + wraps the view and provides permission checking by calling + ``self.has_permission``. + + You'll want to use this from within ``AdminSite.get_urls()``: + + class MyAdminSite(AdminSite): + + def get_urls(self): + from django.conf.urls.defaults import patterns, url + + urls = super(MyAdminSite, self).get_urls() + urls += patterns('', + url(r'^my_view/$', self.admin_view(some_view)) + ) + return urls + + By default, admin_views are marked non-cacheable using the + ``never_cache`` decorator. If the view can be safely cached, set + cacheable=True. + """ + def inner(request, *args, **kwargs): + if not self.has_permission(request): + return self.login(request) + return view(request, *args, **kwargs) + if not cacheable: + inner = never_cache(inner) + # We add csrf_protect here so this function can be used as a utility + # function for any view, without having to repeat 'csrf_protect'. + if not getattr(view, 'csrf_exempt', False): + inner = csrf_protect(inner) + return update_wrapper(inner, view) + + def get_urls(self): + from django.conf.urls.defaults import patterns, url, include + + if settings.DEBUG: + self.check_dependencies() + + def wrap(view, cacheable=False): + def wrapper(*args, **kwargs): + return self.admin_view(view, cacheable)(*args, **kwargs) + return update_wrapper(wrapper, view) + + # Admin-site-wide views. + urlpatterns = patterns('', + url(r'^$', + wrap(self.index), + name='index'), + url(r'^logout/$', + wrap(self.logout), + name='logout'), + url(r'^password_change/$', + wrap(self.password_change, cacheable=True), + name='password_change'), + url(r'^password_change/done/$', + wrap(self.password_change_done, cacheable=True), + name='password_change_done'), + url(r'^jsi18n/$', + wrap(self.i18n_javascript, cacheable=True), + name='jsi18n'), + 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='app_list') + ) + + # 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 + + def urls(self): + return self.get_urls(), self.app_name, self.name + urls = property(urls) + + def password_change(self, request): + """ + Handles the "change password" task -- both form display and validation. + """ + from django.contrib.auth.views import password_change + if self.root_path is not None: + url = '%spassword_change/done/' % self.root_path + else: + url = reverse('admin:password_change_done', current_app=self.name) + defaults = { + 'post_change_redirect': url + } + if self.password_change_template is not None: + defaults['template_name'] = self.password_change_template + return password_change(request, **defaults) + + def password_change_done(self, request): + """ + Displays the "success" page after a password change. + """ + from django.contrib.auth.views import password_change_done + defaults = {} + if self.password_change_done_template is not None: + defaults['template_name'] = self.password_change_done_template + return password_change_done(request, **defaults) + + def i18n_javascript(self, request): + """ + Displays the i18n JavaScript that the Django admin requires. + + 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') + + def logout(self, request): + """ + Logs out the user for the given HttpRequest. + + This should *not* assume the user is already logged in. + """ + from django.contrib.auth.views import logout + defaults = {} + if self.logout_template is not None: + defaults['template_name'] = self.logout_template + return logout(request, **defaults) + logout = never_cache(logout) + + def login(self, request): + """ + Displays the login form for the given HttpRequest. + """ + from django.contrib.auth.models import User + + # If this isn't already the login page, display it. + if not request.POST.has_key(LOGIN_FORM_KEY): + if request.POST: + message = _("Please log in again, because your session has expired.") + else: + message = "" + return self.display_login_form(request, message) + + # 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) + else: + request.session.delete_test_cookie() + + # 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 username is not None and 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): + pass + else: + if user.check_password(password): + message = _("Your e-mail address is not your username." + " Try '%s' instead.") % user.username + return self.display_login_form(request, message) + + # The user data is correct; log in the user in and continue. + else: + if user.is_active and user.is_staff: + login(request, user) + return http.HttpResponseRedirect(request.get_full_path()) + else: + return self.display_login_form(request, ERROR_MESSAGE) + login = never_cache(login) + + 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) + + if has_module_perms: + perms = model_admin.get_model_perms(request) + + # 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(), + 'app_url': app_label + '/', + 'has_module_perms': has_module_perms, + 'models': [model_dict], + } + + # Sort the apps alphabetically. + app_list = app_dict.values() + app_list.sort(lambda x, y: cmp(x['name'], y['name'])) + + # Sort the models alphabetically within each app. + for app in app_list: + app['models'].sort(lambda x, y: cmp(x['name'], y['name'])) + + context = { + 'title': _('Site administration'), + 'app_list': app_list, + 'root_path': self.root_path, + } + context.update(extra_context or {}) + context_instance = template.RequestContext(request, current_app=self.name) + return render_to_response(self.index_template or 'admin/index.html', context, + context_instance=context_instance + ) + index = never_cache(index) + + def display_login_form(self, request, error_message='', extra_context=None): + request.session.set_test_cookie() + context = { + 'title': _('Log in'), + 'app_path': request.get_full_path(), + 'error_message': error_message, + 'root_path': self.root_path, + } + context.update(extra_context or {}) + context_instance = template.RequestContext(request, current_app=self.name) + return render_to_response(self.login_template or 'admin/login.html', context, + context_instance=context_instance + ) + + def app_index(self, request, app_label, extra_context=None): + 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 = model_admin.get_model_perms(request) + + # 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, + } + 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.') + # Sort the models alphabetically within each app. + app_dict['models'].sort(lambda x, y: cmp(x['name'], y['name'])) + context = { + 'title': _('%s administration') % capfirst(app_label), + 'app_list': [app_dict], + 'root_path': self.root_path, + } + context.update(extra_context or {}) + context_instance = template.RequestContext(request, current_app=self.name) + return render_to_response(self.app_index_template or ('admin/%s/app_index.html' % app_label, + 'admin/app_index.html'), context, + context_instance=context_instance + ) + + 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()``. + + This function still exists for backwards-compatibility; it will be + removed in Django 1.3. + """ + import warnings + warnings.warn( + "AdminSite.root() is deprecated; use include(admin.site.urls) instead.", + DeprecationWarning + ) + + # + # 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! + # + + if request.method == 'GET' and not request.path.endswith('/'): + return http.HttpResponseRedirect(request.path + '/') + + if settings.DEBUG: + self.check_dependencies() + + # Figure out the admin base URL path and stash it for later use + self.root_path = re.sub(re.escape(url) + '$', '', request.path) + + url = url.rstrip('/') # Trim trailing slash, if it exists. + + # The 'logout' view doesn't require that the person is logged in. + if url == 'logout': + return self.logout(request) + + # Check permission to continue or display login form. + if not self.has_permission(request): + return self.login(request) + + 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) + + raise http.Http404('The requested admin page does not exist.') + + 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) + model_page = never_cache(model_page) + +# 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() |