Ticket #17198: 17198.changelist-order.2.diff

File 17198.changelist-order.2.diff, 11.5 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..acd5106 100644
    a b class ChangeList(object):  
    6666            self.list_editable = ()
    6767        else:
    6868            self.list_editable = list_editable
    69         self.ordering = self.get_ordering(request)
    7069        self.query = request.GET.get(SEARCH_VAR, '')
    7170        self.query_set = self.get_query_set(request)
    7271        self.get_results(request)
    class ChangeList(object):  
    218217                attr = getattr(self.model, field_name)
    219218            return getattr(attr, 'admin_order_field', None)
    220219
    221     def get_ordering(self, request):
     220    def get_ordering(self, request, queryset):
    222221        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
     222        # For ordering, first check the "get_ordering" method in model admin,
     223        # then check the object's default ordering. Finally, a
    226224        # manually-specified ordering from the query string overrides anything.
    227         ordering = self.model_admin.get_ordering(request) or self._get_default_ordering()
     225        ordering = list(self.model_admin.get_ordering(request)
     226                        or self._get_default_ordering())
    228227        if ORDER_VAR in params:
    229228            # Clear ordering and used params
    230229            ordering = []
    class ChangeList(object):  
    239238                    ordering.append(pfx + order_field)
    240239                except (IndexError, ValueError):
    241240                    continue # Invalid ordering specified, skip it.
     241
     242        # Add the given query's ordering fields, if any.
     243        ordering.extend(queryset.query.order_by)
     244
     245        # Ensure that the primary key is systematically present in the list of
     246        # ordering fields so we can guarantee a deterministic order.
     247        pk_name = self.lookup_opts.pk.name
     248        if not (set(ordering) & set(['pk', '-pk', pk_name, '-' + pk_name])):
     249            # The two sets do not intersect, meaning the pk isn't present. So
     250            # we add it.
     251            ordering.append('pk')
     252
    242253        return ordering
    243254
    244255    def get_ordering_field_columns(self):
    class ChangeList(object):  
    322333                            break
    323334
    324335        # Set ordering.
    325         if self.ordering:
    326             qs = qs.order_by(*self.ordering)
     336        ordering = self.get_ordering(request, qs)
     337        qs = qs.order_by(*ordering)
    327338
    328339        # Apply keyword searches.
    329340        def construct_search(field_name):
  • 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 dc8f29a..9e12131 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.
    277 
     276        """
     277        If no ordering is defined in `ModelAdmin.ordering` or in the query
     278        string, then the underlying order of the queryset should not be
     279        changed, even if it is defined in `Modeladmin.queryset()`.
     280        Refs #11868, #7309.
     281        """
    278282        p1 = Person.objects.create(name="Amy", gender=1, alive=True, age=80)
    279283        p2 = Person.objects.create(name="Bob", gender=1, alive=True, age=70)
    280284        p3 = Person.objects.create(name="Chris", gender=2, alive=False, age=60)
    class AdminViewListEditable(TestCase):  
    18811885        self.assertEqual(Category.objects.get(id=3).order, 1)
    18821886        self.assertEqual(Category.objects.get(id=4).order, 0)
    18831887
     1888    def test_list_editable_ordering_default(self):
     1889        """
     1890        Ensure that the list_editable items on the changelist are ordered
     1891        in a deterministic order and without raising exceptions, even when
     1892        no default ordering is explicitly defined.
     1893        Refs #16819.
     1894        """
     1895        UnorderedObject.objects.create(id=1, name='Unordered object #1')
     1896        UnorderedObject.objects.create(id=2, name='Unordered object #2')
     1897        UnorderedObject.objects.create(id=3, name='Unordered object #3')
     1898        response = self.client.get('/test_admin/admin/admin_views/unorderedobject/')
     1899        self.assertContains(response, 'Unordered object #1')
     1900        self.assertContains(response, 'Unordered object #2')
     1901        self.assertNotContains(response, 'Unordered object #3')
     1902        response = self.client.get('/test_admin/admin/admin_views/unorderedobject/?p=1')
     1903        self.assertNotContains(response, 'Unordered object #1')
     1904        self.assertNotContains(response, 'Unordered object #2')
     1905        self.assertContains(response, 'Unordered object #3')
     1906
    18841907    def test_list_editable_action_submit(self):
    18851908        # List editable changes should not be executed if the action "Go" button is
    18861909        # used to submit the form.
Back to Top