How to limit the number of results in a Django REST serializar? - django

Hi have this serializer:
class ActivitiesSerializer(serializers.ModelSerializer):
activity = serializers.CharField(source='task.name')
project = serializers.CharField(source='project.name')
discipline = serializers.CharField(source='task.discipline.name')
class Meta:
model = Activities
fields = (
'id',
'activity',
'project',
'discipline',
)
How can I limit the number of results to 10?
This is my view:
class ActivitiesAPIView(generics.ListCreateAPIView):
search_fields = ['task__name', 'task__discipline__name', 'project__name']
filter_backends = (filters.SearchFilter,)
queryset = Activities.objects.all()
serializer_class = ActivitiesSerializer
Note that I want to limit the number of results to 10, but I want to search through all the model, so it wouldn't work to just limit my queryset to 10.

You can use pagination:
from rest_framework.pagination import PageNumberPagination
class DefaultPagination(PageNumberPagination):
page_size = 10
page_size_query_param = 'page_size'
max_page_size = 1000
Then in View:
class ActivitiesAPIView(generics.ListCreateAPIView):
pagination_class = DefaultPagination
https://www.django-rest-framework.org/api-guide/pagination/#pagenumberpagination

Related

How to filter on date in Django Rest Framework

I wrote the following code, trying to filter on dates using DRF:
class FixtureFilter(django_filters.FilterSet):
date = django_filters.DateFilter('date__date', lookup_expr='exact')
class Meta:
model = Fixture
fields = ['date']
class FixtureViewSet(viewsets.ReadOnlyModelViewSet):
queryset = Fixture.objects.all().order_by('-date')
serializer_class = FixtureSerializer
permission_classes = [permissions.IsAuthenticated]
filter_class = FixtureFilter
When I make a call to the API like http://localhost:8000/api/v1/fixtures?date=2021-11-29 it returns me more than 1 object whereas it should just return 1 object.
How do I implement this properly?
I believe you must add filter_backends field to FixtureViewSet to get it working, how documentation describes. Also, don't forget to update INSTALLED_APPS with django_filter as well. The corrected version would be:
from django_filters import rest_framework as filter
class FixtureFilter(django_filters.FilterSet):
date = django_filters.DateFilter('date', lookup_expr='exact')
class Meta:
model = Fixture
fields = ['date']
class FixtureViewSet(viewsets.ReadOnlyModelViewSet):
queryset = Fixture.objects.all().order_by('-date')
serializer_class = FixtureSerializer
permission_classes = [permissions.IsAuthenticated]
filter_backends = (filters.DjangoFilterBackend,)
filter_class = FixtureFilter

DRF pagination with ordering

Have trouble with pagination and ordering at the same time.
Problem is that one instance can be returned by view multiple times: for example on page 9 and then on page 11.
View:
from rest_framework import filters, viewsets
from django_filters.rest_framework import DjangoFilterBackend
from django_filters import FilterSet
from django_filters.filters import CharFilter
class TariffFilter(FilterSet):
concat_code = CharFilter(field_name='concat_code')
class Meta:
model = Tariff
fields = {
'id': ['in', 'exact'],
'title': ['exact', 'icontains', 'in'],
.... }
class TariffPagination(PageNumberPagination):
page_size = 10
page_size_query_param = 'page_size'
class TariffListViewSet(viewsets.ModelViewSet):
pagination_class = TariffPagination
serializer_class = tariff_serializer.TariffSerializer
filter_backends = (DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter,)
filterset_class = TariffFilter
ordering_fields = ['id', 'title', ...]
search_fields = ['title', ...]
def get_queryset(self):
concated_code = Concat(...., output_field=CharField())
queryset = Tariff.not_deleted_objects.annotate(concat_code=concated_code)
return queryset
Serializer:
class TariffSerializer(serializers.ModelSerializer):
element = TariffElementSerializer()
service = ServiceElementSerializer()
class Meta:
model = Tariff
fields = ('pk', 'title', 'code', 'element', 'service', 'concated_code', )
Where is problem?
example problem query:
http://127.0.0.1:8000/api/?ordering=-title&page=2

How to search query sets that have a specific value in a list in Django Rest framework

What I want to do
Getting all query set which has a specific value in a list which is ManyToManyField.
In short, if I type http://localhost:8000/api/pickup/?choosingUser=5, I would like to get all PickUp_Places that matches like choosingUser = 5. choosingUser comes from ManyToManyField so a PickUp_Places model has like choosingUser = [2,3,5,7].
Problem
I cannot search query sets by a specific value in list.
When I type http://localhost:8000/api/pickup/?choosingUser=5, I get all query sets that include ones don't have choosingUser = 5.
I am using django_filters in order to filter query sets. I read the documentation but I couldn't find how to get query sets by a value in a list.
If it is alright with you, would you please tell me how to do that?
Thank you very much.
===== ========= ========== =========
My code is like this.
models.py
class PickUp_Places(models.Model):
name = models.CharField(max_length=200, unique=True)
choosing_user = models.ManyToManyField(
settings.AUTH_USER_MODEL, related_name="pick_up")
def __str__(self):
return self.name
class Meta:
db_table = "pickup_places"
serializers.py
class PickUp_PlacesSerializer(serializers.ModelSerializer):
class Meta:
model = PickUp_Places
fields = "__all__"
views.py
class PickUp_PlacesViewSet(viewsets.ModelViewSet):
queryset = PickUp_Places.objects.all()
permission_classes = [
permissions.IsAuthenticatedOrReadOnly
]
serializer_class = PickUp_PlacesSerializer
filter_backends = [filters.DjangoFilterBackend]
filterset_fields = "__all__"
To get choosingUser from url override get_queryset method:
def get_queryset(self):
choosingUser = self.request.query_params.get('choosingUser')
queryset = choosingUser is not None and PickUp_Places.objects.filter(choosing_user__id=choosingUser) or PickUp_Places.objects.all()
return queryset
Elaborated version:
def get_queryset(self):
choosingUser = self.request.query_params.get('choosingUser')
if choosingUser is not None:
queryset = PickUp_Places.objects.filter(choosing_user__id=choosingUser)
else:
queryset = PickUp_Places.objects.all()
return queryset
One way to add filter based on M2M fields is to add filter_fields to your viewset, something like:
class PickUp_PlacesViewSet(viewsets.ModelViewSet):
queryset = PickUp_Places.objects.all()
permission_classes = [
permissions.IsAuthenticatedOrReadOnly
]
serializer_class = PickUp_PlacesSerializer
filter_backends = [filters.DjangoFilterBackend]
filter_fields = ('choosing_user', )
and then for filtering based on this field use http://localhost:8000/api/pickup/?choosing_user=5 which will return all the places that have user with id = 5 as it's choosing user.
If you want to filter based on multiple choosing_users you can do:
class PickUp_PlacesViewSet(viewsets.ModelViewSet):
queryset = PickUp_Places.objects.all()
permission_classes = [
permissions.IsAuthenticatedOrReadOnly
]
serializer_class = PickUp_PlacesSerializer
filter_backends = [filters.DjangoFilterBackend]
filter_fields = {
'choosing_user__id': ['in',]
}
and use http://localhost:8000/api/pickup/?choosing_user=5,1,2 to filter multiple ids.

How to filter by date range in django-rest?

I'd like to filter my data by date range typed in browser, all other filtering are working.
views.py
class BookView(generics.ListAPIView):
queryset = Book.objects.all()
serializer_class = BookSerializers
filter_backends = [filters.SearchFilter]
search_fields = ['title', 'language', 'authors', 'date']
You need create a new filter:
class StatementItemFilter(filters.FilterSet):
date_between = filters.DateFromToRangeFilter(field_name="MODEL_FIELD_NAME", label="Date (Between)")
class Meta:
model = StatementItem
fields = [
...
"date_between"
]
and use in your viewset
class MODELItemViewSet(viewsets.ReadOnlyModelViewSet):
filter_backends = (DjangoFilterBackend,)
filterset_class = StatementItemFilter
...
Move your list ['title', 'language', 'authors', 'date'] to your new filterset class

Django rest api - searching a method field with the search filter

im trying to filter search a rest api page and want to use a method field as one of the search fields, however when I do this I get an error stating the field is not valid and it then lists the field in my model as the only valid source
serialiser:
class SubnetDetailsSerializer(QueryFieldsMixin, serializers.HyperlinkedModelSerializer):
subnet = serializers.SerializerMethodField()
device = serializers.ReadOnlyField(
source='device.hostname',
)
circuit_name = serializers.ReadOnlyField(
source='circuit.name',
)
subnet_name = serializers.ReadOnlyField(
source='subnet.description',
)
safe_subnet = serializers.SerializerMethodField()
def get_safe_subnet(self, obj):
return '{}{}'.format(obj.subnet.subnet, obj.subnet.mask.replace('/','_'))
def get_subnet(self, obj):
return '{}{}'.format(obj.subnet.subnet, obj.subnet.mask)
class Meta:
model = DeviceCircuitSubnets
fields = ('id','device_id','subnet_id','circuit_id','subnet','safe_subnet','subnet_name','device','circuit_name')
views:
class SubnetDetailsSet(viewsets.ReadOnlyModelViewSet):
queryset = DeviceCircuitSubnets.objects.all().select_related('circuit','subnet','device')
serializer_class = SubnetDetailsSerializer
permission_classes = (IsAdminUser,)
filter_class = DeviceCircuitSubnets
filter_backends = (filters.SearchFilter,)
search_fields = (
'device__hostname',
'circuit__name',
'subnet__subnet',
'safe_subnet'
)
how can include the safe_subnet in the search fields?
Thanks
EDIT
This is the code now
views.py
class SubnetDetailsSet(viewsets.ReadOnlyModelViewSet):
queryset = DeviceCircuitSubnets.objects.all()
serializer_class = SubnetDetailsSerializer
permission_classes = (IsAdminUser,)
filter_class = DeviceCircuitSubnets
filter_backends = (filters.SearchFilter,)
search_fields = (
'device__hostname',
'circuit__name',
'subnet__subnet',
'safe_subnet'
)
def get_queryset(self):
return (
super().get_queryset()
.select_related('circuit','subnet','device')
.annotate(
safe_subnet=Concat(
F('subnet__subnet'),
Replace(F('subnet__mask'), V('/'), V('_')),
output_field=CharField()
)
)
)
serializer.py
class SubnetDetailsSerializer(QueryFieldsMixin, serializers.HyperlinkedModelSerializer):
subnet = serializers.SerializerMethodField()
device = serializers.ReadOnlyField(
source='device.hostname',
)
circuit_name = serializers.ReadOnlyField(
source='circuit.name',
)
subnet_name = serializers.ReadOnlyField(
source='subnet.description',
)
def get_safe_subnet(self, obj):
return getattr(obj, 'safe_subnet', None)
def get_subnet(self, obj):
return '{}{}'.format(obj.subnet.subnet, obj.subnet.mask)
class Meta:
model = DeviceCircuitSubnets
fields = ('id','device_id','subnet_id','circuit_id','subnet','safe_subnet','subnet_name','device','circuit_name')
Model:
class DeviceCircuitSubnets(models.Model):
device = models.ForeignKey(Device, on_delete=models.CASCADE)
circuit = models.ForeignKey(Circuit, on_delete=models.CASCADE, blank=True, null=True)
subnet = models.ForeignKey(Subnet, on_delete=models.CASCADE)
active_link = models.BooleanField(default=False, verbose_name="Active Link?")
active_link_timestamp = models.DateTimeField(auto_now=True, blank=True, null=True)
Error:
Exception Type: ImproperlyConfigured at /api/subnets/
Exception Value: Field name `safe_subnet` is not valid for model `DeviceCircuitSubnets`.
You need to annotate your queryset with the safe_subnet attribute so it becomes searchable.
from django.db.models import F, Value as V
from django.db.models.functions import Concat, Replace
class SubnetDetailsSet(viewsets.ReadOnlyModelViewSet):
queryset = DeviceCircuitSubnets.objects.all()
serializer_class = SubnetDetailsSerializer
permission_classes = (IsAdminUser,)
filter_class = DeviceCircuitSubnets
filter_backends = (filters.SearchFilter,)
search_fields = (
'device__hostname',
'circuit__name',
'subnet__subnet',
'safe_subnet'
)
def get_queryset(self):
return (
super().get_queryset()
.select_related('circuit','subnet','device')
.annotate(
safe_subnet=Concat(
F('subnet__subnet'),
Replace(F('subnet__mask'), V('/'), V('_')),
output_field=CharField()
)
)
)
Then in your serializer you can use the following.
def get_safe_subnet(self, obj):
return obj.safe_subnet
Previous answer with annotate is a really good start:
from .rest_filters import DeviceCircuitSubnetsFilter
class SubnetDetailsSet(viewsets.ReadOnlyModelViewSet):
queryset = DeviceCircuitSubnets.objects.all()
serializer_class = SubnetDetailsSerializer
permission_classes = (IsAdminUser,)
# That's where hint lays
filter_class = DeviceCircuitSubnetsFilter
#filter_backends = (filters.SearchFilter,)
search_fields = (
'device__hostname',
'circuit__name',
'subnet__subnet',
'safe_subnet'
)
#No need to override your queryset
Now in rest_filters.py
from django_filters import rest_framework as filters
from django.db.models import F, Value as V
from django.db.models.functions import Concat, Replace
#.... import models
class DeviceCircuitSubnets(filters.FilterSet):
safe_subnet = filters.CharFilter(
name='safe_subnet',
method='safe_subnet_filter')
def safe_subnet_filter(self, queryset, name, value):
"""
Those line will make ?safe_subnet=your_pk available
"""
return queryset.annotate(
safe_subnet=Concat(
F('subnet__subnet'),
Replace(F('subnet__mask'), V('/'), V('_')),
output_field=CharField()
)
).filter(safe_subnet=value)
)
class Meta:
model = DeviceCircuitSubnets
# See https://django-filter.readthedocs.io/en/master/guide/usage.html#generating-filters-with-meta-fields
# This pattern is definitely a killer!
fields = {
'device': ['exact', 'in'],
'circuit': ['exact', 'in'],
'subnet': ['exact', 'in'],
'active_link': ['exact'],
'active_link_timestamp': ['lte', 'gte']
}
Please note: I'm annotating safe_subnet within the filer, depending on how much you use this, you might want to set this up in your model's manager!
Going in a completely different direction from the other (excellent) answers. Since you want to be able to filter frequently on the safe_subnet field, why not just let it be an actual database field in your model? You could calculate and populate/update the value during one of your save methods and then just let django-filters do it's thing. This also has the advantage of allowing the filtering to be done directly through SQL which would theoretically provide better performance.