App Engine Python SDK version 1.7.4 (2)
[gae.git] / python / lib / django_1_4 / django / contrib / admin / sites.py
blob4bb6440877326216faf976701357df9af4810b2e
1 from functools import update_wrapper
2 from django.http import Http404, HttpResponseRedirect
3 from django.contrib.admin import ModelAdmin, actions
4 from django.contrib.admin.forms import AdminAuthenticationForm
5 from django.contrib.auth import REDIRECT_FIELD_NAME
6 from django.contrib.contenttypes import views as contenttype_views
7 from django.views.decorators.csrf import csrf_protect
8 from django.db.models.base import ModelBase
9 from django.core.exceptions import ImproperlyConfigured
10 from django.core.urlresolvers import reverse, NoReverseMatch
11 from django.template.response import TemplateResponse
12 from django.utils.safestring import mark_safe
13 from django.utils.text import capfirst
14 from django.utils.translation import ugettext as _
15 from django.views.decorators.cache import never_cache
16 from django.conf import settings
18 LOGIN_FORM_KEY = 'this_is_the_login_form'
20 class AlreadyRegistered(Exception):
21 pass
23 class NotRegistered(Exception):
24 pass
26 class AdminSite(object):
27 """
28 An AdminSite object encapsulates an instance of the Django admin application, ready
29 to be hooked in to your URLconf. Models are registered with the AdminSite using the
30 register() method, and the get_urls() method can then be used to access Django view
31 functions that present a full admin interface for the collection of registered
32 models.
33 """
34 login_form = None
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
42 def __init__(self, name='admin', app_name='admin'):
43 self._registry = {} # model_class class -> admin_class instance
44 self.name = name
45 self.app_name = app_name
46 self._actions = {'delete_selected': actions.delete_selected}
47 self._global_actions = self._actions.copy()
49 def register(self, model_or_iterable, admin_class=None, **options):
50 """
51 Registers the given model(s) with the given admin class.
53 The model(s) should be Model classes, not instances.
55 If an admin class isn't given, it will use ModelAdmin (the default
56 admin options). If keyword arguments are given -- e.g., list_display --
57 they'll be applied as options to the admin class.
59 If a model is already registered, this will raise AlreadyRegistered.
61 If a model is abstract, this will raise ImproperlyConfigured.
62 """
63 if not admin_class:
64 admin_class = ModelAdmin
66 # Don't import the humongous validation code unless required
67 if admin_class and settings.DEBUG:
68 from django.contrib.admin.validation import validate
69 else:
70 validate = lambda model, adminclass: None
72 if isinstance(model_or_iterable, ModelBase):
73 model_or_iterable = [model_or_iterable]
74 for model in model_or_iterable:
75 if model._meta.abstract:
76 raise ImproperlyConfigured('The model %s is abstract, so it '
77 'cannot be registered with admin.' % model.__name__)
79 if model in self._registry:
80 raise AlreadyRegistered('The model %s is already registered' % model.__name__)
82 # If we got **options then dynamically construct a subclass of
83 # admin_class with those **options.
84 if options:
85 # For reasons I don't quite understand, without a __module__
86 # the created class appears to "live" in the wrong place,
87 # which causes issues later on.
88 options['__module__'] = __name__
89 admin_class = type("%sAdmin" % model.__name__, (admin_class,), options)
91 # Validate (which might be a no-op)
92 validate(admin_class, model)
94 # Instantiate the admin class to save in the registry
95 self._registry[model] = admin_class(model, self)
97 def unregister(self, model_or_iterable):
98 """
99 Unregisters the given model(s).
101 If a model isn't already registered, this will raise NotRegistered.
103 if isinstance(model_or_iterable, ModelBase):
104 model_or_iterable = [model_or_iterable]
105 for model in model_or_iterable:
106 if model not in self._registry:
107 raise NotRegistered('The model %s is not registered' % model.__name__)
108 del self._registry[model]
110 def add_action(self, action, name=None):
112 Register an action to be available globally.
114 name = name or action.__name__
115 self._actions[name] = action
116 self._global_actions[name] = action
118 def disable_action(self, name):
120 Disable a globally-registered action. Raises KeyError for invalid names.
122 del self._actions[name]
124 def get_action(self, name):
126 Explicitally get a registered global action wheather it's enabled or
127 not. Raises KeyError for invalid names.
129 return self._global_actions[name]
131 @property
132 def actions(self):
134 Get all the enabled actions as an iterable of (name, func).
136 return self._actions.iteritems()
138 def has_permission(self, request):
140 Returns True if the given HttpRequest has permission to view
141 *at least one* page in the admin site.
143 return request.user.is_active and request.user.is_staff
145 def check_dependencies(self):
147 Check that all things needed to run the admin have been correctly installed.
149 The default implementation checks that LogEntry, ContentType and the
150 auth context processor are installed.
152 from django.contrib.admin.models import LogEntry
153 from django.contrib.contenttypes.models import ContentType
155 if not LogEntry._meta.installed:
156 raise ImproperlyConfigured("Put 'django.contrib.admin' in your "
157 "INSTALLED_APPS setting in order to use the admin application.")
158 if not ContentType._meta.installed:
159 raise ImproperlyConfigured("Put 'django.contrib.contenttypes' in "
160 "your INSTALLED_APPS setting in order to use the admin application.")
161 if not ('django.contrib.auth.context_processors.auth' in settings.TEMPLATE_CONTEXT_PROCESSORS or
162 'django.core.context_processors.auth' in settings.TEMPLATE_CONTEXT_PROCESSORS):
163 raise ImproperlyConfigured("Put 'django.contrib.auth.context_processors.auth' "
164 "in your TEMPLATE_CONTEXT_PROCESSORS setting in order to use the admin application.")
166 def admin_view(self, view, cacheable=False):
168 Decorator to create an admin view attached to this ``AdminSite``. This
169 wraps the view and provides permission checking by calling
170 ``self.has_permission``.
172 You'll want to use this from within ``AdminSite.get_urls()``:
174 class MyAdminSite(AdminSite):
176 def get_urls(self):
177 from django.conf.urls import patterns, url
179 urls = super(MyAdminSite, self).get_urls()
180 urls += patterns('',
181 url(r'^my_view/$', self.admin_view(some_view))
183 return urls
185 By default, admin_views are marked non-cacheable using the
186 ``never_cache`` decorator. If the view can be safely cached, set
187 cacheable=True.
189 def inner(request, *args, **kwargs):
190 if not self.has_permission(request):
191 if request.path == reverse('admin:logout',
192 current_app=self.name):
193 index_path = reverse('admin:index', current_app=self.name)
194 return HttpResponseRedirect(index_path)
195 return self.login(request)
196 return view(request, *args, **kwargs)
197 if not cacheable:
198 inner = never_cache(inner)
199 # We add csrf_protect here so this function can be used as a utility
200 # function for any view, without having to repeat 'csrf_protect'.
201 if not getattr(view, 'csrf_exempt', False):
202 inner = csrf_protect(inner)
203 return update_wrapper(inner, view)
205 def get_urls(self):
206 from django.conf.urls import patterns, url, include
208 if settings.DEBUG:
209 self.check_dependencies()
211 def wrap(view, cacheable=False):
212 def wrapper(*args, **kwargs):
213 return self.admin_view(view, cacheable)(*args, **kwargs)
214 return update_wrapper(wrapper, view)
216 # Admin-site-wide views.
217 urlpatterns = patterns('',
218 url(r'^$',
219 wrap(self.index),
220 name='index'),
221 url(r'^logout/$',
222 wrap(self.logout),
223 name='logout'),
224 url(r'^password_change/$',
225 wrap(self.password_change, cacheable=True),
226 name='password_change'),
227 url(r'^password_change/done/$',
228 wrap(self.password_change_done, cacheable=True),
229 name='password_change_done'),
230 url(r'^jsi18n/$',
231 wrap(self.i18n_javascript, cacheable=True),
232 name='jsi18n'),
233 url(r'^r/(?P<content_type_id>\d+)/(?P<object_id>.+)/$',
234 wrap(contenttype_views.shortcut)),
235 url(r'^(?P<app_label>\w+)/$',
236 wrap(self.app_index),
237 name='app_list')
240 # Add in each model's views.
241 for model, model_admin in self._registry.iteritems():
242 urlpatterns += patterns('',
243 url(r'^%s/%s/' % (model._meta.app_label, model._meta.module_name),
244 include(model_admin.urls))
246 return urlpatterns
248 @property
249 def urls(self):
250 return self.get_urls(), self.app_name, self.name
252 def password_change(self, request):
254 Handles the "change password" task -- both form display and validation.
256 from django.contrib.auth.views import password_change
257 url = reverse('admin:password_change_done', current_app=self.name)
258 defaults = {
259 'current_app': self.name,
260 'post_change_redirect': url
262 if self.password_change_template is not None:
263 defaults['template_name'] = self.password_change_template
264 return password_change(request, **defaults)
266 def password_change_done(self, request, extra_context=None):
268 Displays the "success" page after a password change.
270 from django.contrib.auth.views import password_change_done
271 defaults = {
272 'current_app': self.name,
273 'extra_context': extra_context or {},
275 if self.password_change_done_template is not None:
276 defaults['template_name'] = self.password_change_done_template
277 return password_change_done(request, **defaults)
279 def i18n_javascript(self, request):
281 Displays the i18n JavaScript that the Django admin requires.
283 This takes into account the USE_I18N setting. If it's set to False, the
284 generated JavaScript will be leaner and faster.
286 if settings.USE_I18N:
287 from django.views.i18n import javascript_catalog
288 else:
289 from django.views.i18n import null_javascript_catalog as javascript_catalog
290 return javascript_catalog(request, packages=['django.conf', 'django.contrib.admin'])
292 @never_cache
293 def logout(self, request, extra_context=None):
295 Logs out the user for the given HttpRequest.
297 This should *not* assume the user is already logged in.
299 from django.contrib.auth.views import logout
300 defaults = {
301 'current_app': self.name,
302 'extra_context': extra_context or {},
304 if self.logout_template is not None:
305 defaults['template_name'] = self.logout_template
306 return logout(request, **defaults)
308 @never_cache
309 def login(self, request, extra_context=None):
311 Displays the login form for the given HttpRequest.
313 from django.contrib.auth.views import login
314 context = {
315 'title': _('Log in'),
316 'app_path': request.get_full_path(),
317 REDIRECT_FIELD_NAME: request.get_full_path(),
319 context.update(extra_context or {})
320 defaults = {
321 'extra_context': context,
322 'current_app': self.name,
323 'authentication_form': self.login_form or AdminAuthenticationForm,
324 'template_name': self.login_template or 'admin/login.html',
326 return login(request, **defaults)
328 @never_cache
329 def index(self, request, extra_context=None):
331 Displays the main admin index page, which lists all of the installed
332 apps that have been registered in this site.
334 app_dict = {}
335 user = request.user
336 for model, model_admin in self._registry.items():
337 app_label = model._meta.app_label
338 has_module_perms = user.has_module_perms(app_label)
340 if has_module_perms:
341 perms = model_admin.get_model_perms(request)
343 # Check whether user has any perm for this module.
344 # If so, add the module to the model_list.
345 if True in perms.values():
346 info = (app_label, model._meta.module_name)
347 model_dict = {
348 'name': capfirst(model._meta.verbose_name_plural),
349 'perms': perms,
351 if perms.get('change', False):
352 try:
353 model_dict['admin_url'] = reverse('admin:%s_%s_changelist' % info, current_app=self.name)
354 except NoReverseMatch:
355 pass
356 if perms.get('add', False):
357 try:
358 model_dict['add_url'] = reverse('admin:%s_%s_add' % info, current_app=self.name)
359 except NoReverseMatch:
360 pass
361 if app_label in app_dict:
362 app_dict[app_label]['models'].append(model_dict)
363 else:
364 app_dict[app_label] = {
365 'name': app_label.title(),
366 'app_url': reverse('admin:app_list', kwargs={'app_label': app_label}, current_app=self.name),
367 'has_module_perms': has_module_perms,
368 'models': [model_dict],
371 # Sort the apps alphabetically.
372 app_list = app_dict.values()
373 app_list.sort(key=lambda x: x['name'])
375 # Sort the models alphabetically within each app.
376 for app in app_list:
377 app['models'].sort(key=lambda x: x['name'])
379 context = {
380 'title': _('Site administration'),
381 'app_list': app_list,
383 context.update(extra_context or {})
384 return TemplateResponse(request, [
385 self.index_template or 'admin/index.html',
386 ], context, current_app=self.name)
388 def app_index(self, request, app_label, extra_context=None):
389 user = request.user
390 has_module_perms = user.has_module_perms(app_label)
391 app_dict = {}
392 for model, model_admin in self._registry.items():
393 if app_label == model._meta.app_label:
394 if has_module_perms:
395 perms = model_admin.get_model_perms(request)
397 # Check whether user has any perm for this module.
398 # If so, add the module to the model_list.
399 if True in perms.values():
400 info = (app_label, model._meta.module_name)
401 model_dict = {
402 'name': capfirst(model._meta.verbose_name_plural),
403 'perms': perms,
405 if perms.get('change', False):
406 try:
407 model_dict['admin_url'] = reverse('admin:%s_%s_changelist' % info, current_app=self.name)
408 except NoReverseMatch:
409 pass
410 if perms.get('add', False):
411 try:
412 model_dict['add_url'] = reverse('admin:%s_%s_add' % info, current_app=self.name)
413 except NoReverseMatch:
414 pass
415 if app_dict:
416 app_dict['models'].append(model_dict),
417 else:
418 # First time around, now that we know there's
419 # something to display, add in the necessary meta
420 # information.
421 app_dict = {
422 'name': app_label.title(),
423 'app_url': '',
424 'has_module_perms': has_module_perms,
425 'models': [model_dict],
427 if not app_dict:
428 raise Http404('The requested admin page does not exist.')
429 # Sort the models alphabetically within each app.
430 app_dict['models'].sort(key=lambda x: x['name'])
431 context = {
432 'title': _('%s administration') % capfirst(app_label),
433 'app_list': [app_dict],
435 context.update(extra_context or {})
437 return TemplateResponse(request, self.app_index_template or [
438 'admin/%s/app_index.html' % app_label,
439 'admin/app_index.html'
440 ], context, current_app=self.name)
442 # This global object represents the default admin site, for the common case.
443 # You can instantiate AdminSite in your own code to create a custom admin site.
444 site = AdminSite()