Puis-je faire un filtre de liste dans django admin pour ne montrer que les ForeignKeys référencés?
j'ai une application django qui a deux modèles comme celui-ci:
class MyModel(models.Model):
name = models.CharField()
country = models.ForeignKey('Country')
class Country(models.Model):
code2 = models.CharField(max_length=2, primary_key=True)
name = models.CharField()
la classe admin pour MyModel
ressemble à ceci:
class MyModelAdmin(admin.ModelAdmin):
list_display = ('name', 'country',)
list_filter = ('country',)
admin.site.register(models.MyModel, MyModelAdmin)
le tableau Country
contient ~250 pays. Seul un petit nombre de pays sont en fait mentionnés par certains exemples MyModel
.
le problème est que le filtre de liste dans le django admin liste tous les pays dans le panneau de filtre. La liste de tous les pays (et pas seulement ceux qui sont référencés par une instance) va à peu près à l'encontre de l'objectif de filtrer la liste dans ce cas.
y en a-t-il qui n'affichent que les pays référencés par MyModel
comme choix dans le filtre de la liste? (J'utilise Django 1.3.)
7 réponses
à partir de Django 1.8 , il y a un construit dans RelatedOnlyFieldListFilter
, que vous pouvez utiliser pour montrer les pays connexes.
class MyModelAdmin(admin.ModelAdmin):
list_display = ('name', 'country',)
list_filter = (
('country', admin.RelatedOnlyFieldListFilter),
)
pour Django 1.4-1.7, list_filter
vous permet d'utiliser une sous-classe de SimpleListFilter
. Il devrait être possible de créer un filtre de liste simple qui liste les valeurs que vous voulez.
si vous ne pouvez pas mettre à jour à partir de Django 1.3, vous devez utiliser l'api FilterSpec
interne, et non documentée. Le Débordement De La Pile la question filtre personnalisé dans Django Admin devrait vous diriger dans la bonne direction.
je sais que la question était au sujet de Django 1.3 mais vous avez mentionné sur la mise à niveau prochaine à 1.4. Aussi pour les gens, comme moi qui était à la recherche d'une solution pour 1.4, mais a trouvé cette entrée, j'ai décidé de montrer l'exemple complet de l'utilisation de SimpleListFilter (disponible Django 1.4) Pour montrer seulement référencé (lié, utilisé) les valeurs de clé étrangère
from django.contrib.admin import SimpleListFilter
# admin.py
class CountryFilter(SimpleListFilter):
title = 'country' # or use _('country') for translated title
parameter_name = 'country'
def lookups(self, request, model_admin):
countries = set([c.country for c in model_admin.model.objects.all()])
return [(c.id, c.name) for c in countries]
# You can also use hardcoded model name like "Country" instead of
# "model_admin.model" if this is not direct foreign key filter
def queryset(self, request, queryset):
if self.value():
return queryset.filter(country__id__exact=self.value())
else:
return queryset
# Example setup and usage
# models.py
from django.db import models
class Country(models.Model):
name = models.CharField(max_length=64)
class City(models.Model):
name = models.CharField(max_length=64)
country = models.ForeignKey(Country)
# admin.py
from django.contrib.admin import ModelAdmin
class CityAdmin(ModelAdmin):
list_filter = (CountryFilter,)
admin.site.register(City, CityAdmin)
par exemple, vous pouvez voir deux modèles - ville et la campagne. La ville a ForeignKey to Country. Si vous utilisez régulièrement list_filter = ('country',) vous aurez tous les pays dans le chooser. Cet extrait ne filtre cependant que les pays liés - ceux qui ont au moins une relation avec la ville.
idée originale de ici . Un grand merci à l'auteur. Noms de classe améliorés pour une meilleure clarté et utilisation de model_admin.modèle au lieu du nom de modèle codé en dur.
exemple également disponible dans Django Snippets: http://djangosnippets.org/snippets/2885 /
depuis Django 1.8 il y a: admin.RelatedOnlyFieldListFilter
l'exemple d'usage est:
class BookAdmin(admin.ModelAdmin):
list_filter = (
('author', admin.RelatedOnlyFieldListFilter),
)
je changerais les recherches dans le code de darklow comme ceci:
def lookups(self, request, model_admin):
users = User.objects.filter(id__in = model_admin.model.objects.all().values_list('user_id', flat = True).distinct())
return [(user.id, unicode(user)) for user in users]
C'est mieux pour la base de données ;)
c'est mon point de vue sur une implémentation générale et réutilisable pour Django 1.4, Si vous êtes bloqué sur cette version. Il est inspiré de la version qui fait maintenant partie de Django 1.8 et plus. En outre, il devrait être tout à fait une petite tâche de s'adapter à 1.5–1.7, principalement le queryset méthodes ont changé de nom. J'ai mis le filtre lui-même dans une application core
que j'ai mais vous pouvez évidemment le mettre n'importe où.
mise en Œuvre:
# myproject/core/admin/filters.py:
from django.contrib.admin.filters import RelatedFieldListFilter
class RelatedOnlyFieldListFilter(RelatedFieldListFilter):
def __init__(self, field, request, params, model, model_admin, field_path):
self.request = request
self.model_admin = model_admin
super(RelatedOnlyFieldListFilter, self).__init__(field, request, params, model, model_admin, field_path)
def choices(self, cl):
limit_choices_to = set(self.model_admin.queryset(self.request).values_list(self.field.name, flat=True))
self.lookup_choices = [(pk_val, val) for pk_val, val in self.lookup_choices if pk_val in limit_choices_to]
return super(RelatedOnlyFieldListFilter, self).choices(cl)
Utilisation:
# myapp/admin.py:
from django.contrib import admin
from myproject.core.admin.filters import RelatedOnlyFieldListFilter
from myproject.myapp.models import MyClass
class MyClassAdmin(admin.ModelAdmin):
list_filter = (
('myfield', RelatedOnlyFieldListFilter),
)
admin.site.register(MyClass, MyClassAdmin)
si vous mettez à jour plus tard vers Django 1.8 vous devriez être en mesure de changer cette importation:
from myproject.core.admin.filters import RelatedOnlyFieldListFilter
à ceci:
from django.contrib.admin.filters import RelatedOnlyFieldListFilter
@andi, merci de faire savoir que Django 1.8 aura cette fonctionnalité.
j'ai regardé comment il a été mis en œuvre et basé sur cette version créée qui fonctionne pour Django 1.7. C'est une meilleure implémentation que ma réponse précédente, parce que maintenant vous pouvez réutiliser ce filtre avec n'importe quel champ Clé étranger. Testé uniquement dans Django 1.7, pas sûr qu'il fonctionne dans les versions précédentes.
voici ma solution finale:
from django.contrib.admin import RelatedFieldListFilter
class RelatedOnlyFieldListFilter(RelatedFieldListFilter):
def __init__(self, field, request, params, model, model_admin, field_path):
super(RelatedOnlyFieldListFilter, self).__init__(
field, request, params, model, model_admin, field_path)
qs = field.related_field.model.objects.filter(
id__in=model_admin.get_queryset(request).values_list(
field.name, flat=True).distinct())
self.lookup_choices = [(each.id, unicode(each)) for each in qs]
Utilisation:
class MyAdmin(admin.ModelAdmin):
list_filter = (
('user', RelatedOnlyFieldListFilter),
('category', RelatedOnlyFieldListFilter),
# ...
)
une version réutilisable généralisée de la réponse de la grande @darklow:
def make_RelatedOnlyFieldListFilter(attr_name, filter_title):
class RelatedOnlyFieldListFilter(admin.SimpleListFilter):
"""Filter that shows only referenced options, i.e. options having at least a single object."""
title = filter_title
parameter_name = attr_name
def lookups(self, request, model_admin):
related_objects = set([getattr(obj, attr_name) for obj in model_admin.model.objects.all()])
return [(related_obj.id, unicode(related_obj)) for related_obj in related_objects]
def queryset(self, request, queryset):
if self.value():
return queryset.filter(**{'%s__id__exact' % attr_name: self.value()})
else:
return queryset
return RelatedOnlyFieldListFilter
Utilisation:
class CityAdmin(ModelAdmin):
list_filter = (
make_RelatedOnlyFieldListFilter("country", "Country with cities"),
)