|
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() |