Statistics
| Branch: | Revision:

root / env / lib / python2.7 / site-packages / django / contrib / admin / sites.py @ 1a305335

History | View | Annotate | Download (17.9 KB)

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
17

    
18
LOGIN_FORM_KEY = 'this_is_the_login_form'
19

    
20
class AlreadyRegistered(Exception):
21
    pass
22

    
23
class NotRegistered(Exception):
24
    pass
25

    
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
41

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

    
49
    def register(self, model_or_iterable, admin_class=None, **options):
50
        """
51
        Registers the given model(s) with the given admin class.
52

53
        The model(s) should be Model classes, not instances.
54

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.
58

59
        If a model is already registered, this will raise AlreadyRegistered.
60

61
        If a model is abstract, this will raise ImproperlyConfigured.
62
        """
63
        if not admin_class:
64
            admin_class = ModelAdmin
65

    
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
71

    
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__)
78

    
79
            if model in self._registry:
80
                raise AlreadyRegistered('The model %s is already registered' % model.__name__)
81

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

    
91
            # Validate (which might be a no-op)
92
            validate(admin_class, model)
93

    
94
            # Instantiate the admin class to save in the registry
95
            self._registry[model] = admin_class(model, self)
96

    
97
    def unregister(self, model_or_iterable):
98
        """
99
        Unregisters the given model(s).
100

101
        If a model isn't already registered, this will raise NotRegistered.
102
        """
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]
109

    
110
    def add_action(self, action, name=None):
111
        """
112
        Register an action to be available globally.
113
        """
114
        name = name or action.__name__
115
        self._actions[name] = action
116
        self._global_actions[name] = action
117

    
118
    def disable_action(self, name):
119
        """
120
        Disable a globally-registered action. Raises KeyError for invalid names.
121
        """
122
        del self._actions[name]
123

    
124
    def get_action(self, name):
125
        """
126
        Explicitally get a registered global action wheather it's enabled or
127
        not. Raises KeyError for invalid names.
128
        """
129
        return self._global_actions[name]
130

    
131
    @property
132
    def actions(self):
133
        """
134
        Get all the enabled actions as an iterable of (name, func).
135
        """
136
        return self._actions.iteritems()
137

    
138
    def has_permission(self, request):
139
        """
140
        Returns True if the given HttpRequest has permission to view
141
        *at least one* page in the admin site.
142
        """
143
        return request.user.is_active and request.user.is_staff
144

    
145
    def check_dependencies(self):
146
        """
147
        Check that all things needed to run the admin have been correctly installed.
148

149
        The default implementation checks that LogEntry, ContentType and the
150
        auth context processor are installed.
151
        """
152
        from django.contrib.admin.models import LogEntry
153
        from django.contrib.contenttypes.models import ContentType
154

    
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.")
165

    
166
    def admin_view(self, view, cacheable=False):
167
        """
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``.
171

172
        You'll want to use this from within ``AdminSite.get_urls()``:
173

174
            class MyAdminSite(AdminSite):
175

176
                def get_urls(self):
177
                    from django.conf.urls import patterns, url
178

179
                    urls = super(MyAdminSite, self).get_urls()
180
                    urls += patterns('',
181
                        url(r'^my_view/$', self.admin_view(some_view))
182
                    )
183
                    return urls
184

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.
188
        """
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)
204

    
205
    def get_urls(self):
206
        from django.conf.urls import patterns, url, include
207

    
208
        if settings.DEBUG:
209
            self.check_dependencies()
210

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

    
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')
238
        )
239

    
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))
245
            )
246
        return urlpatterns
247

    
248
    @property
249
    def urls(self):
250
        return self.get_urls(), self.app_name, self.name
251

    
252
    def password_change(self, request):
253
        """
254
        Handles the "change password" task -- both form display and validation.
255
        """
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
261
        }
262
        if self.password_change_template is not None:
263
            defaults['template_name'] = self.password_change_template
264
        return password_change(request, **defaults)
265

    
266
    def password_change_done(self, request, extra_context=None):
267
        """
268
        Displays the "success" page after a password change.
269
        """
270
        from django.contrib.auth.views import password_change_done
271
        defaults = {
272
            'current_app': self.name,
273
            'extra_context': extra_context or {},
274
        }
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)
278

    
279
    def i18n_javascript(self, request):
280
        """
281
        Displays the i18n JavaScript that the Django admin requires.
282

283
        This takes into account the USE_I18N setting. If it's set to False, the
284
        generated JavaScript will be leaner and faster.
285
        """
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'])
291

    
292
    @never_cache
293
    def logout(self, request, extra_context=None):
294
        """
295
        Logs out the user for the given HttpRequest.
296

297
        This should *not* assume the user is already logged in.
298
        """
299
        from django.contrib.auth.views import logout
300
        defaults = {
301
            'current_app': self.name,
302
            'extra_context': extra_context or {},
303
        }
304
        if self.logout_template is not None:
305
            defaults['template_name'] = self.logout_template
306
        return logout(request, **defaults)
307

    
308
    @never_cache
309
    def login(self, request, extra_context=None):
310
        """
311
        Displays the login form for the given HttpRequest.
312
        """
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(),
318
        }
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',
325
        }
326
        return login(request, **defaults)
327

    
328
    @never_cache
329
    def index(self, request, extra_context=None):
330
        """
331
        Displays the main admin index page, which lists all of the installed
332
        apps that have been registered in this site.
333
        """
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)
339

    
340
            if has_module_perms:
341
                perms = model_admin.get_model_perms(request)
342

    
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,
350
                    }
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],
369
                        }
370

    
371
        # Sort the apps alphabetically.
372
        app_list = app_dict.values()
373
        app_list.sort(key=lambda x: x['name'])
374

    
375
        # Sort the models alphabetically within each app.
376
        for app in app_list:
377
            app['models'].sort(key=lambda x: x['name'])
378

    
379
        context = {
380
            'title': _('Site administration'),
381
            'app_list': app_list,
382
        }
383
        context.update(extra_context or {})
384
        return TemplateResponse(request, [
385
            self.index_template or 'admin/index.html',
386
        ], context, current_app=self.name)
387

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

    
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,
404
                        }
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],
426
                            }
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],
434
        }
435
        context.update(extra_context or {})
436

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

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