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.
This commit is contained in:
Juan Pedro Fisanotti 2013-02-23 15:02:01 -03:00 committed by Ramiro Morales
parent 3ea0c7d35a
commit d9330d5be2
4 changed files with 72 additions and 0 deletions

View File

@ -165,6 +165,7 @@ answer newbie questions, and generally made Django that much better:
Matt Dennenbaum Matt Dennenbaum
deric@monowerks.com deric@monowerks.com
Max Derkachev <mderk@yandex.ru> Max Derkachev <mderk@yandex.ru>
Carlos Matías de la Torre <cmdelatorre@gmail.com>
Rajesh Dhawan <rajesh.dhawan@gmail.com> Rajesh Dhawan <rajesh.dhawan@gmail.com>
Sander Dijkhuis <sander.dijkhuis@gmail.com> Sander Dijkhuis <sander.dijkhuis@gmail.com>
Jordan Dimov <s3x3y1@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> Stefane Fermgier <sf@fermigier.com>
J. Pablo Fernandez <pupeno@pupeno.com> J. Pablo Fernandez <pupeno@pupeno.com>
Maciej Fijalkowski Maciej Fijalkowski
Juan Pedro Fisanotti <fisadev@gmail.com>
Ben Firshman <ben@firshman.co.uk> Ben Firshman <ben@firshman.co.uk>
Matthew Flanagan <http://wadofstuff.blogspot.com> Matthew Flanagan <http://wadofstuff.blogspot.com>
Eric Floehr <eric@intellovations.com> Eric Floehr <eric@intellovations.com>

View File

@ -157,6 +157,19 @@ class BaseModelAdmin(six.with_metaclass(forms.MediaDefiningClass)):
) )
return db_field.formfield(**kwargs) 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): def formfield_for_foreignkey(self, db_field, request=None, **kwargs):
""" """
Get a form Field for a ForeignKey. 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 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) return db_field.formfield(**kwargs)
def formfield_for_manytomany(self, db_field, request=None, **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)): 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)) 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) return db_field.formfield(**kwargs)
def _declared_fieldsets(self): def _declared_fieldsets(self):

View File

@ -15,6 +15,7 @@ class Song(models.Model):
band = models.ForeignKey(Band) band = models.ForeignKey(Band)
name = models.CharField(max_length=100) name = models.CharField(max_length=100)
duration = models.IntegerField() duration = models.IntegerField()
other_interpreters = models.ManyToManyField(Band, related_name='covers')
class Meta: class Meta:
ordering = ('name',) ordering = ('name',)

View File

@ -1,6 +1,7 @@
from __future__ import absolute_import, unicode_literals from __future__ import absolute_import, unicode_literals
from django.test import TestCase, RequestFactory from django.test import TestCase, RequestFactory
from django.contrib import admin
from django.contrib.admin.options import ModelAdmin from django.contrib.admin.options import ModelAdmin
from django.contrib.auth.models import User from django.contrib.auth.models import User
@ -104,3 +105,50 @@ class TestInlineModelAdminOrdering(TestCase):
inline = SongInlineNewOrdering(self.b, None) inline = SongInlineNewOrdering(self.b, None)
names = [s.name for s in inline.queryset(request)] names = [s.name for s in inline.queryset(request)]
self.assertEqual(['Jaded', 'Pink', 'Dude (Looks Like a Lady)'], names) 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)