Commit d9330d5b authored by Juan Pedro Fisanotti's avatar Juan Pedro Fisanotti Committed by Ramiro Morales
Browse files

Fixed #6585 -- Admin relationship widgets: Respect ordering defined by target model's ModelAdmin.

Thanks Gary Wilson for the report and Juan Pedro Fisanotti, Carlos
Matías de la Torre for the fix.
parent 3ea0c7d3
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -165,6 +165,7 @@ answer newbie questions, and generally made Django that much better:
    Matt Dennenbaum
    deric@monowerks.com
    Max Derkachev <mderk@yandex.ru>
    Carlos Matías de la Torre <cmdelatorre@gmail.com>
    Rajesh Dhawan <rajesh.dhawan@gmail.com>
    Sander Dijkhuis <sander.dijkhuis@gmail.com>
    Jordan Dimov <s3x3y1@gmail.com>
@@ -205,6 +206,7 @@ answer newbie questions, and generally made Django that much better:
    Stefane Fermgier <sf@fermigier.com>
    J. Pablo Fernandez <pupeno@pupeno.com>
    Maciej Fijalkowski
    Juan Pedro Fisanotti <fisadev@gmail.com>
    Ben Firshman <ben@firshman.co.uk>
    Matthew Flanagan <http://wadofstuff.blogspot.com>
    Eric Floehr <eric@intellovations.com>
+21 −0
Original line number Diff line number Diff line
@@ -157,6 +157,19 @@ class BaseModelAdmin(six.with_metaclass(forms.MediaDefiningClass)):
                )
        return db_field.formfield(**kwargs)

    def get_field_queryset(self, db, db_field, request):
        """
        If the ModelAdmin specifies ordering, the queryset should respect that
        ordering.  Otherwise don't specify the queryset, let the field decide
        (returns None in that case).
        """
        related_admin = self.admin_site._registry.get(db_field.rel.to, None)
        if related_admin is not None:
            ordering = related_admin.get_ordering(request)
            if ordering is not None and ordering != ():
                return db_field.rel.to._default_manager.using(db).order_by(*ordering).complex_filter(db_field.rel.limit_choices_to)
        return None

    def formfield_for_foreignkey(self, db_field, request=None, **kwargs):
        """
        Get a form Field for a ForeignKey.
@@ -171,6 +184,10 @@ class BaseModelAdmin(six.with_metaclass(forms.MediaDefiningClass)):
            })
            kwargs['empty_label'] = db_field.blank and _('None') or None

        queryset = self.get_field_queryset(db, db_field, request)
        if queryset is not None:
            kwargs['queryset'] = queryset

        return db_field.formfield(**kwargs)

    def formfield_for_manytomany(self, db_field, request=None, **kwargs):
@@ -190,6 +207,10 @@ class BaseModelAdmin(six.with_metaclass(forms.MediaDefiningClass)):
        elif db_field.name in (list(self.filter_vertical) + list(self.filter_horizontal)):
            kwargs['widget'] = widgets.FilteredSelectMultiple(db_field.verbose_name, (db_field.name in self.filter_vertical))

        queryset = self.get_field_queryset(db, db_field, request)
        if queryset is not None:
            kwargs['queryset'] = queryset

        return db_field.formfield(**kwargs)

    def _declared_fieldsets(self):
+1 −0
Original line number Diff line number Diff line
@@ -15,6 +15,7 @@ class Song(models.Model):
    band = models.ForeignKey(Band)
    name = models.CharField(max_length=100)
    duration = models.IntegerField()
    other_interpreters = models.ManyToManyField(Band, related_name='covers')

    class Meta:
        ordering = ('name',)
+48 −0
Original line number Diff line number Diff line
from __future__ import absolute_import, unicode_literals

from django.test import TestCase, RequestFactory
from django.contrib import admin
from django.contrib.admin.options import ModelAdmin
from django.contrib.auth.models import User

@@ -104,3 +105,50 @@ class TestInlineModelAdminOrdering(TestCase):
        inline = SongInlineNewOrdering(self.b, None)
        names = [s.name for s in inline.queryset(request)]
        self.assertEqual(['Jaded', 'Pink', 'Dude (Looks Like a Lady)'], names)


class TestRelatedFieldsAdminOrdering(TestCase):
    def setUp(self):
        self.b1 = Band(name='Pink Floyd', bio='', rank=1)
        self.b1.save()
        self.b2 = Band(name='Foo Fighters', bio='', rank=5)
        self.b2.save()

        # we need to register a custom ModelAdmin (instead of just using
        # ModelAdmin) because the field creator tries to find the ModelAdmin
        # for the related model
        class SongAdmin(admin.ModelAdmin):
            pass
        admin.site.register(Song, SongAdmin)

    def check_ordering_of_field_choices(self, correct_ordering):
        fk_field = admin.site._registry[Song].formfield_for_foreignkey(Song.band.field)
        m2m_field = admin.site._registry[Song].formfield_for_manytomany(Song.other_interpreters.field)

        self.assertEqual(list(fk_field.queryset), correct_ordering)
        self.assertEqual(list(m2m_field.queryset), correct_ordering)

    def test_no_admin_fallback_to_model_ordering(self):
        # should be ordered by name (as defined by the model)
        self.check_ordering_of_field_choices([self.b2, self.b1])

    def test_admin_with_no_ordering_fallback_to_model_ordering(self):
        class NoOrderingBandAdmin(admin.ModelAdmin):
            pass
        admin.site.register(Band, NoOrderingBandAdmin)

        # should be ordered by name (as defined by the model)
        self.check_ordering_of_field_choices([self.b2, self.b1])

    def test_admin_ordering_beats_model_ordering(self):
        class StaticOrderingBandAdmin(admin.ModelAdmin):
            ordering = ('rank', )
        admin.site.register(Band, StaticOrderingBandAdmin)

        # should be ordered by rank (defined by the ModelAdmin)
        self.check_ordering_of_field_choices([self.b1, self.b2])

    def tearDown(self):
        admin.site.unregister(Song)
        if Band in admin.site._registry:
            admin.site.unregister(Band)