web-dev-qa-db-fra.com

Django admin ajouter un filtre personnalisé

j'utilise Django 1.10 et j'ai besoin d'afficher des données et de créer un filtre basé sur une valeur d'un modèle différent (qui a une clé étrangère référençant mon modèle qui est utilisé sur le modèle d'administration) Ce sont mes 2 modèles: Celui-ci est utilisé pour générer le modèle:

class Job(models.Model):
    company = models.ForeignKey(Company)
    title = models.CharField(max_length=100, blank=False)
    description = models.TextField(blank=False, default='')
    store = models.CharField(max_length=100, blank=True, default='')
    phone_number = models.CharField(max_length=60, null=True, blank=True)

C'est l'autre qui contient une référence de clé étrangère à ma première:

class JobAdDuration(models.Model):
    job = models.ForeignKey(Job)
    ad_activated = models.DateTimeField(auto_now_add=True)
    ad_finished = models.DateTimeField(blank=True, null=True)

Dans mon modèle, j'ai pu afficher les (dernières) heures de début et de fin

def start_date(self,obj):
    if JobAdDuration.objects.filter(job=obj.id).exists():
        tempad = JobAdDuration.objects.filter(job=obj).order_by("-id")[0]
        return tempad.ad_activated

Et puis j'appelle juste cela dans le list_display et cela fonctionne très bien. Cependant, j'ai du mal à définir un champ de filtre en utilisant ces critères.

Si je l'ajoute simplement à mon list_filter, j'obtiens une erreur indiquant qu'il n'y a pas un tel champ à l'intérieur de mon modèle, ce qui est vrai (puisque celui-ci se trouve dans une autre table qui fait référence à ma table de travail). Je me demandais donc quelle était la bonne approche pour résoudre ce problème? Dois-je créer une autre fonction pour le filtre lui-même, mais même dans ce cas, je ne sais pas comment l'appeler dans le list_filter.

Voici un extrait de ma page d'administration Django.

class JobAdmin(admin.OSMGeoAdmin, ImportExportModelAdmin):
    inlines = [
    ]

    readonly_fields = ( 'id', "start_date", )

    raw_id_fields = ("company",)

    list_filter = (('JobAdDuration__ad_activated', DateRangeFilter), 'recruitment', 'active', 'deleted', 'position', ('created', DateRangeFilter), 'town')
    search_fields = ('title', 'description', 'company__name', 'id', 'phone_number', 'town')
    list_display = ('title', 'id', 'description', 'active', 'transaction_number', 'company', 'get_position', 'town','created', 'expires', 'views', 'recruitment', 'recruits', 'paid', 'deleted', "start_date", "end_Date", "ad_consultant")


    def start_date(self,obj):
        if JobAdDuration.objects.filter(job=obj.id).exists():
            tempad = JobAdDuration.objects.filter(job=obj).order_by("-id")[0]
            return tempad.ad_activated

EDIT: En attendant, j'ai essayé de le résoudre avec un simple filtre de liste, mais je ne peux pas le faire fonctionner. Je voudrais placer 2 champs de saisie avec un calendrier (comme le DateRangeFilter par défaut) qui représenterait l'heure de début et de fin, puis retourner des données basées sur ces valeurs. Ceci est ma fonctionnalité "prototype" pour le filtre simple, cela fonctionne mais il renvoie des données codées en dur.

class StartTimeFilter(SimpleListFilter):
    title = ('Start date')
    parameter_name = 'ad_finished'

    def lookups(self, request, model_admin):
       #return JobAdDuration.objects.values_list("ad_finished")
       return (
       ('startDate', 'stest1'),
       ('startDate1', 'test2')
       )

    def queryset(self, request, queryset):
        if not self.value():
            return queryset


    assigned = JobAdDuration.objects.filter(ad_finished__range=(datetime.now() - timedelta(minutes=45000), datetime.now()))
    allJobs = Job.objects.filter(pk__in=[current.job.id for current in assigned])
    return allJobs
18
Proxy

J'irais avec un FieldListFilter personnalisé car il permet de lier le filtre à différents champs du modèle en fonction de vos besoins.

Ce que nous faisons réellement pour implémenter un tel filtre est le suivant:

  • construire lookup_kwargs gte et lte et les spécifier comme expected_parameters
  • définir des choix pour retourner une liste vide sinon NotImplementedError
  • créer un formulaire pour soigner la validation des champs
  • créer un modèle personnalisé qui sort simplement le formulaire, par exemple {{spec.form}}
  • si le formulaire est valide, prenez les données nettoyées, filtrez les Nones et filtrez le jeu de requêtes, sinon faites quelque chose avec des erreurs (dans le code ci-dessous, les erreurs sont réduites au silence)

Code de filtre:

class StartTimeFilter(admin.filters.FieldListFilter):
    # custom template which just outputs form, e.g. {{spec.form}}
    template = 'start_time_filter.html'

    def __init__(self, *args, **kwargs):
        field_path = kwargs['field_path']
        self.lookup_kwarg_since = '%s__gte' % field_path
        self.lookup_kwarg_upto = '%s__lte' % field_path
        super(StartTimeFilter, self).__init__(*args, **kwargs)
        self.form = StartTimeForm(data=self.used_parameters, field_name=field_path)

    def expected_parameters(self):
        return [self.lookup_kwarg_since, self.lookup_kwarg_upto]

    # no predefined choices
    def choices(self, cl):
        return []

    def queryset(self, request, queryset):
        if self.form.is_valid():
            filter_params = {
                p: self.form.cleaned_data.get(p) for p in self.expected_parameters()
                if self.form.cleaned_data.get(p) is not None
            }
            return queryset.filter(**filter_params)
        else:
            return queryset

Le formulaire peut être aussi simple que suit:

class StartTimeForm(forms.Form):

    def __init__(self, *args, **kwargs):
        self.field_name = kwargs.pop('field_name')
        super(StartTimeForm, self).__init__(*args, **kwargs)
        self.fields['%s__gte' % self.field_name] = forms.DateField()
        self.fields['%s__lte' % self.field_name] = forms.DateField()
9
bellum

Ce n'est pas exactement ce que vous avez demandé, mais vous pourriez plutôt avoir le filtre sur JobAdDuration modelAdmin. De cette façon, vous pouvez filtrer les travaux correspondants en fonction du ad_activated et ad_finished des champs. Et j'ai ajouté un lien vers le champ job, afin que vous puissiez directement cliquer dessus pour une navigation plus facile.

Pour en faire un filtre de date html5, j'ai utilisé la bibliothèque Django-admin-rangefilter .

from Django.urls import reverse
from Django.contrib import admin
from .models import Job, JobAdDuration
from Django.utils.html import format_html
from rangefilter.filter import DateRangeFilter


@admin.register(JobAdDuration)
class JobAdDurationAdmin(admin.ModelAdmin):

    list_filter = (('ad_activated', DateRangeFilter), ('ad_finished', DateRangeFilter))
    list_display = ('id', 'job_link', 'ad_activated', 'ad_finished')

    def job_link(self, obj):
        return format_html('<a href="{}">{}</a>', reverse('admin:job_job_change', args=[obj.job.id]), obj.job.title)
    job_link.short_description = 'Job'

Si vous voulez en effet emprunter la route existante (filtre à l'intérieur de JobAdmin), alors les choses deviendront assez compliquées.

5
shad0w_wa1k3r

J'ai récemment rencontré un problème similaire où j'avais besoin de filtrer les données en fonction de la valeur d'un autre modèle. Cela peut être fait en utilisant SimpleListFilter. Vous avez juste besoin d'un petit tweak dans la fonction de recherche et de requête. Je vais vous suggérer d'installer Django debug toolbar afin que vous puissiez savoir quelles requêtes SQL sont exécutées en interne par Django.

#import your corresponding models first

class StartTimeFilter(SimpleListFilter):
title = ('Start date')
parameter_name = 'ad_finished'

  def lookups(self, request, model_admin):

   data = []
   qs = JobAdDuration.objects.filter()   # Note : if you do not have distinct values of ad_activated apply distinct filter here to only get distinct values
   print qs
   for c in qs:
       data.append([c.ad_activated, c.ad_activated])  # The first c.activated is the queryset condition your filter will execute on your Job model to filter data ... and second c.ad_activated is the data that will be displayed in dropdown in StartTimeFilter
   return data

  def queryset(self, request, queryset):
     if self.value():
       assigned = JobAdDuration.objects.filter(ad_activated__exact = self.value())  # add your custom filter function based on your requirement
       return Job.objects.filter(pk__in=[current.job.id for current in assigned])
     else:
       return queryset

et dans list_filter

list_filter = (StartTimeFilter) # no quotes else it will search for a field in the model 'job'.
3
Aman trigunait