How can we write a function in a ModelViewSet that get a list of distinct record in the database?
Supposed that we have this model.
class Animal(models.Model):
this_id = models.CharField(max_length=25)
name = models.CharField(max_length=25)
species_type = models.CharField(max_length=25)
...
and serializer
class AnimalSerializer(serializers.ModelSerializer):
class Meta:
model = Animal
fields = (
'this_id',
'name',
'species_type',
...,
)
read_only_fields = ('id', 'created_at', 'updated_at')
and ViewSet.
class AnimalViewSet(viewsets.ModelViewSet):
"""
This viewset automatically provides `list`, `create`, `retrieve`,
`update` and `destroy` actions.
"""
queryset = Animal.objects.all()
serializer_class = AnimalSerializer
I found this link useful such as decorators like #list_route()
but i can't understand it well.
I would like to get list of distinct Animal.species_type record from the ViewSet. Please help.
There are several different options in filtering. You can send the species type via your request /animals?species_type=MusMusculus and reference it when you over ride the get_queryset() method in the view.
In your view
def get_queryset(self):
species = self.request.query_params.get('species_type', None)
if species is not None:
queryset = Animals.objects.all().distinct('species_type')
species = SpeciesSerializer(data=queryset)
return queryset
Serializer
from rest_framework import serializers
class Species(serializers.Serializer):
species_type = serializers.Charfield()
alternatively, you can adopt a django filter framework http://www.django-rest-framework.org/api-guide/filtering/#djangofilterbackend
Related
These are simplified versions of my models (the user model is just an id and name)
class Convo(models.Model):
owner = models.ForeignKey(User, on_delete=models.CASCADE, related_name='convo_owner')
users = models.ManyToManyField(User, through='Convo_user')
class Convo_user (models.Model):
user = models.ForeignKey(UserProfile, on_delete=models.CASCADE)
convo = models.ForeignKey(Convo, on_delete=models.CASCADE)
class Comments(models.Model):
name = models.CharField(max_length=255)
content = models.TextField(max_length=1024)
convo = models.ForeignKey(Convo, on_delete=models.CASCADE)
This is my view
class ConvoViewSet(viewsets.ModelViewSet):
serializer_class = serializers.ConvoSerializer
def get_queryset(self):
return None
def list(self, request):
curr_user = request.user.id
# Collecting the list of conversations
conversations = models.Conversation.object.filter(ConvoUser__user_id=request.user.id)
#Getting list of conversation id's
conv_ids = list(conversations.values_list('id', flat=True).order_by('id'))
#Getting list of relevant comments
comments = models.Comments.objects.filter(conversation_id__in=conv_ids)
return Response(self.get_serializer(conversations, many=True).data)
And my current serializer
class ConvoSerializer(serializers.ModelSerializer):
"""A serializer for messaging objects"""
# access = AccessSerializer(many=True)
# model = models.Comments
# fields = ('id', 'name', 'content', 'convo_id')
class Meta:
model = models.Convo
fields = ('id', 'owner_id')
The current response I get is of the form
[
{
"id": 1,
"owner_id": 32
}, ...
]
But I would like to add a comments field that shows all the properties of comments into the response, so basically everything in the second queryset (called comments) and I'm not sure how to go about this at all. (I retrieve the comments in the way I do because I'm trying to minimize the calls to the database). Would I need to create a new view for comments, make its own serializer and then somehow combine them into the serializer for the convo?
The way you've set up your models, you can access the comments of each Convo through Django's ORM by using convo_object.comments_set.all(), so you could set up your ConvoSerializer to access that instance's comments, like this:
class ConvoSerializer(serializers.ModelSerializer):
"""A serializer for messaging objects"""
comments_set = CommentSerializer(many=True)
class Meta:
model = models.Convo
fields = ('id', 'owner_id', 'comments_set')
and then you define your CommentSerializer like:
class CommentSerializer(serializers.ModelSerializer):
class Meta:
model = models.Comments
fields = ('id', 'name', 'content')
No data appears because my serializers are using the default database, not sure why but a step forward
EDIT:
Django: Database used for prefetch_related is not the same that the parent query Provided me the correct answer, I was able to choose the database with this method because for some reason inner queries use the default DB
I have an issue with my DRF API.
I would like to filter a list of issues against a list of ids. Like so:
127.0.0.1:8000/api/issues/?id=2,12
This returns the entire list of issues
I've also tried
http://127.0.0.1:8000/api/issues/?id=2&id=12
This returns a list containing only the object with the last supplied id (the object with id 12
I've also tried the following which all return the entire set
http://127.0.0.1:8000/api/issues/?id__in=2&id__in=12
http://127.0.0.1:8000/api/issues/?id__in=2,12
Here's my serializer
from rest_framework import serializers
...
class IssueSerializer(serializers.HyperlinkedModelSerializer):
'''Serializer for issues'''
class Meta:
'''Model filed definitions'''
model = Issue
fields = ('id', 'inspection_sheet', 'picture', 'description', 'resolution')
And the view
from rest_framework import filters
from rest_framework import viewset
...
class IssueSet(viewsets.ModelViewSet):
'''Views for issues'''
queryset = Issue.objects.all()
serializer_class = IssueSerializer
filter_backends = (filters.DjangoFilterBackend,)
filter_fields = ('id',)
To achieve this you will have to create a Filterset using a BaseInFilter:
class NumberInFilter(BaseInFilter, NumberFilter):
pass
class IssueFilter(FilterSet):
id = NumberInFilter(name='id', lookup_expr='in')
class Meta:
fields = ['id']
model = Issue
class IssueSet(viewsets.ModelViewSet):
'''Views for issues'''
queryset = Issue.objects.all()
serializer_class = IssueSerializer
filter_backends = (filters.DjangoFilterBackend,)
filter_class = IssueFilter
You should then be able to use :
http://127.0.0.1:8000/api/issues/?id=2&id=12
I define this viewset and i would like to create a custom function that returns distinct of animals species_type called distinct_species.
class AnimalViewSet(viewsets.ModelViewSet):
"""
This viewset automatically provides `list`, `create`, `retrieve`,
`update` and `destroy` actions.
"""
queryset = Animal.objects.all()
serializer_class = AnimalSerializer
#list_route()
def distinct_species(self, request):
query_set = Animal.objects.values('species_type').distinct()
serializer = self.get_serializer(query_set, many=True)
return Response(serializer.data)
This is my Animal model
class Animal(models.Model):
this_id = models.CharField(max_length=25)
name = models.CharField(max_length=25)
species_type = models.CharField(max_length=25)
breed = models.CharField(max_length=25)
....
This is my AnimalSerializer
class AnimalSerializer(serializers.ModelSerializer):
class Meta:
model = Animal
fields = (
'this_id',
'name',
'species_type',
'breed',
...
)
read_only_fields = ('id', 'created_at', 'updated_at')
I register the route here.
# Create a router and register our viewsets with it.
router = DefaultRouter()
router.register(r'animal', AnimalViewSet)
urlpatterns = [
url(r'^api/', include(router.urls)),
url(r'^', IndexView.as_view(), name='index'),
]
But when i do this /api/animal/distinct_species/
I got this KeyError:
u"Got KeyError when attempting to get a value for field this_id on
serializer AnimalSerializer.\nThe serializer field might be named
incorrectly and not match any attribute or key on the dict
instance.\nOriginal exception text was: u'this_id'."
The trouble stems from this one:
query_set = Animal.objects.values('species_type').distinct()
Your query_set object now a list of dictionaries. The dictionary contains exactly one field - species_type
[{'species_type': 'a'},{'species_type': 'b'},...]
Thus it does not have a this_id which your serializer requires. If you were on postgresql, you could change your query to
query_set = Animal.objects.distinct('species_type')
Second option is to list all the fields required by the serializer in the values() call
Third option is to create a different serializer for use by the distinct_species end point this serializer will not be a ModelSerializer
I have following models in django app:
models.py:
class Make(BaseModel):
slug = models.CharField(max_length=32) #alfa-romeo
name = models.CharField(max_length=32) #Alfa Romeo
def __unicode__(self):
return self.name
class Model(BaseModel):
make = models.ForeignKey(Make) #Alfa Romeo
name = models.CharField(max_length=64) # line[2]
engine_capacity = models.IntegerField()
trim = models.CharField(max_length=128) # line[4]
And serializers.py:
from .models import Make,Model
from rest_framework import serializers
class MakeSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Make
fields = ('url', 'slug', 'name')
class ModelSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Model
fields = ('url', 'make', 'name', 'trim', 'engine_capacity')
and also views.py:
from rest_framework import viewsets
from rest_framework import filters
from rest_framework import generics
from .models import Make, Model
from .serializers import MakeSerializer, ModelSerializer
class MakeViewSet(viewsets.ModelViewSet):
queryset = Make.objects.all()
serializer_class = MakeSerializer
filter_backends = (filters.DjangoFilterBackend,)
class ModelViewSet(viewsets.ModelViewSet):
make = MakeSerializer
queryset = Model.objects.all()
serializer_class = ModelSerializer
filter_backends = (filters.DjangoFilterBackend,)
What I need to to, I want to fetch all Models manufactured by specific make.
How can I get all models with particular make foreign key using query params? And my 2nd question - can I filter results using queryparams to get models with specific engine_capacity?
One comment: It would be perfect, if I can to query results using something like this in url: /api/models/?make=ford where make is slug field in Make model
You can specify filter_fields = ('make__slug', ) in your view set. Don't forget to include filter_backends = (DjangoFilterBackend, ) as well. Also you will need to add django-filter dependency.
class ModelViewSet(viewsets.ModelViewSet):
queryset = Model.objects.all()
serializer_class = ModelSerializer
filter_backends = (filters.DjangoFilterBackend,)
filter_fields = ('make__slug',)
Then you query like /api/models/?make__slug=ford. Note double underscore symbol.
Docs.
If you don't like make__slug keyword argument in the URL, then you can create a filter class:
import django_filters
from myapp.models import Make
class ModelFilter(django_filters.FilterSet):
make = django_filters.ModelChoiceFilter(field_name="make__slug",
queryset=Make.objects.all())
class Meta:
model = Model
fields = ('make',)
and then
class ModelViewSet(viewsets.ModelViewSet):
make = MakeSerializer
queryset = Model.objects.all()
serializer_class = ModelSerializer
filter_backends = (filters.DjangoFilterBackend,)
filter_class = ModelFilter
/api/models/?make=ford should work.
urls.py
url('^model/by/(?P<make>\w+)/$', ModelByMakerList.as_view()),
views.py
class ModelByMakerList(generics.ListAPIView):
serializer_class = ModelSerializer
def get_queryset(self):
"""
This view should return a list of all models by
the maker passed in the URL
"""
maker = self.kwargs['make']
return Model.objects.filter(make=maker)
For more info checkout the docs.
You can also use filtering with QUERY_PARAMS, but IMHO this looks better.
To expand on #vladimir-prudnikov's answer:
Things changed a bit in recent versions of django-filter. You probably want:
class ModelFilter(django_filters.FilterSet):
make = django_filters.ModelChoiceFilter(field_name='make__slug',
to_field_name='slug',
queryset=Make.objects.all())
class Meta:
model = Model
fields = ('make',)
See https://django-filter.readthedocs.io/en/master/ref/filters.html#field-name and https://django-filter.readthedocs.io/en/master/ref/filters.html#to-field-name
What you need to do in your view is something like this:
It is called "Lookups that span relationships"
queryset = Model.objects.filter(make__name__exact='Alfa Romeo')
the filtering of models with specific engine capacity is similar
queryset = Model.objects.filter(engine_capacity__exact=5)
if you want both filters combined, you can chain them:
queryset = Model.objects.filter(make__name__exact='Alfa Romeo').filter(engine_capacity__exact=5)
more examples can be found here django query making
I created my "API" using REST framework, now I am trying to do filtering for it.
This is how my models.py looks like:
class Airline(models.Model):
name = models.TextField()
class Workspace(models.Model):
airline = models.ForeignKey(Airline)
name = models.CharField(max_length=100)
class Passenger(models.Model):
workspace = models.ForeignKey(Workspace)
title = models.CharField(max_length=200)
I would like to see "all passengers in particular workspace" or "all passengers in particular airline" etc in my JSON file.
Here is my serializers.py:
class AirlineSerializer(serializers.ModelSerializer):
class Meta:
model = Airline
class WorkspaceSerializer(serializers.ModelSerializer):
class Meta:
model = Workspace
class PassengerSerializer(serializers.ModelSerializer):
class Meta:
model = Passenger
And views.py:
class AirlineList(generics.ListCreateAPIView):
model = Airline
serializer_class = AirlineSerializer
class AirlineDetail(generics.RetrieveUpdateDestroyAPIView):
model = Airline
serializer_class = AirlineSerializer
class WorkspaceList(generics.ListCreateAPIView):
model = Workspace
serializer_class = WorkspaceSerializer
class WorkspaceDetail(generics.RetrieveUpdateDestroyAPIView):
model = Workspace
serializer_class = WorkspaceSerializer
class PassengerList(generics.ListCreateAPIView):
model = Passenger
serializer_class = PassengerSerializer
class PassengerDetail(generics.RetrieveUpdateDestroyAPIView):
model = Passenger
serializer_class = PassengerSerializer
I would like to use Filtering against query parameter but I can't really get it...
Here is the code:
class PassengerList(generics.ListCreateAPIView):
model = Passenger
serializer_class = PassengerSerializer
# Show all of the PASSENGERS in particular WORKSPACE
# or all of the PASSENGERS in particular AIRLINE
def get_queryset(self):
queryset = Passenger.objects.all()
workspace = self.request.query_params.get('workspace')
airline = self.request.query_params.get('airline')
if workspace:
queryset = queryset.filter(workspace_id=workspace)
elif airline:
queryset = queryset.filter(workspace__airline_id=airline)
return queryset
You can get the same functionality out of the box just by using django-filter package as stated in the docs: DjangoFilterBackend
from rest_framework import filters
class PassengerList(generics.ListCreateAPIView):
model = Passenger
serializer_class = PassengerSerializer
queryset = Passenger.objects.all()
filter_backends = (filters.DjangoFilterBackend,)
filter_fields = ('workspace', 'workspace__airline')
In this case you will have to make filtering using workspace=1 or workspace__airline=1.
This django app applies filters on the queryset of a view using the incoming query parameters in an clean and elegant way.
Which can be installed with pip as
pip install drf-url-filters
Usage Example
validations.py
from filters.schema import base_query_param_schema
from filters.validations import (
CSVofIntegers,
IntegerLike,
DatetimeWithTZ
)
# make a validation schema for players filter query params
players_query_schema = base_query_param_schema.extend(
{
"id": IntegerLike(),
"name": unicode,
"team_id": CSVofIntegers(), # /?team_id=1,2,3
"install_ts": DatetimeWithTZ(),
"update_ts": DatetimeWithTZ(),
}
)
views.py
from rest_framework import (
viewsets,
filters,
)
from .models import Player, Team
from .serializers import PlayerSerializer, TeamSerializer
from .pagination import ResultSetPagination
from .validations import teams_query_schema, players_query_schema
from filters.mixins import (
FiltersMixin,
)
class PlayersViewSet(FiltersMixin, viewsets.ModelViewSet):
"""
This viewset automatically provides `list`, `create`, `retrieve`,
`update` and `destroy` actions.
"""
serializer_class = PlayerSerializer
pagination_class = ResultSetPagination
filter_backends = (filters.OrderingFilter,)
ordering_fields = ('id', 'name', 'update_ts')
ordering = ('id',)
# add a mapping of query_params to db_columns(queries)
filter_mappings = {
'id': 'id',
'name': 'name__icontains',
'team_id': 'teams', # many-to-many relationship
'install_ts': 'install_ts',
'update_ts': 'update_ts',
'update_ts__gte': 'update_ts__gte',
'update_ts__lte': 'update_ts__lte',
}
# add validation on filters
filter_validation_schema = players_query_schema
def get_queryset(self):
"""
Optionally restricts the queryset by filtering against
query parameters in the URL.
"""
query_params = self.request.query_params
queryset = Player.objects.prefetch_related(
'teams' # use prefetch_related to minimize db hits.
).all()
# This dict will hold filter kwargs to pass in to Django ORM calls.
db_filters = {}
# update filters dict with incoming query params and then pass as
# **kwargs to queryset.filter()
db_filters.update(
self.get_queryset_filters(
query_params
)
)
return queryset.filter(**db_filters)