Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Fixed #6470: made the admin use a URL resolver.

This *is* backwards compatible, but `admin.site.root()` has been deprecated. The new style is `('^admin/', include(admin.site.urls))`; users will need to update their code to take advantage of the new customizable admin URLs.

Thanks to Alex Gaynor.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@9739 bcc190cf-cafb-0310-a4f2-bffc1f526a37
  • Loading branch information...
commit 1f84630c87f8032b0167e6db41acaf50ab710879 1 parent 6c4e5f0
@jacobian jacobian authored
View
2  django/conf/project_template/urls.py
@@ -13,5 +13,5 @@
# (r'^admin/doc/', include('django.contrib.admindocs.urls')),
# Uncomment the next line to enable the admin:
- # (r'^admin/(.*)', admin.site.root),
+ # (r'^admin/', include(admin.site.urls)),
)
View
241 django/contrib/admin/options.py
@@ -5,11 +5,12 @@
from django.contrib.contenttypes.models import ContentType
from django.contrib.admin import widgets
from django.contrib.admin import helpers
-from django.contrib.admin.util import quote, unquote, flatten_fieldsets, get_deleted_objects
+from django.contrib.admin.util import unquote, flatten_fieldsets, get_deleted_objects
from django.core.exceptions import PermissionDenied
from django.db import models, transaction
from django.http import Http404, HttpResponse, HttpResponseRedirect
from django.shortcuts import get_object_or_404, render_to_response
+from django.utils.functional import update_wrapper
from django.utils.html import escape
from django.utils.safestring import mark_safe
from django.utils.text import capfirst, get_text_list
@@ -38,12 +39,12 @@ class BaseModelAdmin(object):
filter_horizontal = ()
radio_fields = {}
prepopulated_fields = {}
-
+
def formfield_for_dbfield(self, db_field, **kwargs):
"""
Hook for specifying the form Field instance for a given database Field
instance.
-
+
If kwargs are given, they're passed to the form Field's constructor.
"""
@@ -63,18 +64,18 @@ def formfield_for_dbfield(self, db_field, **kwargs):
else:
# Otherwise, use the default select widget.
return db_field.formfield(**kwargs)
-
+
# For DateTimeFields, use a special field and widget.
if isinstance(db_field, models.DateTimeField):
kwargs['form_class'] = forms.SplitDateTimeField
kwargs['widget'] = widgets.AdminSplitDateTime()
return db_field.formfield(**kwargs)
-
+
# For DateFields, add a custom CSS class.
if isinstance(db_field, models.DateField):
kwargs['widget'] = widgets.AdminDateWidget
return db_field.formfield(**kwargs)
-
+
# For TimeFields, add a custom CSS class.
if isinstance(db_field, models.TimeField):
kwargs['widget'] = widgets.AdminTimeWidget
@@ -94,22 +95,22 @@ def formfield_for_dbfield(self, db_field, **kwargs):
if isinstance(db_field, models.IntegerField):
kwargs['widget'] = widgets.AdminIntegerFieldWidget
return db_field.formfield(**kwargs)
-
+
# For CommaSeparatedIntegerFields, add a custom CSS class.
if isinstance(db_field, models.CommaSeparatedIntegerField):
kwargs['widget'] = widgets.AdminCommaSeparatedIntegerFieldWidget
return db_field.formfield(**kwargs)
-
+
# For TextInputs, add a custom CSS class.
if isinstance(db_field, models.CharField):
kwargs['widget'] = widgets.AdminTextInputWidget
return db_field.formfield(**kwargs)
-
+
# For FileFields and ImageFields add a link to the current file.
if isinstance(db_field, models.ImageField) or isinstance(db_field, models.FileField):
kwargs['widget'] = widgets.AdminFileWidget
return db_field.formfield(**kwargs)
-
+
# For ForeignKey or ManyToManyFields, use a special widget.
if isinstance(db_field, (models.ForeignKey, models.ManyToManyField)):
if isinstance(db_field, models.ForeignKey) and db_field.name in self.raw_id_fields:
@@ -139,10 +140,10 @@ def formfield_for_dbfield(self, db_field, **kwargs):
if formfield is not None:
formfield.widget = widgets.RelatedFieldWidgetWrapper(formfield.widget, db_field.rel, self.admin_site)
return formfield
-
+
# For any other type of field, just call its formfield() method.
return db_field.formfield(**kwargs)
-
+
def _declared_fieldsets(self):
if self.fieldsets:
return self.fieldsets
@@ -154,7 +155,7 @@ def _declared_fieldsets(self):
class ModelAdmin(BaseModelAdmin):
"Encapsulates all admin options and functionality for a given model."
__metaclass__ = forms.MediaDefiningClass
-
+
list_display = ('__str__',)
list_display_links = ()
list_filter = ()
@@ -166,13 +167,13 @@ class ModelAdmin(BaseModelAdmin):
save_on_top = False
ordering = None
inlines = []
-
+
# Custom templates (designed to be over-ridden in subclasses)
change_form_template = None
change_list_template = None
delete_confirmation_template = None
object_history_template = None
-
+
def __init__(self, model, admin_site):
self.model = model
self.opts = model._meta
@@ -182,59 +183,79 @@ def __init__(self, model, admin_site):
inline_instance = inline_class(self.model, self.admin_site)
self.inline_instances.append(inline_instance)
super(ModelAdmin, self).__init__()
-
- def __call__(self, request, url):
- # Delegate to the appropriate method, based on the URL.
- if url is None:
- return self.changelist_view(request)
- elif url == "add":
- return self.add_view(request)
- elif url.endswith('/history'):
- return self.history_view(request, unquote(url[:-8]))
- elif url.endswith('/delete'):
- return self.delete_view(request, unquote(url[:-7]))
- else:
- return self.change_view(request, unquote(url))
-
+
+ def get_urls(self):
+ from django.conf.urls.defaults import patterns, url
+
+ def wrap(view):
+ def wrapper(*args, **kwargs):
+ return self.admin_site.admin_view(view)(*args, **kwargs)
+ return update_wrapper(wrapper, view)
+
+ info = self.admin_site.name, self.model._meta.app_label, self.model._meta.module_name
+
+ urlpatterns = patterns('',
+ url(r'^$',
+ wrap(self.changelist_view),
+ name='%sadmin_%s_%s_changelist' % info),
+ url(r'^add/$',
+ wrap(self.add_view),
+ name='%sadmin_%s_%s_add' % info),
+ url(r'^(.+)/history/$',
+ wrap(self.history_view),
+ name='%sadmin_%s_%s_history' % info),
+ url(r'^(.+)/delete/$',
+ wrap(self.delete_view),
+ name='%sadmin_%s_%s_delete' % info),
+ url(r'^(.+)/$',
+ wrap(self.change_view),
+ name='%sadmin_%s_%s_change' % info),
+ )
+ return urlpatterns
+
+ def urls(self):
+ return self.get_urls()
+ urls = property(urls)
+
def _media(self):
from django.conf import settings
-
+
js = ['js/core.js', 'js/admin/RelatedObjectLookups.js']
if self.prepopulated_fields:
js.append('js/urlify.js')
if self.opts.get_ordered_objects():
js.extend(['js/getElementsBySelector.js', 'js/dom-drag.js' , 'js/admin/ordering.js'])
-
+
return forms.Media(js=['%s%s' % (settings.ADMIN_MEDIA_PREFIX, url) for url in js])
media = property(_media)
-
+
def has_add_permission(self, request):
"Returns True if the given request has permission to add an object."
opts = self.opts
return request.user.has_perm(opts.app_label + '.' + opts.get_add_permission())
-
+
def has_change_permission(self, request, obj=None):
"""
Returns True if the given request has permission to change the given
Django model instance.
-
+
If `obj` is None, this should return True if the given request has
permission to change *any* object of the given type.
"""
opts = self.opts
return request.user.has_perm(opts.app_label + '.' + opts.get_change_permission())
-
+
def has_delete_permission(self, request, obj=None):
"""
Returns True if the given request has permission to change the given
Django model instance.
-
+
If `obj` is None, this should return True if the given request has
permission to delete *any* object of the given type.
"""
opts = self.opts
return request.user.has_perm(opts.app_label + '.' + opts.get_delete_permission())
-
+
def queryset(self, request):
"""
Returns a QuerySet of all model instances that can be edited by the
@@ -246,14 +267,14 @@ def queryset(self, request):
if ordering:
qs = qs.order_by(*ordering)
return qs
-
+
def get_fieldsets(self, request, obj=None):
"Hook for specifying fieldsets for the add form."
if self.declared_fieldsets:
return self.declared_fieldsets
form = self.get_form(request, obj)
return [(None, {'fields': form.base_fields.keys()})]
-
+
def get_form(self, request, obj=None, **kwargs):
"""
Returns a Form class for use in the admin add view. This is used by
@@ -275,42 +296,42 @@ def get_form(self, request, obj=None, **kwargs):
}
defaults.update(kwargs)
return modelform_factory(self.model, **defaults)
-
+
def get_formsets(self, request, obj=None):
for inline in self.inline_instances:
yield inline.get_formset(request, obj)
-
+
def log_addition(self, request, object):
"""
- Log that an object has been successfully added.
+ Log that an object has been successfully added.
The default implementation creates an admin LogEntry object.
"""
from django.contrib.admin.models import LogEntry, ADDITION
LogEntry.objects.log_action(
- user_id = request.user.pk,
+ user_id = request.user.pk,
content_type_id = ContentType.objects.get_for_model(object).pk,
object_id = object.pk,
- object_repr = force_unicode(object),
+ object_repr = force_unicode(object),
action_flag = ADDITION
)
-
+
def log_change(self, request, object, message):
"""
- Log that an object has been successfully changed.
+ Log that an object has been successfully changed.
The default implementation creates an admin LogEntry object.
"""
from django.contrib.admin.models import LogEntry, CHANGE
LogEntry.objects.log_action(
- user_id = request.user.pk,
- content_type_id = ContentType.objects.get_for_model(object).pk,
- object_id = object.pk,
- object_repr = force_unicode(object),
- action_flag = CHANGE,
+ user_id = request.user.pk,
+ content_type_id = ContentType.objects.get_for_model(object).pk,
+ object_id = object.pk,
+ object_repr = force_unicode(object),
+ action_flag = CHANGE,
change_message = message
)
-
+
def log_deletion(self, request, object, object_repr):
"""
Log that an object has been successfully deleted. Note that since the
@@ -321,13 +342,13 @@ def log_deletion(self, request, object, object_repr):
"""
from django.contrib.admin.models import LogEntry, DELETION
LogEntry.objects.log_action(
- user_id = request.user.id,
- content_type_id = ContentType.objects.get_for_model(self.model).pk,
- object_id = object.pk,
+ user_id = request.user.id,
+ content_type_id = ContentType.objects.get_for_model(self.model).pk,
+ object_id = object.pk,
object_repr = object_repr,
action_flag = DELETION
)
-
+
def construct_change_message(self, request, form, formsets):
"""
@@ -336,7 +357,7 @@ def construct_change_message(self, request, form, formsets):
change_message = []
if form.changed_data:
change_message.append(_('Changed %s.') % get_text_list(form.changed_data, _('and')))
-
+
if formsets:
for formset in formsets:
for added_object in formset.new_objects:
@@ -357,11 +378,11 @@ def construct_change_message(self, request, form, formsets):
def message_user(self, request, message):
"""
- Send a message to the user. The default implementation
+ Send a message to the user. The default implementation
posts a message using the auth Message object.
"""
request.user.message_set.create(message=message)
-
+
def save_form(self, request, form, change):
"""
Given a ModelForm return an unsaved instance. ``change`` is True if
@@ -374,13 +395,13 @@ def save_model(self, request, obj, form, change):
Given a model instance save it to the database.
"""
obj.save()
-
+
def save_formset(self, request, form, formset, change):
"""
Given an inline formset save it to the database.
"""
formset.save()
-
+
def render_change_form(self, request, context, add=False, change=False, form_url='', obj=None):
opts = self.model._meta
app_label = opts.app_label
@@ -432,7 +453,7 @@ def response_add(self, request, obj, post_url_continue='../%s/'):
return HttpResponseRedirect(request.path)
else:
self.message_user(request, msg)
-
+
# Figure out where to redirect. If the user has change permission,
# redirect to the change-list page for this object. Otherwise,
# redirect to the admin index.
@@ -466,15 +487,15 @@ def response_change(self, request, obj):
else:
self.message_user(request, msg)
return HttpResponseRedirect("../")
-
+
def add_view(self, request, form_url='', extra_context=None):
"The 'add' admin view for this model."
model = self.model
opts = model._meta
-
+
if not self.has_add_permission(request):
raise PermissionDenied
-
+
ModelForm = self.get_form(request)
formsets = []
if request.method == 'POST':
@@ -513,17 +534,17 @@ def add_view(self, request, form_url='', extra_context=None):
for FormSet in self.get_formsets(request):
formset = FormSet(instance=self.model())
formsets.append(formset)
-
+
adminForm = helpers.AdminForm(form, list(self.get_fieldsets(request)), self.prepopulated_fields)
media = self.media + adminForm.media
-
+
inline_admin_formsets = []
for inline, formset in zip(self.inline_instances, formsets):
fieldsets = list(inline.get_fieldsets(request))
inline_admin_formset = helpers.InlineAdminFormSet(inline, formset, fieldsets)
inline_admin_formsets.append(inline_admin_formset)
media = media + inline_admin_formset.media
-
+
context = {
'title': _('Add %s') % force_unicode(opts.verbose_name),
'adminform': adminForm,
@@ -538,29 +559,29 @@ def add_view(self, request, form_url='', extra_context=None):
context.update(extra_context or {})
return self.render_change_form(request, context, add=True)
add_view = transaction.commit_on_success(add_view)
-
+
def change_view(self, request, object_id, extra_context=None):
"The 'change' admin view for this model."
model = self.model
opts = model._meta
-
+
try:
- obj = model._default_manager.get(pk=object_id)
+ obj = model._default_manager.get(pk=unquote(object_id))
except model.DoesNotExist:
# Don't raise Http404 just yet, because we haven't checked
# permissions yet. We don't want an unauthenticated user to be able
# to determine whether a given object exists.
obj = None
-
+
if not self.has_change_permission(request, obj):
raise PermissionDenied
-
+
if obj is None:
raise Http404(_('%(name)s object with primary key %(key)r does not exist.') % {'name': force_unicode(opts.verbose_name), 'key': escape(object_id)})
-
+
if request.method == 'POST' and request.POST.has_key("_saveasnew"):
return self.add_view(request, form_url='../../add/')
-
+
ModelForm = self.get_form(request, obj)
formsets = []
if request.method == 'POST':
@@ -575,7 +596,7 @@ def change_view(self, request, object_id, extra_context=None):
formset = FormSet(request.POST, request.FILES,
instance=new_object)
formsets.append(formset)
-
+
if all_valid(formsets) and form_validated:
self.save_model(request, new_object, form, change=True)
form.save_m2m()
@@ -585,16 +606,16 @@ def change_view(self, request, object_id, extra_context=None):
change_message = self.construct_change_message(request, form, formsets)
self.log_change(request, new_object, change_message)
return self.response_change(request, new_object)
-
+
else:
form = ModelForm(instance=obj)
for FormSet in self.get_formsets(request, obj):
formset = FormSet(instance=obj)
formsets.append(formset)
-
+
adminForm = helpers.AdminForm(form, self.get_fieldsets(request, obj), self.prepopulated_fields)
media = self.media + adminForm.media
-
+
inline_admin_formsets = []
for inline, formset in zip(self.inline_instances, formsets):
fieldsets = list(inline.get_fieldsets(request, obj))
@@ -617,7 +638,7 @@ def change_view(self, request, object_id, extra_context=None):
context.update(extra_context or {})
return self.render_change_form(request, context, change=True, obj=obj)
change_view = transaction.commit_on_success(change_view)
-
+
def changelist_view(self, request, extra_context=None):
"The 'change list' admin view for this model."
from django.contrib.admin.views.main import ChangeList, ERROR_FLAG
@@ -637,7 +658,7 @@ def changelist_view(self, request, extra_context=None):
if ERROR_FLAG in request.GET.keys():
return render_to_response('admin/invalid_setup.html', {'title': _('Database error')})
return HttpResponseRedirect(request.path + '?' + ERROR_FLAG + '=1')
-
+
context = {
'title': cl.title,
'is_popup': cl.is_popup,
@@ -652,32 +673,32 @@ def changelist_view(self, request, extra_context=None):
'admin/%s/change_list.html' % app_label,
'admin/change_list.html'
], context, context_instance=template.RequestContext(request))
-
+
def delete_view(self, request, object_id, extra_context=None):
"The 'delete' admin view for this model."
opts = self.model._meta
app_label = opts.app_label
-
+
try:
- obj = self.model._default_manager.get(pk=object_id)
+ obj = self.model._default_manager.get(pk=unquote(object_id))
except self.model.DoesNotExist:
# Don't raise Http404 just yet, because we haven't checked
# permissions yet. We don't want an unauthenticated user to be able
# to determine whether a given object exists.
obj = None
-
+
if not self.has_delete_permission(request, obj):
raise PermissionDenied
-
+
if obj is None:
raise Http404(_('%(name)s object with primary key %(key)r does not exist.') % {'name': force_unicode(opts.verbose_name), 'key': escape(object_id)})
-
+
# Populate deleted_objects, a data structure of all related objects that
# will also be deleted.
- deleted_objects = [mark_safe(u'%s: <a href="../../%s/">%s</a>' % (escape(force_unicode(capfirst(opts.verbose_name))), quote(object_id), escape(obj))), []]
+ deleted_objects = [mark_safe(u'%s: <a href="../../%s/">%s</a>' % (escape(force_unicode(capfirst(opts.verbose_name))), object_id, escape(obj))), []]
perms_needed = set()
get_deleted_objects(deleted_objects, perms_needed, request.user, obj, opts, 1, self.admin_site)
-
+
if request.POST: # The user has already confirmed the deletion.
if perms_needed:
raise PermissionDenied
@@ -690,7 +711,7 @@ def delete_view(self, request, object_id, extra_context=None):
if not self.has_change_permission(request, None):
return HttpResponseRedirect("../../../../")
return HttpResponseRedirect("../../")
-
+
context = {
"title": _("Are you sure?"),
"object_name": force_unicode(opts.verbose_name),
@@ -707,7 +728,7 @@ def delete_view(self, request, object_id, extra_context=None):
"admin/%s/delete_confirmation.html" % app_label,
"admin/delete_confirmation.html"
], context, context_instance=template.RequestContext(request))
-
+
def history_view(self, request, object_id, extra_context=None):
"The 'history' admin view for this model."
from django.contrib.admin.models import LogEntry
@@ -735,10 +756,38 @@ def history_view(self, request, object_id, extra_context=None):
"admin/object_history.html"
], context, context_instance=template.RequestContext(request))
+ #
+ # DEPRECATED methods.
+ #
+ def __call__(self, request, url):
+ """
+ DEPRECATED: this is the old way of URL resolution, replaced by
+ ``get_urls()``. This only called by AdminSite.root(), which is also
+ deprecated.
+
+ Again, remember that the following code only exists for
+ backwards-compatibility. Any new URLs, changes to existing URLs, or
+ whatever need to be done up in get_urls(), above!
+
+ This function still exists for backwards-compatibility; it will be
+ removed in Django 1.3.
+ """
+ # Delegate to the appropriate method, based on the URL.
+ if url is None:
+ return self.changelist_view(request)
+ elif url == "add":
+ return self.add_view(request)
+ elif url.endswith('/history'):
+ return self.history_view(request, unquote(url[:-8]))
+ elif url.endswith('/delete'):
+ return self.delete_view(request, unquote(url[:-7]))
+ else:
+ return self.change_view(request, unquote(url))
+
class InlineModelAdmin(BaseModelAdmin):
"""
Options for inline editing of ``model`` instances.
-
+
Provide ``name`` to specify the attribute name of the ``ForeignKey`` from
``model`` to its parent. This is required if ``model`` has more than one
``ForeignKey`` to its parent.
@@ -751,7 +800,7 @@ class InlineModelAdmin(BaseModelAdmin):
template = None
verbose_name = None
verbose_name_plural = None
-
+
def __init__(self, parent_model, admin_site):
self.admin_site = admin_site
self.parent_model = parent_model
@@ -771,7 +820,7 @@ def _media(self):
js.extend(['js/SelectBox.js' , 'js/SelectFilter2.js'])
return forms.Media(js=['%s%s' % (settings.ADMIN_MEDIA_PREFIX, url) for url in js])
media = property(_media)
-
+
def get_formset(self, request, obj=None, **kwargs):
"""Returns a BaseInlineFormSet class for use in admin add/change views."""
if self.declared_fieldsets:
@@ -794,13 +843,13 @@ def get_formset(self, request, obj=None, **kwargs):
}
defaults.update(kwargs)
return inlineformset_factory(self.parent_model, self.model, **defaults)
-
+
def get_fieldsets(self, request, obj=None):
if self.declared_fieldsets:
return self.declared_fieldsets
form = self.get_formset(request).form
return [(None, {'fields': form.base_fields.keys()})]
-
+
class StackedInline(InlineModelAdmin):
template = 'admin/edit_inline/stacked.html'
View
285 django/contrib/admin/sites.py
@@ -1,4 +1,3 @@
-import base64
import re
from django import http, template
from django.contrib.admin import ModelAdmin
@@ -6,12 +5,12 @@
from django.db.models.base import ModelBase
from django.core.exceptions import ImproperlyConfigured
from django.shortcuts import render_to_response
+from django.utils.functional import update_wrapper
from django.utils.safestring import mark_safe
from django.utils.text import capfirst
from django.utils.translation import ugettext_lazy, ugettext as _
from django.views.decorators.cache import never_cache
from django.conf import settings
-from django.utils.hashcompat import md5_constructor
ERROR_MESSAGE = ugettext_lazy("Please enter a correct username and password. Note that both fields are case-sensitive.")
LOGIN_FORM_KEY = 'this_is_the_login_form'
@@ -29,24 +28,33 @@ class AdminSite(object):
register() method, and the root() method can then be used as a Django view function
that presents a full admin interface for the collection of registered models.
"""
-
+
index_template = None
login_template = None
app_index_template = None
-
- def __init__(self):
+
+ def __init__(self, name=None):
self._registry = {} # model_class class -> admin_class instance
-
+ # TODO Root path is used to calculate urls under the old root() method
+ # in order to maintain backwards compatibility we are leaving that in
+ # so root_path isn't needed, not sure what to do about this.
+ self.root_path = 'admin/'
+ if name is None:
+ name = ''
+ else:
+ name += '_'
+ self.name = name
+
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.
"""
# Don't import the humongous validation code unless required
@@ -54,7 +62,7 @@ def register(self, model_or_iterable, admin_class=None, **options):
from django.contrib.admin.validation import validate
else:
validate = lambda model, adminclass: None
-
+
if not admin_class:
admin_class = ModelAdmin
if isinstance(model_or_iterable, ModelBase):
@@ -62,7 +70,7 @@ def register(self, model_or_iterable, admin_class=None, **options):
for model in model_or_iterable:
if model in self._registry:
raise AlreadyRegistered('The model %s is already registered' % model.__name__)
-
+
# If we got **options then dynamically construct a subclass of
# admin_class with those **options.
if options:
@@ -71,17 +79,17 @@ def register(self, model_or_iterable, admin_class=None, **options):
# which causes issues later on.
options['__module__'] = __name__
admin_class = type("%sAdmin" % model.__name__, (admin_class,), options)
-
+
# Validate (which might be a no-op)
validate(admin_class, model)
-
+
# Instantiate the admin class to save in the registry
self._registry[model] = admin_class(model, self)
-
+
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):
@@ -90,92 +98,100 @@ def unregister(self, model_or_iterable):
if model not in self._registry:
raise NotRegistered('The model %s is not registered' % model.__name__)
del self._registry[model]
-
+
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_authenticated() 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 LogEntry, ContentType and the
auth context processor are installed.
"""
from django.contrib.admin.models import LogEntry
from django.contrib.contenttypes.models import ContentType
-
+
if not LogEntry._meta.installed:
raise ImproperlyConfigured("Put 'django.contrib.admin' in your INSTALLED_APPS setting in order to use the admin application.")
if not ContentType._meta.installed:
raise ImproperlyConfigured("Put 'django.contrib.contenttypes' in your INSTALLED_APPS setting in order to use the admin application.")
if 'django.core.context_processors.auth' not in settings.TEMPLATE_CONTEXT_PROCESSORS:
raise ImproperlyConfigured("Put 'django.core.context_processors.auth' in your TEMPLATE_CONTEXT_PROCESSORS setting in order to use the admin application.")
-
- def root(self, request, url):
+
+ def admin_view(self, view):
"""
- Handles main URL routing for the admin app.
-
- `url` is the remainder of the URL -- e.g. 'comments/comment/'.
+ 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.defaults import patterns, url
+
+ urls = super(MyAdminSite, self).get_urls()
+ urls += patterns('',
+ url(r'^my_view/$', self.protected_view(some_view))
+ )
+ return urls
"""
- if request.method == 'GET' and not request.path.endswith('/'):
- return http.HttpResponseRedirect(request.path + '/')
-
- if settings.DEBUG:
- self.check_dependencies()
-
- # Figure out the admin base URL path and stash it for later use
- self.root_path = re.sub(re.escape(url) + '$', '', request.path)
-
- url = url.rstrip('/') # Trim trailing slash, if it exists.
-
- # The 'logout' view doesn't require that the person is logged in.
- if url == 'logout':
- return self.logout(request)
-
- # Check permission to continue or display login form.
- if not self.has_permission(request):
- return self.login(request)
-
- if url == '':
- return self.index(request)
- elif url == 'password_change':
- return self.password_change(request)
- elif url == 'password_change/done':
- return self.password_change_done(request)
- elif url == 'jsi18n':
- return self.i18n_javascript(request)
- # URLs starting with 'r/' are for the "View on site" links.
- elif url.startswith('r/'):
- from django.contrib.contenttypes.views import shortcut
- return shortcut(request, *url.split('/')[1:])
- else:
- if '/' in url:
- return self.model_page(request, *url.split('/', 2))
- else:
- return self.app_index(request, url)
-
- raise http.Http404('The requested admin page does not exist.')
-
- def model_page(self, request, app_label, model_name, rest_of_url=None):
- """
- Handles the model-specific functionality of the admin site, delegating
- to the appropriate ModelAdmin class.
- """
- from django.db import models
- model = models.get_model(app_label, model_name)
- if model is None:
- raise http.Http404("App %r, model %r, not found." % (app_label, model_name))
- try:
- admin_obj = self._registry[model]
- except KeyError:
- raise http.Http404("This model exists but has not been registered with the admin site.")
- return admin_obj(request, rest_of_url)
- model_page = never_cache(model_page)
-
+ def inner(request, *args, **kwargs):
+ if not self.has_permission(request):
+ return self.login(request)
+ return view(request, *args, **kwargs)
+ return update_wrapper(inner, view)
+
+ def get_urls(self):
+ from django.conf.urls.defaults import patterns, url, include
+
+ def wrap(view):
+ def wrapper(*args, **kwargs):
+ return self.admin_view(view)(*args, **kwargs)
+ return update_wrapper(wrapper, view)
+
+ # Admin-site-wide views.
+ urlpatterns = patterns('',
+ url(r'^$',
+ wrap(self.index),
+ name='%sadmin_index' % self.name),
+ url(r'^logout/$',
+ wrap(self.logout),
+ name='%sadmin_logout'),
+ url(r'^password_change/$',
+ wrap(self.password_change),
+ name='%sadmin_password_change' % self.name),
+ url(r'^password_change/done/$',
+ wrap(self.password_change_done),
+ name='%sadmin_password_change_done' % self.name),
+ url(r'^jsi18n/$',
+ wrap(self.i18n_javascript),
+ name='%sadmin_jsi18n' % self.name),
+ url(r'^r/(?P<content_type_id>\d+)/(?P<object_id>.+)/$',
+ 'django.views.defaults.shortcut'),
+ url(r'^(?P<app_label>\w+)/$',
+ wrap(self.app_index),
+ name='%sadmin_app_list' % self.name),
+ )
+
+ # Add in each model's views.
+ for model, model_admin in self._registry.iteritems():
+ urlpatterns += patterns('',
+ url(r'^%s/%s/' % (model._meta.app_label, model._meta.module_name),
+ include(model_admin.urls))
+ )
+ return urlpatterns
+
+ def urls(self):
+ return self.get_urls()
+ urls = property(urls)
+
def password_change(self, request):
"""
Handles the "change password" task -- both form display and validation.
@@ -183,18 +199,18 @@ def password_change(self, request):
from django.contrib.auth.views import password_change
return password_change(request,
post_change_redirect='%spassword_change/done/' % self.root_path)
-
+
def password_change_done(self, request):
"""
Displays the "success" page after a password change.
"""
from django.contrib.auth.views import password_change_done
return password_change_done(request)
-
+
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.
"""
@@ -203,23 +219,23 @@ def i18n_javascript(self, request):
else:
from django.views.i18n import null_javascript_catalog as javascript_catalog
return javascript_catalog(request, packages='django.conf')
-
+
def logout(self, request):
"""
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
return logout(request)
logout = never_cache(logout)
-
+
def login(self, request):
"""
Displays the login form for the given HttpRequest.
"""
from django.contrib.auth.models import User
-
+
# If this isn't already the login page, display it.
if not request.POST.has_key(LOGIN_FORM_KEY):
if request.POST:
@@ -227,14 +243,14 @@ def login(self, request):
else:
message = ""
return self.display_login_form(request, message)
-
+
# Check that the user accepts cookies.
if not request.session.test_cookie_worked():
message = _("Looks like your browser isn't configured to accept cookies. Please enable cookies, reload this page, and try again.")
return self.display_login_form(request, message)
else:
request.session.delete_test_cookie()
-
+
# Check the password.
username = request.POST.get('username', None)
password = request.POST.get('password', None)
@@ -254,7 +270,7 @@ def login(self, request):
else:
message = _("Usernames cannot contain the '@' character.")
return self.display_login_form(request, message)
-
+
# The user data is correct; log in the user in and continue.
else:
if user.is_active and user.is_staff:
@@ -263,7 +279,7 @@ def login(self, request):
else:
return self.display_login_form(request, ERROR_MESSAGE)
login = never_cache(login)
-
+
def index(self, request, extra_context=None):
"""
Displays the main admin index page, which lists all of the installed
@@ -274,14 +290,14 @@ def index(self, request, extra_context=None):
for model, model_admin in self._registry.items():
app_label = model._meta.app_label
has_module_perms = user.has_module_perms(app_label)
-
+
if has_module_perms:
perms = {
'add': model_admin.has_add_permission(request),
'change': model_admin.has_change_permission(request),
'delete': model_admin.has_delete_permission(request),
}
-
+
# Check whether user has any perm for this module.
# If so, add the module to the model_list.
if True in perms.values():
@@ -299,15 +315,15 @@ def index(self, request, extra_context=None):
'has_module_perms': has_module_perms,
'models': [model_dict],
}
-
+
# Sort the apps alphabetically.
app_list = app_dict.values()
app_list.sort(lambda x, y: cmp(x['name'], y['name']))
-
+
# Sort the models alphabetically within each app.
for app in app_list:
app['models'].sort(lambda x, y: cmp(x['name'], y['name']))
-
+
context = {
'title': _('Site administration'),
'app_list': app_list,
@@ -318,7 +334,7 @@ def index(self, request, extra_context=None):
context_instance=template.RequestContext(request)
)
index = never_cache(index)
-
+
def display_login_form(self, request, error_message='', extra_context=None):
request.session.set_test_cookie()
context = {
@@ -331,7 +347,7 @@ def display_login_form(self, request, error_message='', extra_context=None):
return render_to_response(self.login_template or 'admin/login.html', context,
context_instance=template.RequestContext(request)
)
-
+
def app_index(self, request, app_label, extra_context=None):
user = request.user
has_module_perms = user.has_module_perms(app_label)
@@ -377,6 +393,81 @@ def app_index(self, request, app_label, extra_context=None):
return render_to_response(self.app_index_template or 'admin/app_index.html', context,
context_instance=template.RequestContext(request)
)
+
+ def root(self, request, url):
+ """
+ DEPRECATED. This function is the old way of handling URL resolution, and
+ is deprecated in favor of real URL resolution -- see ``get_urls()``.
+
+ This function still exists for backwards-compatibility; it will be
+ removed in Django 1.3.
+ """
+ import warnings
+ warnings.warn(
+ "AdminSite.root() is deprecated; use include(admin.site.urls) instead.",
+ PendingDeprecationWarning
+ )
+
+ #
+ # Again, remember that the following only exists for
+ # backwards-compatibility. Any new URLs, changes to existing URLs, or
+ # whatever need to be done up in get_urls(), above!
+ #
+
+ if request.method == 'GET' and not request.path.endswith('/'):
+ return http.HttpResponseRedirect(request.path + '/')
+
+ if settings.DEBUG:
+ self.check_dependencies()
+
+ # Figure out the admin base URL path and stash it for later use
+ self.root_path = re.sub(re.escape(url) + '$', '', request.path)
+
+ url = url.rstrip('/') # Trim trailing slash, if it exists.
+
+ # The 'logout' view doesn't require that the person is logged in.
+ if url == 'logout':
+ return self.logout(request)
+
+ # Check permission to continue or display login form.
+ if not self.has_permission(request):
+ return self.login(request)
+
+ if url == '':
+ return self.index(request)
+ elif url == 'password_change':
+ return self.password_change(request)
+ elif url == 'password_change/done':
+ return self.password_change_done(request)
+ elif url == 'jsi18n':
+ return self.i18n_javascript(request)
+ # URLs starting with 'r/' are for the "View on site" links.
+ elif url.startswith('r/'):
+ from django.contrib.contenttypes.views import shortcut
+ return shortcut(request, *url.split('/')[1:])
+ else:
+ if '/' in url:
+ return self.model_page(request, *url.split('/', 2))
+ else:
+ return self.app_index(request, url)
+
+ raise http.Http404('The requested admin page does not exist.')
+
+ def model_page(self, request, app_label, model_name, rest_of_url=None):
+ """
+ DEPRECATED. This is the old way of handling a model view on the admin
+ site; the new views should use get_urls(), above.
+ """
+ from django.db import models
+ model = models.get_model(app_label, model_name)
+ if model is None:
+ raise http.Http404("App %r, model %r, not found." % (app_label, model_name))
+ try:
+ admin_obj = self._registry[model]
+ except KeyError:
+ raise http.Http404("This model exists but has not been registered with the admin site.")
+ return admin_obj(request, rest_of_url)
+ model_page = never_cache(model_page)
# 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.
View
1  django/contrib/admin/util.py
@@ -6,7 +6,6 @@
from django.utils.encoding import force_unicode
from django.utils.translation import ugettext as _
-
def quote(s):
"""
Ensure that primary key values do not confuse the admin URLs by escaping
View
6 django/contrib/auth/admin.py
@@ -40,6 +40,12 @@ def __call__(self, request, url):
if url.endswith('password'):
return self.user_change_password(request, url.split('/')[0])
return super(UserAdmin, self).__call__(request, url)
+
+ def get_urls(self):
+ from django.conf.urls.defaults import patterns
+ return patterns('',
+ (r'^(\d+)/password/$', self.admin_site.admin_view(self.user_change_password))
+ ) + super(UserAdmin, self).get_urls()
def add_view(self, request):
# It's an error for a user to have add permission but NOT change
View
2  docs/intro/tutorial02.txt
@@ -57,7 +57,7 @@ activate the admin site for your installation, do these three things:
# (r'^admin/doc/', include('django.contrib.admindocs.urls')),
# Uncomment the next line to enable the admin:
- **(r'^admin/(.*)', admin.site.root),**
+ **(r'^admin/', include(admin.site.urls)),**
)
(The bold lines are the ones that needed to be uncommented.)
View
76 docs/ref/contrib/admin.txt
@@ -632,6 +632,49 @@ model instance::
instance.save()
formset.save_m2m()
+``get_urls(self)``
+~~~~~~~~~~~~~~~~~~~
+
+The ``get_urls`` method on a ``ModelAdmin`` returns the URLs to be used for
+that ModelAdmin in the same way as a URLconf. Therefore you can extend them as
+documented in :ref:`topics-http-urls`::
+
+ class MyModelAdmin(admin.ModelAdmin):
+ def get_urls(self):
+ urls = super(MyModelAdmin, self).get_urls()
+ my_urls = patterns('',
+ (r'^my_view/$', self.my_view)
+ )
+ return my_urls + urls
+
+.. note::
+
+ Notice that the custom patterns are included *before* the regular admin
+ URLs: the admin URL patterns are very permissive and will match nearly
+ anything, so you'll usually want to prepend your custom URLs to the built-in
+ ones.
+
+Note, however, that the ``self.my_view`` function registered above will *not*
+have any permission check done; it'll be accessible to the general public. Since
+this is usually not what you want, Django provides a convience wrapper to check
+permissions. This wrapper is :meth:`AdminSite.admin_view` (i.e.
+``self.admin_site.admin_view`` inside a ``ModelAdmin`` instance); use it like
+so::
+
+ class MyModelAdmin(admin.ModelAdmin):
+ def get_urls(self):
+ urls = super(MyModelAdmin, self).get_urls()
+ my_urls = patterns('',
+ (r'^my_view/$', self.admin_site.admin_view(self.my_view))
+ )
+ return my_urls + urls
+
+Notice the wrapped view in the fifth line above::
+
+ (r'^my_view/$', self.admin_site.admin_view(self.my_view))
+
+This wrapping will protect ``self.my_view`` from unauthorized access.
+
``ModelAdmin`` media definitions
--------------------------------
@@ -1027,7 +1070,7 @@ In this example, we register the default ``AdminSite`` instance
admin.autodiscover()
urlpatterns = patterns('',
- ('^admin/(.*)', admin.site.root),
+ ('^admin/', include(admin.site.urls)),
)
Above we used ``admin.autodiscover()`` to automatically load the
@@ -1041,15 +1084,13 @@ In this example, we register the ``AdminSite`` instance
from myproject.admin import admin_site
urlpatterns = patterns('',
- ('^myadmin/(.*)', admin_site.root),
+ ('^myadmin/', include(admin_site.urls)),
)
There is really no need to use autodiscover when using your own ``AdminSite``
instance since you will likely be importing all the per-app admin.py modules
in your ``myproject.admin`` module.
-Note that the regular expression in the URLpattern *must* group everything in
-the URL that comes after the URL root -- hence the ``(.*)`` in these examples.
Multiple admin sites in the same URLconf
----------------------------------------
@@ -1068,6 +1109,29 @@ respectively::
from myproject.admin import basic_site, advanced_site
urlpatterns = patterns('',
- ('^basic-admin/(.*)', basic_site.root),
- ('^advanced-admin/(.*)', advanced_site.root),
+ ('^basic-admin/', include(basic_site.urls)),
+ ('^advanced-admin/', include(advanced_site.urls)),
)
+
+Adding views to admin sites
+---------------------------
+
+It possible to add additional views to the admin site in the same way one can
+add them to ``ModelAdmins``. This by using the ``get_urls()`` method on an
+AdminSite in the same way as `described above`__
+
+__ `get_urls(self)`_
+
+Protecting Custom ``AdminSite`` and ``ModelAdmin``
+--------------------------------------------------
+
+By default all the views in the Django admin are protected so that only staff
+members can access them. If you add your own views to either a ``ModelAdmin``
+or ``AdminSite`` you should ensure that where necessary they are protected in
+the same manner. To do this use the ``admin_perm_test`` decorator provided in
+``django.contrib.admin.utils.admin_perm_test``. It can be used in the same way
+as the ``login_requied`` decorator.
+
+.. note::
+ The ``admin_perm_test`` decorator can only be used on methods which are on
+ ``ModelAdmins`` or ``AdminSites``, you cannot use it on arbitrary functions.
View
30 tests/regressiontests/admin_views/customadmin.py
@@ -0,0 +1,30 @@
+"""
+A second, custom AdminSite -- see tests.CustomAdminSiteTests.
+"""
+from django.conf.urls.defaults import patterns
+from django.contrib import admin
+from django.http import HttpResponse
+
+import models
+
+class Admin2(admin.AdminSite):
+ login_template = 'custom_admin/login.html'
+ index_template = 'custom_admin/index.html'
+
+ # A custom index view.
+ def index(self, request, extra_context=None):
+ return super(Admin2, self).index(request, {'foo': '*bar*'})
+
+ def get_urls(self):
+ return patterns('',
+ (r'^my_view/$', self.admin_view(self.my_view)),
+ ) + super(Admin2, self).get_urls()
+
+ def my_view(self, request):
+ return HttpResponse("Django is a magical pony!")
+
+site = Admin2(name="admin2")
+
+site.register(models.Article, models.ArticleAdmin)
+site.register(models.Section, inlines=[models.ArticleInline])
+site.register(models.Thing, models.ThingAdmin)
View
94 tests/regressiontests/admin_views/tests.py
@@ -14,6 +14,11 @@
class AdminViewBasicTest(TestCase):
fixtures = ['admin-views-users.xml', 'admin-views-colors.xml']
+ # Store the bit of the URL where the admin is registered as a class
+ # variable. That way we can test a second AdminSite just by subclassing
+ # this test case and changing urlbit.
+ urlbit = 'admin'
+
def setUp(self):
self.client.login(username='super', password='secret')
@@ -24,20 +29,20 @@ def testTrailingSlashRequired(self):
"""
If you leave off the trailing slash, app should redirect and add it.
"""
- request = self.client.get('/test_admin/admin/admin_views/article/add')
+ request = self.client.get('/test_admin/%s/admin_views/article/add' % self.urlbit)
self.assertRedirects(request,
- '/test_admin/admin/admin_views/article/add/'
+ '/test_admin/%s/admin_views/article/add/' % self.urlbit, status_code=301
)
def testBasicAddGet(self):
"""
A smoke test to ensure GET on the add_view works.
"""
- response = self.client.get('/test_admin/admin/admin_views/section/add/')
+ response = self.client.get('/test_admin/%s/admin_views/section/add/' % self.urlbit)
self.failUnlessEqual(response.status_code, 200)
def testAddWithGETArgs(self):
- response = self.client.get('/test_admin/admin/admin_views/section/add/', {'name': 'My Section'})
+ response = self.client.get('/test_admin/%s/admin_views/section/add/' % self.urlbit, {'name': 'My Section'})
self.failUnlessEqual(response.status_code, 200)
self.failUnless(
'value="My Section"' in response.content,
@@ -48,7 +53,7 @@ def testBasicEditGet(self):
"""
A smoke test to ensureGET on the change_view works.
"""
- response = self.client.get('/test_admin/admin/admin_views/section/1/')
+ response = self.client.get('/test_admin/%s/admin_views/section/1/' % self.urlbit)
self.failUnlessEqual(response.status_code, 200)
def testBasicAddPost(self):
@@ -61,7 +66,7 @@ def testBasicAddPost(self):
"article_set-TOTAL_FORMS": u"3",
"article_set-INITIAL_FORMS": u"0",
}
- response = self.client.post('/test_admin/admin/admin_views/section/add/', post_data)
+ response = self.client.post('/test_admin/%s/admin_views/section/add/' % self.urlbit, post_data)
self.failUnlessEqual(response.status_code, 302) # redirect somewhere
def testBasicEditPost(self):
@@ -106,7 +111,7 @@ def testBasicEditPost(self):
"article_set-5-date_0": u"",
"article_set-5-date_1": u"",
}
- response = self.client.post('/test_admin/admin/admin_views/section/1/', post_data)
+ response = self.client.post('/test_admin/%s/admin_views/section/1/' % self.urlbit, post_data)
self.failUnlessEqual(response.status_code, 302) # redirect somewhere
def testChangeListSortingCallable(self):
@@ -114,7 +119,7 @@ def testChangeListSortingCallable(self):
Ensure we can sort on a list_display field that is a callable
(column 2 is callable_year in ArticleAdmin)
"""
- response = self.client.get('/test_admin/admin/admin_views/article/', {'ot': 'asc', 'o': 2})
+ response = self.client.get('/test_admin/%s/admin_views/article/' % self.urlbit, {'ot': 'asc', 'o': 2})
self.failUnlessEqual(response.status_code, 200)
self.failUnless(
response.content.index('Oldest content') < response.content.index('Middle content') and
@@ -127,7 +132,7 @@ def testChangeListSortingModel(self):
Ensure we can sort on a list_display field that is a Model method
(colunn 3 is 'model_year' in ArticleAdmin)
"""
- response = self.client.get('/test_admin/admin/admin_views/article/', {'ot': 'dsc', 'o': 3})
+ response = self.client.get('/test_admin/%s/admin_views/article/' % self.urlbit, {'ot': 'dsc', 'o': 3})
self.failUnlessEqual(response.status_code, 200)
self.failUnless(
response.content.index('Newest content') < response.content.index('Middle content') and
@@ -140,7 +145,7 @@ def testChangeListSortingModelAdmin(self):
Ensure we can sort on a list_display field that is a ModelAdmin method
(colunn 4 is 'modeladmin_year' in ArticleAdmin)
"""
- response = self.client.get('/test_admin/admin/admin_views/article/', {'ot': 'asc', 'o': 4})
+ response = self.client.get('/test_admin/%s/admin_views/article/' % self.urlbit, {'ot': 'asc', 'o': 4})
self.failUnlessEqual(response.status_code, 200)
self.failUnless(
response.content.index('Oldest content') < response.content.index('Middle content') and
@@ -150,7 +155,7 @@ def testChangeListSortingModelAdmin(self):
def testLimitedFilter(self):
"""Ensure admin changelist filters do not contain objects excluded via limit_choices_to."""
- response = self.client.get('/test_admin/admin/admin_views/thing/')
+ response = self.client.get('/test_admin/%s/admin_views/thing/' % self.urlbit)
self.failUnlessEqual(response.status_code, 200)
self.failUnless(
'<div id="changelist-filter">' in response.content,
@@ -163,11 +168,30 @@ def testLimitedFilter(self):
def testIncorrectLookupParameters(self):
"""Ensure incorrect lookup parameters are handled gracefully."""
- response = self.client.get('/test_admin/admin/admin_views/thing/', {'notarealfield': '5'})
- self.assertRedirects(response, '/test_admin/admin/admin_views/thing/?e=1')
- response = self.client.get('/test_admin/admin/admin_views/thing/', {'color__id__exact': 'StringNotInteger!'})
- self.assertRedirects(response, '/test_admin/admin/admin_views/thing/?e=1')
-
+ response = self.client.get('/test_admin/%s/admin_views/thing/' % self.urlbit, {'notarealfield': '5'})
+ self.assertRedirects(response, '/test_admin/%s/admin_views/thing/?e=1' % self.urlbit)
+ response = self.client.get('/test_admin/%s/admin_views/thing/' % self.urlbit, {'color__id__exact': 'StringNotInteger!'})
+ self.assertRedirects(response, '/test_admin/%s/admin_views/thing/?e=1' % self.urlbit)
+
+class CustomModelAdminTest(AdminViewBasicTest):
+ urlbit = "admin2"
+
+ def testCustomAdminSiteLoginTemplate(self):
+ self.client.logout()
+ request = self.client.get('/test_admin/admin2/')
+ self.assertTemplateUsed(request, 'custom_admin/login.html')
+ self.assert_('Hello from a custom login template' in request.content)
+
+ def testCustomAdminSiteIndexViewAndTemplate(self):
+ request = self.client.get('/test_admin/admin2/')
+ self.assertTemplateUsed(request, 'custom_admin/index.html')
+ self.assert_('Hello from a custom index template *bar*' in request.content)
+
+ def testCustomAdminSiteView(self):
+ self.client.login(username='super', password='secret')
+ response = self.client.get('/test_admin/%s/my_view/' % self.urlbit)
+ self.assert_(response.content == "Django is a magical pony!", response.content)
+
def get_perm(Model, perm):
"""Return the permission object, for the Model"""
ct = ContentType.objects.get_for_model(Model)
@@ -432,44 +456,6 @@ def testCustomModelAdminTemplates(self):
self.client.get('/test_admin/admin/logout/')
- def testCustomAdminSiteTemplates(self):
- from django.contrib import admin
- self.assertEqual(admin.site.index_template, None)
- self.assertEqual(admin.site.login_template, None)
-
- self.client.get('/test_admin/admin/logout/')
- request = self.client.get('/test_admin/admin/')
- self.assertTemplateUsed(request, 'admin/login.html')
- self.client.post('/test_admin/admin/', self.changeuser_login)
- request = self.client.get('/test_admin/admin/')
- self.assertTemplateUsed(request, 'admin/index.html')
-
- self.client.get('/test_admin/admin/logout/')
- admin.site.login_template = 'custom_admin/login.html'
- admin.site.index_template = 'custom_admin/index.html'
- request = self.client.get('/test_admin/admin/')
- self.assertTemplateUsed(request, 'custom_admin/login.html')
- self.assert_('Hello from a custom login template' in request.content)
- self.client.post('/test_admin/admin/', self.changeuser_login)
- request = self.client.get('/test_admin/admin/')
- self.assertTemplateUsed(request, 'custom_admin/index.html')
- self.assert_('Hello from a custom index template' in request.content)
-
- # Finally, using monkey patching check we can inject custom_context arguments in to index
- original_index = admin.site.index
- def index(*args, **kwargs):
- kwargs['extra_context'] = {'foo': '*bar*'}
- return original_index(*args, **kwargs)
- admin.site.index = index
- request = self.client.get('/test_admin/admin/')
- self.assertTemplateUsed(request, 'custom_admin/index.html')
- self.assert_('Hello from a custom index template *bar*' in request.content)
-
- self.client.get('/test_admin/admin/logout/')
- del admin.site.index # Resets to using the original
- admin.site.login_template = None
- admin.site.index_template = None
-
def testDeleteView(self):
"""Delete view should restrict access and actually delete items."""
View
4 tests/regressiontests/admin_views/urls.py
@@ -1,9 +1,11 @@
from django.conf.urls.defaults import *
from django.contrib import admin
import views
+import customadmin
urlpatterns = patterns('',
(r'^admin/doc/', include('django.contrib.admindocs.urls')),
(r'^admin/secure-view/$', views.secure_view),
- (r'^admin/(.*)', admin.site.root),
+ (r'^admin/', include(admin.site.urls)),
+ (r'^admin2/', include(customadmin.site.urls)),
)

0 comments on commit 1f84630

Please sign in to comment.
Something went wrong with that request. Please try again.