123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440 |
- import datetime
- from django.contrib import admin
- from django.contrib.admin.models import LogEntry
- from django.contrib.admin.options import IncorrectLookupParameters
- from django.contrib.admin.templatetags.admin_list import pagination
- from django.contrib.admin.tests import AdminSeleniumTestCase
- from django.contrib.admin.views.main import ALL_VAR, SEARCH_VAR
- from django.contrib.auth.models import User
- from django.contrib.contenttypes.models import ContentType
- from django.contrib.messages.storage.cookie import CookieStorage
- from django.db import connection, models
- from django.db.models import F, Field, IntegerField
- from django.db.models.functions import Upper
- from django.db.models.lookups import Contains, Exact
- from django.template import Context, Template, TemplateSyntaxError
- from django.test import TestCase, override_settings
- from django.test.client import RequestFactory
- from django.test.utils import (
- CaptureQueriesContext, isolate_apps, register_lookup,
- )
- from django.urls import reverse
- from django.utils import formats
- from .admin import (
- BandAdmin, ChildAdmin, ChordsBandAdmin, ConcertAdmin,
- CustomPaginationAdmin, CustomPaginator, DynamicListDisplayChildAdmin,
- DynamicListDisplayLinksChildAdmin, DynamicListFilterChildAdmin,
- DynamicSearchFieldsChildAdmin, EmptyValueChildAdmin, EventAdmin,
- FilteredChildAdmin, GroupAdmin, InvitationAdmin,
- NoListDisplayLinksParentAdmin, ParentAdmin, QuartetAdmin, SwallowAdmin,
- site as custom_site,
- )
- from .models import (
- Band, CharPK, Child, ChordsBand, ChordsMusician, Concert, CustomIdUser,
- Event, Genre, Group, Invitation, Membership, Musician, OrderedObject,
- Parent, Quartet, Swallow, SwallowOneToOne, UnorderedObject,
- )
- def build_tbody_html(pk, href, extra_fields):
- return (
- '<tbody><tr>'
- '<td class="action-checkbox">'
- '<input type="checkbox" name="_selected_action" value="{}" '
- 'class="action-select"></td>'
- '<th class="field-name"><a href="{}">name</a></th>'
- '{}</tr></tbody>'
- ).format(pk, href, extra_fields)
- @override_settings(ROOT_URLCONF="admin_changelist.urls")
- class ChangeListTests(TestCase):
- factory = RequestFactory()
- @classmethod
- def setUpTestData(cls):
- cls.superuser = User.objects.create_superuser(username='super', email='a@b.com', password='xxx')
- def _create_superuser(self, username):
- return User.objects.create_superuser(username=username, email='a@b.com', password='xxx')
- def _mocked_authenticated_request(self, url, user):
- request = self.factory.get(url)
- request.user = user
- return request
- def test_specified_ordering_by_f_expression(self):
- class OrderedByFBandAdmin(admin.ModelAdmin):
- list_display = ['name', 'genres', 'nr_of_members']
- ordering = (
- F('nr_of_members').desc(nulls_last=True),
- Upper(F('name')).asc(),
- F('genres').asc(),
- )
- m = OrderedByFBandAdmin(Band, custom_site)
- request = self.factory.get('/band/')
- request.user = self.superuser
- cl = m.get_changelist_instance(request)
- self.assertEqual(cl.get_ordering_field_columns(), {3: 'desc', 2: 'asc'})
- def test_specified_ordering_by_f_expression_without_asc_desc(self):
- class OrderedByFBandAdmin(admin.ModelAdmin):
- list_display = ['name', 'genres', 'nr_of_members']
- ordering = (F('nr_of_members'), Upper('name'), F('genres'))
- m = OrderedByFBandAdmin(Band, custom_site)
- request = self.factory.get('/band/')
- request.user = self.superuser
- cl = m.get_changelist_instance(request)
- self.assertEqual(cl.get_ordering_field_columns(), {3: 'asc', 2: 'asc'})
- def test_select_related_preserved(self):
- """
- Regression test for #10348: ChangeList.get_queryset() shouldn't
- overwrite a custom select_related provided by ModelAdmin.get_queryset().
- """
- m = ChildAdmin(Child, custom_site)
- request = self.factory.get('/child/')
- request.user = self.superuser
- cl = m.get_changelist_instance(request)
- self.assertEqual(cl.queryset.query.select_related, {'parent': {}})
- def test_select_related_as_tuple(self):
- ia = InvitationAdmin(Invitation, custom_site)
- request = self.factory.get('/invitation/')
- request.user = self.superuser
- cl = ia.get_changelist_instance(request)
- self.assertEqual(cl.queryset.query.select_related, {'player': {}})
- def test_select_related_as_empty_tuple(self):
- ia = InvitationAdmin(Invitation, custom_site)
- ia.list_select_related = ()
- request = self.factory.get('/invitation/')
- request.user = self.superuser
- cl = ia.get_changelist_instance(request)
- self.assertIs(cl.queryset.query.select_related, False)
- def test_get_select_related_custom_method(self):
- class GetListSelectRelatedAdmin(admin.ModelAdmin):
- list_display = ('band', 'player')
- def get_list_select_related(self, request):
- return ('band', 'player')
- ia = GetListSelectRelatedAdmin(Invitation, custom_site)
- request = self.factory.get('/invitation/')
- request.user = self.superuser
- cl = ia.get_changelist_instance(request)
- self.assertEqual(cl.queryset.query.select_related, {'player': {}, 'band': {}})
- def test_result_list_empty_changelist_value(self):
- """
- Regression test for #14982: EMPTY_CHANGELIST_VALUE should be honored
- for relationship fields
- """
- new_child = Child.objects.create(name='name', parent=None)
- request = self.factory.get('/child/')
- request.user = self.superuser
- m = ChildAdmin(Child, custom_site)
- cl = m.get_changelist_instance(request)
- cl.formset = None
- template = Template('{% load admin_list %}{% spaceless %}{% result_list cl %}{% endspaceless %}')
- context = Context({'cl': cl, 'opts': Child._meta})
- table_output = template.render(context)
- link = reverse('admin:admin_changelist_child_change', args=(new_child.id,))
- row_html = build_tbody_html(new_child.id, link, '<td class="field-parent nowrap">-</td>')
- self.assertNotEqual(table_output.find(row_html), -1, 'Failed to find expected row element: %s' % table_output)
- def test_result_list_set_empty_value_display_on_admin_site(self):
- """
- Empty value display can be set on AdminSite.
- """
- new_child = Child.objects.create(name='name', parent=None)
- request = self.factory.get('/child/')
- request.user = self.superuser
-
- admin.site.empty_value_display = '???'
- m = ChildAdmin(Child, admin.site)
- cl = m.get_changelist_instance(request)
- cl.formset = None
- template = Template('{% load admin_list %}{% spaceless %}{% result_list cl %}{% endspaceless %}')
- context = Context({'cl': cl, 'opts': Child._meta})
- table_output = template.render(context)
- link = reverse('admin:admin_changelist_child_change', args=(new_child.id,))
- row_html = build_tbody_html(new_child.id, link, '<td class="field-parent nowrap">???</td>')
- self.assertNotEqual(table_output.find(row_html), -1, 'Failed to find expected row element: %s' % table_output)
- def test_result_list_set_empty_value_display_in_model_admin(self):
- """
- Empty value display can be set in ModelAdmin or individual fields.
- """
- new_child = Child.objects.create(name='name', parent=None)
- request = self.factory.get('/child/')
- request.user = self.superuser
- m = EmptyValueChildAdmin(Child, admin.site)
- cl = m.get_changelist_instance(request)
- cl.formset = None
- template = Template('{% load admin_list %}{% spaceless %}{% result_list cl %}{% endspaceless %}')
- context = Context({'cl': cl, 'opts': Child._meta})
- table_output = template.render(context)
- link = reverse('admin:admin_changelist_child_change', args=(new_child.id,))
- row_html = build_tbody_html(
- new_child.id,
- link,
- '<td class="field-age_display">&dagger;</td>'
- '<td class="field-age">-empty-</td>'
- )
- self.assertNotEqual(table_output.find(row_html), -1, 'Failed to find expected row element: %s' % table_output)
- def test_result_list_html(self):
- """
- Inclusion tag result_list generates a table when with default
- ModelAdmin settings.
- """
- new_parent = Parent.objects.create(name='parent')
- new_child = Child.objects.create(name='name', parent=new_parent)
- request = self.factory.get('/child/')
- request.user = self.superuser
- m = ChildAdmin(Child, custom_site)
- cl = m.get_changelist_instance(request)
- cl.formset = None
- template = Template('{% load admin_list %}{% spaceless %}{% result_list cl %}{% endspaceless %}')
- context = Context({'cl': cl, 'opts': Child._meta})
- table_output = template.render(context)
- link = reverse('admin:admin_changelist_child_change', args=(new_child.id,))
- row_html = build_tbody_html(new_child.id, link, '<td class="field-parent nowrap">%s</td>' % new_parent)
- self.assertNotEqual(table_output.find(row_html), -1, 'Failed to find expected row element: %s' % table_output)
- def test_result_list_editable_html(self):
- """
- Regression tests for #11791: Inclusion tag result_list generates a
- table and this checks that the items are nested within the table
- element tags.
- Also a regression test for #13599, verifies that hidden fields
- when list_editable is enabled are rendered in a div outside the
- table.
- """
- new_parent = Parent.objects.create(name='parent')
- new_child = Child.objects.create(name='name', parent=new_parent)
- request = self.factory.get('/child/')
- request.user = self.superuser
- m = ChildAdmin(Child, custom_site)
-
- m.list_display = ['id', 'name', 'parent']
- m.list_display_links = ['id']
- m.list_editable = ['name']
- cl = m.get_changelist_instance(request)
- FormSet = m.get_changelist_formset(request)
- cl.formset = FormSet(queryset=cl.result_list)
- template = Template('{% load admin_list %}{% spaceless %}{% result_list cl %}{% endspaceless %}')
- context = Context({'cl': cl, 'opts': Child._meta})
- table_output = template.render(context)
-
- hiddenfields_div = (
- '<div class="hiddenfields">'
- '<input type="hidden" name="form-0-id" value="%d" id="id_form-0-id">'
- '</div>'
- ) % new_child.id
- self.assertInHTML(hiddenfields_div, table_output, msg_prefix='Failed to find hidden fields')
-
- editable_name_field = (
- '<input name="form-0-name" value="name" class="vTextField" '
- 'maxlength="30" type="text" id="id_form-0-name">'
- )
- self.assertInHTML(
- '<td class="field-name">%s</td>' % editable_name_field,
- table_output,
- msg_prefix='Failed to find "name" list_editable field',
- )
- def test_result_list_editable(self):
- """
- Regression test for #14312: list_editable with pagination
- """
- new_parent = Parent.objects.create(name='parent')
- for i in range(200):
- Child.objects.create(name='name %s' % i, parent=new_parent)
- request = self.factory.get('/child/', data={'p': -1})
- request.user = self.superuser
- m = ChildAdmin(Child, custom_site)
-
- m.list_display = ['id', 'name', 'parent']
- m.list_display_links = ['id']
- m.list_editable = ['name']
- with self.assertRaises(IncorrectLookupParameters):
- m.get_changelist_instance(request)
- def test_custom_paginator(self):
- new_parent = Parent.objects.create(name='parent')
- for i in range(200):
- Child.objects.create(name='name %s' % i, parent=new_parent)
- request = self.factory.get('/child/')
- request.user = self.superuser
- m = CustomPaginationAdmin(Child, custom_site)
- cl = m.get_changelist_instance(request)
- cl.get_results(request)
- self.assertIsInstance(cl.paginator, CustomPaginator)
- def test_distinct_for_m2m_in_list_filter(self):
- """
- Regression test for #13902: When using a ManyToMany in list_filter,
- results shouldn't appear more than once. Basic ManyToMany.
- """
- blues = Genre.objects.create(name='Blues')
- band = Band.objects.create(name='B.B. King Review', nr_of_members=11)
- band.genres.add(blues)
- band.genres.add(blues)
- m = BandAdmin(Band, custom_site)
- request = self.factory.get('/band/', data={'genres': blues.pk})
- request.user = self.superuser
- cl = m.get_changelist_instance(request)
- cl.get_results(request)
-
- self.assertEqual(cl.result_count, 1)
- def test_distinct_for_through_m2m_in_list_filter(self):
- """
- Regression test for #13902: When using a ManyToMany in list_filter,
- results shouldn't appear more than once. With an intermediate model.
- """
- lead = Musician.objects.create(name='Vox')
- band = Group.objects.create(name='The Hype')
- Membership.objects.create(group=band, music=lead, role='lead voice')
- Membership.objects.create(group=band, music=lead, role='bass player')
- m = GroupAdmin(Group, custom_site)
- request = self.factory.get('/group/', data={'members': lead.pk})
- request.user = self.superuser
- cl = m.get_changelist_instance(request)
- cl.get_results(request)
-
- self.assertEqual(cl.result_count, 1)
- def test_distinct_for_through_m2m_at_second_level_in_list_filter(self):
- """
- When using a ManyToMany in list_filter at the second level behind a
- ForeignKey, distinct() must be called and results shouldn't appear more
- than once.
- """
- lead = Musician.objects.create(name='Vox')
- band = Group.objects.create(name='The Hype')
- Concert.objects.create(name='Woodstock', group=band)
- Membership.objects.create(group=band, music=lead, role='lead voice')
- Membership.objects.create(group=band, music=lead, role='bass player')
- m = ConcertAdmin(Concert, custom_site)
- request = self.factory.get('/concert/', data={'group__members': lead.pk})
- request.user = self.superuser
- cl = m.get_changelist_instance(request)
- cl.get_results(request)
-
- self.assertEqual(cl.result_count, 1)
- def test_distinct_for_inherited_m2m_in_list_filter(self):
- """
- Regression test for #13902: When using a ManyToMany in list_filter,
- results shouldn't appear more than once. Model managed in the
- admin inherits from the one that defines the relationship.
- """
- lead = Musician.objects.create(name='John')
- four = Quartet.objects.create(name='The Beatles')
- Membership.objects.create(group=four, music=lead, role='lead voice')
- Membership.objects.create(group=four, music=lead, role='guitar player')
- m = QuartetAdmin(Quartet, custom_site)
- request = self.factory.get('/quartet/', data={'members': lead.pk})
- request.user = self.superuser
- cl = m.get_changelist_instance(request)
- cl.get_results(request)
-
- self.assertEqual(cl.result_count, 1)
- def test_distinct_for_m2m_to_inherited_in_list_filter(self):
- """
- Regression test for #13902: When using a ManyToMany in list_filter,
- results shouldn't appear more than once. Target of the relationship
- inherits from another.
- """
- lead = ChordsMusician.objects.create(name='Player A')
- three = ChordsBand.objects.create(name='The Chords Trio')
- Invitation.objects.create(band=three, player=lead, instrument='guitar')
- Invitation.objects.create(band=three, player=lead, instrument='bass')
- m = ChordsBandAdmin(ChordsBand, custom_site)
- request = self.factory.get('/chordsband/', data={'members': lead.pk})
- request.user = self.superuser
- cl = m.get_changelist_instance(request)
- cl.get_results(request)
-
- self.assertEqual(cl.result_count, 1)
- def test_distinct_for_non_unique_related_object_in_list_filter(self):
- """
- Regressions tests for #15819: If a field listed in list_filters
- is a non-unique related object, distinct() must be called.
- """
- parent = Parent.objects.create(name='Mary')
-
- Child.objects.create(parent=parent, name='Daniel')
- Child.objects.create(parent=parent, name='Daniel')
- m = ParentAdmin(Parent, custom_site)
- request = self.factory.get('/parent/', data={'child__name': 'Daniel'})
- request.user = self.superuser
- cl = m.get_changelist_instance(request)
-
- self.assertEqual(cl.queryset.count(), 1)
- def test_changelist_search_form_validation(self):
- m = ConcertAdmin(Concert, custom_site)
- tests = [
- ({SEARCH_VAR: '\x00'}, 'Null characters are not allowed.'),
- ({SEARCH_VAR: 'some\x00thing'}, 'Null characters are not allowed.'),
- ]
- for case, error in tests:
- with self.subTest(case=case):
- request = self.factory.get('/concert/', case)
- request.user = self.superuser
- request._messages = CookieStorage(request)
- m.get_changelist_instance(request)
- messages = [m.message for m in request._messages]
- self.assertEqual(1, len(messages))
- self.assertEqual(error, messages[0])
- def test_distinct_for_non_unique_related_object_in_search_fields(self):
- """
- Regressions tests for #15819: If a field listed in search_fields
- is a non-unique related object, distinct() must be called.
- """
- parent = Parent.objects.create(name='Mary')
- Child.objects.create(parent=parent, name='Danielle')
- Child.objects.create(parent=parent, name='Daniel')
- m = ParentAdmin(Parent, custom_site)
- request = self.factory.get('/parent/', data={SEARCH_VAR: 'daniel'})
- request.user = self.superuser
- cl = m.get_changelist_instance(request)
-
- self.assertEqual(cl.queryset.count(), 1)
- def test_distinct_for_many_to_many_at_second_level_in_search_fields(self):
- """
- When using a ManyToMany in search_fields at the second level behind a
- ForeignKey, distinct() must be called and results shouldn't appear more
- than once.
- """
- lead = Musician.objects.create(name='Vox')
- band = Group.objects.create(name='The Hype')
- Concert.objects.create(name='Woodstock', group=band)
- Membership.objects.create(group=band, music=lead, role='lead voice')
- Membership.objects.create(group=band, music=lead, role='bass player')
- m = ConcertAdmin(Concert, custom_site)
- request = self.factory.get('/concert/', data={SEARCH_VAR: 'vox'})
- request.user = self.superuser
- cl = m.get_changelist_instance(request)
-
- self.assertEqual(cl.queryset.count(), 1)
- def test_pk_in_search_fields(self):
- band = Group.objects.create(name='The Hype')
- Concert.objects.create(name='Woodstock', group=band)
- m = ConcertAdmin(Concert, custom_site)
- m.search_fields = ['group__pk']
- request = self.factory.get('/concert/', data={SEARCH_VAR: band.pk})
- request.user = self.superuser
- cl = m.get_changelist_instance(request)
- self.assertEqual(cl.queryset.count(), 1)
- request = self.factory.get('/concert/', data={SEARCH_VAR: band.pk + 5})
- request.user = self.superuser
- cl = m.get_changelist_instance(request)
- self.assertEqual(cl.queryset.count(), 0)
- def test_builtin_lookup_in_search_fields(self):
- band = Group.objects.create(name='The Hype')
- concert = Concert.objects.create(name='Woodstock', group=band)
- m = ConcertAdmin(Concert, custom_site)
- m.search_fields = ['name__iexact']
- request = self.factory.get('/', data={SEARCH_VAR: 'woodstock'})
- request.user = self.superuser
- cl = m.get_changelist_instance(request)
- self.assertCountEqual(cl.queryset, [concert])
- request = self.factory.get('/', data={SEARCH_VAR: 'wood'})
- request.user = self.superuser
- cl = m.get_changelist_instance(request)
- self.assertCountEqual(cl.queryset, [])
- def test_custom_lookup_in_search_fields(self):
- band = Group.objects.create(name='The Hype')
- concert = Concert.objects.create(name='Woodstock', group=band)
- m = ConcertAdmin(Concert, custom_site)
- m.search_fields = ['group__name__cc']
- with register_lookup(Field, Contains, lookup_name='cc'):
- request = self.factory.get('/', data={SEARCH_VAR: 'Hype'})
- request.user = self.superuser
- cl = m.get_changelist_instance(request)
- self.assertCountEqual(cl.queryset, [concert])
- request = self.factory.get('/', data={SEARCH_VAR: 'Woodstock'})
- request.user = self.superuser
- cl = m.get_changelist_instance(request)
- self.assertCountEqual(cl.queryset, [])
- def test_spanning_relations_with_custom_lookup_in_search_fields(self):
- hype = Group.objects.create(name='The Hype')
- concert = Concert.objects.create(name='Woodstock', group=hype)
- vox = Musician.objects.create(name='Vox', age=20)
- Membership.objects.create(music=vox, group=hype)
-
-
- with register_lookup(IntegerField, Exact, lookup_name='exactly'):
- m = ConcertAdmin(Concert, custom_site)
- m.search_fields = ['group__members__age__exactly']
- request = self.factory.get('/', data={SEARCH_VAR: '20'})
- request.user = self.superuser
- cl = m.get_changelist_instance(request)
- self.assertCountEqual(cl.queryset, [concert])
- request = self.factory.get('/', data={SEARCH_VAR: '21'})
- request.user = self.superuser
- cl = m.get_changelist_instance(request)
- self.assertCountEqual(cl.queryset, [])
- def test_custom_lookup_with_pk_shortcut(self):
- self.assertEqual(CharPK._meta.pk.name, 'char_pk')
- m = admin.ModelAdmin(CustomIdUser, custom_site)
- abc = CharPK.objects.create(char_pk='abc')
- abcd = CharPK.objects.create(char_pk='abcd')
- m = admin.ModelAdmin(CharPK, custom_site)
- m.search_fields = ['pk__exact']
- request = self.factory.get('/', data={SEARCH_VAR: 'abc'})
- request.user = self.superuser
- cl = m.get_changelist_instance(request)
- self.assertCountEqual(cl.queryset, [abc])
- request = self.factory.get('/', data={SEARCH_VAR: 'abcd'})
- request.user = self.superuser
- cl = m.get_changelist_instance(request)
- self.assertCountEqual(cl.queryset, [abcd])
- def test_no_distinct_for_m2m_in_list_filter_without_params(self):
- """
- If a ManyToManyField is in list_filter but isn't in any lookup params,
- the changelist's query shouldn't have distinct.
- """
- m = BandAdmin(Band, custom_site)
- for lookup_params in ({}, {'name': 'test'}):
- request = self.factory.get('/band/', lookup_params)
- request.user = self.superuser
- cl = m.get_changelist_instance(request)
- self.assertFalse(cl.queryset.query.distinct)
-
- request = self.factory.get('/band/', {'genres': '0'})
- request.user = self.superuser
- cl = m.get_changelist_instance(request)
- self.assertTrue(cl.queryset.query.distinct)
- def test_pagination(self):
- """
- Regression tests for #12893: Pagination in admins changelist doesn't
- use queryset set by modeladmin.
- """
- parent = Parent.objects.create(name='anything')
- for i in range(30):
- Child.objects.create(name='name %s' % i, parent=parent)
- Child.objects.create(name='filtered %s' % i, parent=parent)
- request = self.factory.get('/child/')
- request.user = self.superuser
-
- m = ChildAdmin(Child, custom_site)
- cl = m.get_changelist_instance(request)
- self.assertEqual(cl.queryset.count(), 60)
- self.assertEqual(cl.paginator.count, 60)
- self.assertEqual(list(cl.paginator.page_range), [1, 2, 3, 4, 5, 6])
-
- m = FilteredChildAdmin(Child, custom_site)
- cl = m.get_changelist_instance(request)
- self.assertEqual(cl.queryset.count(), 30)
- self.assertEqual(cl.paginator.count, 30)
- self.assertEqual(list(cl.paginator.page_range), [1, 2, 3])
- def test_computed_list_display_localization(self):
- """
- Regression test for #13196: output of functions should be localized
- in the changelist.
- """
- self.client.force_login(self.superuser)
- event = Event.objects.create(date=datetime.date.today())
- response = self.client.get(reverse('admin:admin_changelist_event_changelist'))
- self.assertContains(response, formats.localize(event.date))
- self.assertNotContains(response, str(event.date))
- def test_dynamic_list_display(self):
- """
- Regression tests for #14206: dynamic list_display support.
- """
- parent = Parent.objects.create(name='parent')
- for i in range(10):
- Child.objects.create(name='child %s' % i, parent=parent)
- user_noparents = self._create_superuser('noparents')
- user_parents = self._create_superuser('parents')
-
- m = custom_site._registry[Child]
- request = self._mocked_authenticated_request('/child/', user_noparents)
- response = m.changelist_view(request)
- self.assertNotContains(response, 'Parent object')
- list_display = m.get_list_display(request)
- list_display_links = m.get_list_display_links(request, list_display)
- self.assertEqual(list_display, ['name', 'age'])
- self.assertEqual(list_display_links, ['name'])
-
- m = DynamicListDisplayChildAdmin(Child, custom_site)
- request = self._mocked_authenticated_request('/child/', user_parents)
- response = m.changelist_view(request)
- self.assertContains(response, 'Parent object')
- custom_site.unregister(Child)
- list_display = m.get_list_display(request)
- list_display_links = m.get_list_display_links(request, list_display)
- self.assertEqual(list_display, ('parent', 'name', 'age'))
- self.assertEqual(list_display_links, ['parent'])
-
- custom_site.register(Child, ChildAdmin)
- m = custom_site._registry[Child]
- request = self._mocked_authenticated_request('/child/', user_noparents)
- response = m.changelist_view(request)
- self.assertContains(response, 'Parent object')
- def test_show_all(self):
- parent = Parent.objects.create(name='anything')
- for i in range(30):
- Child.objects.create(name='name %s' % i, parent=parent)
- Child.objects.create(name='filtered %s' % i, parent=parent)
-
- request = self.factory.get('/child/', data={ALL_VAR: ''})
- request.user = self.superuser
-
- m = ChildAdmin(Child, custom_site)
- m.list_max_show_all = 200
-
- cl = m.get_changelist_instance(request)
- cl.get_results(request)
- self.assertEqual(len(cl.result_list), 60)
-
-
- m = ChildAdmin(Child, custom_site)
- m.list_max_show_all = 30
-
- cl = m.get_changelist_instance(request)
- cl.get_results(request)
- self.assertEqual(len(cl.result_list), 10)
- def test_dynamic_list_display_links(self):
- """
- Regression tests for #16257: dynamic list_display_links support.
- """
- parent = Parent.objects.create(name='parent')
- for i in range(1, 10):
- Child.objects.create(id=i, name='child %s' % i, parent=parent, age=i)
- m = DynamicListDisplayLinksChildAdmin(Child, custom_site)
- superuser = self._create_superuser('superuser')
- request = self._mocked_authenticated_request('/child/', superuser)
- response = m.changelist_view(request)
- for i in range(1, 10):
- link = reverse('admin:admin_changelist_child_change', args=(i,))
- self.assertContains(response, '<a href="%s">%s</a>' % (link, i))
- list_display = m.get_list_display(request)
- list_display_links = m.get_list_display_links(request, list_display)
- self.assertEqual(list_display, ('parent', 'name', 'age'))
- self.assertEqual(list_display_links, ['age'])
- def test_no_list_display_links(self):
- """#15185 -- Allow no links from the 'change list' view grid."""
- p = Parent.objects.create(name='parent')
- m = NoListDisplayLinksParentAdmin(Parent, custom_site)
- superuser = self._create_superuser('superuser')
- request = self._mocked_authenticated_request('/parent/', superuser)
- response = m.changelist_view(request)
- link = reverse('admin:admin_changelist_parent_change', args=(p.pk,))
- self.assertNotContains(response, '<a href="%s">' % link)
- def test_clear_all_filters_link(self):
- self.client.force_login(self.superuser)
- link = '<a href="?">✖ Clear all filters</a>'
- response = self.client.get(reverse('admin:auth_user_changelist'))
- self.assertNotContains(response, link)
- for data in (
- {SEARCH_VAR: 'test'},
- {'is_staff__exact': '0'},
- ):
- response = self.client.get(reverse('admin:auth_user_changelist'), data=data)
- self.assertContains(response, link)
- def test_tuple_list_display(self):
- swallow = Swallow.objects.create(origin='Africa', load='12.34', speed='22.2')
- swallow2 = Swallow.objects.create(origin='Africa', load='12.34', speed='22.2')
- swallow_o2o = SwallowOneToOne.objects.create(swallow=swallow2)
- model_admin = SwallowAdmin(Swallow, custom_site)
- superuser = self._create_superuser('superuser')
- request = self._mocked_authenticated_request('/swallow/', superuser)
- response = model_admin.changelist_view(request)
-
- self.assertContains(response, str(swallow.origin))
- self.assertContains(response, str(swallow.load))
- self.assertContains(response, str(swallow.speed))
-
- self.assertContains(response, '<td class="field-swallowonetoone">-</td>')
- self.assertContains(response, '<td class="field-swallowonetoone">%s</td>' % swallow_o2o)
- def test_multiuser_edit(self):
- """
- Simultaneous edits of list_editable fields on the changelist by
- different users must not result in one user's edits creating a new
- object instead of modifying the correct existing object (#11313).
- """
-
-
-
-
-
-
-
-
-
-
-
- a = Swallow.objects.create(origin='Swallow A', load=4, speed=1)
- b = Swallow.objects.create(origin='Swallow B', load=2, speed=2)
- c = Swallow.objects.create(origin='Swallow C', load=5, speed=5)
- d = Swallow.objects.create(origin='Swallow D', load=9, speed=9)
- superuser = self._create_superuser('superuser')
- self.client.force_login(superuser)
- changelist_url = reverse('admin:admin_changelist_swallow_changelist')
-
-
- data = {
- 'form-TOTAL_FORMS': '3',
- 'form-INITIAL_FORMS': '3',
- 'form-MIN_NUM_FORMS': '0',
- 'form-MAX_NUM_FORMS': '1000',
- 'form-0-uuid': str(d.pk),
- 'form-1-uuid': str(c.pk),
- 'form-2-uuid': str(a.pk),
- 'form-0-load': '9.0',
- 'form-0-speed': '9.0',
- 'form-1-load': '5.0',
- 'form-1-speed': '5.0',
- 'form-2-load': '5.0',
- 'form-2-speed': '4.0',
- '_save': 'Save',
- }
- response = self.client.post(changelist_url, data, follow=True, extra={'o': '-2'})
-
-
- self.assertContains(response, '1 swallow was changed successfully.')
- self.assertContains(response, a.origin)
- a.refresh_from_db()
- self.assertEqual(a.load, float(data['form-2-load']))
- self.assertEqual(a.speed, float(data['form-2-speed']))
- b.refresh_from_db()
- self.assertEqual(b.load, 2)
- self.assertEqual(b.speed, 2)
- c.refresh_from_db()
- self.assertEqual(c.load, float(data['form-1-load']))
- self.assertEqual(c.speed, float(data['form-1-speed']))
- d.refresh_from_db()
- self.assertEqual(d.load, float(data['form-0-load']))
- self.assertEqual(d.speed, float(data['form-0-speed']))
-
- self.assertEqual(len(Swallow.objects.all()), 4)
- def test_get_edited_object_ids(self):
- a = Swallow.objects.create(origin='Swallow A', load=4, speed=1)
- b = Swallow.objects.create(origin='Swallow B', load=2, speed=2)
- c = Swallow.objects.create(origin='Swallow C', load=5, speed=5)
- superuser = self._create_superuser('superuser')
- self.client.force_login(superuser)
- changelist_url = reverse('admin:admin_changelist_swallow_changelist')
- m = SwallowAdmin(Swallow, custom_site)
- data = {
- 'form-TOTAL_FORMS': '3',
- 'form-INITIAL_FORMS': '3',
- 'form-MIN_NUM_FORMS': '0',
- 'form-MAX_NUM_FORMS': '1000',
- 'form-0-uuid': str(a.pk),
- 'form-1-uuid': str(b.pk),
- 'form-2-uuid': str(c.pk),
- 'form-0-load': '9.0',
- 'form-0-speed': '9.0',
- 'form-1-load': '5.0',
- 'form-1-speed': '5.0',
- 'form-2-load': '5.0',
- 'form-2-speed': '4.0',
- '_save': 'Save',
- }
- request = self.factory.post(changelist_url, data=data)
- pks = m._get_edited_object_pks(request, prefix='form')
- self.assertEqual(sorted(pks), sorted([str(a.pk), str(b.pk), str(c.pk)]))
- def test_get_list_editable_queryset(self):
- a = Swallow.objects.create(origin='Swallow A', load=4, speed=1)
- Swallow.objects.create(origin='Swallow B', load=2, speed=2)
- data = {
- 'form-TOTAL_FORMS': '2',
- 'form-INITIAL_FORMS': '2',
- 'form-MIN_NUM_FORMS': '0',
- 'form-MAX_NUM_FORMS': '1000',
- 'form-0-uuid': str(a.pk),
- 'form-0-load': '10',
- '_save': 'Save',
- }
- superuser = self._create_superuser('superuser')
- self.client.force_login(superuser)
- changelist_url = reverse('admin:admin_changelist_swallow_changelist')
- m = SwallowAdmin(Swallow, custom_site)
- request = self.factory.post(changelist_url, data=data)
- queryset = m._get_list_editable_queryset(request, prefix='form')
- self.assertEqual(queryset.count(), 1)
- data['form-0-uuid'] = 'INVALD_PRIMARY_KEY'
-
- request = self.factory.post(changelist_url, data=data)
- queryset = m._get_list_editable_queryset(request, prefix='form')
- self.assertEqual(queryset.count(), 2)
- def test_get_list_editable_queryset_with_regex_chars_in_prefix(self):
- a = Swallow.objects.create(origin='Swallow A', load=4, speed=1)
- Swallow.objects.create(origin='Swallow B', load=2, speed=2)
- data = {
- 'form$-TOTAL_FORMS': '2',
- 'form$-INITIAL_FORMS': '2',
- 'form$-MIN_NUM_FORMS': '0',
- 'form$-MAX_NUM_FORMS': '1000',
- 'form$-0-uuid': str(a.pk),
- 'form$-0-load': '10',
- '_save': 'Save',
- }
- superuser = self._create_superuser('superuser')
- self.client.force_login(superuser)
- changelist_url = reverse('admin:admin_changelist_swallow_changelist')
- m = SwallowAdmin(Swallow, custom_site)
- request = self.factory.post(changelist_url, data=data)
- queryset = m._get_list_editable_queryset(request, prefix='form$')
- self.assertEqual(queryset.count(), 1)
- def test_changelist_view_list_editable_changed_objects_uses_filter(self):
- """list_editable edits use a filtered queryset to limit memory usage."""
- a = Swallow.objects.create(origin='Swallow A', load=4, speed=1)
- Swallow.objects.create(origin='Swallow B', load=2, speed=2)
- data = {
- 'form-TOTAL_FORMS': '2',
- 'form-INITIAL_FORMS': '2',
- 'form-MIN_NUM_FORMS': '0',
- 'form-MAX_NUM_FORMS': '1000',
- 'form-0-uuid': str(a.pk),
- 'form-0-load': '10',
- '_save': 'Save',
- }
- superuser = self._create_superuser('superuser')
- self.client.force_login(superuser)
- changelist_url = reverse('admin:admin_changelist_swallow_changelist')
- with CaptureQueriesContext(connection) as context:
- response = self.client.post(changelist_url, data=data)
- self.assertEqual(response.status_code, 200)
- self.assertIn('WHERE', context.captured_queries[4]['sql'])
- self.assertIn('IN', context.captured_queries[4]['sql'])
-
- self.assertIn(str(a.pk)[:8], context.captured_queries[4]['sql'])
- def test_deterministic_order_for_unordered_model(self):
- """
- The primary key is used in the ordering of the changelist's results to
- guarantee a deterministic order, even when the model doesn't have any
- default ordering defined (#17198).
- """
- superuser = self._create_superuser('superuser')
- for counter in range(1, 51):
- UnorderedObject.objects.create(id=counter, bool=True)
- class UnorderedObjectAdmin(admin.ModelAdmin):
- list_per_page = 10
- def check_results_order(ascending=False):
- custom_site.register(UnorderedObject, UnorderedObjectAdmin)
- model_admin = UnorderedObjectAdmin(UnorderedObject, custom_site)
- counter = 0 if ascending else 51
- for page in range(0, 5):
- request = self._mocked_authenticated_request('/unorderedobject/?p=%s' % page, superuser)
- response = model_admin.changelist_view(request)
- for result in response.context_data['cl'].result_list:
- counter += 1 if ascending else -1
- self.assertEqual(result.id, counter)
- custom_site.unregister(UnorderedObject)
-
- check_results_order()
-
-
- UnorderedObjectAdmin.ordering = ['bool']
- check_results_order()
-
- UnorderedObjectAdmin.ordering = ['bool', '-pk']
- check_results_order()
- UnorderedObjectAdmin.ordering = ['bool', 'pk']
- check_results_order(ascending=True)
- UnorderedObjectAdmin.ordering = ['-id', 'bool']
- check_results_order()
- UnorderedObjectAdmin.ordering = ['id', 'bool']
- check_results_order(ascending=True)
- def test_deterministic_order_for_model_ordered_by_its_manager(self):
- """
- The primary key is used in the ordering of the changelist's results to
- guarantee a deterministic order, even when the model has a manager that
- defines a default ordering (#17198).
- """
- superuser = self._create_superuser('superuser')
- for counter in range(1, 51):
- OrderedObject.objects.create(id=counter, bool=True, number=counter)
- class OrderedObjectAdmin(admin.ModelAdmin):
- list_per_page = 10
- def check_results_order(ascending=False):
- custom_site.register(OrderedObject, OrderedObjectAdmin)
- model_admin = OrderedObjectAdmin(OrderedObject, custom_site)
- counter = 0 if ascending else 51
- for page in range(0, 5):
- request = self._mocked_authenticated_request('/orderedobject/?p=%s' % page, superuser)
- response = model_admin.changelist_view(request)
- for result in response.context_data['cl'].result_list:
- counter += 1 if ascending else -1
- self.assertEqual(result.id, counter)
- custom_site.unregister(OrderedObject)
-
- check_results_order(ascending=True)
-
-
- OrderedObjectAdmin.ordering = ['bool']
- check_results_order()
-
- OrderedObjectAdmin.ordering = ['bool', '-pk']
- check_results_order()
- OrderedObjectAdmin.ordering = ['bool', 'pk']
- check_results_order(ascending=True)
- OrderedObjectAdmin.ordering = ['-id', 'bool']
- check_results_order()
- OrderedObjectAdmin.ordering = ['id', 'bool']
- check_results_order(ascending=True)
- @isolate_apps('admin_changelist')
- def test_total_ordering_optimization(self):
- class Related(models.Model):
- unique_field = models.BooleanField(unique=True)
- class Meta:
- ordering = ('unique_field',)
- class Model(models.Model):
- unique_field = models.BooleanField(unique=True)
- unique_nullable_field = models.BooleanField(unique=True, null=True)
- related = models.ForeignKey(Related, models.CASCADE)
- other_related = models.ForeignKey(Related, models.CASCADE)
- related_unique = models.OneToOneField(Related, models.CASCADE)
- field = models.BooleanField()
- other_field = models.BooleanField()
- null_field = models.BooleanField(null=True)
- class Meta:
- unique_together = {
- ('field', 'other_field'),
- ('field', 'null_field'),
- ('related', 'other_related_id'),
- }
- class ModelAdmin(admin.ModelAdmin):
- def get_queryset(self, request):
- return Model.objects.none()
- request = self._mocked_authenticated_request('/', self.superuser)
- site = admin.AdminSite(name='admin')
- model_admin = ModelAdmin(Model, site)
- change_list = model_admin.get_changelist_instance(request)
- tests = (
- ([], ['-pk']),
-
- (['unique_field'], ['unique_field']),
- (['-unique_field'], ['-unique_field']),
-
- (['unique_nullable_field'], ['unique_nullable_field', '-pk']),
-
- (['field'], ['field', '-pk']),
-
- (['related__unique_field'], ['related__unique_field', '-pk']),
-
- (['related_unique_id'], ['related_unique_id']),
-
- (['related_unique'], ['related_unique', '-pk']),
-
- (['field', '-other_field'], ['field', '-other_field']),
-
- (['-field', 'null_field'], ['-field', 'null_field', '-pk']),
-
- (['-field', 'null_field', 'other_field'], ['-field', 'null_field', 'other_field']),
-
- (['related_id', '-other_related_id'], ['related_id', '-other_related_id']),
-
- (['related', '-other_related_id'], ['related', '-other_related_id', '-pk']),
- )
-
- total_ordering = [F('field'), F('other_field').desc(nulls_last=True)]
-
- non_total_ordering = [F('field'), F('null_field').desc(nulls_last=True)]
- tests += (
- (total_ordering, total_ordering),
- (non_total_ordering, non_total_ordering + ['-pk']),
- )
- for ordering, expected in tests:
- with self.subTest(ordering=ordering):
- self.assertEqual(change_list._get_deterministic_ordering(ordering), expected)
- @isolate_apps('admin_changelist')
- def test_total_ordering_optimization_meta_constraints(self):
- class Related(models.Model):
- unique_field = models.BooleanField(unique=True)
- class Meta:
- ordering = ('unique_field',)
- class Model(models.Model):
- field_1 = models.BooleanField()
- field_2 = models.BooleanField()
- field_3 = models.BooleanField()
- field_4 = models.BooleanField()
- field_5 = models.BooleanField()
- field_6 = models.BooleanField()
- nullable_1 = models.BooleanField(null=True)
- nullable_2 = models.BooleanField(null=True)
- related_1 = models.ForeignKey(Related, models.CASCADE)
- related_2 = models.ForeignKey(Related, models.CASCADE)
- related_3 = models.ForeignKey(Related, models.CASCADE)
- related_4 = models.ForeignKey(Related, models.CASCADE)
- class Meta:
- constraints = [
- *[
- models.UniqueConstraint(fields=fields, name=''.join(fields))
- for fields in (
- ['field_1'],
- ['nullable_1'],
- ['related_1'],
- ['related_2_id'],
- ['field_2', 'field_3'],
- ['field_2', 'nullable_2'],
- ['field_2', 'related_3'],
- ['field_3', 'related_4_id'],
- )
- ],
- models.CheckConstraint(check=models.Q(id__gt=0), name='foo'),
- models.UniqueConstraint(
- fields=['field_5'],
- condition=models.Q(id__gt=10),
- name='total_ordering_1',
- ),
- models.UniqueConstraint(
- fields=['field_6'],
- condition=models.Q(),
- name='total_ordering',
- ),
- ]
- class ModelAdmin(admin.ModelAdmin):
- def get_queryset(self, request):
- return Model.objects.none()
- request = self._mocked_authenticated_request('/', self.superuser)
- site = admin.AdminSite(name='admin')
- model_admin = ModelAdmin(Model, site)
- change_list = model_admin.get_changelist_instance(request)
- tests = (
-
- (['field_1'], ['field_1']),
-
- (['nullable_1'], ['nullable_1', '-pk']),
-
- (['related_1_id'], ['related_1_id']),
- (['related_2_id'], ['related_2_id']),
-
- (['related_1'], ['related_1', '-pk']),
-
- (['-field_2', 'field_3'], ['-field_2', 'field_3']),
-
- (['field_2', '-nullable_2'], ['field_2', '-nullable_2', '-pk']),
-
- (
- ['field_2', '-nullable_2', 'field_3'],
- ['field_2', '-nullable_2', 'field_3'],
- ),
-
- (['field_2', '-related_3'], ['field_2', '-related_3', '-pk']),
- (['field_3', 'related_4'], ['field_3', 'related_4', '-pk']),
-
- (['field_2', 'related_3_id'], ['field_2', 'related_3_id']),
- (['field_3', '-related_4_id'], ['field_3', '-related_4_id']),
-
- (['field_5'], ['field_5', '-pk']),
-
- (['field_6'], ['field_6']),
- )
- for ordering, expected in tests:
- with self.subTest(ordering=ordering):
- self.assertEqual(change_list._get_deterministic_ordering(ordering), expected)
- def test_dynamic_list_filter(self):
- """
- Regression tests for ticket #17646: dynamic list_filter support.
- """
- parent = Parent.objects.create(name='parent')
- for i in range(10):
- Child.objects.create(name='child %s' % i, parent=parent)
- user_noparents = self._create_superuser('noparents')
- user_parents = self._create_superuser('parents')
-
- m = DynamicListFilterChildAdmin(Child, custom_site)
- request = self._mocked_authenticated_request('/child/', user_noparents)
- response = m.changelist_view(request)
- self.assertEqual(response.context_data['cl'].list_filter, ['name', 'age'])
-
- m = DynamicListFilterChildAdmin(Child, custom_site)
- request = self._mocked_authenticated_request('/child/', user_parents)
- response = m.changelist_view(request)
- self.assertEqual(response.context_data['cl'].list_filter, ('parent', 'name', 'age'))
- def test_dynamic_search_fields(self):
- child = self._create_superuser('child')
- m = DynamicSearchFieldsChildAdmin(Child, custom_site)
- request = self._mocked_authenticated_request('/child/', child)
- response = m.changelist_view(request)
- self.assertEqual(response.context_data['cl'].search_fields, ('name', 'age'))
- def test_pagination_page_range(self):
- """
- Regression tests for ticket #15653: ensure the number of pages
- generated for changelist views are correct.
- """
-
- m = GroupAdmin(Group, custom_site)
- request = self.factory.get('/group/')
- request.user = self.superuser
- cl = m.get_changelist_instance(request)
- per_page = cl.list_per_page = 10
- for page_num, objects_count, expected_page_range in [
- (0, per_page, []),
- (0, per_page * 2, list(range(2))),
- (5, per_page * 11, list(range(11))),
- (5, per_page * 12, [0, 1, 2, 3, 4, 5, 6, 7, 8, '.', 10, 11]),
- (6, per_page * 12, [0, 1, '.', 3, 4, 5, 6, 7, 8, 9, 10, 11]),
- (6, per_page * 13, [0, 1, '.', 3, 4, 5, 6, 7, 8, 9, '.', 11, 12]),
- ]:
-
- Group.objects.all().delete()
- for i in range(objects_count):
- Group.objects.create(name='test band')
-
- cl.page_num = page_num
- cl.get_results(request)
- real_page_range = pagination(cl)['page_range']
- self.assertEqual(expected_page_range, list(real_page_range))
- def test_object_tools_displayed_no_add_permission(self):
- """
- When ModelAdmin.has_add_permission() returns False, the object-tools
- block is still shown.
- """
- superuser = self._create_superuser('superuser')
- m = EventAdmin(Event, custom_site)
- request = self._mocked_authenticated_request('/event/', superuser)
- self.assertFalse(m.has_add_permission(request))
- response = m.changelist_view(request)
- self.assertIn('<ul class="object-tools">', response.rendered_content)
-
- self.assertNotIn('Add ', response.rendered_content)
- class GetAdminLogTests(TestCase):
- def test_custom_user_pk_not_named_id(self):
- """
- {% get_admin_log %} works if the user model's primary key isn't named
- 'id'.
- """
- context = Context({'user': CustomIdUser()})
- template = Template('{% load log %}{% get_admin_log 10 as admin_log for_user user %}')
-
- self.assertEqual(template.render(context), '')
- def test_no_user(self):
- """{% get_admin_log %} works without specifying a user."""
- user = User(username='jondoe', password='secret', email='super@example.com')
- user.save()
- ct = ContentType.objects.get_for_model(User)
- LogEntry.objects.log_action(user.pk, ct.pk, user.pk, repr(user), 1)
- t = Template(
- '{% load log %}'
- '{% get_admin_log 100 as admin_log %}'
- '{% for entry in admin_log %}'
- '{{ entry|safe }}'
- '{% endfor %}'
- )
- self.assertEqual(t.render(Context({})), 'Added “<User: jondoe>”.')
- def test_missing_args(self):
- msg = "'get_admin_log' statements require two arguments"
- with self.assertRaisesMessage(TemplateSyntaxError, msg):
- Template('{% load log %}{% get_admin_log 10 as %}')
- def test_non_integer_limit(self):
- msg = "First argument to 'get_admin_log' must be an integer"
- with self.assertRaisesMessage(TemplateSyntaxError, msg):
- Template('{% load log %}{% get_admin_log "10" as admin_log for_user user %}')
- def test_without_as(self):
- msg = "Second argument to 'get_admin_log' must be 'as'"
- with self.assertRaisesMessage(TemplateSyntaxError, msg):
- Template('{% load log %}{% get_admin_log 10 ad admin_log for_user user %}')
- def test_without_for_user(self):
- msg = "Fourth argument to 'get_admin_log' must be 'for_user'"
- with self.assertRaisesMessage(TemplateSyntaxError, msg):
- Template('{% load log %}{% get_admin_log 10 as admin_log foruser user %}')
- @override_settings(ROOT_URLCONF='admin_changelist.urls')
- class SeleniumTests(AdminSeleniumTestCase):
- available_apps = ['admin_changelist'] + AdminSeleniumTestCase.available_apps
- def setUp(self):
- User.objects.create_superuser(username='super', password='secret', email=None)
- def test_add_row_selection(self):
- """
- The status line for selected rows gets updated correctly (#22038).
- """
- self.admin_login(username='super', password='secret')
- self.selenium.get(self.live_server_url + reverse('admin:auth_user_changelist'))
- form_id = '#changelist-form'
-
- rows = self.selenium.find_elements_by_css_selector(
- '%s #result_list tbody tr' % form_id
- )
- self.assertEqual(len(rows), 1)
- row = rows[0]
- selection_indicator = self.selenium.find_element_by_css_selector(
- '%s .action-counter' % form_id
- )
- all_selector = self.selenium.find_element_by_id('action-toggle')
- row_selector = self.selenium.find_element_by_css_selector(
- '%s #result_list tbody tr:first-child .action-select' % form_id
- )
-
- self.assertEqual(selection_indicator.text, "0 of 1 selected")
- self.assertIs(all_selector.get_property('checked'), False)
- self.assertEqual(row.get_attribute('class'), '')
-
- row_selector.click()
- self.assertEqual(selection_indicator.text, "1 of 1 selected")
- self.assertIs(all_selector.get_property('checked'), True)
- self.assertEqual(row.get_attribute('class'), 'selected')
-
- row_selector.click()
- self.assertEqual(selection_indicator.text, "0 of 1 selected")
- self.assertIs(all_selector.get_property('checked'), False)
- self.assertEqual(row.get_attribute('class'), '')
- def test_select_all_across_pages(self):
- Parent.objects.bulk_create([Parent(name='parent%d' % i) for i in range(101)])
- self.admin_login(username='super', password='secret')
- self.selenium.get(self.live_server_url + reverse('admin:admin_changelist_parent_changelist'))
- selection_indicator = self.selenium.find_element_by_css_selector('.action-counter')
- select_all_indicator = self.selenium.find_element_by_css_selector('.actions .all')
- question = self.selenium.find_element_by_css_selector('.actions > .question')
- clear = self.selenium.find_element_by_css_selector('.actions > .clear')
- select_all = self.selenium.find_element_by_id('action-toggle')
- select_across = self.selenium.find_element_by_name('select_across')
- self.assertIs(question.is_displayed(), False)
- self.assertIs(clear.is_displayed(), False)
- self.assertIs(select_all.get_property('checked'), False)
- self.assertEqual(select_across.get_property('value'), '0')
- self.assertIs(selection_indicator.is_displayed(), True)
- self.assertEqual(selection_indicator.text, '0 of 100 selected')
- self.assertIs(select_all_indicator.is_displayed(), False)
- select_all.click()
- self.assertIs(question.is_displayed(), True)
- self.assertIs(clear.is_displayed(), False)
- self.assertIs(select_all.get_property('checked'), True)
- self.assertEqual(select_across.get_property('value'), '0')
- self.assertIs(selection_indicator.is_displayed(), True)
- self.assertEqual(selection_indicator.text, '100 of 100 selected')
- self.assertIs(select_all_indicator.is_displayed(), False)
- question.click()
- self.assertIs(question.is_displayed(), False)
- self.assertIs(clear.is_displayed(), True)
- self.assertIs(select_all.get_property('checked'), True)
- self.assertEqual(select_across.get_property('value'), '1')
- self.assertIs(selection_indicator.is_displayed(), False)
- self.assertIs(select_all_indicator.is_displayed(), True)
- clear.click()
- self.assertIs(question.is_displayed(), False)
- self.assertIs(clear.is_displayed(), False)
- self.assertIs(select_all.get_property('checked'), False)
- self.assertEqual(select_across.get_property('value'), '0')
- self.assertIs(selection_indicator.is_displayed(), True)
- self.assertEqual(selection_indicator.text, '0 of 100 selected')
- self.assertIs(select_all_indicator.is_displayed(), False)
- def test_actions_warn_on_pending_edits(self):
- Parent.objects.create(name='foo')
- self.admin_login(username='super', password='secret')
- self.selenium.get(self.live_server_url + reverse('admin:admin_changelist_parent_changelist'))
- name_input = self.selenium.find_element_by_id('id_form-0-name')
- name_input.clear()
- name_input.send_keys('bar')
- self.selenium.find_element_by_id('action-toggle').click()
- self.selenium.find_element_by_name('index').click()
- alert = self.selenium.switch_to.alert
- try:
- self.assertEqual(
- alert.text,
- 'You have unsaved changes on individual editable fields. If you '
- 'run an action, your unsaved changes will be lost.'
- )
- finally:
- alert.dismiss()
- def test_save_with_changes_warns_on_pending_action(self):
- from selenium.webdriver.support.ui import Select
- Parent.objects.create(name='parent')
- self.admin_login(username='super', password='secret')
- self.selenium.get(self.live_server_url + reverse('admin:admin_changelist_parent_changelist'))
- name_input = self.selenium.find_element_by_id('id_form-0-name')
- name_input.clear()
- name_input.send_keys('other name')
- Select(
- self.selenium.find_element_by_name('action')
- ).select_by_value('delete_selected')
- self.selenium.find_element_by_name('_save').click()
- alert = self.selenium.switch_to.alert
- try:
- self.assertEqual(
- alert.text,
- 'You have selected an action, but you haven’t saved your '
- 'changes to individual fields yet. Please click OK to save. '
- 'You’ll need to re-run the action.',
- )
- finally:
- alert.dismiss()
- def test_save_without_changes_warns_on_pending_action(self):
- from selenium.webdriver.support.ui import Select
- Parent.objects.create(name='parent')
- self.admin_login(username='super', password='secret')
- self.selenium.get(self.live_server_url + reverse('admin:admin_changelist_parent_changelist'))
- Select(
- self.selenium.find_element_by_name('action')
- ).select_by_value('delete_selected')
- self.selenium.find_element_by_name('_save').click()
- alert = self.selenium.switch_to.alert
- try:
- self.assertEqual(
- alert.text,
- 'You have selected an action, and you haven’t made any '
- 'changes on individual fields. You’re probably looking for '
- 'the Go button rather than the Save button.',
- )
- finally:
- alert.dismiss()
|