mirror of https://github.com/django/django.git
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
This commit is contained in:
parent
6c4e5f0f0e
commit
1f84630c87
|
@ -13,5 +13,5 @@ urlpatterns = patterns('',
|
|||
# (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)),
|
||||
)
|
||||
|
|
|
@ -5,11 +5,12 @@ from django.forms.models import BaseInlineFormSet
|
|||
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 @@ class BaseModelAdmin(object):
|
|||
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 @@ class BaseModelAdmin(object):
|
|||
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 @@ class BaseModelAdmin(object):
|
|||
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 @@ class BaseModelAdmin(object):
|
|||
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 @@ class ModelAdmin(BaseModelAdmin):
|
|||
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 @@ class ModelAdmin(BaseModelAdmin):
|
|||
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 @@ class ModelAdmin(BaseModelAdmin):
|
|||
}
|
||||
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 @@ class ModelAdmin(BaseModelAdmin):
|
|||
"""
|
||||
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 @@ class ModelAdmin(BaseModelAdmin):
|
|||
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 @@ class ModelAdmin(BaseModelAdmin):
|
|||
|
||||
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 @@ class ModelAdmin(BaseModelAdmin):
|
|||
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 @@ class ModelAdmin(BaseModelAdmin):
|
|||
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 @@ class ModelAdmin(BaseModelAdmin):
|
|||
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 @@ class ModelAdmin(BaseModelAdmin):
|
|||
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 @@ class ModelAdmin(BaseModelAdmin):
|
|||
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 @@ class ModelAdmin(BaseModelAdmin):
|
|||
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 @@ class ModelAdmin(BaseModelAdmin):
|
|||
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 @@ class ModelAdmin(BaseModelAdmin):
|
|||
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 @@ class ModelAdmin(BaseModelAdmin):
|
|||
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 @@ class ModelAdmin(BaseModelAdmin):
|
|||
'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 @@ class ModelAdmin(BaseModelAdmin):
|
|||
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 @@ class ModelAdmin(BaseModelAdmin):
|
|||
"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 @@ class ModelAdmin(BaseModelAdmin):
|
|||
"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 @@ class InlineModelAdmin(BaseModelAdmin):
|
|||
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 @@ class InlineModelAdmin(BaseModelAdmin):
|
|||
}
|
||||
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'
|
||||
|
||||
|
|
|
@ -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.contrib.auth import authenticate, login
|
|||
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 @@ class AdminSite(object):
|
|||
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 @@ class AdminSite(object):
|
|||
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 @@ class AdminSite(object):
|
|||
# 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 @@ class AdminSite(object):
|
|||
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 @@ class AdminSite(object):
|
|||
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 @@ class AdminSite(object):
|
|||
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 @@ class AdminSite(object):
|
|||
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 @@ class AdminSite(object):
|
|||
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 @@ class AdminSite(object):
|
|||
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 @@ class AdminSite(object):
|
|||
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 @@ class AdminSite(object):
|
|||
'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 @@ class AdminSite(object):
|
|||
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 @@ class AdminSite(object):
|
|||
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 @@ class AdminSite(object):
|
|||
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.
|
||||
|
|
|
@ -6,7 +6,6 @@ from django.utils.text import capfirst
|
|||
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
|
||||
|
|
|
@ -40,6 +40,12 @@ class UserAdmin(admin.ModelAdmin):
|
|||
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
|
||||
|
|
|
@ -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.)
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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)
|
|
@ -14,6 +14,11 @@ from models import Article, CustomArticle, Section, ModelWithStringPrimaryKey
|
|||
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 @@ class AdminViewBasicTest(TestCase):
|
|||
"""
|
||||
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 @@ class AdminViewBasicTest(TestCase):
|
|||
"""
|
||||
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 @@ class AdminViewBasicTest(TestCase):
|
|||
"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 @@ class AdminViewBasicTest(TestCase):
|
|||
"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 @@ class AdminViewBasicTest(TestCase):
|
|||
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 @@ class AdminViewBasicTest(TestCase):
|
|||
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 @@ class AdminViewBasicTest(TestCase):
|
|||
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 @@ class AdminViewBasicTest(TestCase):
|
|||
|
||||
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 @@ class AdminViewBasicTest(TestCase):
|
|||
|
||||
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 @@ class AdminViewPermissionsTest(TestCase):
|
|||
|
||||
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."""
|
||||
|
||||
|
|
|
@ -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)),
|
||||
)
|
||||
|
|
Loading…
Reference in New Issue