diff --git a/django/contrib/admin/sites.py b/django/contrib/admin/sites.py index 3a5c12b70a..83a08699c2 100644 --- a/django/contrib/admin/sites.py +++ b/django/contrib/admin/sites.py @@ -7,7 +7,7 @@ from django.views.decorators.csrf import csrf_protect from django.db.models.base import ModelBase 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.utils.safestring import mark_safe from django.utils.text import capfirst @@ -342,10 +342,18 @@ def index(self, request, extra_context=None): info = (app_label, model._meta.module_name) model_dict = { '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, } + 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: app_dict[app_label]['models'].append(model_dict) else: @@ -388,10 +396,18 @@ def app_index(self, request, app_label, extra_context=None): info = (app_label, model._meta.module_name) model_dict = { '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, } + 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: app_dict['models'].append(model_dict), else: diff --git a/django/contrib/admin/templates/admin/index.html b/django/contrib/admin/templates/admin/index.html index b301edc5ba..a8ced39121 100644 --- a/django/contrib/admin/templates/admin/index.html +++ b/django/contrib/admin/templates/admin/index.html @@ -19,19 +19,19 @@ {% blocktrans with name=app.name %}{{ name }}{% endblocktrans %} {% for model in app.models %} - {% if model.perms.change %} + {% if model.admin_url %} {{ model.name }} {% else %} {{ model.name }} {% endif %} - {% if model.perms.add %} + {% if model.add_url %} {% trans 'Add' %} {% else %}   {% endif %} - {% if model.perms.change %} + {% if model.admin_url %} {% trans 'Change' %} {% else %}   diff --git a/tests/regressiontests/admin_views/admin.py b/tests/regressiontests/admin_views/admin.py index 514538fb6d..8c5b511d1e 100644 --- a/tests/regressiontests/admin_views/admin.py +++ b/tests/regressiontests/admin_views/admin.py @@ -1,7 +1,6 @@ # -*- coding: utf-8 -*- from __future__ import absolute_import -import datetime import tempfile import os @@ -10,8 +9,10 @@ from django.contrib.admin.views.main import ChangeList from django.core.files.storage import FileSystemStorage from django.core.mail import EmailMessage +from django.conf.urls import patterns, url from django.db import models from django.forms.models import BaseModelFormSet +from django.http import HttpResponse from .models import (Article, Chapter, Account, Media, Child, Parent, Picture, Widget, DooHickey, Grommet, Whatsit, FancyDoodad, Category, Link, @@ -24,7 +25,7 @@ CoverLetter, Story, OtherStory, Book, Promo, ChapterXtra1, Pizza, Topping, Album, Question, Answer, ComplexSortedPerson, PrePopulatedPostLargeSlug, AdminOrderedField, AdminOrderedModelMethod, AdminOrderedAdminMethod, - AdminOrderedCallable) + AdminOrderedCallable, Report) def callable_year(dt_value): @@ -499,6 +500,17 @@ class AdminOrderedCallableAdmin(admin.ModelAdmin): ordering = ('order',) 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.register(Article, ArticleAdmin) @@ -543,6 +555,7 @@ class AdminOrderedCallableAdmin(admin.ModelAdmin): site.register(CoverLetter, CoverLetterAdmin) site.register(Story, StoryAdmin) site.register(OtherStory, OtherStoryAdmin) +site.register(Report, ReportAdmin) # We intentionally register Promo and ChapterXtra1 but not Chapter nor ChapterXtra2. # That way we cover all four cases: diff --git a/tests/regressiontests/admin_views/models.py b/tests/regressiontests/admin_views/models.py index eb56f9b6e3..0029fcc152 100644 --- a/tests/regressiontests/admin_views/models.py +++ b/tests/regressiontests/admin_views/models.py @@ -566,3 +566,9 @@ class AdminOrderedAdminMethod(models.Model): class AdminOrderedCallable(models.Model): order = models.IntegerField() stuff = models.CharField(max_length=200) + +class Report(models.Model): + title = models.CharField(max_length=100) + + def __unicode__(self): + return self.title diff --git a/tests/regressiontests/admin_views/tests.py b/tests/regressiontests/admin_views/tests.py index 756597c70f..6037c2b187 100644 --- a/tests/regressiontests/admin_views/tests.py +++ b/tests/regressiontests/admin_views/tests.py @@ -38,7 +38,8 @@ Book, Promo, WorkHour, Employee, Question, Answer, Inquisition, Actor, FoodDelivery, RowLevelChangePermissionModel, Paper, CoverLetter, Story, OtherStory, ComplexSortedPerson, Parent, Child, AdminOrderedField, - AdminOrderedModelMethod, AdminOrderedAdminMethod, AdminOrderedCallable) + AdminOrderedModelMethod, AdminOrderedAdminMethod, AdminOrderedCallable, + Report) ERROR_MESSAGE = "Please enter the correct username and password \ @@ -1090,6 +1091,37 @@ def testDisabledPermissionsWhenLoggedIn(self): 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): urls = "regressiontests.admin_views.urls" fixtures = ['admin-views-users.xml', 'deleted-objects.xml']