简体   繁体   中英

Django - How to filter by date with Django Rest Framework?

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?

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 eg

from django.db import models as django_models
import django_filters
from rest_framework import 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.

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

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

  • 2016-01-02 00:00+0000

use:

  • 2016-01-02T00:00:00Z

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

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',

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)

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)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM