diff --git a/tests/regressiontests/admin_changelist/admin.py b/tests/regressiontests/admin_changelist/admin.py new file mode 100644 index 0000000000..9490cd54e2 --- /dev/null +++ b/tests/regressiontests/admin_changelist/admin.py @@ -0,0 +1,67 @@ +from django.core.paginator import Paginator +from django.contrib import admin + +from models import (Child, Parent, Genre, Band, Musician, Group, Quartet, + Membership, ChordsMusician, ChordsBand, Invitation) + +site = admin.AdminSite(name="admin") + +class CustomPaginator(Paginator): + def __init__(self, queryset, page_size, orphans=0, allow_empty_first_page=True): + super(CustomPaginator, self).__init__(queryset, 5, orphans=2, + allow_empty_first_page=allow_empty_first_page) + + +class ParentAdmin(admin.ModelAdmin): + list_filter = ['child__name'] + search_fields = ['child__name'] + + +class ChildAdmin(admin.ModelAdmin): + list_display = ['name', 'parent'] + list_per_page = 10 + + def queryset(self, request): + return super(ChildAdmin, self).queryset(request).select_related("parent__name") + + +class CustomPaginationAdmin(ChildAdmin): + paginator = CustomPaginator + + +class FilteredChildAdmin(admin.ModelAdmin): + list_display = ['name', 'parent'] + list_per_page = 10 + + def queryset(self, request): + return super(FilteredChildAdmin, self).queryset(request).filter( + name__contains='filtered') + + +class BandAdmin(admin.ModelAdmin): + list_filter = ['genres'] + + +class GroupAdmin(admin.ModelAdmin): + list_filter = ['members'] + + +class QuartetAdmin(admin.ModelAdmin): + list_filter = ['members'] + + +class ChordsBandAdmin(admin.ModelAdmin): + list_filter = ['members'] + + +class DynamicListDisplayChildAdmin(admin.ModelAdmin): + list_display = ('name', 'parent') + + def get_list_display(self, request): + my_list_display = list(self.list_display) + if request.user.username == 'noparents': + my_list_display.remove('parent') + + return my_list_display + +site.register(Child, DynamicListDisplayChildAdmin) diff --git a/tests/regressiontests/admin_changelist/tests.py b/tests/regressiontests/admin_changelist/tests.py index 09ec190785..9347dbf382 100644 --- a/tests/regressiontests/admin_changelist/tests.py +++ b/tests/regressiontests/admin_changelist/tests.py @@ -3,7 +3,6 @@ from __future__ import with_statement from django.contrib import admin from django.contrib.admin.options import IncorrectLookupParameters from django.contrib.admin.views.main import ChangeList, SEARCH_VAR, ALL_VAR -from django.core.paginator import Paginator from django.template import Context, Template from django.test import TestCase from django.test.client import RequestFactory @@ -12,8 +11,14 @@ from django.contrib.auth.models import User from models import (Child, Parent, Genre, Band, Musician, Group, Quartet, Membership, ChordsMusician, ChordsBand, Invitation) +from admin import (ChildAdmin, QuartetAdmin, BandAdmin, ChordsBandAdmin, + GroupAdmin, ParentAdmin, DynamicListDisplayChildAdmin, CustomPaginationAdmin, + FilteredChildAdmin, CustomPaginator, site as custom_site) + class ChangeListTests(TestCase): + urls = "regressiontests.admin_changelist.urls" + def setUp(self): self.factory = RequestFactory() @@ -129,11 +134,7 @@ class ChangeListTests(TestCase): new_child = Child.objects.create(name='name %s' % i, parent=new_parent) request = self.factory.get('/child/') - m = ChildAdmin(Child, admin.site) - m.list_display = ['id', 'name', 'parent'] - m.list_display_links = ['id'] - m.list_editable = ['name'] - m.paginator = CustomPaginator + m = CustomPaginationAdmin(Child, admin.site) cl = ChangeList(request, Child, m.list_display, m.list_display_links, m.list_filter, m.date_hierarchy, m.search_fields, @@ -331,7 +332,7 @@ class ChangeListTests(TestCase): return request # Test with user 'noparents' - m = DynamicListDisplayChildAdmin(Child, admin.site) + m = custom_site._registry[Child] request = _mocked_authenticated_request(user_noparents) response = m.changelist_view(request) # XXX - Calling render here to avoid ContentNotRenderedError to be @@ -347,8 +348,11 @@ class ChangeListTests(TestCase): response.render() self.assertContains(response, 'Parent object') + custom_site.unregister(Child) + # Test default implementation - m = ChildAdmin(Child, admin.site) + custom_site.register(Child, ChildAdmin) + m = custom_site._registry[Child] request = _mocked_authenticated_request(user_noparents) response = m.changelist_view(request) # XXX - #15826 @@ -383,57 +387,3 @@ class ChangeListTests(TestCase): cl.get_results(request) self.assertEqual(len(cl.result_list), 10) - -class ParentAdmin(admin.ModelAdmin): - list_filter = ['child__name'] - search_fields = ['child__name'] - - -class ChildAdmin(admin.ModelAdmin): - list_display = ['name', 'parent'] - list_per_page = 10 - - def queryset(self, request): - return super(ChildAdmin, self).queryset(request).select_related("parent__name") - - -class FilteredChildAdmin(admin.ModelAdmin): - list_display = ['name', 'parent'] - list_per_page = 10 - - def queryset(self, request): - return super(FilteredChildAdmin, self).queryset(request).filter( - name__contains='filtered') - - -class CustomPaginator(Paginator): - def __init__(self, queryset, page_size, orphans=0, allow_empty_first_page=True): - super(CustomPaginator, self).__init__(queryset, 5, orphans=2, - allow_empty_first_page=allow_empty_first_page) - - -class BandAdmin(admin.ModelAdmin): - list_filter = ['genres'] - - -class GroupAdmin(admin.ModelAdmin): - list_filter = ['members'] - - -class QuartetAdmin(admin.ModelAdmin): - list_filter = ['members'] - - -class ChordsBandAdmin(admin.ModelAdmin): - list_filter = ['members'] - - -class DynamicListDisplayChildAdmin(admin.ModelAdmin): - list_display = ('name', 'parent') - - def get_list_display(self, request): - my_list_display = list(self.list_display) - if request.user.username == 'noparents': - my_list_display.remove('parent') - - return my_list_display diff --git a/tests/regressiontests/admin_changelist/urls.py b/tests/regressiontests/admin_changelist/urls.py new file mode 100644 index 0000000000..017a0010ae --- /dev/null +++ b/tests/regressiontests/admin_changelist/urls.py @@ -0,0 +1,7 @@ +from django.conf.urls import patterns, include + +import admin + +urlpatterns = patterns('', + (r'^admin/', include(admin.site.urls)), +) diff --git a/tests/regressiontests/admin_inlines/admin.py b/tests/regressiontests/admin_inlines/admin.py new file mode 100644 index 0000000000..6f8076abfa --- /dev/null +++ b/tests/regressiontests/admin_inlines/admin.py @@ -0,0 +1,117 @@ +from django.contrib import admin +from django import forms + +from models import * + +site = admin.AdminSite(name="admin") + + +class BookInline(admin.TabularInline): + model = Author.books.through + + +class AuthorAdmin(admin.ModelAdmin): + inlines = [BookInline] + + +class InnerInline(admin.StackedInline): + model = Inner + can_delete = False + readonly_fields = ('readonly',) # For bug #13174 tests. + + +class HolderAdmin(admin.ModelAdmin): + + class Media: + js = ('my_awesome_admin_scripts.js',) + + +class InnerInline2(admin.StackedInline): + model = Inner2 + + class Media: + js = ('my_awesome_inline_scripts.js',) + + +class InnerInline3(admin.StackedInline): + model = Inner3 + + class Media: + js = ('my_awesome_inline_scripts.js',) + + +class TitleForm(forms.ModelForm): + + def clean(self): + cleaned_data = self.cleaned_data + title1 = cleaned_data.get("title1") + title2 = cleaned_data.get("title2") + if title1 != title2: + raise forms.ValidationError("The two titles must be the same") + return cleaned_data + + +class TitleInline(admin.TabularInline): + model = Title + form = TitleForm + extra = 1 + + +class Inner4StackedInline(admin.StackedInline): + model = Inner4Stacked + + +class Inner4TabularInline(admin.TabularInline): + model = Inner4Tabular + + +class Holder4Admin(admin.ModelAdmin): + inlines = [Inner4StackedInline, Inner4TabularInline] + + +class InlineWeakness(admin.TabularInline): + model = ShoppingWeakness + extra = 1 + + +class QuestionInline(admin.TabularInline): + model = Question + readonly_fields=['call_me'] + + def call_me(self, obj): + return 'Callable in QuestionInline' + + +class PollAdmin(admin.ModelAdmin): + inlines = [QuestionInline] + + def call_me(self, obj): + return 'Callable in PollAdmin' + + +class ChapterInline(admin.TabularInline): + model = Chapter + readonly_fields=['call_me'] + + def call_me(self, obj): + return 'Callable in ChapterInline' + + +class NovelAdmin(admin.ModelAdmin): + inlines = [ChapterInline] + + +site.register(TitleCollection, inlines=[TitleInline]) +# Test bug #12561 and #12778 +# only ModelAdmin media +site.register(Holder, HolderAdmin, inlines=[InnerInline]) +# ModelAdmin and Inline media +site.register(Holder2, HolderAdmin, inlines=[InnerInline2]) +# only Inline media +site.register(Holder3, inlines=[InnerInline3]) + +site.register(Poll, PollAdmin) +site.register(Novel, NovelAdmin) +site.register(Fashionista, inlines=[InlineWeakness]) +site.register(Holder4, Holder4Admin) +site.register(Author, AuthorAdmin) diff --git a/tests/regressiontests/admin_inlines/models.py b/tests/regressiontests/admin_inlines/models.py index 8d9ca7c3e9..f4e58f2f28 100644 --- a/tests/regressiontests/admin_inlines/models.py +++ b/tests/regressiontests/admin_inlines/models.py @@ -3,10 +3,9 @@ Testing of admin inline formsets. """ from django.db import models -from django.contrib import admin from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes import generic -from django import forms + class Parent(models.Model): name = models.CharField(max_length=50) @@ -14,12 +13,14 @@ class Parent(models.Model): def __unicode__(self): return self.name + class Teacher(models.Model): name = models.CharField(max_length=50) def __unicode__(self): return self.name + class Child(models.Model): name = models.CharField(max_length=50) teacher = models.ForeignKey(Teacher) @@ -31,20 +32,15 @@ class Child(models.Model): def __unicode__(self): return u'I am %s, a child of %s' % (self.name, self.parent) + class Book(models.Model): name = models.CharField(max_length=50) + class Author(models.Model): name = models.CharField(max_length=50) books = models.ManyToManyField(Book) -class BookInline(admin.TabularInline): - model = Author.books.through - -class AuthorAdmin(admin.ModelAdmin): - inlines = [BookInline] - -admin.site.register(Author, AuthorAdmin) class Holder(models.Model): dummy = models.IntegerField() @@ -56,12 +52,6 @@ class Inner(models.Model): readonly = models.CharField("Inner readonly label", max_length=1) -class InnerInline(admin.StackedInline): - model = Inner - can_delete = False - readonly_fields = ('readonly',) # For bug #13174 tests. - - class Holder2(models.Model): dummy = models.IntegerField() @@ -70,17 +60,6 @@ class Inner2(models.Model): dummy = models.IntegerField() holder = models.ForeignKey(Holder2) -class HolderAdmin(admin.ModelAdmin): - - class Media: - js = ('my_awesome_admin_scripts.js',) - -class InnerInline2(admin.StackedInline): - model = Inner2 - - class Media: - js = ('my_awesome_inline_scripts.js',) - class Holder3(models.Model): dummy = models.IntegerField() @@ -89,21 +68,6 @@ class Inner3(models.Model): dummy = models.IntegerField() holder = models.ForeignKey(Holder3) -class InnerInline3(admin.StackedInline): - model = Inner3 - - class Media: - js = ('my_awesome_inline_scripts.js',) - -# Test bug #12561 and #12778 -# only ModelAdmin media -admin.site.register(Holder, HolderAdmin, inlines=[InnerInline]) -# ModelAdmin and Inline media -admin.site.register(Holder2, HolderAdmin, inlines=[InnerInline2]) -# only Inline media -admin.site.register(Holder3, inlines=[InnerInline3]) - - # Models for ticket #8190 class Holder4(models.Model): @@ -117,17 +81,6 @@ class Inner4Tabular(models.Model): dummy = models.IntegerField(help_text="Awesome tabular help text is awesome.") holder = models.ForeignKey(Holder4) -class Inner4StackedInline(admin.StackedInline): - model = Inner4Stacked - -class Inner4TabularInline(admin.TabularInline): - model = Inner4Tabular - -class Holder4Admin(admin.ModelAdmin): - inlines = [Inner4StackedInline, Inner4TabularInline] - -admin.site.register(Holder4, Holder4Admin) - # Models for #12749 @@ -145,12 +98,6 @@ class ShoppingWeakness(models.Model): fashionista = models.ForeignKey(Fashionista) item = models.ForeignKey(OutfitItem) -class InlineWeakness(admin.TabularInline): - model = ShoppingWeakness - extra = 1 - -admin.site.register(Fashionista, inlines=[InlineWeakness]) - # Models for #13510 class TitleCollection(models.Model): @@ -161,23 +108,6 @@ class Title(models.Model): title1 = models.CharField(max_length=100) title2 = models.CharField(max_length=100) -class TitleForm(forms.ModelForm): - - def clean(self): - cleaned_data = self.cleaned_data - title1 = cleaned_data.get("title1") - title2 = cleaned_data.get("title2") - if title1 != title2: - raise forms.ValidationError("The two titles must be the same") - return cleaned_data - -class TitleInline(admin.TabularInline): - model = Title - form = TitleForm - extra = 1 - -admin.site.register(TitleCollection, inlines=[TitleInline]) - # Models for #15424 class Poll(models.Model): @@ -186,34 +116,9 @@ class Poll(models.Model): class Question(models.Model): poll = models.ForeignKey(Poll) -class QuestionInline(admin.TabularInline): - model = Question - readonly_fields=['call_me'] - - def call_me(self, obj): - return 'Callable in QuestionInline' - -class PollAdmin(admin.ModelAdmin): - inlines = [QuestionInline] - - def call_me(self, obj): - return 'Callable in PollAdmin' - class Novel(models.Model): name = models.CharField(max_length=40) class Chapter(models.Model): novel = models.ForeignKey(Novel) -class ChapterInline(admin.TabularInline): - model = Chapter - readonly_fields=['call_me'] - - def call_me(self, obj): - return 'Callable in ChapterInline' - -class NovelAdmin(admin.ModelAdmin): - inlines = [ChapterInline] - -admin.site.register(Poll, PollAdmin) -admin.site.register(Novel, NovelAdmin) diff --git a/tests/regressiontests/admin_inlines/tests.py b/tests/regressiontests/admin_inlines/tests.py index 45ecb702af..efa6cf6934 100644 --- a/tests/regressiontests/admin_inlines/tests.py +++ b/tests/regressiontests/admin_inlines/tests.py @@ -3,18 +3,20 @@ from django.contrib.contenttypes.models import ContentType from django.test import TestCase # local test models -from models import (Holder, Inner, InnerInline, Holder2, Inner2, Holder3, +from models import (Holder, Inner, Holder2, Inner2, Holder3, Inner3, Person, OutfitItem, Fashionista, Teacher, Parent, Child) +from admin import InnerInline class TestInline(TestCase): + urls = "regressiontests.admin_inlines.urls" fixtures = ['admin-views-users.xml'] def setUp(self): holder = Holder(dummy=13) holder.save() Inner(dummy=42, holder=holder).save() - self.change_url = '/test_admin/admin/admin_inlines/holder/%i/' % holder.id + self.change_url = '/admin/admin_inlines/holder/%i/' % holder.id result = self.client.login(username='super', password='secret') self.assertEqual(result, True) @@ -36,13 +38,13 @@ class TestInline(TestCase): """Bug #13174.""" holder = Holder.objects.create(dummy=42) inner = Inner.objects.create(holder=holder, dummy=42, readonly='') - response = self.client.get('/test_admin/admin/admin_inlines/holder/%i/' + response = self.client.get('/admin/admin_inlines/holder/%i/' % holder.id) self.assertContains(response, '') def test_many_to_many_inlines(self): "Autogenerated many-to-many inlines are displayed correctly (#13407)" - response = self.client.get('/test_admin/admin/admin_inlines/author/add/') + response = self.client.get('/admin/admin_inlines/author/add/') # The heading for the m2m inline block uses the right text self.assertContains(response, '
Awesome stacked help text is awesome.
', 4) self.assertContains(response, '', 1) class TestInlineMedia(TestCase): + urls = "regressiontests.admin_inlines.urls" fixtures = ['admin-views-users.xml'] def setUp(self): @@ -128,7 +131,7 @@ class TestInlineMedia(TestCase): holder = Holder(dummy=13) holder.save() Inner(dummy=42, holder=holder).save() - change_url = '/test_admin/admin/admin_inlines/holder/%i/' % holder.id + change_url = '/admin/admin_inlines/holder/%i/' % holder.id response = self.client.get(change_url) self.assertContains(response, 'my_awesome_admin_scripts.js') @@ -136,7 +139,7 @@ class TestInlineMedia(TestCase): holder = Holder3(dummy=13) holder.save() Inner3(dummy=42, holder=holder).save() - change_url = '/test_admin/admin/admin_inlines/holder3/%i/' % holder.id + change_url = '/admin/admin_inlines/holder3/%i/' % holder.id response = self.client.get(change_url) self.assertContains(response, 'my_awesome_inline_scripts.js') @@ -144,12 +147,13 @@ class TestInlineMedia(TestCase): holder = Holder2(dummy=13) holder.save() Inner2(dummy=42, holder=holder).save() - change_url = '/test_admin/admin/admin_inlines/holder2/%i/' % holder.id + change_url = '/admin/admin_inlines/holder2/%i/' % holder.id response = self.client.get(change_url) self.assertContains(response, 'my_awesome_admin_scripts.js') self.assertContains(response, 'my_awesome_inline_scripts.js') class TestInlineAdminForm(TestCase): + urls = "regressiontests.admin_inlines.urls" def test_immutable_content_type(self): """Regression for #9362 diff --git a/tests/regressiontests/admin_inlines/urls.py b/tests/regressiontests/admin_inlines/urls.py new file mode 100644 index 0000000000..017a0010ae --- /dev/null +++ b/tests/regressiontests/admin_inlines/urls.py @@ -0,0 +1,7 @@ +from django.conf.urls import patterns, include + +import admin + +urlpatterns = patterns('', + (r'^admin/', include(admin.site.urls)), +) diff --git a/tests/regressiontests/admin_views/admin.py b/tests/regressiontests/admin_views/admin.py new file mode 100644 index 0000000000..e4aae4fed9 --- /dev/null +++ b/tests/regressiontests/admin_views/admin.py @@ -0,0 +1,526 @@ +# -*- coding: utf-8 -*- +import datetime +import tempfile +import os + +from django.contrib import admin +from django.contrib.admin.views.main import ChangeList +from django.forms.models import BaseModelFormSet +from django.core.mail import EmailMessage + +from models import * + + +def callable_year(dt_value): + return dt_value.year +callable_year.admin_order_field = 'date' + + +class ArticleInline(admin.TabularInline): + model = Article + + +class ChapterInline(admin.TabularInline): + model = Chapter + + +class ChapterXtra1Admin(admin.ModelAdmin): + list_filter = ('chap', + 'chap__title', + 'chap__book', + 'chap__book__name', + 'chap__book__promo', + 'chap__book__promo__name',) + + +class ArticleAdmin(admin.ModelAdmin): + list_display = ('content', 'date', callable_year, 'model_year', 'modeladmin_year') + list_filter = ('date', 'section') + + def changelist_view(self, request): + "Test that extra_context works" + return super(ArticleAdmin, self).changelist_view( + request, extra_context={ + 'extra_var': 'Hello!' + } + ) + + def modeladmin_year(self, obj): + return obj.date.year + modeladmin_year.admin_order_field = 'date' + modeladmin_year.short_description = None + + def delete_model(self, request, obj): + EmailMessage( + 'Greetings from a deleted object', + 'I hereby inform you that some user deleted me', + 'from@example.com', + ['to@example.com'] + ).send() + return super(ArticleAdmin, self).delete_model(request, obj) + + def save_model(self, request, obj, form, change=True): + EmailMessage( + 'Greetings from a created object', + 'I hereby inform you that some user created me', + 'from@example.com', + ['to@example.com'] + ).send() + return super(ArticleAdmin, self).save_model(request, obj, form, change) + + +class RowLevelChangePermissionModelAdmin(admin.ModelAdmin): + def has_change_permission(self, request, obj=None): + """ Only allow changing objects with even id number """ + return request.user.is_staff and (obj is not None) and (obj.id % 2 == 0) + + +class CustomArticleAdmin(admin.ModelAdmin): + """ + Tests various hooks for using custom templates and contexts. + """ + change_list_template = 'custom_admin/change_list.html' + change_form_template = 'custom_admin/change_form.html' + add_form_template = 'custom_admin/add_form.html' + object_history_template = 'custom_admin/object_history.html' + delete_confirmation_template = 'custom_admin/delete_confirmation.html' + delete_selected_confirmation_template = 'custom_admin/delete_selected_confirmation.html' + + def changelist_view(self, request): + "Test that extra_context works" + return super(CustomArticleAdmin, self).changelist_view( + request, extra_context={ + 'extra_var': 'Hello!' + } + ) + + +class ThingAdmin(admin.ModelAdmin): + list_filter = ('color__warm', 'color__value') + + +class InquisitionAdmin(admin.ModelAdmin): + list_display = ('leader', 'country', 'expected') + + +class SketchAdmin(admin.ModelAdmin): + raw_id_fields = ('inquisition',) + + +class FabricAdmin(admin.ModelAdmin): + list_display = ('surface',) + list_filter = ('surface',) + + +class BasePersonModelFormSet(BaseModelFormSet): + def clean(self): + for person_dict in self.cleaned_data: + person = person_dict.get('id') + alive = person_dict.get('alive') + if person and alive and person.name == "Grace Hopper": + raise forms.ValidationError("Grace is not a Zombie") + + +class PersonAdmin(admin.ModelAdmin): + list_display = ('name', 'gender', 'alive') + list_editable = ('gender', 'alive') + list_filter = ('gender',) + search_fields = ('^name',) + save_as = True + + def get_changelist_formset(self, request, **kwargs): + return super(PersonAdmin, self).get_changelist_formset(request, + formset=BasePersonModelFormSet, **kwargs) + + def queryset(self, request): + # Order by a field that isn't in list display, to be able to test + # whether ordering is preserved. + return super(PersonAdmin, self).queryset(request).order_by('age') + + +class FooAccount(Account): + """A service-specific account of type Foo.""" + servicename = u'foo' + + +class BarAccount(Account): + """A service-specific account of type Bar.""" + servicename = u'bar' + + +class FooAccountAdmin(admin.StackedInline): + model = FooAccount + extra = 1 + + +class BarAccountAdmin(admin.StackedInline): + model = BarAccount + extra = 1 + + +class PersonaAdmin(admin.ModelAdmin): + inlines = ( + FooAccountAdmin, + BarAccountAdmin + ) + + +class SubscriberAdmin(admin.ModelAdmin): + actions = ['mail_admin'] + + def mail_admin(self, request, selected): + EmailMessage( + 'Greetings from a ModelAdmin action', + 'This is the test email from a admin action', + 'from@example.com', + ['to@example.com'] + ).send() + + +def external_mail(modeladmin, request, selected): + EmailMessage( + 'Greetings from a function action', + 'This is the test email from a function action', + 'from@example.com', + ['to@example.com'] + ).send() +external_mail.short_description = 'External mail (Another awesome action)' + + +def redirect_to(modeladmin, request, selected): + from django.http import HttpResponseRedirect + return HttpResponseRedirect('/some-where-else/') +redirect_to.short_description = 'Redirect to (Awesome action)' + + +class ExternalSubscriberAdmin(admin.ModelAdmin): + actions = [redirect_to, external_mail] + + +class Podcast(Media): + release_date = models.DateField() + + class Meta: + ordering = ('release_date',) # overridden in PodcastAdmin + + +class PodcastAdmin(admin.ModelAdmin): + list_display = ('name', 'release_date') + list_editable = ('release_date',) + date_hierarchy = 'release_date' + ordering = ('name',) + + +class VodcastAdmin(admin.ModelAdmin): + list_display = ('name', 'released') + list_editable = ('released',) + + ordering = ('name',) + + +class ChildInline(admin.StackedInline): + model = Child + + +class ParentAdmin(admin.ModelAdmin): + model = Parent + inlines = [ChildInline] + + list_editable = ('name',) + + def save_related(self, request, form, formsets, change): + super(ParentAdmin, self).save_related(request, form, formsets, change) + first_name, last_name = form.instance.name.split() + for child in form.instance.child_set.all(): + if len(child.name.split()) < 2: + child.name = child.name + ' ' + last_name + child.save() + + +class EmptyModelAdmin(admin.ModelAdmin): + def queryset(self, request): + return super(EmptyModelAdmin, self).queryset(request).filter(pk__gt=1) + + +class OldSubscriberAdmin(admin.ModelAdmin): + actions = None + + +temp_storage = FileSystemStorage(tempfile.mkdtemp()) +UPLOAD_TO = os.path.join(temp_storage.location, 'test_upload') + + +class PictureInline(admin.TabularInline): + model = Picture + extra = 1 + + +class GalleryAdmin(admin.ModelAdmin): + inlines = [PictureInline] + + +class PictureAdmin(admin.ModelAdmin): + pass + + +class LanguageAdmin(admin.ModelAdmin): + list_display = ['iso', 'shortlist', 'english_name', 'name'] + list_editable = ['shortlist'] + + +class RecommendationAdmin(admin.ModelAdmin): + search_fields = ('=titletranslation__text', '=recommender__titletranslation__text',) + + +class WidgetInline(admin.StackedInline): + model = Widget + + +class DooHickeyInline(admin.StackedInline): + model = DooHickey + + +class GrommetInline(admin.StackedInline): + model = Grommet + + +class WhatsitInline(admin.StackedInline): + model = Whatsit + + +class FancyDoodadInline(admin.StackedInline): + model = FancyDoodad + + +class CategoryAdmin(admin.ModelAdmin): + list_display = ('id', 'collector', 'order') + list_editable = ('order',) + + +class CategoryInline(admin.StackedInline): + model = Category + + +class CollectorAdmin(admin.ModelAdmin): + inlines = [ + WidgetInline, DooHickeyInline, GrommetInline, WhatsitInline, + FancyDoodadInline, CategoryInline + ] + + +class LinkInline(admin.TabularInline): + model = Link + extra = 1 + + readonly_fields = ("posted",) + + +class SubPostInline(admin.TabularInline): + model = PrePopulatedSubPost + + prepopulated_fields = { + 'subslug' : ('subtitle',) + } + + def get_readonly_fields(self, request, obj=None): + if obj and obj.published: + return ('subslug',) + return self.readonly_fields + + def get_prepopulated_fields(self, request, obj=None): + if obj and obj.published: + return {} + return self.prepopulated_fields + + +class PrePopulatedPostAdmin(admin.ModelAdmin): + list_display = ['title', 'slug'] + prepopulated_fields = { + 'slug' : ('title',) + } + + inlines = [SubPostInline] + + def get_readonly_fields(self, request, obj=None): + if obj and obj.published: + return ('slug',) + return self.readonly_fields + + def get_prepopulated_fields(self, request, obj=None): + if obj and obj.published: + return {} + return self.prepopulated_fields + + +class PostAdmin(admin.ModelAdmin): + list_display = ['title', 'public'] + readonly_fields = ('posted', 'awesomeness_level', 'coolness', 'value', lambda obj: "foo") + + inlines = [ + LinkInline + ] + + def coolness(self, instance): + if instance.pk: + return "%d amount of cool." % instance.pk + else: + return "Unkown coolness." + + def value(self, instance): + return 1000 + value.short_description = 'Value in $US' + + +class CustomChangeList(ChangeList): + def get_query_set(self, request): + return self.root_query_set.filter(pk=9999) # Does not exist + + +class GadgetAdmin(admin.ModelAdmin): + def get_changelist(self, request, **kwargs): + return CustomChangeList + + +class PizzaAdmin(admin.ModelAdmin): + readonly_fields = ('toppings',) + + +class WorkHourAdmin(admin.ModelAdmin): + list_display = ('datum', 'employee') + list_filter = ('employee',) + + +class FoodDeliveryAdmin(admin.ModelAdmin): + list_display=('reference', 'driver', 'restaurant') + list_editable = ('driver', 'restaurant') + + +class PaperAdmin(admin.ModelAdmin): + """ + A ModelAdmin with a custom queryset() method that uses only(), to test + verbose_name display in messages shown after adding Paper instances. + """ + + def queryset(self, request): + return super(PaperAdmin, self).queryset(request).only('title') + + +class CoverLetterAdmin(admin.ModelAdmin): + """ + A ModelAdmin with a custom queryset() method that uses only(), to test + verbose_name display in messages shown after adding CoverLetter instances. + Note that the CoverLetter model defines a __unicode__ method. + """ + + def queryset(self, request): + return super(CoverLetterAdmin, self).queryset(request).defer('date_written') + + +class StoryForm(forms.ModelForm): + class Meta: + widgets = {'title': forms.HiddenInput} + + +class StoryAdmin(admin.ModelAdmin): + list_display = ('id', 'title', 'content') + list_display_links = ('title',) # 'id' not in list_display_links + list_editable = ('content', ) + form = StoryForm + ordering = ["-pk"] + + +class OtherStoryAdmin(admin.ModelAdmin): + list_display = ('id', 'title', 'content') + list_display_links = ('title', 'id') # 'id' in list_display_links + list_editable = ('content', ) + ordering = ["-pk"] + + +class ComplexSortedPersonAdmin(admin.ModelAdmin): + list_display = ('name', 'age', 'is_employee', 'colored_name') + ordering = ('name',) + + def colored_name(self, obj): + return '%s' % ('ff00ff', obj.name) + colored_name.allow_tags = True + colored_name.admin_order_field = 'name' + + +class AlbumAdmin(admin.ModelAdmin): + list_filter = ['title'] + + +class WorkHourAdmin(admin.ModelAdmin): + list_display = ('datum', 'employee') + list_filter = ('employee',) + + +site = admin.AdminSite(name="admin") +site.register(Article, ArticleAdmin) +site.register(CustomArticle, CustomArticleAdmin) +site.register(Section, save_as=True, inlines=[ArticleInline]) +site.register(ModelWithStringPrimaryKey) +site.register(Color) +site.register(Thing, ThingAdmin) +site.register(Actor) +site.register(Inquisition, InquisitionAdmin) +site.register(Sketch, SketchAdmin) +site.register(Person, PersonAdmin) +site.register(Persona, PersonaAdmin) +site.register(Subscriber, SubscriberAdmin) +site.register(ExternalSubscriber, ExternalSubscriberAdmin) +site.register(OldSubscriber, OldSubscriberAdmin) +site.register(Podcast, PodcastAdmin) +site.register(Vodcast, VodcastAdmin) +site.register(Parent, ParentAdmin) +site.register(EmptyModel, EmptyModelAdmin) +site.register(Fabric, FabricAdmin) +site.register(Gallery, GalleryAdmin) +site.register(Picture, PictureAdmin) +site.register(Language, LanguageAdmin) +site.register(Recommendation, RecommendationAdmin) +site.register(Recommender) +site.register(Collector, CollectorAdmin) +site.register(Category, CategoryAdmin) +site.register(Post, PostAdmin) +site.register(Gadget, GadgetAdmin) +site.register(Villain) +site.register(SuperVillain) +site.register(Plot) +site.register(PlotDetails) +site.register(CyclicOne) +site.register(CyclicTwo) +site.register(WorkHour, WorkHourAdmin) +site.register(Reservation) +site.register(FoodDelivery, FoodDeliveryAdmin) +site.register(RowLevelChangePermissionModel, RowLevelChangePermissionModelAdmin) +site.register(Paper, PaperAdmin) +site.register(CoverLetter, CoverLetterAdmin) +site.register(Story, StoryAdmin) +site.register(OtherStory, OtherStoryAdmin) + +# We intentionally register Promo and ChapterXtra1 but not Chapter nor ChapterXtra2. +# That way we cover all four cases: +# related ForeignKey object registered in admin +# related ForeignKey object not registered in admin +# related OneToOne object registered in admin +# related OneToOne object not registered in admin +# when deleting Book so as exercise all four troublesome (w.r.t escaping +# and calling force_unicode to avoid problems on Python 2.3) paths through +# contrib.admin.util's get_deleted_objects function. +site.register(Book, inlines=[ChapterInline]) +site.register(Promo) +site.register(ChapterXtra1, ChapterXtra1Admin) +site.register(Pizza, PizzaAdmin) +site.register(Topping) +site.register(Album, AlbumAdmin) +site.register(Question) +site.register(Answer) +site.register(PrePopulatedPost, PrePopulatedPostAdmin) +site.register(ComplexSortedPerson, ComplexSortedPersonAdmin) + +# Register core models we need in our tests +from django.contrib.auth.models import User, Group +from django.contrib.auth.admin import UserAdmin, GroupAdmin +site.register(User, UserAdmin) +site.register(Group, GroupAdmin) diff --git a/tests/regressiontests/admin_views/customadmin.py b/tests/regressiontests/admin_views/customadmin.py index 760e93f0f8..a696e9f565 100644 --- a/tests/regressiontests/admin_views/customadmin.py +++ b/tests/regressiontests/admin_views/customadmin.py @@ -5,7 +5,7 @@ from django.conf.urls import patterns from django.contrib import admin from django.http import HttpResponse -import models, forms +import models, forms, admin as base_admin class Admin2(admin.AdminSite): login_form = forms.CustomAdminAuthenticationForm @@ -29,8 +29,8 @@ class Admin2(admin.AdminSite): site = Admin2(name="admin2") -site.register(models.Article, models.ArticleAdmin) -site.register(models.Section, inlines=[models.ArticleInline]) -site.register(models.Thing, models.ThingAdmin) -site.register(models.Fabric, models.FabricAdmin) -site.register(models.ChapterXtra1, models.ChapterXtra1Admin) +site.register(models.Article, base_admin.ArticleAdmin) +site.register(models.Section, inlines=[base_admin.ArticleInline]) +site.register(models.Thing, base_admin.ThingAdmin) +site.register(models.Fabric, base_admin.FabricAdmin) +site.register(models.ChapterXtra1, base_admin.ChapterXtra1Admin) diff --git a/tests/regressiontests/admin_views/models.py b/tests/regressiontests/admin_views/models.py index 8dc61e34e5..bb8d026e26 100644 --- a/tests/regressiontests/admin_views/models.py +++ b/tests/regressiontests/admin_views/models.py @@ -3,17 +3,14 @@ import datetime import tempfile import os -from django.contrib import admin from django.core.files.storage import FileSystemStorage -from django.contrib.admin.views.main import ChangeList -from django.core.mail import EmailMessage from django.db import models from django import forms -from django.forms.models import BaseModelFormSet from django.contrib.auth.models import User from django.contrib.contenttypes import generic from django.contrib.contenttypes.models import ContentType + class Section(models.Model): """ A simple section that links to articles, to test linking to related items @@ -21,6 +18,7 @@ class Section(models.Model): """ name = models.CharField(max_length=100) + class Article(models.Model): """ A simple article to test admin views. Test backwards compatibility. @@ -38,6 +36,7 @@ class Article(models.Model): model_year.admin_order_field = 'date' model_year.short_description = '' + class Book(models.Model): """ A simple book that has chapters. @@ -47,6 +46,7 @@ class Book(models.Model): def __unicode__(self): return self.name + class Promo(models.Model): name = models.CharField(max_length=100, verbose_name=u'¿Name?') book = models.ForeignKey(Book) @@ -54,6 +54,7 @@ class Promo(models.Model): def __unicode__(self): return self.name + class Chapter(models.Model): title = models.CharField(max_length=100, verbose_name=u'¿Title?') content = models.TextField() @@ -66,6 +67,7 @@ class Chapter(models.Model): # Use a utf-8 bytestring to ensure it works (see #11710) verbose_name = '¿Chapter?' + class ChapterXtra1(models.Model): chap = models.OneToOneField(Chapter, verbose_name=u'¿Chap?') xtra = models.CharField(max_length=100, verbose_name=u'¿Xtra?') @@ -73,6 +75,7 @@ class ChapterXtra1(models.Model): def __unicode__(self): return u'¿Xtra1: %s' % self.xtra + class ChapterXtra2(models.Model): chap = models.OneToOneField(Chapter, verbose_name=u'¿Chap?') xtra = models.CharField(max_length=100, verbose_name=u'¿Xtra?') @@ -80,89 +83,15 @@ class ChapterXtra2(models.Model): def __unicode__(self): return u'¿Xtra2: %s' % self.xtra -def callable_year(dt_value): - return dt_value.year -callable_year.admin_order_field = 'date' - -class ArticleInline(admin.TabularInline): - model = Article - -class ChapterInline(admin.TabularInline): - model = Chapter - -class ChapterXtra1Admin(admin.ModelAdmin): - list_filter = ('chap', - 'chap__title', - 'chap__book', - 'chap__book__name', - 'chap__book__promo', - 'chap__book__promo__name',) - -class ArticleAdmin(admin.ModelAdmin): - list_display = ('content', 'date', callable_year, 'model_year', 'modeladmin_year') - list_filter = ('date', 'section') - - def changelist_view(self, request): - "Test that extra_context works" - return super(ArticleAdmin, self).changelist_view( - request, extra_context={ - 'extra_var': 'Hello!' - } - ) - - def modeladmin_year(self, obj): - return obj.date.year - modeladmin_year.admin_order_field = 'date' - modeladmin_year.short_description = None - - def delete_model(self, request, obj): - EmailMessage( - 'Greetings from a deleted object', - 'I hereby inform you that some user deleted me', - 'from@example.com', - ['to@example.com'] - ).send() - return super(ArticleAdmin, self).delete_model(request, obj) - - def save_model(self, request, obj, form, change=True): - EmailMessage( - 'Greetings from a created object', - 'I hereby inform you that some user created me', - 'from@example.com', - ['to@example.com'] - ).send() - return super(ArticleAdmin, self).save_model(request, obj, form, change) class RowLevelChangePermissionModel(models.Model): name = models.CharField(max_length=100, blank=True) -class RowLevelChangePermissionModelAdmin(admin.ModelAdmin): - def has_change_permission(self, request, obj=None): - """ Only allow changing objects with even id number """ - return request.user.is_staff and (obj is not None) and (obj.id % 2 == 0) class CustomArticle(models.Model): content = models.TextField() date = models.DateTimeField() -class CustomArticleAdmin(admin.ModelAdmin): - """ - Tests various hooks for using custom templates and contexts. - """ - change_list_template = 'custom_admin/change_list.html' - change_form_template = 'custom_admin/change_form.html' - add_form_template = 'custom_admin/add_form.html' - object_history_template = 'custom_admin/object_history.html' - delete_confirmation_template = 'custom_admin/delete_confirmation.html' - delete_selected_confirmation_template = 'custom_admin/delete_selected_confirmation.html' - - def changelist_view(self, request): - "Test that extra_context works" - return super(CustomArticleAdmin, self).changelist_view( - request, extra_context={ - 'extra_var': 'Hello!' - } - ) class ModelWithStringPrimaryKey(models.Model): id = models.CharField(max_length=255, primary_key=True) @@ -170,20 +99,20 @@ class ModelWithStringPrimaryKey(models.Model): def __unicode__(self): return self.id + class Color(models.Model): value = models.CharField(max_length=10) warm = models.BooleanField() def __unicode__(self): return self.value + class Thing(models.Model): title = models.CharField(max_length=20) color = models.ForeignKey(Color, limit_choices_to={'warm': True}) def __unicode__(self): return self.title -class ThingAdmin(admin.ModelAdmin): - list_filter = ('color__warm', 'color__value') class Actor(models.Model): name = models.CharField(max_length=50) @@ -191,6 +120,7 @@ class Actor(models.Model): def __unicode__(self): return self.name + class Inquisition(models.Model): expected = models.BooleanField() leader = models.ForeignKey(Actor) @@ -199,8 +129,6 @@ class Inquisition(models.Model): def __unicode__(self): return u"by %s from %s" % (self.leader, self.country) -class InquisitionAdmin(admin.ModelAdmin): - list_display = ('leader', 'country', 'expected') class Sketch(models.Model): title = models.CharField(max_length=100) @@ -212,8 +140,6 @@ class Sketch(models.Model): def __unicode__(self): return self.title -class SketchAdmin(admin.ModelAdmin): - raw_id_fields = ('inquisition',) class Fabric(models.Model): NG_CHOICES = ( @@ -226,9 +152,6 @@ class Fabric(models.Model): ) surface = models.CharField(max_length=20, choices=NG_CHOICES) -class FabricAdmin(admin.ModelAdmin): - list_display = ('surface',) - list_filter = ('surface',) class Person(models.Model): GENDER_CHOICES = ( @@ -243,30 +166,6 @@ class Person(models.Model): def __unicode__(self): return self.name -class BasePersonModelFormSet(BaseModelFormSet): - def clean(self): - for person_dict in self.cleaned_data: - person = person_dict.get('id') - alive = person_dict.get('alive') - if person and alive and person.name == "Grace Hopper": - raise forms.ValidationError("Grace is not a Zombie") - -class PersonAdmin(admin.ModelAdmin): - list_display = ('name', 'gender', 'alive') - list_editable = ('gender', 'alive') - list_filter = ('gender',) - search_fields = ('^name',) - save_as = True - - def get_changelist_formset(self, request, **kwargs): - return super(PersonAdmin, self).get_changelist_formset(request, - formset=BasePersonModelFormSet, **kwargs) - - def queryset(self, request): - # Order by a field that isn't in list display, to be able to test - # whether ordering is preserved. - return super(PersonAdmin, self).queryset(request).order_by('age') - class Persona(models.Model): """ @@ -277,6 +176,7 @@ class Persona(models.Model): def __unicode__(self): return self.name + class Account(models.Model): """ A simple, generic account encapsulating the information shared by all @@ -289,27 +189,16 @@ class Account(models.Model): def __unicode__(self): return "%s: %s" % (self.servicename, self.username) + class FooAccount(Account): """A service-specific account of type Foo.""" servicename = u'foo' + class BarAccount(Account): """A service-specific account of type Bar.""" servicename = u'bar' -class FooAccountAdmin(admin.StackedInline): - model = FooAccount - extra = 1 - -class BarAccountAdmin(admin.StackedInline): - model = BarAccount - extra = 1 - -class PersonaAdmin(admin.ModelAdmin): - inlines = ( - FooAccountAdmin, - BarAccountAdmin - ) class Subscriber(models.Model): name = models.CharField(blank=False, max_length=80) @@ -318,120 +207,58 @@ class Subscriber(models.Model): def __unicode__(self): return "%s (%s)" % (self.name, self.email) -class SubscriberAdmin(admin.ModelAdmin): - actions = ['mail_admin'] - - def mail_admin(self, request, selected): - EmailMessage( - 'Greetings from a ModelAdmin action', - 'This is the test email from a admin action', - 'from@example.com', - ['to@example.com'] - ).send() class ExternalSubscriber(Subscriber): pass + class OldSubscriber(Subscriber): pass -def external_mail(modeladmin, request, selected): - EmailMessage( - 'Greetings from a function action', - 'This is the test email from a function action', - 'from@example.com', - ['to@example.com'] - ).send() -external_mail.short_description = 'External mail (Another awesome action)' - -def redirect_to(modeladmin, request, selected): - from django.http import HttpResponseRedirect - return HttpResponseRedirect('/some-where-else/') -redirect_to.short_description = 'Redirect to (Awesome action)' - -class ExternalSubscriberAdmin(admin.ModelAdmin): - actions = [redirect_to, external_mail] class Media(models.Model): name = models.CharField(max_length=60) + class Podcast(Media): release_date = models.DateField() class Meta: ordering = ('release_date',) # overridden in PodcastAdmin -class PodcastAdmin(admin.ModelAdmin): - list_display = ('name', 'release_date') - list_editable = ('release_date',) - date_hierarchy = 'release_date' - ordering = ('name',) class Vodcast(Media): media = models.OneToOneField(Media, primary_key=True, parent_link=True) released = models.BooleanField(default=False) -class VodcastAdmin(admin.ModelAdmin): - list_display = ('name', 'released') - list_editable = ('released',) - - ordering = ('name',) class Parent(models.Model): name = models.CharField(max_length=128) + class Child(models.Model): parent = models.ForeignKey(Parent, editable=False) name = models.CharField(max_length=30, blank=True) -class ChildInline(admin.StackedInline): - model = Child - -class ParentAdmin(admin.ModelAdmin): - model = Parent - inlines = [ChildInline] - - list_editable = ('name',) - - def save_related(self, request, form, formsets, change): - super(ParentAdmin, self).save_related(request, form, formsets, change) - first_name, last_name = form.instance.name.split() - for child in form.instance.child_set.all(): - if len(child.name.split()) < 2: - child.name = child.name + ' ' + last_name - child.save() class EmptyModel(models.Model): def __unicode__(self): return "Primary key = %s" % self.id -class EmptyModelAdmin(admin.ModelAdmin): - def queryset(self, request): - return super(EmptyModelAdmin, self).queryset(request).filter(pk__gt=1) - -class OldSubscriberAdmin(admin.ModelAdmin): - actions = None temp_storage = FileSystemStorage(tempfile.mkdtemp()) UPLOAD_TO = os.path.join(temp_storage.location, 'test_upload') + class Gallery(models.Model): name = models.CharField(max_length=100) + class Picture(models.Model): name = models.CharField(max_length=100) image = models.FileField(storage=temp_storage, upload_to='test_upload') gallery = models.ForeignKey(Gallery, related_name="pictures") -class PictureInline(admin.TabularInline): - model = Picture - extra = 1 - -class GalleryAdmin(admin.ModelAdmin): - inlines = [PictureInline] - -class PictureAdmin(admin.ModelAdmin): - pass class Language(models.Model): iso = models.CharField(max_length=5, primary_key=True) @@ -442,70 +269,60 @@ class Language(models.Model): class Meta: ordering = ('iso',) -class LanguageAdmin(admin.ModelAdmin): - list_display = ['iso', 'shortlist', 'english_name', 'name'] - list_editable = ['shortlist'] # a base class for Recommender and Recommendation class Title(models.Model): pass + class TitleTranslation(models.Model): title = models.ForeignKey(Title) text = models.CharField(max_length=100) + class Recommender(Title): pass + class Recommendation(Title): recommender = models.ForeignKey(Recommender) -class RecommendationAdmin(admin.ModelAdmin): - search_fields = ('=titletranslation__text', '=recommender__titletranslation__text',) class Collector(models.Model): name = models.CharField(max_length=100) + class Widget(models.Model): owner = models.ForeignKey(Collector) name = models.CharField(max_length=100) + class DooHickey(models.Model): code = models.CharField(max_length=10, primary_key=True) owner = models.ForeignKey(Collector) name = models.CharField(max_length=100) + class Grommet(models.Model): code = models.AutoField(primary_key=True) owner = models.ForeignKey(Collector) name = models.CharField(max_length=100) + class Whatsit(models.Model): index = models.IntegerField(primary_key=True) owner = models.ForeignKey(Collector) name = models.CharField(max_length=100) + class Doodad(models.Model): name = models.CharField(max_length=100) + class FancyDoodad(Doodad): owner = models.ForeignKey(Collector) expensive = models.BooleanField(default=True) -class WidgetInline(admin.StackedInline): - model = Widget - -class DooHickeyInline(admin.StackedInline): - model = DooHickey - -class GrommetInline(admin.StackedInline): - model = Grommet - -class WhatsitInline(admin.StackedInline): - model = Whatsit - -class FancyDoodadInline(admin.StackedInline): - model = FancyDoodad class Category(models.Model): collector = models.ForeignKey(Collector) @@ -517,18 +334,6 @@ class Category(models.Model): def __unicode__(self): return u'%s:o%s' % (self.id, self.order) -class CategoryAdmin(admin.ModelAdmin): - list_display = ('id', 'collector', 'order') - list_editable = ('order',) - -class CategoryInline(admin.StackedInline): - model = Category - -class CollectorAdmin(admin.ModelAdmin): - inlines = [ - WidgetInline, DooHickeyInline, GrommetInline, WhatsitInline, - FancyDoodadInline, CategoryInline - ] class Link(models.Model): posted = models.DateField( @@ -538,57 +343,17 @@ class Link(models.Model): post = models.ForeignKey("Post") -class LinkInline(admin.TabularInline): - model = Link - extra = 1 - - readonly_fields = ("posted",) - - class PrePopulatedPost(models.Model): title = models.CharField(max_length=100) published = models.BooleanField() slug = models.SlugField() + class PrePopulatedSubPost(models.Model): post = models.ForeignKey(PrePopulatedPost) subtitle = models.CharField(max_length=100) subslug = models.SlugField() -class SubPostInline(admin.TabularInline): - model = PrePopulatedSubPost - - prepopulated_fields = { - 'subslug' : ('subtitle',) - } - - def get_readonly_fields(self, request, obj=None): - if obj and obj.published: - return ('subslug',) - return self.readonly_fields - - def get_prepopulated_fields(self, request, obj=None): - if obj and obj.published: - return {} - return self.prepopulated_fields - -class PrePopulatedPostAdmin(admin.ModelAdmin): - list_display = ['title', 'slug'] - prepopulated_fields = { - 'slug' : ('title',) - } - - inlines = [SubPostInline] - - def get_readonly_fields(self, request, obj=None): - if obj and obj.published: - return ('slug',) - return self.readonly_fields - - def get_prepopulated_fields(self, request, obj=None): - if obj and obj.published: - return {} - return self.prepopulated_fields class Post(models.Model): title = models.CharField(max_length=100, help_text="Some help text for the title (with unicode ŠĐĆŽćžšđ)") @@ -602,23 +367,6 @@ class Post(models.Model): def awesomeness_level(self): return "Very awesome." -class PostAdmin(admin.ModelAdmin): - list_display = ['title', 'public'] - readonly_fields = ('posted', 'awesomeness_level', 'coolness', 'value', lambda obj: "foo") - - inlines = [ - LinkInline - ] - - def coolness(self, instance): - if instance.pk: - return "%d amount of cool." % instance.pk - else: - return "Unkown coolness." - - def value(self, instance): - return 1000 - value.short_description = 'Value in $US' class Gadget(models.Model): name = models.CharField(max_length=100) @@ -626,13 +374,6 @@ class Gadget(models.Model): def __unicode__(self): return self.name -class CustomChangeList(ChangeList): - def get_query_set(self, request): - return self.root_query_set.filter(pk=9999) # Does not exist - -class GadgetAdmin(admin.ModelAdmin): - def get_changelist(self, request, **kwargs): - return CustomChangeList class Villain(models.Model): name = models.CharField(max_length=100) @@ -640,9 +381,11 @@ class Villain(models.Model): def __unicode__(self): return self.name + class SuperVillain(Villain): pass + class FunkyTag(models.Model): "Because we all know there's only one real use case for GFKs." name = models.CharField(max_length=25) @@ -653,6 +396,7 @@ class FunkyTag(models.Model): def __unicode__(self): return self.name + class Plot(models.Model): name = models.CharField(max_length=100) team_leader = models.ForeignKey(Villain, related_name='lead_plots') @@ -662,6 +406,7 @@ class Plot(models.Model): def __unicode__(self): return self.name + class PlotDetails(models.Model): details = models.CharField(max_length=100) plot = models.OneToOneField(Plot) @@ -669,6 +414,7 @@ class PlotDetails(models.Model): def __unicode__(self): return self.details + class SecretHideout(models.Model): """ Secret! Not registered with the admin! """ location = models.CharField(max_length=100) @@ -677,6 +423,7 @@ class SecretHideout(models.Model): def __unicode__(self): return self.location + class SuperSecretHideout(models.Model): """ Secret! Not registered with the admin! """ location = models.CharField(max_length=100) @@ -685,6 +432,7 @@ class SuperSecretHideout(models.Model): def __unicode__(self): return self.location + class CyclicOne(models.Model): name = models.CharField(max_length=25) two = models.ForeignKey('CyclicTwo') @@ -692,6 +440,7 @@ class CyclicOne(models.Model): def __unicode__(self): return self.name + class CyclicTwo(models.Model): name = models.CharField(max_length=25) one = models.ForeignKey(CyclicOne) @@ -699,37 +448,34 @@ class CyclicTwo(models.Model): def __unicode__(self): return self.name + class Topping(models.Model): name = models.CharField(max_length=20) + class Pizza(models.Model): name = models.CharField(max_length=20) toppings = models.ManyToManyField('Topping') -class PizzaAdmin(admin.ModelAdmin): - readonly_fields = ('toppings',) class Album(models.Model): owner = models.ForeignKey(User) title = models.CharField(max_length=30) -class AlbumAdmin(admin.ModelAdmin): - list_filter = ['title'] class Employee(Person): code = models.CharField(max_length=20) + class WorkHour(models.Model): datum = models.DateField() employee = models.ForeignKey(Employee) -class WorkHourAdmin(admin.ModelAdmin): - list_display = ('datum', 'employee') - list_filter = ('employee',) class Question(models.Model): question = models.CharField(max_length=20) + class Answer(models.Model): question = models.ForeignKey(Question, on_delete=models.PROTECT) answer = models.CharField(max_length=20) @@ -737,6 +483,7 @@ class Answer(models.Model): def __unicode__(self): return self.answer + class Reservation(models.Model): start_date = models.DateTimeField() price = models.IntegerField() @@ -753,6 +500,7 @@ RESTAURANT_CHOICES = ( (u'pizza', u'Pizza Mama'), ) + class FoodDelivery(models.Model): reference = models.CharField(max_length=100) driver = models.CharField(max_length=100, choices=DRIVER_CHOICES, blank=True) @@ -761,14 +509,12 @@ class FoodDelivery(models.Model): class Meta: unique_together = (("driver", "restaurant"),) -class FoodDeliveryAdmin(admin.ModelAdmin): - list_display=('reference', 'driver', 'restaurant') - list_editable = ('driver', 'restaurant') class Paper(models.Model): title = models.CharField(max_length=30) author = models.CharField(max_length=30, blank=True, null=True) + class CoverLetter(models.Model): author = models.CharField(max_length=30) date_written = models.DateField(null=True, blank=True) @@ -776,123 +522,19 @@ class CoverLetter(models.Model): def __unicode__(self): return self.author -class PaperAdmin(admin.ModelAdmin): - """ - A ModelAdin with a custom queryset() method that uses only(), to test - verbose_name display in messages shown after adding Paper instances. - """ - - def queryset(self, request): - return super(PaperAdmin, self).queryset(request).only('title') - -class CoverLetterAdmin(admin.ModelAdmin): - """ - A ModelAdin with a custom queryset() method that uses only(), to test - verbose_name display in messages shown after adding CoverLetter instances. - Note that the CoverLetter model defines a __unicode__ method. - """ - - def queryset(self, request): - return super(CoverLetterAdmin, self).queryset(request).defer('date_written') class Story(models.Model): title = models.CharField(max_length=100) content = models.TextField() -class StoryForm(forms.ModelForm): - class Meta: - widgets = {'title': forms.HiddenInput} - -class StoryAdmin(admin.ModelAdmin): - list_display = ('id', 'title', 'content') - list_display_links = ('title',) # 'id' not in list_display_links - list_editable = ('content', ) - form = StoryForm - ordering = ["-pk"] class OtherStory(models.Model): title = models.CharField(max_length=100) content = models.TextField() -class OtherStoryAdmin(admin.ModelAdmin): - list_display = ('id', 'title', 'content') - list_display_links = ('title', 'id') # 'id' in list_display_links - list_editable = ('content', ) - ordering = ["-pk"] class ComplexSortedPerson(models.Model): name = models.CharField(max_length=100) age = models.PositiveIntegerField() is_employee = models.NullBooleanField() -class ComplexSortedPersonAdmin(admin.ModelAdmin): - list_display = ('name', 'age', 'is_employee', 'colored_name') - ordering = ('name',) - - def colored_name(self, obj): - return '%s' % ('ff00ff', obj.name) - colored_name.allow_tags = True - colored_name.admin_order_field = 'name' - -admin.site.register(Article, ArticleAdmin) -admin.site.register(CustomArticle, CustomArticleAdmin) -admin.site.register(Section, save_as=True, inlines=[ArticleInline]) -admin.site.register(ModelWithStringPrimaryKey) -admin.site.register(Color) -admin.site.register(Thing, ThingAdmin) -admin.site.register(Actor) -admin.site.register(Inquisition, InquisitionAdmin) -admin.site.register(Sketch, SketchAdmin) -admin.site.register(Person, PersonAdmin) -admin.site.register(Persona, PersonaAdmin) -admin.site.register(Subscriber, SubscriberAdmin) -admin.site.register(ExternalSubscriber, ExternalSubscriberAdmin) -admin.site.register(OldSubscriber, OldSubscriberAdmin) -admin.site.register(Podcast, PodcastAdmin) -admin.site.register(Vodcast, VodcastAdmin) -admin.site.register(Parent, ParentAdmin) -admin.site.register(EmptyModel, EmptyModelAdmin) -admin.site.register(Fabric, FabricAdmin) -admin.site.register(Gallery, GalleryAdmin) -admin.site.register(Picture, PictureAdmin) -admin.site.register(Language, LanguageAdmin) -admin.site.register(Recommendation, RecommendationAdmin) -admin.site.register(Recommender) -admin.site.register(Collector, CollectorAdmin) -admin.site.register(Category, CategoryAdmin) -admin.site.register(Post, PostAdmin) -admin.site.register(Gadget, GadgetAdmin) -admin.site.register(Villain) -admin.site.register(SuperVillain) -admin.site.register(Plot) -admin.site.register(PlotDetails) -admin.site.register(CyclicOne) -admin.site.register(CyclicTwo) -admin.site.register(WorkHour, WorkHourAdmin) -admin.site.register(Reservation) -admin.site.register(FoodDelivery, FoodDeliveryAdmin) -admin.site.register(RowLevelChangePermissionModel, RowLevelChangePermissionModelAdmin) -admin.site.register(Paper, PaperAdmin) -admin.site.register(CoverLetter, CoverLetterAdmin) -admin.site.register(Story, StoryAdmin) -admin.site.register(OtherStory, OtherStoryAdmin) - -# We intentionally register Promo and ChapterXtra1 but not Chapter nor ChapterXtra2. -# That way we cover all four cases: -# related ForeignKey object registered in admin -# related ForeignKey object not registered in admin -# related OneToOne object registered in admin -# related OneToOne object not registered in admin -# when deleting Book so as exercise all four troublesome (w.r.t escaping -# and calling force_unicode to avoid problems on Python 2.3) paths through -# contrib.admin.util's get_deleted_objects function. -admin.site.register(Book, inlines=[ChapterInline]) -admin.site.register(Promo) -admin.site.register(ChapterXtra1, ChapterXtra1Admin) -admin.site.register(Pizza, PizzaAdmin) -admin.site.register(Topping) -admin.site.register(Album, AlbumAdmin) -admin.site.register(Question) -admin.site.register(Answer) -admin.site.register(PrePopulatedPost, PrePopulatedPostAdmin) -admin.site.register(ComplexSortedPerson, ComplexSortedPersonAdmin) diff --git a/tests/regressiontests/admin_views/tests.py b/tests/regressiontests/admin_views/tests.py index 22b65a6cb8..668c71076e 100644 --- a/tests/regressiontests/admin_views/tests.py +++ b/tests/regressiontests/admin_views/tests.py @@ -32,7 +32,7 @@ from django.utils import unittest # local test models from models import (Article, BarAccount, CustomArticle, EmptyModel, - FooAccount, Gallery, PersonAdmin, ModelWithStringPrimaryKey, + FooAccount, Gallery, ModelWithStringPrimaryKey, Person, Persona, Picture, Podcast, Section, Subscriber, Vodcast, Language, Collector, Widget, Grommet, DooHickey, FancyDoodad, Whatsit, Category, Post, Plot, FunkyTag, Chapter, Book, Promo, WorkHour, Employee, @@ -50,6 +50,8 @@ class AdminViewBasicTest(TestCase): # this test case and changing urlbit. urlbit = 'admin' + urls = "regressiontests.admin_views.urls" + def setUp(self): self.old_USE_I18N = settings.USE_I18N self.old_USE_L10N = settings.USE_L10N @@ -301,7 +303,7 @@ class AdminViewBasicTest(TestCase): response.content.index(link % l1.pk) < response.content.index(link % l2.pk) ) - def testChangeListSortingModelAdmin(self): + def testChangeListSortingOverrideModelAdmin(self): # Test ordering on Model Admin is respected, and overrides Model Meta dt = datetime.datetime.now() p1 = Podcast.objects.create(name="A", release_date=dt) @@ -542,6 +544,8 @@ class AdminViewBasicTest(TestCase): self.fail("Filters should be allowed if they are defined on a ForeignKey pointing to this model") class AdminJavaScriptTest(AdminViewBasicTest): + urls = "regressiontests.admin_views.urls" + def testSingleWidgetFirsFieldFocus(self): """ JavaScript-assisted auto-focus on first field. @@ -565,6 +569,7 @@ class AdminJavaScriptTest(AdminViewBasicTest): class SaveAsTests(TestCase): + urls = "regressiontests.admin_views.urls" fixtures = ['admin-views-users.xml','admin-views-person.xml'] def setUp(self): @@ -593,6 +598,7 @@ class SaveAsTests(TestCase): self.assertEqual(response.context['form_url'], '../add/') class CustomModelAdminTest(AdminViewBasicTest): + urls = "regressiontests.admin_views.urls" urlbit = "admin2" def testCustomAdminSiteLoginForm(self): @@ -654,6 +660,7 @@ def get_perm(Model, perm): class AdminViewPermissionsTest(TestCase): """Tests for Admin Views Permissions.""" + urls = "regressiontests.admin_views.urls" fixtures = ['admin-views-users.xml'] def setUp(self): @@ -1055,6 +1062,7 @@ class AdminViewPermissionsTest(TestCase): class AdminViewDeletedObjectsTest(TestCase): + urls = "regressiontests.admin_views.urls" fixtures = ['admin-views-users.xml', 'deleted-objects.xml'] def setUp(self): @@ -1170,6 +1178,7 @@ class AdminViewDeletedObjectsTest(TestCase): self.assertContains(response, should_contain) class AdminViewStringPrimaryKeyTest(TestCase): + urls = "regressiontests.admin_views.urls" fixtures = ['admin-views-users.xml', 'string-primary-key.xml'] def __init__(self, *args): @@ -1261,6 +1270,7 @@ class AdminViewStringPrimaryKeyTest(TestCase): class SecureViewTests(TestCase): + urls = "regressiontests.admin_views.urls" fixtures = ['admin-views-users.xml'] def setUp(self): @@ -1418,6 +1428,7 @@ class SecureViewTests(TestCase): self.assertEqual(response['Location'], 'http://example.com/users/super/') class AdminViewUnicodeTest(TestCase): + urls = "regressiontests.admin_views.urls" fixtures = ['admin-views-unicode.xml'] def setUp(self): @@ -1471,6 +1482,7 @@ class AdminViewUnicodeTest(TestCase): class AdminViewListEditable(TestCase): + urls = "regressiontests.admin_views.urls" fixtures = ['admin-views-users.xml', 'admin-views-person.xml'] def setUp(self): @@ -1827,6 +1839,7 @@ class AdminViewListEditable(TestCase): class AdminSearchTest(TestCase): + urls = "regressiontests.admin_views.urls" fixtures = ['admin-views-users', 'multiple-child-classes', 'admin-views-person'] @@ -1873,6 +1886,7 @@ class AdminSearchTest(TestCase): class AdminInheritedInlinesTest(TestCase): + urls = "regressiontests.admin_views.urls" fixtures = ['admin-views-users.xml',] def setUp(self): @@ -1958,6 +1972,7 @@ class AdminInheritedInlinesTest(TestCase): self.assertEqual(Persona.objects.all()[0].accounts.count(), 2) class AdminActionsTest(TestCase): + urls = "regressiontests.admin_views.urls" fixtures = ['admin-views-users.xml', 'admin-views-actions.xml'] def setUp(self): @@ -2179,6 +2194,7 @@ class AdminActionsTest(TestCase): class TestCustomChangeList(TestCase): + urls = "regressiontests.admin_views.urls" fixtures = ['admin-views-users.xml'] urlbit = 'admin' @@ -2206,6 +2222,7 @@ class TestCustomChangeList(TestCase): class TestInlineNotEditable(TestCase): + urls = "regressiontests.admin_views.urls" fixtures = ['admin-views-users.xml'] def setUp(self): @@ -2223,6 +2240,7 @@ class TestInlineNotEditable(TestCase): self.assertEqual(response.status_code, 200) class AdminCustomQuerysetTest(TestCase): + urls = "regressiontests.admin_views.urls" fixtures = ['admin-views-users.xml'] def setUp(self): @@ -2277,6 +2295,7 @@ class AdminCustomQuerysetTest(TestCase): self.assertContains(response, '