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')),
|
# (r'^admin/doc/', include('django.contrib.admindocs.urls')),
|
||||||
|
|
||||||
# Uncomment the next line to enable the admin:
|
# 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.contenttypes.models import ContentType
|
||||||
from django.contrib.admin import widgets
|
from django.contrib.admin import widgets
|
||||||
from django.contrib.admin import helpers
|
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.core.exceptions import PermissionDenied
|
||||||
from django.db import models, transaction
|
from django.db import models, transaction
|
||||||
from django.http import Http404, HttpResponse, HttpResponseRedirect
|
from django.http import Http404, HttpResponse, HttpResponseRedirect
|
||||||
from django.shortcuts import get_object_or_404, render_to_response
|
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.html import escape
|
||||||
from django.utils.safestring import mark_safe
|
from django.utils.safestring import mark_safe
|
||||||
from django.utils.text import capfirst, get_text_list
|
from django.utils.text import capfirst, get_text_list
|
||||||
|
@ -38,12 +39,12 @@ class BaseModelAdmin(object):
|
||||||
filter_horizontal = ()
|
filter_horizontal = ()
|
||||||
radio_fields = {}
|
radio_fields = {}
|
||||||
prepopulated_fields = {}
|
prepopulated_fields = {}
|
||||||
|
|
||||||
def formfield_for_dbfield(self, db_field, **kwargs):
|
def formfield_for_dbfield(self, db_field, **kwargs):
|
||||||
"""
|
"""
|
||||||
Hook for specifying the form Field instance for a given database Field
|
Hook for specifying the form Field instance for a given database Field
|
||||||
instance.
|
instance.
|
||||||
|
|
||||||
If kwargs are given, they're passed to the form Field's constructor.
|
If kwargs are given, they're passed to the form Field's constructor.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
@ -63,18 +64,18 @@ class BaseModelAdmin(object):
|
||||||
else:
|
else:
|
||||||
# Otherwise, use the default select widget.
|
# Otherwise, use the default select widget.
|
||||||
return db_field.formfield(**kwargs)
|
return db_field.formfield(**kwargs)
|
||||||
|
|
||||||
# For DateTimeFields, use a special field and widget.
|
# For DateTimeFields, use a special field and widget.
|
||||||
if isinstance(db_field, models.DateTimeField):
|
if isinstance(db_field, models.DateTimeField):
|
||||||
kwargs['form_class'] = forms.SplitDateTimeField
|
kwargs['form_class'] = forms.SplitDateTimeField
|
||||||
kwargs['widget'] = widgets.AdminSplitDateTime()
|
kwargs['widget'] = widgets.AdminSplitDateTime()
|
||||||
return db_field.formfield(**kwargs)
|
return db_field.formfield(**kwargs)
|
||||||
|
|
||||||
# For DateFields, add a custom CSS class.
|
# For DateFields, add a custom CSS class.
|
||||||
if isinstance(db_field, models.DateField):
|
if isinstance(db_field, models.DateField):
|
||||||
kwargs['widget'] = widgets.AdminDateWidget
|
kwargs['widget'] = widgets.AdminDateWidget
|
||||||
return db_field.formfield(**kwargs)
|
return db_field.formfield(**kwargs)
|
||||||
|
|
||||||
# For TimeFields, add a custom CSS class.
|
# For TimeFields, add a custom CSS class.
|
||||||
if isinstance(db_field, models.TimeField):
|
if isinstance(db_field, models.TimeField):
|
||||||
kwargs['widget'] = widgets.AdminTimeWidget
|
kwargs['widget'] = widgets.AdminTimeWidget
|
||||||
|
@ -94,22 +95,22 @@ class BaseModelAdmin(object):
|
||||||
if isinstance(db_field, models.IntegerField):
|
if isinstance(db_field, models.IntegerField):
|
||||||
kwargs['widget'] = widgets.AdminIntegerFieldWidget
|
kwargs['widget'] = widgets.AdminIntegerFieldWidget
|
||||||
return db_field.formfield(**kwargs)
|
return db_field.formfield(**kwargs)
|
||||||
|
|
||||||
# For CommaSeparatedIntegerFields, add a custom CSS class.
|
# For CommaSeparatedIntegerFields, add a custom CSS class.
|
||||||
if isinstance(db_field, models.CommaSeparatedIntegerField):
|
if isinstance(db_field, models.CommaSeparatedIntegerField):
|
||||||
kwargs['widget'] = widgets.AdminCommaSeparatedIntegerFieldWidget
|
kwargs['widget'] = widgets.AdminCommaSeparatedIntegerFieldWidget
|
||||||
return db_field.formfield(**kwargs)
|
return db_field.formfield(**kwargs)
|
||||||
|
|
||||||
# For TextInputs, add a custom CSS class.
|
# For TextInputs, add a custom CSS class.
|
||||||
if isinstance(db_field, models.CharField):
|
if isinstance(db_field, models.CharField):
|
||||||
kwargs['widget'] = widgets.AdminTextInputWidget
|
kwargs['widget'] = widgets.AdminTextInputWidget
|
||||||
return db_field.formfield(**kwargs)
|
return db_field.formfield(**kwargs)
|
||||||
|
|
||||||
# For FileFields and ImageFields add a link to the current file.
|
# For FileFields and ImageFields add a link to the current file.
|
||||||
if isinstance(db_field, models.ImageField) or isinstance(db_field, models.FileField):
|
if isinstance(db_field, models.ImageField) or isinstance(db_field, models.FileField):
|
||||||
kwargs['widget'] = widgets.AdminFileWidget
|
kwargs['widget'] = widgets.AdminFileWidget
|
||||||
return db_field.formfield(**kwargs)
|
return db_field.formfield(**kwargs)
|
||||||
|
|
||||||
# For ForeignKey or ManyToManyFields, use a special widget.
|
# For ForeignKey or ManyToManyFields, use a special widget.
|
||||||
if isinstance(db_field, (models.ForeignKey, models.ManyToManyField)):
|
if isinstance(db_field, (models.ForeignKey, models.ManyToManyField)):
|
||||||
if isinstance(db_field, models.ForeignKey) and db_field.name in self.raw_id_fields:
|
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:
|
if formfield is not None:
|
||||||
formfield.widget = widgets.RelatedFieldWidgetWrapper(formfield.widget, db_field.rel, self.admin_site)
|
formfield.widget = widgets.RelatedFieldWidgetWrapper(formfield.widget, db_field.rel, self.admin_site)
|
||||||
return formfield
|
return formfield
|
||||||
|
|
||||||
# For any other type of field, just call its formfield() method.
|
# For any other type of field, just call its formfield() method.
|
||||||
return db_field.formfield(**kwargs)
|
return db_field.formfield(**kwargs)
|
||||||
|
|
||||||
def _declared_fieldsets(self):
|
def _declared_fieldsets(self):
|
||||||
if self.fieldsets:
|
if self.fieldsets:
|
||||||
return self.fieldsets
|
return self.fieldsets
|
||||||
|
@ -154,7 +155,7 @@ class BaseModelAdmin(object):
|
||||||
class ModelAdmin(BaseModelAdmin):
|
class ModelAdmin(BaseModelAdmin):
|
||||||
"Encapsulates all admin options and functionality for a given model."
|
"Encapsulates all admin options and functionality for a given model."
|
||||||
__metaclass__ = forms.MediaDefiningClass
|
__metaclass__ = forms.MediaDefiningClass
|
||||||
|
|
||||||
list_display = ('__str__',)
|
list_display = ('__str__',)
|
||||||
list_display_links = ()
|
list_display_links = ()
|
||||||
list_filter = ()
|
list_filter = ()
|
||||||
|
@ -166,13 +167,13 @@ class ModelAdmin(BaseModelAdmin):
|
||||||
save_on_top = False
|
save_on_top = False
|
||||||
ordering = None
|
ordering = None
|
||||||
inlines = []
|
inlines = []
|
||||||
|
|
||||||
# Custom templates (designed to be over-ridden in subclasses)
|
# Custom templates (designed to be over-ridden in subclasses)
|
||||||
change_form_template = None
|
change_form_template = None
|
||||||
change_list_template = None
|
change_list_template = None
|
||||||
delete_confirmation_template = None
|
delete_confirmation_template = None
|
||||||
object_history_template = None
|
object_history_template = None
|
||||||
|
|
||||||
def __init__(self, model, admin_site):
|
def __init__(self, model, admin_site):
|
||||||
self.model = model
|
self.model = model
|
||||||
self.opts = model._meta
|
self.opts = model._meta
|
||||||
|
@ -182,59 +183,79 @@ class ModelAdmin(BaseModelAdmin):
|
||||||
inline_instance = inline_class(self.model, self.admin_site)
|
inline_instance = inline_class(self.model, self.admin_site)
|
||||||
self.inline_instances.append(inline_instance)
|
self.inline_instances.append(inline_instance)
|
||||||
super(ModelAdmin, self).__init__()
|
super(ModelAdmin, self).__init__()
|
||||||
|
|
||||||
def __call__(self, request, url):
|
def get_urls(self):
|
||||||
# Delegate to the appropriate method, based on the URL.
|
from django.conf.urls.defaults import patterns, url
|
||||||
if url is None:
|
|
||||||
return self.changelist_view(request)
|
def wrap(view):
|
||||||
elif url == "add":
|
def wrapper(*args, **kwargs):
|
||||||
return self.add_view(request)
|
return self.admin_site.admin_view(view)(*args, **kwargs)
|
||||||
elif url.endswith('/history'):
|
return update_wrapper(wrapper, view)
|
||||||
return self.history_view(request, unquote(url[:-8]))
|
|
||||||
elif url.endswith('/delete'):
|
info = self.admin_site.name, self.model._meta.app_label, self.model._meta.module_name
|
||||||
return self.delete_view(request, unquote(url[:-7]))
|
|
||||||
else:
|
urlpatterns = patterns('',
|
||||||
return self.change_view(request, unquote(url))
|
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):
|
def _media(self):
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
|
||||||
js = ['js/core.js', 'js/admin/RelatedObjectLookups.js']
|
js = ['js/core.js', 'js/admin/RelatedObjectLookups.js']
|
||||||
if self.prepopulated_fields:
|
if self.prepopulated_fields:
|
||||||
js.append('js/urlify.js')
|
js.append('js/urlify.js')
|
||||||
if self.opts.get_ordered_objects():
|
if self.opts.get_ordered_objects():
|
||||||
js.extend(['js/getElementsBySelector.js', 'js/dom-drag.js' , 'js/admin/ordering.js'])
|
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])
|
return forms.Media(js=['%s%s' % (settings.ADMIN_MEDIA_PREFIX, url) for url in js])
|
||||||
media = property(_media)
|
media = property(_media)
|
||||||
|
|
||||||
def has_add_permission(self, request):
|
def has_add_permission(self, request):
|
||||||
"Returns True if the given request has permission to add an object."
|
"Returns True if the given request has permission to add an object."
|
||||||
opts = self.opts
|
opts = self.opts
|
||||||
return request.user.has_perm(opts.app_label + '.' + opts.get_add_permission())
|
return request.user.has_perm(opts.app_label + '.' + opts.get_add_permission())
|
||||||
|
|
||||||
def has_change_permission(self, request, obj=None):
|
def has_change_permission(self, request, obj=None):
|
||||||
"""
|
"""
|
||||||
Returns True if the given request has permission to change the given
|
Returns True if the given request has permission to change the given
|
||||||
Django model instance.
|
Django model instance.
|
||||||
|
|
||||||
If `obj` is None, this should return True if the given request has
|
If `obj` is None, this should return True if the given request has
|
||||||
permission to change *any* object of the given type.
|
permission to change *any* object of the given type.
|
||||||
"""
|
"""
|
||||||
opts = self.opts
|
opts = self.opts
|
||||||
return request.user.has_perm(opts.app_label + '.' + opts.get_change_permission())
|
return request.user.has_perm(opts.app_label + '.' + opts.get_change_permission())
|
||||||
|
|
||||||
def has_delete_permission(self, request, obj=None):
|
def has_delete_permission(self, request, obj=None):
|
||||||
"""
|
"""
|
||||||
Returns True if the given request has permission to change the given
|
Returns True if the given request has permission to change the given
|
||||||
Django model instance.
|
Django model instance.
|
||||||
|
|
||||||
If `obj` is None, this should return True if the given request has
|
If `obj` is None, this should return True if the given request has
|
||||||
permission to delete *any* object of the given type.
|
permission to delete *any* object of the given type.
|
||||||
"""
|
"""
|
||||||
opts = self.opts
|
opts = self.opts
|
||||||
return request.user.has_perm(opts.app_label + '.' + opts.get_delete_permission())
|
return request.user.has_perm(opts.app_label + '.' + opts.get_delete_permission())
|
||||||
|
|
||||||
def queryset(self, request):
|
def queryset(self, request):
|
||||||
"""
|
"""
|
||||||
Returns a QuerySet of all model instances that can be edited by the
|
Returns a QuerySet of all model instances that can be edited by the
|
||||||
|
@ -246,14 +267,14 @@ class ModelAdmin(BaseModelAdmin):
|
||||||
if ordering:
|
if ordering:
|
||||||
qs = qs.order_by(*ordering)
|
qs = qs.order_by(*ordering)
|
||||||
return qs
|
return qs
|
||||||
|
|
||||||
def get_fieldsets(self, request, obj=None):
|
def get_fieldsets(self, request, obj=None):
|
||||||
"Hook for specifying fieldsets for the add form."
|
"Hook for specifying fieldsets for the add form."
|
||||||
if self.declared_fieldsets:
|
if self.declared_fieldsets:
|
||||||
return self.declared_fieldsets
|
return self.declared_fieldsets
|
||||||
form = self.get_form(request, obj)
|
form = self.get_form(request, obj)
|
||||||
return [(None, {'fields': form.base_fields.keys()})]
|
return [(None, {'fields': form.base_fields.keys()})]
|
||||||
|
|
||||||
def get_form(self, request, obj=None, **kwargs):
|
def get_form(self, request, obj=None, **kwargs):
|
||||||
"""
|
"""
|
||||||
Returns a Form class for use in the admin add view. This is used by
|
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)
|
defaults.update(kwargs)
|
||||||
return modelform_factory(self.model, **defaults)
|
return modelform_factory(self.model, **defaults)
|
||||||
|
|
||||||
def get_formsets(self, request, obj=None):
|
def get_formsets(self, request, obj=None):
|
||||||
for inline in self.inline_instances:
|
for inline in self.inline_instances:
|
||||||
yield inline.get_formset(request, obj)
|
yield inline.get_formset(request, obj)
|
||||||
|
|
||||||
def log_addition(self, request, object):
|
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.
|
The default implementation creates an admin LogEntry object.
|
||||||
"""
|
"""
|
||||||
from django.contrib.admin.models import LogEntry, ADDITION
|
from django.contrib.admin.models import LogEntry, ADDITION
|
||||||
LogEntry.objects.log_action(
|
LogEntry.objects.log_action(
|
||||||
user_id = request.user.pk,
|
user_id = request.user.pk,
|
||||||
content_type_id = ContentType.objects.get_for_model(object).pk,
|
content_type_id = ContentType.objects.get_for_model(object).pk,
|
||||||
object_id = object.pk,
|
object_id = object.pk,
|
||||||
object_repr = force_unicode(object),
|
object_repr = force_unicode(object),
|
||||||
action_flag = ADDITION
|
action_flag = ADDITION
|
||||||
)
|
)
|
||||||
|
|
||||||
def log_change(self, request, object, message):
|
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.
|
The default implementation creates an admin LogEntry object.
|
||||||
"""
|
"""
|
||||||
from django.contrib.admin.models import LogEntry, CHANGE
|
from django.contrib.admin.models import LogEntry, CHANGE
|
||||||
LogEntry.objects.log_action(
|
LogEntry.objects.log_action(
|
||||||
user_id = request.user.pk,
|
user_id = request.user.pk,
|
||||||
content_type_id = ContentType.objects.get_for_model(object).pk,
|
content_type_id = ContentType.objects.get_for_model(object).pk,
|
||||||
object_id = object.pk,
|
object_id = object.pk,
|
||||||
object_repr = force_unicode(object),
|
object_repr = force_unicode(object),
|
||||||
action_flag = CHANGE,
|
action_flag = CHANGE,
|
||||||
change_message = message
|
change_message = message
|
||||||
)
|
)
|
||||||
|
|
||||||
def log_deletion(self, request, object, object_repr):
|
def log_deletion(self, request, object, object_repr):
|
||||||
"""
|
"""
|
||||||
Log that an object has been successfully deleted. Note that since the
|
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
|
from django.contrib.admin.models import LogEntry, DELETION
|
||||||
LogEntry.objects.log_action(
|
LogEntry.objects.log_action(
|
||||||
user_id = request.user.id,
|
user_id = request.user.id,
|
||||||
content_type_id = ContentType.objects.get_for_model(self.model).pk,
|
content_type_id = ContentType.objects.get_for_model(self.model).pk,
|
||||||
object_id = object.pk,
|
object_id = object.pk,
|
||||||
object_repr = object_repr,
|
object_repr = object_repr,
|
||||||
action_flag = DELETION
|
action_flag = DELETION
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def construct_change_message(self, request, form, formsets):
|
def construct_change_message(self, request, form, formsets):
|
||||||
"""
|
"""
|
||||||
|
@ -336,7 +357,7 @@ class ModelAdmin(BaseModelAdmin):
|
||||||
change_message = []
|
change_message = []
|
||||||
if form.changed_data:
|
if form.changed_data:
|
||||||
change_message.append(_('Changed %s.') % get_text_list(form.changed_data, _('and')))
|
change_message.append(_('Changed %s.') % get_text_list(form.changed_data, _('and')))
|
||||||
|
|
||||||
if formsets:
|
if formsets:
|
||||||
for formset in formsets:
|
for formset in formsets:
|
||||||
for added_object in formset.new_objects:
|
for added_object in formset.new_objects:
|
||||||
|
@ -357,11 +378,11 @@ class ModelAdmin(BaseModelAdmin):
|
||||||
|
|
||||||
def message_user(self, request, message):
|
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.
|
posts a message using the auth Message object.
|
||||||
"""
|
"""
|
||||||
request.user.message_set.create(message=message)
|
request.user.message_set.create(message=message)
|
||||||
|
|
||||||
def save_form(self, request, form, change):
|
def save_form(self, request, form, change):
|
||||||
"""
|
"""
|
||||||
Given a ModelForm return an unsaved instance. ``change`` is True if
|
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.
|
Given a model instance save it to the database.
|
||||||
"""
|
"""
|
||||||
obj.save()
|
obj.save()
|
||||||
|
|
||||||
def save_formset(self, request, form, formset, change):
|
def save_formset(self, request, form, formset, change):
|
||||||
"""
|
"""
|
||||||
Given an inline formset save it to the database.
|
Given an inline formset save it to the database.
|
||||||
"""
|
"""
|
||||||
formset.save()
|
formset.save()
|
||||||
|
|
||||||
def render_change_form(self, request, context, add=False, change=False, form_url='', obj=None):
|
def render_change_form(self, request, context, add=False, change=False, form_url='', obj=None):
|
||||||
opts = self.model._meta
|
opts = self.model._meta
|
||||||
app_label = opts.app_label
|
app_label = opts.app_label
|
||||||
|
@ -432,7 +453,7 @@ class ModelAdmin(BaseModelAdmin):
|
||||||
return HttpResponseRedirect(request.path)
|
return HttpResponseRedirect(request.path)
|
||||||
else:
|
else:
|
||||||
self.message_user(request, msg)
|
self.message_user(request, msg)
|
||||||
|
|
||||||
# Figure out where to redirect. If the user has change permission,
|
# Figure out where to redirect. If the user has change permission,
|
||||||
# redirect to the change-list page for this object. Otherwise,
|
# redirect to the change-list page for this object. Otherwise,
|
||||||
# redirect to the admin index.
|
# redirect to the admin index.
|
||||||
|
@ -466,15 +487,15 @@ class ModelAdmin(BaseModelAdmin):
|
||||||
else:
|
else:
|
||||||
self.message_user(request, msg)
|
self.message_user(request, msg)
|
||||||
return HttpResponseRedirect("../")
|
return HttpResponseRedirect("../")
|
||||||
|
|
||||||
def add_view(self, request, form_url='', extra_context=None):
|
def add_view(self, request, form_url='', extra_context=None):
|
||||||
"The 'add' admin view for this model."
|
"The 'add' admin view for this model."
|
||||||
model = self.model
|
model = self.model
|
||||||
opts = model._meta
|
opts = model._meta
|
||||||
|
|
||||||
if not self.has_add_permission(request):
|
if not self.has_add_permission(request):
|
||||||
raise PermissionDenied
|
raise PermissionDenied
|
||||||
|
|
||||||
ModelForm = self.get_form(request)
|
ModelForm = self.get_form(request)
|
||||||
formsets = []
|
formsets = []
|
||||||
if request.method == 'POST':
|
if request.method == 'POST':
|
||||||
|
@ -513,17 +534,17 @@ class ModelAdmin(BaseModelAdmin):
|
||||||
for FormSet in self.get_formsets(request):
|
for FormSet in self.get_formsets(request):
|
||||||
formset = FormSet(instance=self.model())
|
formset = FormSet(instance=self.model())
|
||||||
formsets.append(formset)
|
formsets.append(formset)
|
||||||
|
|
||||||
adminForm = helpers.AdminForm(form, list(self.get_fieldsets(request)), self.prepopulated_fields)
|
adminForm = helpers.AdminForm(form, list(self.get_fieldsets(request)), self.prepopulated_fields)
|
||||||
media = self.media + adminForm.media
|
media = self.media + adminForm.media
|
||||||
|
|
||||||
inline_admin_formsets = []
|
inline_admin_formsets = []
|
||||||
for inline, formset in zip(self.inline_instances, formsets):
|
for inline, formset in zip(self.inline_instances, formsets):
|
||||||
fieldsets = list(inline.get_fieldsets(request))
|
fieldsets = list(inline.get_fieldsets(request))
|
||||||
inline_admin_formset = helpers.InlineAdminFormSet(inline, formset, fieldsets)
|
inline_admin_formset = helpers.InlineAdminFormSet(inline, formset, fieldsets)
|
||||||
inline_admin_formsets.append(inline_admin_formset)
|
inline_admin_formsets.append(inline_admin_formset)
|
||||||
media = media + inline_admin_formset.media
|
media = media + inline_admin_formset.media
|
||||||
|
|
||||||
context = {
|
context = {
|
||||||
'title': _('Add %s') % force_unicode(opts.verbose_name),
|
'title': _('Add %s') % force_unicode(opts.verbose_name),
|
||||||
'adminform': adminForm,
|
'adminform': adminForm,
|
||||||
|
@ -538,29 +559,29 @@ class ModelAdmin(BaseModelAdmin):
|
||||||
context.update(extra_context or {})
|
context.update(extra_context or {})
|
||||||
return self.render_change_form(request, context, add=True)
|
return self.render_change_form(request, context, add=True)
|
||||||
add_view = transaction.commit_on_success(add_view)
|
add_view = transaction.commit_on_success(add_view)
|
||||||
|
|
||||||
def change_view(self, request, object_id, extra_context=None):
|
def change_view(self, request, object_id, extra_context=None):
|
||||||
"The 'change' admin view for this model."
|
"The 'change' admin view for this model."
|
||||||
model = self.model
|
model = self.model
|
||||||
opts = model._meta
|
opts = model._meta
|
||||||
|
|
||||||
try:
|
try:
|
||||||
obj = model._default_manager.get(pk=object_id)
|
obj = model._default_manager.get(pk=unquote(object_id))
|
||||||
except model.DoesNotExist:
|
except model.DoesNotExist:
|
||||||
# Don't raise Http404 just yet, because we haven't checked
|
# Don't raise Http404 just yet, because we haven't checked
|
||||||
# permissions yet. We don't want an unauthenticated user to be able
|
# permissions yet. We don't want an unauthenticated user to be able
|
||||||
# to determine whether a given object exists.
|
# to determine whether a given object exists.
|
||||||
obj = None
|
obj = None
|
||||||
|
|
||||||
if not self.has_change_permission(request, obj):
|
if not self.has_change_permission(request, obj):
|
||||||
raise PermissionDenied
|
raise PermissionDenied
|
||||||
|
|
||||||
if obj is None:
|
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)})
|
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"):
|
if request.method == 'POST' and request.POST.has_key("_saveasnew"):
|
||||||
return self.add_view(request, form_url='../../add/')
|
return self.add_view(request, form_url='../../add/')
|
||||||
|
|
||||||
ModelForm = self.get_form(request, obj)
|
ModelForm = self.get_form(request, obj)
|
||||||
formsets = []
|
formsets = []
|
||||||
if request.method == 'POST':
|
if request.method == 'POST':
|
||||||
|
@ -575,7 +596,7 @@ class ModelAdmin(BaseModelAdmin):
|
||||||
formset = FormSet(request.POST, request.FILES,
|
formset = FormSet(request.POST, request.FILES,
|
||||||
instance=new_object)
|
instance=new_object)
|
||||||
formsets.append(formset)
|
formsets.append(formset)
|
||||||
|
|
||||||
if all_valid(formsets) and form_validated:
|
if all_valid(formsets) and form_validated:
|
||||||
self.save_model(request, new_object, form, change=True)
|
self.save_model(request, new_object, form, change=True)
|
||||||
form.save_m2m()
|
form.save_m2m()
|
||||||
|
@ -585,16 +606,16 @@ class ModelAdmin(BaseModelAdmin):
|
||||||
change_message = self.construct_change_message(request, form, formsets)
|
change_message = self.construct_change_message(request, form, formsets)
|
||||||
self.log_change(request, new_object, change_message)
|
self.log_change(request, new_object, change_message)
|
||||||
return self.response_change(request, new_object)
|
return self.response_change(request, new_object)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
form = ModelForm(instance=obj)
|
form = ModelForm(instance=obj)
|
||||||
for FormSet in self.get_formsets(request, obj):
|
for FormSet in self.get_formsets(request, obj):
|
||||||
formset = FormSet(instance=obj)
|
formset = FormSet(instance=obj)
|
||||||
formsets.append(formset)
|
formsets.append(formset)
|
||||||
|
|
||||||
adminForm = helpers.AdminForm(form, self.get_fieldsets(request, obj), self.prepopulated_fields)
|
adminForm = helpers.AdminForm(form, self.get_fieldsets(request, obj), self.prepopulated_fields)
|
||||||
media = self.media + adminForm.media
|
media = self.media + adminForm.media
|
||||||
|
|
||||||
inline_admin_formsets = []
|
inline_admin_formsets = []
|
||||||
for inline, formset in zip(self.inline_instances, formsets):
|
for inline, formset in zip(self.inline_instances, formsets):
|
||||||
fieldsets = list(inline.get_fieldsets(request, obj))
|
fieldsets = list(inline.get_fieldsets(request, obj))
|
||||||
|
@ -617,7 +638,7 @@ class ModelAdmin(BaseModelAdmin):
|
||||||
context.update(extra_context or {})
|
context.update(extra_context or {})
|
||||||
return self.render_change_form(request, context, change=True, obj=obj)
|
return self.render_change_form(request, context, change=True, obj=obj)
|
||||||
change_view = transaction.commit_on_success(change_view)
|
change_view = transaction.commit_on_success(change_view)
|
||||||
|
|
||||||
def changelist_view(self, request, extra_context=None):
|
def changelist_view(self, request, extra_context=None):
|
||||||
"The 'change list' admin view for this model."
|
"The 'change list' admin view for this model."
|
||||||
from django.contrib.admin.views.main import ChangeList, ERROR_FLAG
|
from django.contrib.admin.views.main import ChangeList, ERROR_FLAG
|
||||||
|
@ -637,7 +658,7 @@ class ModelAdmin(BaseModelAdmin):
|
||||||
if ERROR_FLAG in request.GET.keys():
|
if ERROR_FLAG in request.GET.keys():
|
||||||
return render_to_response('admin/invalid_setup.html', {'title': _('Database error')})
|
return render_to_response('admin/invalid_setup.html', {'title': _('Database error')})
|
||||||
return HttpResponseRedirect(request.path + '?' + ERROR_FLAG + '=1')
|
return HttpResponseRedirect(request.path + '?' + ERROR_FLAG + '=1')
|
||||||
|
|
||||||
context = {
|
context = {
|
||||||
'title': cl.title,
|
'title': cl.title,
|
||||||
'is_popup': cl.is_popup,
|
'is_popup': cl.is_popup,
|
||||||
|
@ -652,32 +673,32 @@ class ModelAdmin(BaseModelAdmin):
|
||||||
'admin/%s/change_list.html' % app_label,
|
'admin/%s/change_list.html' % app_label,
|
||||||
'admin/change_list.html'
|
'admin/change_list.html'
|
||||||
], context, context_instance=template.RequestContext(request))
|
], context, context_instance=template.RequestContext(request))
|
||||||
|
|
||||||
def delete_view(self, request, object_id, extra_context=None):
|
def delete_view(self, request, object_id, extra_context=None):
|
||||||
"The 'delete' admin view for this model."
|
"The 'delete' admin view for this model."
|
||||||
opts = self.model._meta
|
opts = self.model._meta
|
||||||
app_label = opts.app_label
|
app_label = opts.app_label
|
||||||
|
|
||||||
try:
|
try:
|
||||||
obj = self.model._default_manager.get(pk=object_id)
|
obj = self.model._default_manager.get(pk=unquote(object_id))
|
||||||
except self.model.DoesNotExist:
|
except self.model.DoesNotExist:
|
||||||
# Don't raise Http404 just yet, because we haven't checked
|
# Don't raise Http404 just yet, because we haven't checked
|
||||||
# permissions yet. We don't want an unauthenticated user to be able
|
# permissions yet. We don't want an unauthenticated user to be able
|
||||||
# to determine whether a given object exists.
|
# to determine whether a given object exists.
|
||||||
obj = None
|
obj = None
|
||||||
|
|
||||||
if not self.has_delete_permission(request, obj):
|
if not self.has_delete_permission(request, obj):
|
||||||
raise PermissionDenied
|
raise PermissionDenied
|
||||||
|
|
||||||
if obj is None:
|
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)})
|
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
|
# Populate deleted_objects, a data structure of all related objects that
|
||||||
# will also be deleted.
|
# 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()
|
perms_needed = set()
|
||||||
get_deleted_objects(deleted_objects, perms_needed, request.user, obj, opts, 1, self.admin_site)
|
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 request.POST: # The user has already confirmed the deletion.
|
||||||
if perms_needed:
|
if perms_needed:
|
||||||
raise PermissionDenied
|
raise PermissionDenied
|
||||||
|
@ -690,7 +711,7 @@ class ModelAdmin(BaseModelAdmin):
|
||||||
if not self.has_change_permission(request, None):
|
if not self.has_change_permission(request, None):
|
||||||
return HttpResponseRedirect("../../../../")
|
return HttpResponseRedirect("../../../../")
|
||||||
return HttpResponseRedirect("../../")
|
return HttpResponseRedirect("../../")
|
||||||
|
|
||||||
context = {
|
context = {
|
||||||
"title": _("Are you sure?"),
|
"title": _("Are you sure?"),
|
||||||
"object_name": force_unicode(opts.verbose_name),
|
"object_name": force_unicode(opts.verbose_name),
|
||||||
|
@ -707,7 +728,7 @@ class ModelAdmin(BaseModelAdmin):
|
||||||
"admin/%s/delete_confirmation.html" % app_label,
|
"admin/%s/delete_confirmation.html" % app_label,
|
||||||
"admin/delete_confirmation.html"
|
"admin/delete_confirmation.html"
|
||||||
], context, context_instance=template.RequestContext(request))
|
], context, context_instance=template.RequestContext(request))
|
||||||
|
|
||||||
def history_view(self, request, object_id, extra_context=None):
|
def history_view(self, request, object_id, extra_context=None):
|
||||||
"The 'history' admin view for this model."
|
"The 'history' admin view for this model."
|
||||||
from django.contrib.admin.models import LogEntry
|
from django.contrib.admin.models import LogEntry
|
||||||
|
@ -735,10 +756,38 @@ class ModelAdmin(BaseModelAdmin):
|
||||||
"admin/object_history.html"
|
"admin/object_history.html"
|
||||||
], context, context_instance=template.RequestContext(request))
|
], 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):
|
class InlineModelAdmin(BaseModelAdmin):
|
||||||
"""
|
"""
|
||||||
Options for inline editing of ``model`` instances.
|
Options for inline editing of ``model`` instances.
|
||||||
|
|
||||||
Provide ``name`` to specify the attribute name of the ``ForeignKey`` from
|
Provide ``name`` to specify the attribute name of the ``ForeignKey`` from
|
||||||
``model`` to its parent. This is required if ``model`` has more than one
|
``model`` to its parent. This is required if ``model`` has more than one
|
||||||
``ForeignKey`` to its parent.
|
``ForeignKey`` to its parent.
|
||||||
|
@ -751,7 +800,7 @@ class InlineModelAdmin(BaseModelAdmin):
|
||||||
template = None
|
template = None
|
||||||
verbose_name = None
|
verbose_name = None
|
||||||
verbose_name_plural = None
|
verbose_name_plural = None
|
||||||
|
|
||||||
def __init__(self, parent_model, admin_site):
|
def __init__(self, parent_model, admin_site):
|
||||||
self.admin_site = admin_site
|
self.admin_site = admin_site
|
||||||
self.parent_model = parent_model
|
self.parent_model = parent_model
|
||||||
|
@ -771,7 +820,7 @@ class InlineModelAdmin(BaseModelAdmin):
|
||||||
js.extend(['js/SelectBox.js' , 'js/SelectFilter2.js'])
|
js.extend(['js/SelectBox.js' , 'js/SelectFilter2.js'])
|
||||||
return forms.Media(js=['%s%s' % (settings.ADMIN_MEDIA_PREFIX, url) for url in js])
|
return forms.Media(js=['%s%s' % (settings.ADMIN_MEDIA_PREFIX, url) for url in js])
|
||||||
media = property(_media)
|
media = property(_media)
|
||||||
|
|
||||||
def get_formset(self, request, obj=None, **kwargs):
|
def get_formset(self, request, obj=None, **kwargs):
|
||||||
"""Returns a BaseInlineFormSet class for use in admin add/change views."""
|
"""Returns a BaseInlineFormSet class for use in admin add/change views."""
|
||||||
if self.declared_fieldsets:
|
if self.declared_fieldsets:
|
||||||
|
@ -794,13 +843,13 @@ class InlineModelAdmin(BaseModelAdmin):
|
||||||
}
|
}
|
||||||
defaults.update(kwargs)
|
defaults.update(kwargs)
|
||||||
return inlineformset_factory(self.parent_model, self.model, **defaults)
|
return inlineformset_factory(self.parent_model, self.model, **defaults)
|
||||||
|
|
||||||
def get_fieldsets(self, request, obj=None):
|
def get_fieldsets(self, request, obj=None):
|
||||||
if self.declared_fieldsets:
|
if self.declared_fieldsets:
|
||||||
return self.declared_fieldsets
|
return self.declared_fieldsets
|
||||||
form = self.get_formset(request).form
|
form = self.get_formset(request).form
|
||||||
return [(None, {'fields': form.base_fields.keys()})]
|
return [(None, {'fields': form.base_fields.keys()})]
|
||||||
|
|
||||||
class StackedInline(InlineModelAdmin):
|
class StackedInline(InlineModelAdmin):
|
||||||
template = 'admin/edit_inline/stacked.html'
|
template = 'admin/edit_inline/stacked.html'
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import base64
|
|
||||||
import re
|
import re
|
||||||
from django import http, template
|
from django import http, template
|
||||||
from django.contrib.admin import ModelAdmin
|
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.db.models.base import ModelBase
|
||||||
from django.core.exceptions import ImproperlyConfigured
|
from django.core.exceptions import ImproperlyConfigured
|
||||||
from django.shortcuts import render_to_response
|
from django.shortcuts import render_to_response
|
||||||
|
from django.utils.functional import update_wrapper
|
||||||
from django.utils.safestring import mark_safe
|
from django.utils.safestring import mark_safe
|
||||||
from django.utils.text import capfirst
|
from django.utils.text import capfirst
|
||||||
from django.utils.translation import ugettext_lazy, ugettext as _
|
from django.utils.translation import ugettext_lazy, ugettext as _
|
||||||
from django.views.decorators.cache import never_cache
|
from django.views.decorators.cache import never_cache
|
||||||
from django.conf import settings
|
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.")
|
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'
|
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
|
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.
|
that presents a full admin interface for the collection of registered models.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
index_template = None
|
index_template = None
|
||||||
login_template = None
|
login_template = None
|
||||||
app_index_template = None
|
app_index_template = None
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self, name=None):
|
||||||
self._registry = {} # model_class class -> admin_class instance
|
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):
|
def register(self, model_or_iterable, admin_class=None, **options):
|
||||||
"""
|
"""
|
||||||
Registers the given model(s) with the given admin class.
|
Registers the given model(s) with the given admin class.
|
||||||
|
|
||||||
The model(s) should be Model classes, not instances.
|
The model(s) should be Model classes, not instances.
|
||||||
|
|
||||||
If an admin class isn't given, it will use ModelAdmin (the default
|
If an admin class isn't given, it will use ModelAdmin (the default
|
||||||
admin options). If keyword arguments are given -- e.g., list_display --
|
admin options). If keyword arguments are given -- e.g., list_display --
|
||||||
they'll be applied as options to the admin class.
|
they'll be applied as options to the admin class.
|
||||||
|
|
||||||
If a model is already registered, this will raise AlreadyRegistered.
|
If a model is already registered, this will raise AlreadyRegistered.
|
||||||
"""
|
"""
|
||||||
# Don't import the humongous validation code unless required
|
# Don't import the humongous validation code unless required
|
||||||
|
@ -54,7 +62,7 @@ class AdminSite(object):
|
||||||
from django.contrib.admin.validation import validate
|
from django.contrib.admin.validation import validate
|
||||||
else:
|
else:
|
||||||
validate = lambda model, adminclass: None
|
validate = lambda model, adminclass: None
|
||||||
|
|
||||||
if not admin_class:
|
if not admin_class:
|
||||||
admin_class = ModelAdmin
|
admin_class = ModelAdmin
|
||||||
if isinstance(model_or_iterable, ModelBase):
|
if isinstance(model_or_iterable, ModelBase):
|
||||||
|
@ -62,7 +70,7 @@ class AdminSite(object):
|
||||||
for model in model_or_iterable:
|
for model in model_or_iterable:
|
||||||
if model in self._registry:
|
if model in self._registry:
|
||||||
raise AlreadyRegistered('The model %s is already registered' % model.__name__)
|
raise AlreadyRegistered('The model %s is already registered' % model.__name__)
|
||||||
|
|
||||||
# If we got **options then dynamically construct a subclass of
|
# If we got **options then dynamically construct a subclass of
|
||||||
# admin_class with those **options.
|
# admin_class with those **options.
|
||||||
if options:
|
if options:
|
||||||
|
@ -71,17 +79,17 @@ class AdminSite(object):
|
||||||
# which causes issues later on.
|
# which causes issues later on.
|
||||||
options['__module__'] = __name__
|
options['__module__'] = __name__
|
||||||
admin_class = type("%sAdmin" % model.__name__, (admin_class,), options)
|
admin_class = type("%sAdmin" % model.__name__, (admin_class,), options)
|
||||||
|
|
||||||
# Validate (which might be a no-op)
|
# Validate (which might be a no-op)
|
||||||
validate(admin_class, model)
|
validate(admin_class, model)
|
||||||
|
|
||||||
# Instantiate the admin class to save in the registry
|
# Instantiate the admin class to save in the registry
|
||||||
self._registry[model] = admin_class(model, self)
|
self._registry[model] = admin_class(model, self)
|
||||||
|
|
||||||
def unregister(self, model_or_iterable):
|
def unregister(self, model_or_iterable):
|
||||||
"""
|
"""
|
||||||
Unregisters the given model(s).
|
Unregisters the given model(s).
|
||||||
|
|
||||||
If a model isn't already registered, this will raise NotRegistered.
|
If a model isn't already registered, this will raise NotRegistered.
|
||||||
"""
|
"""
|
||||||
if isinstance(model_or_iterable, ModelBase):
|
if isinstance(model_or_iterable, ModelBase):
|
||||||
|
@ -90,92 +98,100 @@ class AdminSite(object):
|
||||||
if model not in self._registry:
|
if model not in self._registry:
|
||||||
raise NotRegistered('The model %s is not registered' % model.__name__)
|
raise NotRegistered('The model %s is not registered' % model.__name__)
|
||||||
del self._registry[model]
|
del self._registry[model]
|
||||||
|
|
||||||
def has_permission(self, request):
|
def has_permission(self, request):
|
||||||
"""
|
"""
|
||||||
Returns True if the given HttpRequest has permission to view
|
Returns True if the given HttpRequest has permission to view
|
||||||
*at least one* page in the admin site.
|
*at least one* page in the admin site.
|
||||||
"""
|
"""
|
||||||
return request.user.is_authenticated() and request.user.is_staff
|
return request.user.is_authenticated() and request.user.is_staff
|
||||||
|
|
||||||
def check_dependencies(self):
|
def check_dependencies(self):
|
||||||
"""
|
"""
|
||||||
Check that all things needed to run the admin have been correctly installed.
|
Check that all things needed to run the admin have been correctly installed.
|
||||||
|
|
||||||
The default implementation checks that LogEntry, ContentType and the
|
The default implementation checks that LogEntry, ContentType and the
|
||||||
auth context processor are installed.
|
auth context processor are installed.
|
||||||
"""
|
"""
|
||||||
from django.contrib.admin.models import LogEntry
|
from django.contrib.admin.models import LogEntry
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
|
|
||||||
if not LogEntry._meta.installed:
|
if not LogEntry._meta.installed:
|
||||||
raise ImproperlyConfigured("Put 'django.contrib.admin' in your INSTALLED_APPS setting in order to use the admin application.")
|
raise ImproperlyConfigured("Put 'django.contrib.admin' in your INSTALLED_APPS setting in order to use the admin application.")
|
||||||
if not ContentType._meta.installed:
|
if not ContentType._meta.installed:
|
||||||
raise ImproperlyConfigured("Put 'django.contrib.contenttypes' in your INSTALLED_APPS setting in order to use the admin application.")
|
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:
|
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.")
|
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.
|
Decorator to create an "admin view attached to this ``AdminSite``. This
|
||||||
|
wraps the view and provides permission checking by calling
|
||||||
`url` is the remainder of the URL -- e.g. 'comments/comment/'.
|
``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('/'):
|
def inner(request, *args, **kwargs):
|
||||||
return http.HttpResponseRedirect(request.path + '/')
|
if not self.has_permission(request):
|
||||||
|
return self.login(request)
|
||||||
if settings.DEBUG:
|
return view(request, *args, **kwargs)
|
||||||
self.check_dependencies()
|
return update_wrapper(inner, view)
|
||||||
|
|
||||||
# Figure out the admin base URL path and stash it for later use
|
def get_urls(self):
|
||||||
self.root_path = re.sub(re.escape(url) + '$', '', request.path)
|
from django.conf.urls.defaults import patterns, url, include
|
||||||
|
|
||||||
url = url.rstrip('/') # Trim trailing slash, if it exists.
|
def wrap(view):
|
||||||
|
def wrapper(*args, **kwargs):
|
||||||
# The 'logout' view doesn't require that the person is logged in.
|
return self.admin_view(view)(*args, **kwargs)
|
||||||
if url == 'logout':
|
return update_wrapper(wrapper, view)
|
||||||
return self.logout(request)
|
|
||||||
|
# Admin-site-wide views.
|
||||||
# Check permission to continue or display login form.
|
urlpatterns = patterns('',
|
||||||
if not self.has_permission(request):
|
url(r'^$',
|
||||||
return self.login(request)
|
wrap(self.index),
|
||||||
|
name='%sadmin_index' % self.name),
|
||||||
if url == '':
|
url(r'^logout/$',
|
||||||
return self.index(request)
|
wrap(self.logout),
|
||||||
elif url == 'password_change':
|
name='%sadmin_logout'),
|
||||||
return self.password_change(request)
|
url(r'^password_change/$',
|
||||||
elif url == 'password_change/done':
|
wrap(self.password_change),
|
||||||
return self.password_change_done(request)
|
name='%sadmin_password_change' % self.name),
|
||||||
elif url == 'jsi18n':
|
url(r'^password_change/done/$',
|
||||||
return self.i18n_javascript(request)
|
wrap(self.password_change_done),
|
||||||
# URLs starting with 'r/' are for the "View on site" links.
|
name='%sadmin_password_change_done' % self.name),
|
||||||
elif url.startswith('r/'):
|
url(r'^jsi18n/$',
|
||||||
from django.contrib.contenttypes.views import shortcut
|
wrap(self.i18n_javascript),
|
||||||
return shortcut(request, *url.split('/')[1:])
|
name='%sadmin_jsi18n' % self.name),
|
||||||
else:
|
url(r'^r/(?P<content_type_id>\d+)/(?P<object_id>.+)/$',
|
||||||
if '/' in url:
|
'django.views.defaults.shortcut'),
|
||||||
return self.model_page(request, *url.split('/', 2))
|
url(r'^(?P<app_label>\w+)/$',
|
||||||
else:
|
wrap(self.app_index),
|
||||||
return self.app_index(request, url)
|
name='%sadmin_app_list' % self.name),
|
||||||
|
)
|
||||||
raise http.Http404('The requested admin page does not exist.')
|
|
||||||
|
# Add in each model's views.
|
||||||
def model_page(self, request, app_label, model_name, rest_of_url=None):
|
for model, model_admin in self._registry.iteritems():
|
||||||
"""
|
urlpatterns += patterns('',
|
||||||
Handles the model-specific functionality of the admin site, delegating
|
url(r'^%s/%s/' % (model._meta.app_label, model._meta.module_name),
|
||||||
to the appropriate ModelAdmin class.
|
include(model_admin.urls))
|
||||||
"""
|
)
|
||||||
from django.db import models
|
return urlpatterns
|
||||||
model = models.get_model(app_label, model_name)
|
|
||||||
if model is None:
|
def urls(self):
|
||||||
raise http.Http404("App %r, model %r, not found." % (app_label, model_name))
|
return self.get_urls()
|
||||||
try:
|
urls = property(urls)
|
||||||
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 password_change(self, request):
|
def password_change(self, request):
|
||||||
"""
|
"""
|
||||||
Handles the "change password" task -- both form display and validation.
|
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
|
from django.contrib.auth.views import password_change
|
||||||
return password_change(request,
|
return password_change(request,
|
||||||
post_change_redirect='%spassword_change/done/' % self.root_path)
|
post_change_redirect='%spassword_change/done/' % self.root_path)
|
||||||
|
|
||||||
def password_change_done(self, request):
|
def password_change_done(self, request):
|
||||||
"""
|
"""
|
||||||
Displays the "success" page after a password change.
|
Displays the "success" page after a password change.
|
||||||
"""
|
"""
|
||||||
from django.contrib.auth.views import password_change_done
|
from django.contrib.auth.views import password_change_done
|
||||||
return password_change_done(request)
|
return password_change_done(request)
|
||||||
|
|
||||||
def i18n_javascript(self, request):
|
def i18n_javascript(self, request):
|
||||||
"""
|
"""
|
||||||
Displays the i18n JavaScript that the Django admin requires.
|
Displays the i18n JavaScript that the Django admin requires.
|
||||||
|
|
||||||
This takes into account the USE_I18N setting. If it's set to False, the
|
This takes into account the USE_I18N setting. If it's set to False, the
|
||||||
generated JavaScript will be leaner and faster.
|
generated JavaScript will be leaner and faster.
|
||||||
"""
|
"""
|
||||||
|
@ -203,23 +219,23 @@ class AdminSite(object):
|
||||||
else:
|
else:
|
||||||
from django.views.i18n import null_javascript_catalog as javascript_catalog
|
from django.views.i18n import null_javascript_catalog as javascript_catalog
|
||||||
return javascript_catalog(request, packages='django.conf')
|
return javascript_catalog(request, packages='django.conf')
|
||||||
|
|
||||||
def logout(self, request):
|
def logout(self, request):
|
||||||
"""
|
"""
|
||||||
Logs out the user for the given HttpRequest.
|
Logs out the user for the given HttpRequest.
|
||||||
|
|
||||||
This should *not* assume the user is already logged in.
|
This should *not* assume the user is already logged in.
|
||||||
"""
|
"""
|
||||||
from django.contrib.auth.views import logout
|
from django.contrib.auth.views import logout
|
||||||
return logout(request)
|
return logout(request)
|
||||||
logout = never_cache(logout)
|
logout = never_cache(logout)
|
||||||
|
|
||||||
def login(self, request):
|
def login(self, request):
|
||||||
"""
|
"""
|
||||||
Displays the login form for the given HttpRequest.
|
Displays the login form for the given HttpRequest.
|
||||||
"""
|
"""
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
|
|
||||||
# If this isn't already the login page, display it.
|
# If this isn't already the login page, display it.
|
||||||
if not request.POST.has_key(LOGIN_FORM_KEY):
|
if not request.POST.has_key(LOGIN_FORM_KEY):
|
||||||
if request.POST:
|
if request.POST:
|
||||||
|
@ -227,14 +243,14 @@ class AdminSite(object):
|
||||||
else:
|
else:
|
||||||
message = ""
|
message = ""
|
||||||
return self.display_login_form(request, message)
|
return self.display_login_form(request, message)
|
||||||
|
|
||||||
# Check that the user accepts cookies.
|
# Check that the user accepts cookies.
|
||||||
if not request.session.test_cookie_worked():
|
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.")
|
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)
|
return self.display_login_form(request, message)
|
||||||
else:
|
else:
|
||||||
request.session.delete_test_cookie()
|
request.session.delete_test_cookie()
|
||||||
|
|
||||||
# Check the password.
|
# Check the password.
|
||||||
username = request.POST.get('username', None)
|
username = request.POST.get('username', None)
|
||||||
password = request.POST.get('password', None)
|
password = request.POST.get('password', None)
|
||||||
|
@ -254,7 +270,7 @@ class AdminSite(object):
|
||||||
else:
|
else:
|
||||||
message = _("Usernames cannot contain the '@' character.")
|
message = _("Usernames cannot contain the '@' character.")
|
||||||
return self.display_login_form(request, message)
|
return self.display_login_form(request, message)
|
||||||
|
|
||||||
# The user data is correct; log in the user in and continue.
|
# The user data is correct; log in the user in and continue.
|
||||||
else:
|
else:
|
||||||
if user.is_active and user.is_staff:
|
if user.is_active and user.is_staff:
|
||||||
|
@ -263,7 +279,7 @@ class AdminSite(object):
|
||||||
else:
|
else:
|
||||||
return self.display_login_form(request, ERROR_MESSAGE)
|
return self.display_login_form(request, ERROR_MESSAGE)
|
||||||
login = never_cache(login)
|
login = never_cache(login)
|
||||||
|
|
||||||
def index(self, request, extra_context=None):
|
def index(self, request, extra_context=None):
|
||||||
"""
|
"""
|
||||||
Displays the main admin index page, which lists all of the installed
|
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():
|
for model, model_admin in self._registry.items():
|
||||||
app_label = model._meta.app_label
|
app_label = model._meta.app_label
|
||||||
has_module_perms = user.has_module_perms(app_label)
|
has_module_perms = user.has_module_perms(app_label)
|
||||||
|
|
||||||
if has_module_perms:
|
if has_module_perms:
|
||||||
perms = {
|
perms = {
|
||||||
'add': model_admin.has_add_permission(request),
|
'add': model_admin.has_add_permission(request),
|
||||||
'change': model_admin.has_change_permission(request),
|
'change': model_admin.has_change_permission(request),
|
||||||
'delete': model_admin.has_delete_permission(request),
|
'delete': model_admin.has_delete_permission(request),
|
||||||
}
|
}
|
||||||
|
|
||||||
# Check whether user has any perm for this module.
|
# Check whether user has any perm for this module.
|
||||||
# If so, add the module to the model_list.
|
# If so, add the module to the model_list.
|
||||||
if True in perms.values():
|
if True in perms.values():
|
||||||
|
@ -299,15 +315,15 @@ class AdminSite(object):
|
||||||
'has_module_perms': has_module_perms,
|
'has_module_perms': has_module_perms,
|
||||||
'models': [model_dict],
|
'models': [model_dict],
|
||||||
}
|
}
|
||||||
|
|
||||||
# Sort the apps alphabetically.
|
# Sort the apps alphabetically.
|
||||||
app_list = app_dict.values()
|
app_list = app_dict.values()
|
||||||
app_list.sort(lambda x, y: cmp(x['name'], y['name']))
|
app_list.sort(lambda x, y: cmp(x['name'], y['name']))
|
||||||
|
|
||||||
# Sort the models alphabetically within each app.
|
# Sort the models alphabetically within each app.
|
||||||
for app in app_list:
|
for app in app_list:
|
||||||
app['models'].sort(lambda x, y: cmp(x['name'], y['name']))
|
app['models'].sort(lambda x, y: cmp(x['name'], y['name']))
|
||||||
|
|
||||||
context = {
|
context = {
|
||||||
'title': _('Site administration'),
|
'title': _('Site administration'),
|
||||||
'app_list': app_list,
|
'app_list': app_list,
|
||||||
|
@ -318,7 +334,7 @@ class AdminSite(object):
|
||||||
context_instance=template.RequestContext(request)
|
context_instance=template.RequestContext(request)
|
||||||
)
|
)
|
||||||
index = never_cache(index)
|
index = never_cache(index)
|
||||||
|
|
||||||
def display_login_form(self, request, error_message='', extra_context=None):
|
def display_login_form(self, request, error_message='', extra_context=None):
|
||||||
request.session.set_test_cookie()
|
request.session.set_test_cookie()
|
||||||
context = {
|
context = {
|
||||||
|
@ -331,7 +347,7 @@ class AdminSite(object):
|
||||||
return render_to_response(self.login_template or 'admin/login.html', context,
|
return render_to_response(self.login_template or 'admin/login.html', context,
|
||||||
context_instance=template.RequestContext(request)
|
context_instance=template.RequestContext(request)
|
||||||
)
|
)
|
||||||
|
|
||||||
def app_index(self, request, app_label, extra_context=None):
|
def app_index(self, request, app_label, extra_context=None):
|
||||||
user = request.user
|
user = request.user
|
||||||
has_module_perms = user.has_module_perms(app_label)
|
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,
|
return render_to_response(self.app_index_template or 'admin/app_index.html', context,
|
||||||
context_instance=template.RequestContext(request)
|
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.
|
# 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.
|
# 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.encoding import force_unicode
|
||||||
from django.utils.translation import ugettext as _
|
from django.utils.translation import ugettext as _
|
||||||
|
|
||||||
|
|
||||||
def quote(s):
|
def quote(s):
|
||||||
"""
|
"""
|
||||||
Ensure that primary key values do not confuse the admin URLs by escaping
|
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'):
|
if url.endswith('password'):
|
||||||
return self.user_change_password(request, url.split('/')[0])
|
return self.user_change_password(request, url.split('/')[0])
|
||||||
return super(UserAdmin, self).__call__(request, url)
|
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):
|
def add_view(self, request):
|
||||||
# It's an error for a user to have add permission but NOT change
|
# 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')),
|
# (r'^admin/doc/', include('django.contrib.admindocs.urls')),
|
||||||
|
|
||||||
# Uncomment the next line to enable the admin:
|
# 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.)
|
(The bold lines are the ones that needed to be uncommented.)
|
||||||
|
|
|
@ -632,6 +632,49 @@ model instance::
|
||||||
instance.save()
|
instance.save()
|
||||||
formset.save_m2m()
|
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
|
``ModelAdmin`` media definitions
|
||||||
--------------------------------
|
--------------------------------
|
||||||
|
|
||||||
|
@ -1027,7 +1070,7 @@ In this example, we register the default ``AdminSite`` instance
|
||||||
admin.autodiscover()
|
admin.autodiscover()
|
||||||
|
|
||||||
urlpatterns = patterns('',
|
urlpatterns = patterns('',
|
||||||
('^admin/(.*)', admin.site.root),
|
('^admin/', include(admin.site.urls)),
|
||||||
)
|
)
|
||||||
|
|
||||||
Above we used ``admin.autodiscover()`` to automatically load the
|
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
|
from myproject.admin import admin_site
|
||||||
|
|
||||||
urlpatterns = patterns('',
|
urlpatterns = patterns('',
|
||||||
('^myadmin/(.*)', admin_site.root),
|
('^myadmin/', include(admin_site.urls)),
|
||||||
)
|
)
|
||||||
|
|
||||||
There is really no need to use autodiscover when using your own ``AdminSite``
|
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
|
instance since you will likely be importing all the per-app admin.py modules
|
||||||
in your ``myproject.admin`` module.
|
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
|
Multiple admin sites in the same URLconf
|
||||||
----------------------------------------
|
----------------------------------------
|
||||||
|
@ -1068,6 +1109,29 @@ respectively::
|
||||||
from myproject.admin import basic_site, advanced_site
|
from myproject.admin import basic_site, advanced_site
|
||||||
|
|
||||||
urlpatterns = patterns('',
|
urlpatterns = patterns('',
|
||||||
('^basic-admin/(.*)', basic_site.root),
|
('^basic-admin/', include(basic_site.urls)),
|
||||||
('^advanced-admin/(.*)', advanced_site.root),
|
('^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):
|
class AdminViewBasicTest(TestCase):
|
||||||
fixtures = ['admin-views-users.xml', 'admin-views-colors.xml']
|
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):
|
def setUp(self):
|
||||||
self.client.login(username='super', password='secret')
|
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.
|
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,
|
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):
|
def testBasicAddGet(self):
|
||||||
"""
|
"""
|
||||||
A smoke test to ensure GET on the add_view works.
|
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)
|
self.failUnlessEqual(response.status_code, 200)
|
||||||
|
|
||||||
def testAddWithGETArgs(self):
|
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.failUnlessEqual(response.status_code, 200)
|
||||||
self.failUnless(
|
self.failUnless(
|
||||||
'value="My Section"' in response.content,
|
'value="My Section"' in response.content,
|
||||||
|
@ -48,7 +53,7 @@ class AdminViewBasicTest(TestCase):
|
||||||
"""
|
"""
|
||||||
A smoke test to ensureGET on the change_view works.
|
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)
|
self.failUnlessEqual(response.status_code, 200)
|
||||||
|
|
||||||
def testBasicAddPost(self):
|
def testBasicAddPost(self):
|
||||||
|
@ -61,7 +66,7 @@ class AdminViewBasicTest(TestCase):
|
||||||
"article_set-TOTAL_FORMS": u"3",
|
"article_set-TOTAL_FORMS": u"3",
|
||||||
"article_set-INITIAL_FORMS": u"0",
|
"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
|
self.failUnlessEqual(response.status_code, 302) # redirect somewhere
|
||||||
|
|
||||||
def testBasicEditPost(self):
|
def testBasicEditPost(self):
|
||||||
|
@ -106,7 +111,7 @@ class AdminViewBasicTest(TestCase):
|
||||||
"article_set-5-date_0": u"",
|
"article_set-5-date_0": u"",
|
||||||
"article_set-5-date_1": 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
|
self.failUnlessEqual(response.status_code, 302) # redirect somewhere
|
||||||
|
|
||||||
def testChangeListSortingCallable(self):
|
def testChangeListSortingCallable(self):
|
||||||
|
@ -114,7 +119,7 @@ class AdminViewBasicTest(TestCase):
|
||||||
Ensure we can sort on a list_display field that is a callable
|
Ensure we can sort on a list_display field that is a callable
|
||||||
(column 2 is callable_year in ArticleAdmin)
|
(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.failUnlessEqual(response.status_code, 200)
|
||||||
self.failUnless(
|
self.failUnless(
|
||||||
response.content.index('Oldest content') < response.content.index('Middle content') and
|
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
|
Ensure we can sort on a list_display field that is a Model method
|
||||||
(colunn 3 is 'model_year' in ArticleAdmin)
|
(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.failUnlessEqual(response.status_code, 200)
|
||||||
self.failUnless(
|
self.failUnless(
|
||||||
response.content.index('Newest content') < response.content.index('Middle content') and
|
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
|
Ensure we can sort on a list_display field that is a ModelAdmin method
|
||||||
(colunn 4 is 'modeladmin_year' in ArticleAdmin)
|
(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.failUnlessEqual(response.status_code, 200)
|
||||||
self.failUnless(
|
self.failUnless(
|
||||||
response.content.index('Oldest content') < response.content.index('Middle content') and
|
response.content.index('Oldest content') < response.content.index('Middle content') and
|
||||||
|
@ -150,7 +155,7 @@ class AdminViewBasicTest(TestCase):
|
||||||
|
|
||||||
def testLimitedFilter(self):
|
def testLimitedFilter(self):
|
||||||
"""Ensure admin changelist filters do not contain objects excluded via limit_choices_to."""
|
"""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.failUnlessEqual(response.status_code, 200)
|
||||||
self.failUnless(
|
self.failUnless(
|
||||||
'<div id="changelist-filter">' in response.content,
|
'<div id="changelist-filter">' in response.content,
|
||||||
|
@ -163,11 +168,30 @@ class AdminViewBasicTest(TestCase):
|
||||||
|
|
||||||
def testIncorrectLookupParameters(self):
|
def testIncorrectLookupParameters(self):
|
||||||
"""Ensure incorrect lookup parameters are handled gracefully."""
|
"""Ensure incorrect lookup parameters are handled gracefully."""
|
||||||
response = self.client.get('/test_admin/admin/admin_views/thing/', {'notarealfield': '5'})
|
response = self.client.get('/test_admin/%s/admin_views/thing/' % self.urlbit, {'notarealfield': '5'})
|
||||||
self.assertRedirects(response, '/test_admin/admin/admin_views/thing/?e=1')
|
self.assertRedirects(response, '/test_admin/%s/admin_views/thing/?e=1' % self.urlbit)
|
||||||
response = self.client.get('/test_admin/admin/admin_views/thing/', {'color__id__exact': 'StringNotInteger!'})
|
response = self.client.get('/test_admin/%s/admin_views/thing/' % self.urlbit, {'color__id__exact': 'StringNotInteger!'})
|
||||||
self.assertRedirects(response, '/test_admin/admin/admin_views/thing/?e=1')
|
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):
|
def get_perm(Model, perm):
|
||||||
"""Return the permission object, for the Model"""
|
"""Return the permission object, for the Model"""
|
||||||
ct = ContentType.objects.get_for_model(Model)
|
ct = ContentType.objects.get_for_model(Model)
|
||||||
|
@ -432,44 +456,6 @@ class AdminViewPermissionsTest(TestCase):
|
||||||
|
|
||||||
self.client.get('/test_admin/admin/logout/')
|
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):
|
def testDeleteView(self):
|
||||||
"""Delete view should restrict access and actually delete items."""
|
"""Delete view should restrict access and actually delete items."""
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
from django.conf.urls.defaults import *
|
from django.conf.urls.defaults import *
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
import views
|
import views
|
||||||
|
import customadmin
|
||||||
|
|
||||||
urlpatterns = patterns('',
|
urlpatterns = patterns('',
|
||||||
(r'^admin/doc/', include('django.contrib.admindocs.urls')),
|
(r'^admin/doc/', include('django.contrib.admindocs.urls')),
|
||||||
(r'^admin/secure-view/$', views.secure_view),
|
(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