diff options
Diffstat (limited to 'tbc/static/admin/sites.py')
-rw-r--r-- | tbc/static/admin/sites.py | 535 |
1 files changed, 535 insertions, 0 deletions
diff --git a/tbc/static/admin/sites.py b/tbc/static/admin/sites.py new file mode 100644 index 0000000..39f22b8 --- /dev/null +++ b/tbc/static/admin/sites.py @@ -0,0 +1,535 @@ +from functools import update_wrapper + +from django.apps import apps +from django.conf import settings +from django.contrib.admin import ModelAdmin, actions +from django.contrib.auth import REDIRECT_FIELD_NAME +from django.core.exceptions import ImproperlyConfigured, PermissionDenied +from django.core.urlresolvers import NoReverseMatch, reverse +from django.db.models.base import ModelBase +from django.http import Http404, HttpResponseRedirect +from django.template.engine import Engine +from django.template.response import TemplateResponse +from django.utils import six +from django.utils.text import capfirst +from django.utils.translation import ugettext as _, ugettext_lazy +from django.views.decorators.cache import never_cache +from django.views.decorators.csrf import csrf_protect + +system_check_errors = [] + + +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 get_urls() method can then be used to access Django view + functions that present a full admin interface for the collection of registered + models. + """ + + # Text to put at the end of each page's <title>. + site_title = ugettext_lazy('Django site admin') + + # Text to put in each page's <h1>. + site_header = ugettext_lazy('Django administration') + + # Text to put at the top of the admin index page. + index_title = ugettext_lazy('Site administration') + + # URL for the "View site" link at the top of each admin page. + site_url = '/' + + _empty_value_display = '-' + + login_form = None + 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='admin'): + self._registry = {} # model_class class -> admin_class instance + self.name = 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 a model is abstract, this will raise ImproperlyConfigured. + """ + if not admin_class: + admin_class = ModelAdmin + + if isinstance(model_or_iterable, ModelBase): + model_or_iterable = [model_or_iterable] + for model in model_or_iterable: + if model._meta.abstract: + raise ImproperlyConfigured('The model %s is abstract, so it ' + 'cannot be registered with admin.' % model.__name__) + + if model in self._registry: + raise AlreadyRegistered('The model %s is already registered' % model.__name__) + + # Ignore the registration if the model has been + # swapped out. + if not model._meta.swapped: + # 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) + + # Instantiate the admin class to save in the registry + admin_obj = admin_class(model, self) + if admin_class is not ModelAdmin and settings.DEBUG: + system_check_errors.extend(admin_obj.check()) + + self._registry[model] = admin_obj + + 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 is_registered(self, model): + """ + Check if a model class is registered with this `AdminSite`. + """ + return model in self._registry + + 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): + """ + Explicitly get a registered global action whether it's enabled or + not. Raises KeyError for invalid names. + """ + return self._global_actions[name] + + @property + def actions(self): + """ + Get all the enabled actions as an iterable of (name, func). + """ + return six.iteritems(self._actions) + + @property + def empty_value_display(self): + return self._empty_value_display + + @empty_value_display.setter + def empty_value_display(self, empty_value_display): + self._empty_value_display = empty_value_display + + 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 admin and contenttypes apps are + installed, as well as the auth context processor. + """ + if not apps.is_installed('django.contrib.admin'): + raise ImproperlyConfigured( + "Put 'django.contrib.admin' in your INSTALLED_APPS " + "setting in order to use the admin application.") + if not apps.is_installed('django.contrib.contenttypes'): + raise ImproperlyConfigured( + "Put 'django.contrib.contenttypes' in your INSTALLED_APPS " + "setting in order to use the admin application.") + try: + default_template_engine = Engine.get_default() + except Exception: + # Skip this non-critical check: + # 1. if the user has a non-trivial TEMPLATES setting and Django + # can't find a default template engine + # 2. if anything goes wrong while loading template engines, in + # order to avoid raising an exception from a confusing location + # Catching ImproperlyConfigured suffices for 1. but 2. requires + # catching all exceptions. + pass + else: + if ('django.contrib.auth.context_processors.auth' + not in default_template_engine.context_processors): + raise ImproperlyConfigured( + "Enable 'django.contrib.auth.context_processors.auth' " + "in your TEMPLATES 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 import url + + urls = super(MyAdminSite, self).get_urls() + urls += [ + 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): + if request.path == reverse('admin:logout', current_app=self.name): + index_path = reverse('admin:index', current_app=self.name) + return HttpResponseRedirect(index_path) + # Inner import to prevent django.contrib.admin (app) from + # importing django.contrib.auth.models.User (unrelated model). + from django.contrib.auth.views import redirect_to_login + return redirect_to_login( + request.get_full_path(), + reverse('admin:login', current_app=self.name) + ) + 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 import url, include + # Since this module gets imported in the application's root package, + # it cannot import models from other applications at the module level, + # and django.contrib.contenttypes.views imports ContentType. + from django.contrib.contenttypes import views as contenttype_views + + if settings.DEBUG: + self.check_dependencies() + + def wrap(view, cacheable=False): + def wrapper(*args, **kwargs): + return self.admin_view(view, cacheable)(*args, **kwargs) + wrapper.admin_site = self + return update_wrapper(wrapper, view) + + # Admin-site-wide views. + urlpatterns = [ + url(r'^$', wrap(self.index), name='index'), + url(r'^login/$', self.login, name='login'), + 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>.+)/$', wrap(contenttype_views.shortcut), + name='view_on_site'), + ] + + # Add in each model's views, and create a list of valid URLS for the + # app_index + valid_app_labels = [] + for model, model_admin in self._registry.items(): + urlpatterns += [ + url(r'^%s/%s/' % (model._meta.app_label, model._meta.model_name), include(model_admin.urls)), + ] + if model._meta.app_label not in valid_app_labels: + valid_app_labels.append(model._meta.app_label) + + # If there were ModelAdmins registered, we should have a list of app + # labels for which we need to allow access to the app_index view, + if valid_app_labels: + regex = r'^(?P<app_label>' + '|'.join(valid_app_labels) + ')/$' + urlpatterns += [ + url(regex, wrap(self.app_index), name='app_list'), + ] + return urlpatterns + + @property + def urls(self): + return self.get_urls(), 'admin', self.name + + def each_context(self, request): + """ + Returns a dictionary of variables to put in the template context for + *every* page in the admin site. + """ + return { + 'site_title': self.site_title, + 'site_header': self.site_header, + 'site_url': self.site_url, + 'has_permission': self.has_permission(request), + 'available_apps': self.get_app_list(request), + } + + def password_change(self, request, extra_context=None): + """ + Handles the "change password" task -- both form display and validation. + """ + from django.contrib.admin.forms import AdminPasswordChangeForm + from django.contrib.auth.views import password_change + url = reverse('admin:password_change_done', current_app=self.name) + defaults = { + 'password_change_form': AdminPasswordChangeForm, + 'post_change_redirect': url, + 'extra_context': dict(self.each_context(request), **(extra_context or {})), + } + if self.password_change_template is not None: + defaults['template_name'] = self.password_change_template + request.current_app = self.name + return password_change(request, **defaults) + + def password_change_done(self, request, extra_context=None): + """ + Displays the "success" page after a password change. + """ + from django.contrib.auth.views import password_change_done + defaults = { + 'extra_context': dict(self.each_context(request), **(extra_context or {})), + } + if self.password_change_done_template is not None: + defaults['template_name'] = self.password_change_done_template + request.current_app = self.name + 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', 'django.contrib.admin']) + + @never_cache + def logout(self, request, extra_context=None): + """ + 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 = { + 'extra_context': dict(self.each_context(request), **(extra_context or {})), + } + if self.logout_template is not None: + defaults['template_name'] = self.logout_template + request.current_app = self.name + return logout(request, **defaults) + + @never_cache + def login(self, request, extra_context=None): + """ + Displays the login form for the given HttpRequest. + """ + if request.method == 'GET' and self.has_permission(request): + # Already logged-in, redirect to admin index + index_path = reverse('admin:index', current_app=self.name) + return HttpResponseRedirect(index_path) + + from django.contrib.auth.views import login + # Since this module gets imported in the application's root package, + # it cannot import models from other applications at the module level, + # and django.contrib.admin.forms eventually imports User. + from django.contrib.admin.forms import AdminAuthenticationForm + context = dict(self.each_context(request), + title=_('Log in'), + app_path=request.get_full_path(), + ) + if (REDIRECT_FIELD_NAME not in request.GET and + REDIRECT_FIELD_NAME not in request.POST): + context[REDIRECT_FIELD_NAME] = reverse('admin:index', current_app=self.name) + context.update(extra_context or {}) + + defaults = { + 'extra_context': context, + 'authentication_form': self.login_form or AdminAuthenticationForm, + 'template_name': self.login_template or 'admin/login.html', + } + request.current_app = self.name + return login(request, **defaults) + + def _build_app_dict(self, request, label=None): + """ + Builds the app dictionary. Takes an optional label parameters to filter + models of a specific app. + """ + app_dict = {} + + if label: + models = { + m: m_a for m, m_a in self._registry.items() + if m._meta.app_label == label + } + else: + models = self._registry + + for model, model_admin in models.items(): + app_label = model._meta.app_label + + has_module_perms = model_admin.has_module_permission(request) + if not has_module_perms: + if label: + raise PermissionDenied + continue + + 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 not in perms.values(): + continue + + info = (app_label, model._meta.model_name) + model_dict = { + 'name': capfirst(model._meta.verbose_name_plural), + 'object_name': model._meta.object_name, + 'perms': perms, + } + if perms.get('change'): + try: + model_dict['admin_url'] = reverse('admin:%s_%s_changelist' % info, current_app=self.name) + except NoReverseMatch: + pass + if perms.get('add'): + try: + model_dict['add_url'] = reverse('admin:%s_%s_add' % info, current_app=self.name) + except NoReverseMatch: + pass + + if app_label in app_dict: + app_dict[app_label]['models'].append(model_dict) + else: + app_dict[app_label] = { + 'name': apps.get_app_config(app_label).verbose_name, + 'app_label': app_label, + 'app_url': reverse( + 'admin:app_list', + kwargs={'app_label': app_label}, + current_app=self.name, + ), + 'has_module_perms': has_module_perms, + 'models': [model_dict], + } + + if label: + return app_dict.get(label) + return app_dict + + def get_app_list(self, request): + """ + Returns a sorted list of all the installed apps that have been + registered in this site. + """ + app_dict = self._build_app_dict(request) + + # Sort the apps alphabetically. + app_list = sorted(app_dict.values(), key=lambda x: x['name'].lower()) + + # Sort the models alphabetically within each app. + for app in app_list: + app['models'].sort(key=lambda x: x['name']) + + return app_list + + @never_cache + 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_list = self.get_app_list(request) + + context = dict( + self.each_context(request), + title=self.index_title, + app_list=app_list, + ) + context.update(extra_context or {}) + + request.current_app = self.name + + return TemplateResponse(request, self.index_template or + 'admin/index.html', context) + + def app_index(self, request, app_label, extra_context=None): + app_dict = self._build_app_dict(request, app_label) + if not app_dict: + raise Http404('The requested admin page does not exist.') + # Sort the models alphabetically within each app. + app_dict['models'].sort(key=lambda x: x['name']) + app_name = apps.get_app_config(app_label).verbose_name + context = dict(self.each_context(request), + title=_('%(app)s administration') % {'app': app_name}, + app_list=[app_dict], + app_label=app_label, + ) + context.update(extra_context or {}) + + request.current_app = self.name + + return TemplateResponse(request, self.app_index_template or [ + 'admin/%s/app_index.html' % app_label, + 'admin/app_index.html' + ], context) + +# 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() |