Ticket #17252: r17130-admin-order-field-better-test.diff

File r17130-admin-order-field-better-test.diff, 9.9 KB (added by sebastian, 3 years ago)
  • django/contrib/admin/views/main.py

     
    169169            ordering = self.lookup_opts.ordering
    170170        return ordering
    171171
     172    def _get_admin_order_field(self, field_name):
     173        """
     174        Get the name of the admin order field defined for the given
     175        element of 'list_display'. The given object can either be the
     176        name of a proper field, the name of a method (on the admin or
     177        model) with the 'admin_order_field' attribute, or a callable
     178        with that attribute. If no admin order is defined for the
     179        given object, the method returns None.
     180        """
     181        try:
     182            f = self.lookup_opts.get_field(field_name)
     183        except models.FieldDoesNotExist:
     184            # See whether field_name is a name of a non-field
     185            # that allows sorting.
     186            try:
     187                if callable(field_name):
     188                    attr = field_name
     189                elif hasattr(self.model_admin, field_name):
     190                    attr = getattr(self.model_admin, field_name)
     191                else:
     192                    attr = getattr(self.model, field_name)
     193                return attr.admin_order_field
     194            except AttributeError:
     195                return None
     196        else:
     197            return f.name
     198
    172199    def get_ordering(self, request):
    173200        params = self.params
    174201        # For ordering, first check the if exists the "get_ordering" method
     
    185212                try:
    186213                    none, pfx, idx = p.rpartition('-')
    187214                    field_name = self.list_display[int(idx)]
    188                     try:
    189                         f = self.lookup_opts.get_field(field_name)
    190                     except models.FieldDoesNotExist:
    191                         # See whether field_name is a name of a non-field
    192                         # that allows sorting.
    193                         try:
    194                             if callable(field_name):
    195                                 attr = field_name
    196                             elif hasattr(self.model_admin, field_name):
    197                                 attr = getattr(self.model_admin, field_name)
    198                             else:
    199                                 attr = getattr(self.model, field_name)
    200                             field_name = attr.admin_order_field
    201                         except AttributeError:
    202                             continue # No 'admin_order_field', skip it
    203                     else:
    204                         field_name = f.name
     215                    order_field = self._get_admin_order_field(field_name)
     216                    if not order_field:
     217                        continue # No 'admin_order_field', skip it
    205218
    206                     ordering.append(pfx + field_name)
     219                    ordering.append(pfx + order_field)
    207220
    208221                except (IndexError, ValueError):
    209222                    continue # Invalid ordering specified, skip it.
     
    235248                    # No match, but there might be a match if we take into
    236249                    # account 'admin_order_field'
    237250                    for _index, attr in enumerate(self.list_display):
    238                         if getattr(attr, 'admin_order_field', '') == field:
     251                        if self._get_admin_order_field(attr) == field:
    239252                            index = _index
    240253                            break
    241254                if index is not None:
  • tests/regressiontests/admin_views/admin.py

     
    2222    Gadget, Villain, SuperVillain, Plot, PlotDetails, CyclicOne, CyclicTwo,
    2323    WorkHour, Reservation, FoodDelivery, RowLevelChangePermissionModel, Paper,
    2424    CoverLetter, Story, OtherStory, Book, Promo, ChapterXtra1, Pizza, Topping,
    25     Album, Question, Answer, ComplexSortedPerson, PrePopulatedPostLargeSlug)
     25    Album, Question, Answer, ComplexSortedPerson, PrePopulatedPostLargeSlug,
     26    AdminOrderedField, AdminOrderedModelMethod, AdminOrderedAdminMethod,
     27    AdminOrderedCallable)
    2628
    2729
    2830def callable_year(dt_value):
     
    474476        'slug' : ('title',)
    475477    }
    476478 
     479
     480class AdminOrderedFieldAdmin(admin.ModelAdmin):
     481    ordering = ('order',)
     482    list_display = ('stuff', 'order')
     483
     484class AdminOrderedModelMethodAdmin(admin.ModelAdmin):
     485    ordering = ('order',)
     486    list_display = ('stuff', 'some_order')
     487
     488class AdminOrderedAdminMethodAdmin(admin.ModelAdmin):
     489    def some_admin_order(self, obj):
     490        return obj.order
     491    some_admin_order.admin_order_field = 'order'
     492    ordering = ('order',)
     493    list_display = ('stuff', 'some_admin_order')
     494
     495def admin_ordered_callable(obj):
     496    return obj.order
     497admin_ordered_callable.admin_order_field = 'order'
     498class AdminOrderedCallableAdmin(admin.ModelAdmin):
     499    ordering = ('order',)
     500    list_display = ('stuff', admin_ordered_callable)
     501
     502
    477503site = admin.AdminSite(name="admin")
    478504site.register(Article, ArticleAdmin)
    479505site.register(CustomArticle, CustomArticleAdmin)
     
    537563site.register(Answer)
    538564site.register(PrePopulatedPost, PrePopulatedPostAdmin)
    539565site.register(ComplexSortedPerson, ComplexSortedPersonAdmin)
     566site.register(PrePopulatedPostLargeSlug, PrePopulatedPostLargeSlugAdmin)
     567site.register(AdminOrderedField, AdminOrderedFieldAdmin)
     568site.register(AdminOrderedModelMethod, AdminOrderedModelMethodAdmin)
     569site.register(AdminOrderedAdminMethod, AdminOrderedAdminMethodAdmin)
     570site.register(AdminOrderedCallable, AdminOrderedCallableAdmin)
    540571
    541572# Register core models we need in our tests
    542573from django.contrib.auth.models import User, Group
    543574from django.contrib.auth.admin import UserAdmin, GroupAdmin
    544575site.register(User, UserAdmin)
    545576site.register(Group, GroupAdmin)
    546 site.register(PrePopulatedPostLargeSlug, PrePopulatedPostLargeSlugAdmin)
  • tests/regressiontests/admin_views/tests.py

     
    3737    DooHickey, FancyDoodad, Whatsit, Category, Post, Plot, FunkyTag, Chapter,
    3838    Book, Promo, WorkHour, Employee, Question, Answer, Inquisition, Actor,
    3939    FoodDelivery, RowLevelChangePermissionModel, Paper, CoverLetter, Story,
    40     OtherStory, ComplexSortedPerson, Parent, Child)
     40    OtherStory, ComplexSortedPerson, Parent, Child, AdminOrderedField,
     41    AdminOrderedModelMethod, AdminOrderedAdminMethod, AdminOrderedCallable)
    4142
    4243
    4344ERROR_MESSAGE = "Please enter the correct username and password \
     
    354355            response.content.index(link % p2.id) < response.content.index(link % p1.id)
    355356        )
    356357
     358    def testSortIndicatorsAdminOrder(self):
     359        """
     360        Ensures that the admin shows default sort indicators for all
     361        kinds of 'ordering' fields: field names, method on the model
     362        admin and model itself, and other callables. See #17252.
     363        """
     364        models = [(AdminOrderedField,       'adminorderedfield'      ),
     365                  (AdminOrderedModelMethod, 'adminorderedmodelmethod'),
     366                  (AdminOrderedAdminMethod, 'adminorderedadminmethod'),
     367                  (AdminOrderedCallable,    'adminorderedcallable'   )]
     368        for model, url in models:
     369            a1 = model.objects.create(stuff='The Last Item',   order=3)
     370            a2 = model.objects.create(stuff='The First Item',  order=1)
     371            a3 = model.objects.create(stuff='The Middle Item', order=2)
     372            response = self.client.get('/test_admin/admin/admin_views/%s/' % url, {})
     373            self.assertEqual(response.status_code, 200)
     374            # Should have 3 columns including action checkbox col.
     375            self.assertContains(response, '<th scope="col"', count=3, msg_prefix=url)
     376            # Check if the correct column was selected. 2 is the index of the
     377            # 'order' column in the model admin's 'list_display' with 0 being
     378            # the implicit 'action_checkbox' and 1 being the column 'stuff'.
     379            self.assertEqual(response.context['cl'].get_ordering_field_columns(), {2: 'asc'})
     380            # Check order of records.
     381            self.assertTrue(response.content.index('The First Item') <
     382                response.content.index('The Middle Item') < response.content.index('The Last Item'))
     383
    357384    def testLimitedFilter(self):
    358385        """Ensure admin changelist filters do not contain objects excluded via limit_choices_to.
    359386        This also tests relation-spanning filters (e.g. 'color__value').
  • tests/regressiontests/admin_views/models.py

     
    538538    age = models.PositiveIntegerField()
    539539    is_employee = models.NullBooleanField()
    540540
     541
    541542class PrePopulatedPostLargeSlug(models.Model):
    542543    """
    543544    Regression test for #15938: a large max_length for the slugfield must not
     
    548549    published = models.BooleanField()
    549550    slug = models.SlugField(max_length=1000)
    550551   
     552
     553class AdminOrderedField(models.Model):
     554    order = models.IntegerField()
     555    stuff = models.CharField(max_length=200)
     556
     557class AdminOrderedModelMethod(models.Model):
     558    order = models.IntegerField()
     559    stuff = models.CharField(max_length=200)
     560    def some_order(self):
     561        return self.order
     562    some_order.admin_order_field = 'order'
     563
     564class AdminOrderedAdminMethod(models.Model):
     565    order = models.IntegerField()
     566    stuff = models.CharField(max_length=200)
     567
     568class AdminOrderedCallable(models.Model):
     569    order = models.IntegerField()
     570    stuff = models.CharField(max_length=200)
Back to Top