Ticket #17198: 17198.changelist-order.diff

File 17198.changelist-order.diff, 10.8 KB (added by Julien Phalip, 13 years ago)
  • django/contrib/admin/views/main.py

    diff --git a/django/contrib/admin/views/main.py b/django/contrib/admin/views/main.py
    index 32113f5..e5f0543 100644
    a b class ChangeList(object):  
    220220
    221221    def get_ordering(self, request):
    222222        params = self.params
    223         # For ordering, first check the if exists the "get_ordering" method
    224         # in model admin, then check "ordering" parameter in the admin
    225         # options, then check the object's default ordering. Finally, a
     223        # For ordering, first check the "get_ordering" method in model admin,
     224        # then check the object's default ordering. Finally, a
    226225        # manually-specified ordering from the query string overrides anything.
    227226        ordering = self.model_admin.get_ordering(request) or self._get_default_ordering()
    228227        if ORDER_VAR in params:
    class ChangeList(object):  
    239238                    ordering.append(pfx + order_field)
    240239                except (IndexError, ValueError):
    241240                    continue # Invalid ordering specified, skip it.
     241        # Ensure that the primary key is systematically present in the list of
     242        # ordering fields so we can guarantee a deterministic order.
     243        pk_name = self.lookup_opts.pk.name
     244        if not (set(ordering) & set(['pk', '-pk', pk_name, '-' + pk_name])):
     245            # The two sets do not intersect, meaning the pk isn't present. So
     246            # we add it.
     247            ordering = list(ordering) + ['pk']
    242248        return ordering
    243249
    244250    def get_ordering_field_columns(self):
  • tests/regressiontests/admin_changelist/models.py

    diff --git a/tests/regressiontests/admin_changelist/models.py b/tests/regressiontests/admin_changelist/models.py
    index 97080fb..efa392a 100644
    a b class Swallow(models.Model):  
    5757
    5858    class Meta:
    5959        ordering = ('speed', 'load')
     60
     61
     62class UnorderedObject(models.Model):
     63    """
     64    Model without any defined `Meta.ordering`.
     65    Refs #17198.
     66    """
     67    name = models.CharField(max_length=255)
     68    bool = models.BooleanField(default=True)
     69 No newline at end of file
  • tests/regressiontests/admin_changelist/tests.py

    diff --git a/tests/regressiontests/admin_changelist/tests.py b/tests/regressiontests/admin_changelist/tests.py
    index b422519..84cad61 100644
    a b from .admin import (ChildAdmin, QuartetAdmin, BandAdmin, ChordsBandAdmin,  
    1414    FilteredChildAdmin, CustomPaginator, site as custom_site,
    1515    SwallowAdmin)
    1616from .models import (Child, Parent, Genre, Band, Musician, Group, Quartet,
    17     Membership, ChordsMusician, ChordsBand, Invitation, Swallow)
     17    Membership, ChordsMusician, ChordsBand, Invitation, Swallow,
     18    UnorderedObject)
    1819
    1920
    2021class ChangeListTests(TestCase):
    class ChangeListTests(TestCase):  
    430431        self.assertContains(response, unicode(swallow.load))
    431432        self.assertContains(response, unicode(swallow.speed))
    432433
     434    def test_default_ordering_by_pk(self):
     435        """
     436        Ensure that the primary key is systematically used in the ordering of
     437        the changelist's results to guarantee a deterministic order.
     438        Refs #17198.
     439        """
     440        superuser = self._create_superuser('superuser')
     441        for counter in range(1, 51):
     442            UnorderedObject.objects.create(
     443                id=counter, name='Unordered object #%s' % counter, bool=True)
     444
     445        class UnorderedObjectAdmin(admin.ModelAdmin):
     446            list_per_page = 10
     447            list_display = ['name']
     448
     449        def check_results_order(reverse=False):
     450            admin.site.register(UnorderedObject, UnorderedObjectAdmin)
     451            model_admin = UnorderedObjectAdmin(UnorderedObject, admin.site)
     452            counter = 51 if reverse else 0
     453            for page in range (0, 5):
     454                request = self._mocked_authenticated_request('/unorderedobject/?p=%s' % page, superuser)
     455                response = model_admin.changelist_view(request)
     456                for result in response.context_data['cl'].result_list:
     457                    counter += -1 if reverse else 1
     458                    self.assertEqual(result.id, counter)
     459            admin.site.unregister(UnorderedObject)
     460
     461        # When no order is defined at all
     462        check_results_order()
     463
     464        # When an order is defined
     465        UnorderedObjectAdmin.ordering = ['bool']
     466        check_results_order()
     467
     468        # When an order is defined, including the pk itself
     469        UnorderedObjectAdmin.ordering = ['bool', '-pk']
     470        check_results_order(reverse=True)
     471        UnorderedObjectAdmin.ordering = ['bool', 'pk']
     472        check_results_order()
     473        UnorderedObjectAdmin.ordering = ['-id', 'bool']
     474        check_results_order(reverse=True)
     475        UnorderedObjectAdmin.ordering = ['id', 'bool']
     476        check_results_order()
     477 No newline at end of file
  • tests/regressiontests/admin_views/admin.py

    diff --git a/tests/regressiontests/admin_views/admin.py b/tests/regressiontests/admin_views/admin.py
    index b10d178..d960749 100644
    a b from .models import (Article, Chapter, Account, Media, Child, Parent, Picture,  
    2626    CoverLetter, Story, OtherStory, Book, Promo, ChapterXtra1, Pizza, Topping,
    2727    Album, Question, Answer, ComplexSortedPerson, PrePopulatedPostLargeSlug,
    2828    AdminOrderedField, AdminOrderedModelMethod, AdminOrderedAdminMethod,
    29     AdminOrderedCallable, Report, Color2, MainPrepopulated, RelatedPrepopulated)
     29    AdminOrderedCallable, Report, Color2, UnorderedObject, MainPrepopulated,
     30    RelatedPrepopulated)
    3031
    3132
    3233def callable_year(dt_value):
    class MainPrepopulatedAdmin(admin.ModelAdmin):  
    562563                           'slug2': ['status', 'name']}
    563564
    564565
     566class UnorderedObjectAdmin(admin.ModelAdmin):
     567    list_display = ['name']
     568    list_editable = ['name']
     569    list_per_page = 2
     570
    565571
    566572
    567573site = admin.AdminSite(name="admin")
    site.register(Story, StoryAdmin)  
    609615site.register(OtherStory, OtherStoryAdmin)
    610616site.register(Report, ReportAdmin)
    611617site.register(MainPrepopulated, MainPrepopulatedAdmin)
     618site.register(UnorderedObject, UnorderedObjectAdmin)
    612619
    613620# We intentionally register Promo and ChapterXtra1 but not Chapter nor ChapterXtra2.
    614621# That way we cover all four cases:
  • tests/regressiontests/admin_views/models.py

    diff --git a/tests/regressiontests/admin_views/models.py b/tests/regressiontests/admin_views/models.py
    index cf2896f..c8cd0e8 100644
    a b class RelatedPrepopulated(models.Model):  
    596596        choices=(('option one', 'Option One'),
    597597                 ('option two', 'Option Two')))
    598598    slug1 = models.SlugField(max_length=50)
    599     slug2 = models.SlugField(max_length=60)
    600  No newline at end of file
     599    slug2 = models.SlugField(max_length=60)
     600
     601
     602class UnorderedObject(models.Model):
     603    """
     604    Model without any defined `Meta.ordering`.
     605    Refs #16819.
     606    """
     607    name = models.CharField(max_length=255)
     608    bool = models.BooleanField(default=True)
     609 No newline at end of file
  • tests/regressiontests/admin_views/tests.py

    diff --git a/tests/regressiontests/admin_views/tests.py b/tests/regressiontests/admin_views/tests.py
    index 760f31a..a6d79d8 100755
    a b from django.core.exceptions import SuspiciousOperation  
    1212from django.core.files import temp as tempfile
    1313from django.core.urlresolvers import reverse
    1414# Register auth models with the admin.
     15from django.contrib import admin
    1516from django.contrib.admin.helpers import ACTION_CHECKBOX_NAME
    1617from django.contrib.admin.models import LogEntry, DELETION
    1718from django.contrib.admin.sites import LOGIN_FORM_KEY
    from .models import (Article, BarAccount, CustomArticle, EmptyModel, FooAccount,  
    4142    FoodDelivery, RowLevelChangePermissionModel, Paper, CoverLetter, Story,
    4243    OtherStory, ComplexSortedPerson, Parent, Child, AdminOrderedField,
    4344    AdminOrderedModelMethod, AdminOrderedAdminMethod, AdminOrderedCallable,
    44     Report, MainPrepopulated, RelatedPrepopulated)
     45    Report, MainPrepopulated, RelatedPrepopulated, UnorderedObject)
    4546
    4647
    4748ERROR_MESSAGE = "Please enter the correct username and password \
    class AdminViewBasicTest(TestCase):  
    272273        )
    273274
    274275    def testChangeListSortingPreserveQuerySetOrdering(self):
    275         # If no ordering on ModelAdmin, or query string, the underlying order of
    276         # the queryset should not be changed.
     276        # If no ordering on ModelAdmin, or query string, the queryset should be
     277        # ordered by ascending pk.
    277278
    278279        p1 = Person.objects.create(name="Amy", gender=1, alive=True, age=80)
    279280        p2 = Person.objects.create(name="Bob", gender=1, alive=True, age=70)
    class AdminViewBasicTest(TestCase):  
    285286        response = self.client.get('/test_admin/admin/admin_views/person/', {})
    286287        self.assertEqual(response.status_code, 200)
    287288        self.assertTrue(
    288             response.content.index(link % p3.id) < response.content.index(link % p2.id) and
    289             response.content.index(link % p2.id) < response.content.index(link % p1.id)
     289            response.content.index(link % p1.id) < response.content.index(link % p2.id) and
     290            response.content.index(link % p2.id) < response.content.index(link % p3.id)
    290291        )
    291292
    292293    def testChangeListSortingModelMeta(self):
    class AdminViewListEditable(TestCase):  
    18731874        self.assertEqual(Category.objects.get(id=3).order, 1)
    18741875        self.assertEqual(Category.objects.get(id=4).order, 0)
    18751876
     1877    def test_list_editable_ordering_default(self):
     1878        """
     1879        Ensure that the list_editable items on the changelist are ordered
     1880        in a deterministic order and without raising exceptions, even when
     1881        no default ordering is explicitly defined.
     1882        Refs #16819.
     1883        """
     1884        UnorderedObject.objects.create(id=1, name='Unordered object #1')
     1885        UnorderedObject.objects.create(id=2, name='Unordered object #2')
     1886        UnorderedObject.objects.create(id=3, name='Unordered object #3')
     1887        response = self.client.get('/test_admin/admin/admin_views/unorderedobject/')
     1888        self.assertContains(response, 'Unordered object #1')
     1889        self.assertContains(response, 'Unordered object #2')
     1890        self.assertNotContains(response, 'Unordered object #3')
     1891        response = self.client.get('/test_admin/admin/admin_views/unorderedobject/?p=1')
     1892        self.assertNotContains(response, 'Unordered object #1')
     1893        self.assertNotContains(response, 'Unordered object #2')
     1894        self.assertContains(response, 'Unordered object #3')
     1895
    18761896    def test_list_editable_action_submit(self):
    18771897        # List editable changes should not be executed if the action "Go" button is
    18781898        # used to submit the form.
Back to Top