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.)

44
demandé sur m000 2012-08-31 16:51:45

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.

64
répondu Alasdair 2017-05-23 12:34:53

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 /

31
répondu darklow 2013-01-28 20:18:15

depuis Django 1.8 il y a: admin.RelatedOnlyFieldListFilter

l'exemple d'usage est:

class BookAdmin(admin.ModelAdmin):
    list_filter = (
        ('author', admin.RelatedOnlyFieldListFilter),
    )
19
répondu Andrzej Kostański 2015-08-23 20:39:15

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 ;)

5
répondu 2014-03-28 09:10:34

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
2
répondu Markus Amalthea Magnuson 2015-05-07 07:41:36

@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),
        # ...
    )
1
répondu darklow 2015-03-24 13:57:45

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"),
    )
1
répondu Dennis Golomazov 2015-04-07 20:55:12