Stopped unconditionally reversing admin model add/change URLs.
Starting with [16857] this could cause HTTP 500 errors when `ModelAdmin.get_urls()` has been customized to the point it doesn't provide these standard URLs. Fixes #17333. Refs #15294. git-svn-id: http://code.djangoproject.com/svn/django/trunk@17237 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
parent
259ebcdeea
commit
554f0601b5
|
@ -7,7 +7,7 @@ from django.contrib.contenttypes import views as contenttype_views
|
||||||
from django.views.decorators.csrf import csrf_protect
|
from django.views.decorators.csrf import csrf_protect
|
||||||
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.core.urlresolvers import reverse
|
from django.core.urlresolvers import reverse, NoReverseMatch
|
||||||
from django.template.response import TemplateResponse
|
from django.template.response import TemplateResponse
|
||||||
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
|
||||||
|
@ -342,10 +342,18 @@ class AdminSite(object):
|
||||||
info = (app_label, model._meta.module_name)
|
info = (app_label, model._meta.module_name)
|
||||||
model_dict = {
|
model_dict = {
|
||||||
'name': capfirst(model._meta.verbose_name_plural),
|
'name': capfirst(model._meta.verbose_name_plural),
|
||||||
'admin_url': reverse('admin:%s_%s_changelist' % info, current_app=self.name),
|
|
||||||
'add_url': reverse('admin:%s_%s_add' % info, current_app=self.name),
|
|
||||||
'perms': perms,
|
'perms': perms,
|
||||||
}
|
}
|
||||||
|
if perms.get('change', False):
|
||||||
|
try:
|
||||||
|
model_dict['admin_url'] = reverse('admin:%s_%s_changelist' % info, current_app=self.name)
|
||||||
|
except NoReverseMatch:
|
||||||
|
pass
|
||||||
|
if perms.get('add', False):
|
||||||
|
try:
|
||||||
|
model_dict['add_url'] = reverse('admin:%s_%s_add' % info, current_app=self.name)
|
||||||
|
except NoReverseMatch:
|
||||||
|
pass
|
||||||
if app_label in app_dict:
|
if app_label in app_dict:
|
||||||
app_dict[app_label]['models'].append(model_dict)
|
app_dict[app_label]['models'].append(model_dict)
|
||||||
else:
|
else:
|
||||||
|
@ -388,10 +396,18 @@ class AdminSite(object):
|
||||||
info = (app_label, model._meta.module_name)
|
info = (app_label, model._meta.module_name)
|
||||||
model_dict = {
|
model_dict = {
|
||||||
'name': capfirst(model._meta.verbose_name_plural),
|
'name': capfirst(model._meta.verbose_name_plural),
|
||||||
'admin_url': reverse('admin:%s_%s_changelist' % info, current_app=self.name),
|
|
||||||
'add_url': reverse('admin:%s_%s_add' % info, current_app=self.name),
|
|
||||||
'perms': perms,
|
'perms': perms,
|
||||||
}
|
}
|
||||||
|
if perms.get('change', False):
|
||||||
|
try:
|
||||||
|
model_dict['admin_url'] = reverse('admin:%s_%s_changelist' % info, current_app=self.name)
|
||||||
|
except NoReverseMatch:
|
||||||
|
pass
|
||||||
|
if perms.get('add', False):
|
||||||
|
try:
|
||||||
|
model_dict['add_url'] = reverse('admin:%s_%s_add' % info, current_app=self.name)
|
||||||
|
except NoReverseMatch:
|
||||||
|
pass
|
||||||
if app_dict:
|
if app_dict:
|
||||||
app_dict['models'].append(model_dict),
|
app_dict['models'].append(model_dict),
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -19,19 +19,19 @@
|
||||||
<caption><a href="{{ app.app_url }}" class="section">{% blocktrans with name=app.name %}{{ name }}{% endblocktrans %}</a></caption>
|
<caption><a href="{{ app.app_url }}" class="section">{% blocktrans with name=app.name %}{{ name }}{% endblocktrans %}</a></caption>
|
||||||
{% for model in app.models %}
|
{% for model in app.models %}
|
||||||
<tr>
|
<tr>
|
||||||
{% if model.perms.change %}
|
{% if model.admin_url %}
|
||||||
<th scope="row"><a href="{{ model.admin_url }}">{{ model.name }}</a></th>
|
<th scope="row"><a href="{{ model.admin_url }}">{{ model.name }}</a></th>
|
||||||
{% else %}
|
{% else %}
|
||||||
<th scope="row">{{ model.name }}</th>
|
<th scope="row">{{ model.name }}</th>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if model.perms.add %}
|
{% if model.add_url %}
|
||||||
<td><a href="{{ model.add_url }}" class="addlink">{% trans 'Add' %}</a></td>
|
<td><a href="{{ model.add_url }}" class="addlink">{% trans 'Add' %}</a></td>
|
||||||
{% else %}
|
{% else %}
|
||||||
<td> </td>
|
<td> </td>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if model.perms.change %}
|
{% if model.admin_url %}
|
||||||
<td><a href="{{ model.admin_url }}" class="changelink">{% trans 'Change' %}</a></td>
|
<td><a href="{{ model.admin_url }}" class="changelink">{% trans 'Change' %}</a></td>
|
||||||
{% else %}
|
{% else %}
|
||||||
<td> </td>
|
<td> </td>
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from __future__ import absolute_import
|
from __future__ import absolute_import
|
||||||
|
|
||||||
import datetime
|
|
||||||
import tempfile
|
import tempfile
|
||||||
import os
|
import os
|
||||||
|
|
||||||
|
@ -10,8 +9,10 @@ from django.contrib import admin
|
||||||
from django.contrib.admin.views.main import ChangeList
|
from django.contrib.admin.views.main import ChangeList
|
||||||
from django.core.files.storage import FileSystemStorage
|
from django.core.files.storage import FileSystemStorage
|
||||||
from django.core.mail import EmailMessage
|
from django.core.mail import EmailMessage
|
||||||
|
from django.conf.urls import patterns, url
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.forms.models import BaseModelFormSet
|
from django.forms.models import BaseModelFormSet
|
||||||
|
from django.http import HttpResponse
|
||||||
|
|
||||||
from .models import (Article, Chapter, Account, Media, Child, Parent, Picture,
|
from .models import (Article, Chapter, Account, Media, Child, Parent, Picture,
|
||||||
Widget, DooHickey, Grommet, Whatsit, FancyDoodad, Category, Link,
|
Widget, DooHickey, Grommet, Whatsit, FancyDoodad, Category, Link,
|
||||||
|
@ -24,7 +25,7 @@ from .models import (Article, Chapter, Account, Media, Child, Parent, Picture,
|
||||||
CoverLetter, Story, OtherStory, Book, Promo, ChapterXtra1, Pizza, Topping,
|
CoverLetter, Story, OtherStory, Book, Promo, ChapterXtra1, Pizza, Topping,
|
||||||
Album, Question, Answer, ComplexSortedPerson, PrePopulatedPostLargeSlug,
|
Album, Question, Answer, ComplexSortedPerson, PrePopulatedPostLargeSlug,
|
||||||
AdminOrderedField, AdminOrderedModelMethod, AdminOrderedAdminMethod,
|
AdminOrderedField, AdminOrderedModelMethod, AdminOrderedAdminMethod,
|
||||||
AdminOrderedCallable)
|
AdminOrderedCallable, Report)
|
||||||
|
|
||||||
|
|
||||||
def callable_year(dt_value):
|
def callable_year(dt_value):
|
||||||
|
@ -499,6 +500,17 @@ class AdminOrderedCallableAdmin(admin.ModelAdmin):
|
||||||
ordering = ('order',)
|
ordering = ('order',)
|
||||||
list_display = ('stuff', admin_ordered_callable)
|
list_display = ('stuff', admin_ordered_callable)
|
||||||
|
|
||||||
|
class ReportAdmin(admin.ModelAdmin):
|
||||||
|
def extra(self, request):
|
||||||
|
return HttpResponse()
|
||||||
|
|
||||||
|
def get_urls(self):
|
||||||
|
# Corner case: Don't call parent implementation
|
||||||
|
return patterns('',
|
||||||
|
url(r'^extra/$',
|
||||||
|
self.extra,
|
||||||
|
name='cable_extra'),
|
||||||
|
)
|
||||||
|
|
||||||
site = admin.AdminSite(name="admin")
|
site = admin.AdminSite(name="admin")
|
||||||
site.register(Article, ArticleAdmin)
|
site.register(Article, ArticleAdmin)
|
||||||
|
@ -543,6 +555,7 @@ site.register(Paper, PaperAdmin)
|
||||||
site.register(CoverLetter, CoverLetterAdmin)
|
site.register(CoverLetter, CoverLetterAdmin)
|
||||||
site.register(Story, StoryAdmin)
|
site.register(Story, StoryAdmin)
|
||||||
site.register(OtherStory, OtherStoryAdmin)
|
site.register(OtherStory, OtherStoryAdmin)
|
||||||
|
site.register(Report, ReportAdmin)
|
||||||
|
|
||||||
# We intentionally register Promo and ChapterXtra1 but not Chapter nor ChapterXtra2.
|
# We intentionally register Promo and ChapterXtra1 but not Chapter nor ChapterXtra2.
|
||||||
# That way we cover all four cases:
|
# That way we cover all four cases:
|
||||||
|
|
|
@ -566,3 +566,9 @@ class AdminOrderedAdminMethod(models.Model):
|
||||||
class AdminOrderedCallable(models.Model):
|
class AdminOrderedCallable(models.Model):
|
||||||
order = models.IntegerField()
|
order = models.IntegerField()
|
||||||
stuff = models.CharField(max_length=200)
|
stuff = models.CharField(max_length=200)
|
||||||
|
|
||||||
|
class Report(models.Model):
|
||||||
|
title = models.CharField(max_length=100)
|
||||||
|
|
||||||
|
def __unicode__(self):
|
||||||
|
return self.title
|
||||||
|
|
|
@ -38,7 +38,8 @@ from .models import (Article, BarAccount, CustomArticle, EmptyModel, FooAccount,
|
||||||
Book, Promo, WorkHour, Employee, Question, Answer, Inquisition, Actor,
|
Book, Promo, WorkHour, Employee, Question, Answer, Inquisition, Actor,
|
||||||
FoodDelivery, RowLevelChangePermissionModel, Paper, CoverLetter, Story,
|
FoodDelivery, RowLevelChangePermissionModel, Paper, CoverLetter, Story,
|
||||||
OtherStory, ComplexSortedPerson, Parent, Child, AdminOrderedField,
|
OtherStory, ComplexSortedPerson, Parent, Child, AdminOrderedField,
|
||||||
AdminOrderedModelMethod, AdminOrderedAdminMethod, AdminOrderedCallable)
|
AdminOrderedModelMethod, AdminOrderedAdminMethod, AdminOrderedCallable,
|
||||||
|
Report)
|
||||||
|
|
||||||
|
|
||||||
ERROR_MESSAGE = "Please enter the correct username and password \
|
ERROR_MESSAGE = "Please enter the correct username and password \
|
||||||
|
@ -1090,6 +1091,37 @@ class AdminViewPermissionsTest(TestCase):
|
||||||
self.assertContains(response, 'id="login-form"')
|
self.assertContains(response, 'id="login-form"')
|
||||||
|
|
||||||
|
|
||||||
|
class AdminViewsNoUrlTest(TestCase):
|
||||||
|
"""Regression test for #17333"""
|
||||||
|
|
||||||
|
urls = "regressiontests.admin_views.urls"
|
||||||
|
fixtures = ['admin-views-users.xml']
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
opts = Report._meta
|
||||||
|
# User who can change Reports
|
||||||
|
change_user = User.objects.get(username='changeuser')
|
||||||
|
change_user.user_permissions.add(get_perm(Report,
|
||||||
|
opts.get_change_permission()))
|
||||||
|
|
||||||
|
# login POST dict
|
||||||
|
self.changeuser_login = {
|
||||||
|
REDIRECT_FIELD_NAME: '/test_admin/admin/',
|
||||||
|
LOGIN_FORM_KEY: 1,
|
||||||
|
'username': 'changeuser',
|
||||||
|
'password': 'secret',
|
||||||
|
}
|
||||||
|
|
||||||
|
def test_no_standard_modeladmin_urls(self):
|
||||||
|
"""Admin index views don't break when user's ModelAdmin removes standard urls"""
|
||||||
|
self.client.get('/test_admin/admin/')
|
||||||
|
self.client.post('/test_admin/admin/', self.changeuser_login)
|
||||||
|
r = self.client.get('/test_admin/admin/')
|
||||||
|
# we shouldn' get an 500 error caused by a NoReverseMatch
|
||||||
|
self.assertEqual(r.status_code, 200)
|
||||||
|
self.client.get('/test_admin/admin/logout/')
|
||||||
|
|
||||||
|
|
||||||
class AdminViewDeletedObjectsTest(TestCase):
|
class AdminViewDeletedObjectsTest(TestCase):
|
||||||
urls = "regressiontests.admin_views.urls"
|
urls = "regressiontests.admin_views.urls"
|
||||||
fixtures = ['admin-views-users.xml', 'deleted-objects.xml']
|
fixtures = ['admin-views-users.xml', 'deleted-objects.xml']
|
||||||
|
|
Loading…
Reference in New Issue