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
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:
expected_parameters
NotImplementedError
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()
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.
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'.