Comment filtrer les objets de requête par plage de dates dans Django?

248

J'ai un champ dans un modèle comme:

class Sample(models.Model):
    date = fields.DateField(auto_now=False)

Maintenant, je dois filtrer les objets par plage de dates.

Comment filtrer tous les objets dont la date est comprise entre 1-Jan-2011et 31-Jan-2011?

user469652
la source

Réponses:

411

Utilisation

Sample.objects.filter(date__range=["2011-01-01", "2011-01-31"])

Ou si vous essayez simplement de filtrer par mois:

Sample.objects.filter(date__year='2011', 
                      date__month='01')

Éditer

Comme l'a dit Bernhard Vallant, si vous voulez un ensemble de requêtes qui exclut le, specified range endsvous devriez considérer sa solution , qui utilise gt / lt (supérieur à / inférieur à).

crodjer
la source
Quel est le type de données de date1? J'ai maintenant un objet datetime.
user469652
8
@dcordjer: En plus, il faut dire que cela __rangeinclut les bordures (comme sql BETWEEN), si vous ne voulez pas que les bordures soient incluses, vous devrez aller avec ma solution gt / lt ...
Bernhard Vallant
Est-ce intrinsèquement trié dans n'importe quel ordre? Si oui, quelle commande? Merci.
Richard Dunn
1
@RichardDunn La commande sera basée sur la commande par défaut de votre modèle, ou si vous utilisez order_byplus que le généré QuerySetpar ce qui précède filter. Je n'ai pas utilisé Django depuis des années.
crodjer
pour date__range vous devez mettre 01 du mois suivant. Voici un lien vers la documentaion qui explique qu'elle traduit à 00: 00: 00.0000 des dates, donc le dernier jour de votre plage n'est pas inclus. docs.djangoproject.com/en/1.10/ref/models/querysets/#range dans ce cas j'utilise: date__range = ["% s-% s-1"% (année, mois), "% s-% s- 1 "% (année, int (mois) +1)]
SpiRail
195

Vous pouvez utiliser les djangofilter avec des datetime.dateobjets :

import datetime
samples = Sample.objects.filter(sampledate__gte=datetime.date(2011, 1, 1),
                                sampledate__lte=datetime.date(2011, 1, 31))
Bernhard Vallant
la source
pour tout obtenir, y compris les jours 1 et 31, nous devrons utiliser gte non?
Sam Stoelinga du
1
Un avantage de l'utilisation de cette méthode par rapport à crodjer est que vous pouvez lui passer des objets datetime au lieu de chaînes.
Brian Kung
79

Lorsque vous effectuez des plages de django avec un filtre, assurez-vous de connaître la différence entre l'utilisation d'un objet date et d'un objet datetime. __range est inclusif sur les dates mais si vous utilisez un objet datetime pour la date de fin, il n'inclura pas les entrées pour ce jour si l'heure n'est pas définie.

    startdate = date.today()
    enddate = startdate + timedelta(days=6)
    Sample.objects.filter(date__range=[startdate, enddate])

renvoie toutes les entrées de la date de début à la date de fin, y compris les entrées à ces dates. Mauvais exemple, car cela renvoie des entrées une semaine dans le futur, mais vous obtenez la dérive.

    startdate = datetime.today()
    enddate = startdate + timedelta(days=6)
    Sample.objects.filter(date__range=[startdate, enddate])

il manquera 24 heures de saisie selon l'heure à laquelle les champs de date sont définis.

cademan
la source
5
Je pense qu'il est important de noter comment importer un dateobjet: >>> from datetime import date >>> startdate = date.today()
Alex Spencer
19

Vous pouvez contourner le «décalage d'impédance» causé par le manque de précision dans la DateTimeField/datecomparaison d'objets - qui peut se produire si vous utilisez la plage - en utilisant un datetime.timedelta pour ajouter un jour à la dernière date de la plage. Cela fonctionne comme:

start = date(2012, 12, 11)
end = date(2012, 12, 18)
new_end = end + datetime.timedelta(days=1)

ExampleModel.objects.filter(some_datetime_field__range=[start, new_end])

Comme discuté précédemment, sans faire quelque chose comme ça, les enregistrements sont ignorés le dernier jour.

Modifié pour éviter l'utilisation de datetime.combine- semble plus logique de s'en tenir aux instances de date lors de la comparaison avec un DateTimeField, au lieu de jouer avec des datetimeobjets jetables (et déroutants) . Voir plus d'explications dans les commentaires ci-dessous.

trojjer
la source
1
Il y a une bibliothèque Delorean impressionnante qui simplifie cela avec une méthode de troncature: delorean.readthedocs.org/en/latest/quickstart.html#truncation
trojjer
@tojjer: semble prometteur, comment utiliser la méthode tronquée ici cependant?
eugene
@eugene: J'ai exploré cela à nouveau tout à l'heure, après tous ces mois, et vous avez raison, cela n'aide pas vraiment dans cette situation après tout. Le seul moyen de contourner cela, comme je le pense, est comme suggéré dans ma réponse d'origine, qui est de fournir le `` remplissage '' supplémentaire pour la comparaison avec un champ de modèle datetime lorsque vous filtrez une instance de date. Cela peut être fait via la méthode datetime.combine comme ci-dessus, mais j'ai trouvé qu'il peut être un peu plus simple de simplement tenir compte de l'écart en ajoutant un timedelta (jours = 1) à la date de début / fin de la plage - - selon le problème.
trojjer
N'inclurait donc Example.objects.filter(created__range=[date(2014, 1, 1), date(2014, 2, 1)])pas les objets créés sur date(2014, 2, 1), comme @cademan l'a expliqué utilement. Mais si vous augmentez la date de fin en ajoutant un jour, vous obtiendrez un ensemble de requêtes couvrant ces objets manquants (et en omettant facilement les objets créés à date(2014, 2, 2)cause de la même bizarrerie). La chose ennuyeuse ici est qu'une plage `` manuelle '' spécifiée avec created__gte ... created__lte=date(2014, 2, 1)ne fonctionne pas non plus, ce qui est certainement à mon humble avis contre-intuitif.
trojjer
1
@tojjer: datetime_field__range = [delorean.parse ('2014-01-01'). date, delorean.parse ('2014-02-01'). date] fonctionne pour moi
eugene
1

Est simple,

YourModel.objects.filter(YOUR_DATE_FIELD__date=timezone.now())

Travaille pour moi

Jonhatan Fajardo
la source
3
Cela a fonctionné pour moi aussi, pour les noobs pour plus de clarté: (date__date = ...) signifie ({quel que soitColumnTheDateIsCalled} __ date)
Ryan Dines
2
OP a toutefois demandé une plage
aliasav
1

Pour le rendre plus flexible, vous pouvez concevoir un FilterBackend comme ci-dessous:

class AnalyticsFilterBackend(generic_filters.BaseFilterBackend):
    def filter_queryset(self, request, queryset, view):
        predicate = request.query_params # or request.data for POST

        if predicate.get('from_date', None) is not None and predicate.get('to_date', None) is not None:
            queryset = queryset.filter(your_date__range=(predicate['from_date'], predicate['to_date']))

        if predicate.get('from_date', None) is not None and predicate.get('to_date', None) is None:
            queryset = queryset.filter(your_date__gte=predicate['from_date'])

        if predicate.get('to_date', None) is not None and predicate.get('from_date', None) is None:
            queryset = queryset.filter(your_date__lte=predicate['to_date'])
        return queryset
saran3h
la source
-2

Toujours d'actualité aujourd'hui. Vous pouvez également faire:

import dateutil
import pytz

date = dateutil.parser.parse('02/11/2019').replace(tzinfo=pytz.UTC)
Bhavik Shah
la source