Django - How to filter by date with Django Rest Framework?
Asked Answered
P

8

17

I have some model with a timestamp field:

models.py

class Event(models.Model):
    event_type = models.CharField(
        max_length=100,
        choices=EVENT_TYPE_CHOICES,
        verbose_name=_("Event Type")
    )
    event_model = models.CharField(
        max_length=100,
        choices=EVENT_MODEL_CHOICES,
        verbose_name=_("Event Model")
    )
    timestamp = models.DateTimeField(auto_now=True, verbose_name=_("Timestamp"))

I'm then using Django-rest-framework to create an API endpoint for this class, with django-filter providing a filtering functionality as follows:

from .models import Event
from .serializers import EventSerializer
from rest_framework import viewsets, filters
from rest_framework import renderers
from rest_framework_csv import renderers as csv_renderers


class EventsView(viewsets.ReadOnlyModelViewSet):
    """
    A read only view that returns all audit events in JSON or CSV.
    """
    queryset = Event.objects.all()
    renderer_classes = (csv_renderers.CSVRenderer, renderers.JSONRenderer)
    serializer_class = EventSerializer
    filter_backends = (filters.DjangoFilterBackend,)
    filter_fields = ('event_type', 'event_model', 'timestamp')

with the following settings:

REST_FRAMEWORK = {
    'DEFAULT_FILTER_BACKENDS': ('rest_framework.filters.DjangoFilterBackend',),
}

I'm able to filter by event_type and event_model, but am having trouble filtering by the timestamp field. Essentially, I want to make an API call that equates to the following:

AuditEvent.objects.filter(timestamp__gte='2016-01-02 00:00+0000')

which I would expect I could do as follows:

response = self.client.get("/api/v1/events/?timestamp=2016-01-02 00:00+0000", **{'HTTP_ACCEPT': 'application/json'})

though that is incorect. How do I make an API call that returns all objects with a timestamp greater than or equal to a certain value?

Photosphere answered 12/5, 2016 at 10:17 Comment(0)
M
17

To expand on Flaiming's answer, if you're only ever going to be filtering via ISO datetime formats, it helps to overwrite the defaults to always use the IsoDateTimeFilter. This can be done per filterset with e.g.

import django_filters
from django.db import models as django_models
from django_filters import rest_framework as filters
from rest_framework import viewsets

class EventFilter(filters.FilterSet):
    class Meta:
        model = Event
        fields = {
            'timestamp': ('lte', 'gte')
        }

    filter_overrides = {
        django_models.DateTimeField: {
            'filter_class': django_filters.IsoDateTimeFilter
        },
    }

class EventsView(viewsets.ReadOnlyModelViewSet):
    ...
    filter_class = EventFilter

You then won't have to worry about setting a different filter for each lookup expression and each field.

Made answered 10/8, 2016 at 0:19 Comment(3)
EventFillter cause django backend filter not work anymoreCoahuila
filters.FilterSet doea not exist anymore in Django 2.1Yeorgi
Use this instead: from django_filters import rest_framework as filtersYeorgi
J
9

You can create specific FilterSet as follows:

import django_filters
from rest_framework import filters
from rest_framework import viewsets

class EventFilter(filters.FilterSet):
    timestamp_gte = django_filters.DateTimeFilter(field_name="timestamp", lookup_expr='gte')
    class Meta:
        model = Event
        fields = ['event_type', 'event_model', 'timestamp', 'timestamp_gte']

class EventsView(viewsets.ReadOnlyModelViewSet):
    ...
    filter_class = EventFilter

Than you can filter by "/api/v1/events/?timestamp_gte=2016-01-02"

EDIT: Just to clarify, this example uses django-filter library.

Jill answered 12/5, 2016 at 11:37 Comment(2)
Hmm, I'm still getting 0 responses with that, when I should be getting 1. I've tried using both django_filters.DateTimeFilter and django_filters.IsoDateTimeFilter. Any idea why that might be the case?Photosphere
It works for me but use DateFilter instead of DateTimeFilter. @PhotosphereMaier
I
8

IsoDateTimeFilter is very picky about the input format; instead of:

  • 2016-01-02 00:00+0000

use:

  • 2016-01-02T00:00:00Z
Internal answered 26/1, 2018 at 16:0 Comment(0)
S
3

Give parameter in date format instead of timestamp

Since you want to filter by only date not timestamp, you can consider writing customized django_filters.FilterSet class.

You can do so by replacing django_filters.DateTimeFilter to django_filters.DateFilter, adding __date as suffix to your timestamp field_name and add lookup_expr as exact value.

Have a look at code below for example.

Model Class

class RegistrationLink(models.Model):
    """marketing campaign links"""
    campaign_name = models.CharField("campaign name", max_length=128, unique=True)
    url = models.URLField("URL", max_length=200, null=True, unique=True)
    created_date = models.DateTimeField("created date and time", auto_now_add=True)
    created_by = models.ForeignKey(User, verbose_name="user ID", on_delete=models.PROTECT)
    visit_count = models.PositiveIntegerField("visit count", null=False, default=0)

FilterSet Class

class RegistrationLinkFilter(django_filters.FilterSet):
    created_date = django_filters.DateFilter(field_name='created_date__date', lookup_expr="exact")
    campaign_name = django_filters.CharFilter(lookup_expr='icontains')

    class Meta:
        model = RegistrationLink
        fields = ['campaign_name', 'created_date']

class based view

import django_filters
from django_filters.rest_framework import DjangoFilterBackend
from rest_framework.filters import OrderingFilter
# ...

class RegistrationLinkViewV2(ListAPIView):
    """Registration links for marketing campaigns"""
    permission_classes = (IsAuthenticated, ManagePermission)

    method = ''
    queryset = RegistrationLink.objects.all()
    serializer_class = LinkSerializer
    pagination_class = PageNumberPagination
    filter_backends = (DjangoFilterBackend, OrderingFilter,)
    filterset_class = RegistrationLinkFilter
    filterset_fields = ('campaign_name', 'created_date')
    ordering_fields = ('campaign_name', 'created_date',
                       'url', 'created_by__username')
    ordering = "-created_date"

    def get(self, request, *args, **kwargs):
        return self.list(request, *args, **kwargs)
Stroboscope answered 13/6, 2022 at 8:16 Comment(0)
W
2

A better way is to filter datetime in get_queryset function

def get_queryset(self):
    queryset = Event.objects.all()
    start_date = self.request.query_params.get('start_date', None)
    end_date = self.request.query_params.get('end_date', None)
    if start_date and end_date:
        queryset = queryset.filter(timstamp__range=[start_date, end_date])
Wieche answered 29/12, 2018 at 1:24 Comment(1)
This is not a good example, although it may work for a particular quick and dirty solution, as soon as you have requests of different types this will fail.Tineid
D
1

I don't know what is the case you are looking for. Basically, you can access the params from the views by date_params = self.request.query_params.get('params_name').

Then you can do Event.objects.filter(date__lte=date_params, date__gte=date_params)

Delacruz answered 18/5, 2022 at 16:22 Comment(1)
Maybe you mean: Event.objects.filter(timestamp__date__gte=date_params_since, timestamp__date__lte=date_params_until)Crapshooter
B
0

None of the answers worked for me but this did:

class EventFilter(filters.FilterSet):
    start = filters.IsoDateTimeFilter(field_name="start", lookup_expr='gte')
    end = filters.IsoDateTimeFilter(field_name="end", lookup_expr='lte')

    class Meta:
        model = Event
        fields = 'start', 'end',
Bueno answered 6/5, 2020 at 13:40 Comment(0)
I
0
from rest_framework import viewsets
from rest_framework.response import Response
from rest_framework.decorators import action
from .models import Product, Category
from .serializers import ProductSerializer

class ProductViewSet(viewsets.ModelViewSet):
    serializer_class = ProductSerializer

    def get_queryset(self):
        queryset = Product.objects.all()
        category_id = self.request.query_params.get('category', None)
        if category_id is not None:
            queryset = queryset.filter(category_id=category_id)
        return queryset
Inboard answered 15/6 at 7:29 Comment(1)
IMO this answer is not clear. A few words explaining how and why it works can help make it more useful.Closestool

© 2022 - 2024 — McMap. All rights reserved.