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 d'administration 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. Seuls quelques pays sont référencés par une instance MyModel
.
Le problème est que le filtre de liste dans le Django admin liste TOUS les pays dans le panneau de filtrage. Liste de tous les pays (et pas seulement ceux qui sont référencés par une instance) défait à peu près le but d'avoir le filtre de liste dans ce cas.
Y en a-t-il pour afficher uniquement les pays référencés par MyModel
comme choix dans le filtre de liste? (J'utilise Django 1.3.)
Depuis Django 1.8, il existe un RelatedOnlyFieldListFilter
intégré, que vous pouvez utiliser pour afficher les pays liés.
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 simple filtre de liste qui répertorie les valeurs souhaitées.
Si vous ne pouvez pas mettre à niveau depuis Django 1.3, vous devez utiliser l'api interne et non documenté FilterSpec
. La question de dépassement de pile Le filtre personnalisé dans Django Admin devrait vous orienter dans la bonne direction.
Je sais que la question portait sur Django 1.3 mais vous l'avez mentionné sur la prochaine mise à niveau vers 1.4. Aussi pour les gens, comme moi qui cherchaient une solution pour 1.4, mais qui ont trouvé cette entrée, j'ai décidé de montrer un exemple complet d'utiliser SimpleListFilter (disponible Django 1.4) pour afficher uniquement les valeurs de clé étrangère référencées (liées, utilisées)
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 Pays. La ville possède une clé étrangère vers le pays. Si vous utilisez list_filter = ('country',) normal, vous aurez tous les pays dans le sélecteur. 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.model 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'utilisation 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 beaucoup mieux pour la base de données;)
Ceci 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. Elle est inspirée par la version intégrée qui fait maintenant partie de Django 1.8 et plus. De plus, ce devrait être une tâche assez petite de l'adapter à 1.5–1.7, principalement les méthodes de jeu de requêtes ont changé de nom dans celles-ci. se filtrer dans une application core
que j'ai mais vous pouvez évidemment le mettre n'importe où.
Implémentation:
# 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 effectuez une mise à jour ultérieure vers Django 1.8, vous devriez pouvoir simplement modifier cette importation:
from myproject.core.admin.filters import RelatedOnlyFieldListFilter
Pour ça:
from Django.contrib.admin.filters import RelatedOnlyFieldListFilter
@andi, merci d'avoir fait savoir que Django 1.8 aura cette fonctionnalité.
J'ai regardé comment il a été implémenté 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, car maintenant vous pouvez réutiliser ce filtre avec n'importe quel étranger Champs clés Testé uniquement dans Django 1.7, je ne sais pas si cela fonctionne dans les versions antérieures.
Voici ma dernière solution:
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]
Usage:
class MyAdmin(admin.ModelAdmin):
list_filter = (
('user', RelatedOnlyFieldListFilter),
('category', RelatedOnlyFieldListFilter),
# ...
)
Une version réutilisable généralisée de la réponse du grand @ 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
Usage:
class CityAdmin(ModelAdmin):
list_filter = (
make_RelatedOnlyFieldListFilter("country", "Country with cities"),
)