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):
23 class NotRegistered(Exception):
26 class AdminSite(object):
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
36 app_index_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
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
):
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.
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
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.
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
):
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
]
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):
177 from django.conf.urls import patterns, url
179 urls = super(MyAdminSite, self).get_urls()
181 url(r'^my_view/$', self.admin_view(some_view))
185 By default, admin_views are marked non-cacheable using the
186 ``never_cache`` decorator. If the view can be safely cached, set
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
)
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
)
206 from django
.conf
.urls
import patterns
, url
, include
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('',
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'),
231 wrap(self
.i18n_javascript
, cacheable
=True),
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
),
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
))
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
)
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
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
289 from django
.views
.i18n
import null_javascript_catalog
as javascript_catalog
290 return javascript_catalog(request
, packages
=['django.conf', 'django.contrib.admin'])
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
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
)
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
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 {})
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
)
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.
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
)
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
)
348 'name': capfirst(model
._meta
.verbose_name_plural
),
351 if perms
.get('change', False):
353 model_dict
['admin_url'] = reverse('admin:%s_%s_changelist' % info
, current_app
=self
.name
)
354 except NoReverseMatch
:
356 if perms
.get('add', False):
358 model_dict
['add_url'] = reverse('admin:%s_%s_add' % info
, current_app
=self
.name
)
359 except NoReverseMatch
:
361 if app_label
in app_dict
:
362 app_dict
[app_label
]['models'].append(model_dict
)
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.
377 app
['models'].sort(key
=lambda x
: x
['name'])
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):
390 has_module_perms
= user
.has_module_perms(app_label
)
392 for model
, model_admin
in self
._registry
.items():
393 if app_label
== model
._meta
.app_label
:
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
)
402 'name': capfirst(model
._meta
.verbose_name_plural
),
405 if perms
.get('change', False):
407 model_dict
['admin_url'] = reverse('admin:%s_%s_changelist' % info
, current_app
=self
.name
)
408 except NoReverseMatch
:
410 if perms
.get('add', False):
412 model_dict
['add_url'] = reverse('admin:%s_%s_add' % info
, current_app
=self
.name
)
413 except NoReverseMatch
:
416 app_dict
['models'].append(model_dict
),
418 # First time around, now that we know there's
419 # something to display, add in the necessary meta
422 'name': app_label
.title(),
424 'has_module_perms': has_module_perms
,
425 'models': [model_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'])
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.