Links repeated in Django Rest Framework - python

this is driving me nuts! I hope you can help me.
I'm trying to get 2 views for the same model (I need one just like in the model and the other one like another app needs it). I have created 2 serializers, 2 views and 2 urls but when I check they are repeated!
I'll try to show you the relevant part of the code:
urls.py
from consumptions.routers_views import MessageViewSet, MessageSapViewSet
router.register(r'messages', MessageViewSet)
router.register(r'messagesforsap', MessageSapViewSet)
routers_views.py
from .serializers import MessageSerializer, MessageSapSerializer
class MessageViewSet(viewsets.ModelViewSet):
queryset = Message.objects.all()
serializer_class = MessageSerializer
permission_classes = [permissions.IsAuthenticated]
filter_backends = [DjangoFilterBackend]
filter_fields = ['user','date','consumption','content','read', 'deleted']
class MessageSapViewSet(viewsets.ModelViewSet):
queryset = Message.objects.all()
serializer_class = MessageSapSerializer
permission_classes = [permissions.IsAuthenticated]
filter_backends = [DjangoFilterBackend]
filter_fields = ['user','date','consumption','content','read', 'deleted']
serializers.py
class MessageSerializer(serializers.HyperlinkedModelSerializer):
consumption = ConsumptionSerializer(allow_null=True)
user = UserSerializer(allow_null=True)
class Meta:
model = Message
fields = [
"id",
"user",
"date",
"consumption",
"content",
"read",
"deleted"
]
class MessageSapSerializer(serializers.ModelSerializer):
user = UserSerializer(allow_null=True)
class Meta:
model = Message
fields = [
"user",
"date",
"content",
"read",
"deleted"
]
My problem is that when I check the links in the main page of the api I find that links are repeated

Use the base_name (or basename if you use the newer DRF version) argument:
router.register(r'messages', MessageViewSet, base_name='messages')
router.register(r'messagesforsap', MessageSapViewSet, base_name='messagesforsap')
It's better in this case to explicitly set the base_name because your serializers share the same model and DRF might duplicate it like that trying to automatically discover the url naming pattern.

Related

Restrict url base Django Api with drf

Hi I want to filter one of my Api models by one field. Building the queryset for the view I need to use the whole model using model.objects.all() and the model serializer. Once I set the filterset which the parameter I want to filter.
It filters correctly when I make the consult directly but the problem is when I access to the url for the filter, it by default shows me all the elements of this models and if I search for an element that not exist it also shows al the elements.
Those are my serializers.py to the full model and the filtered.
class EstacionSerializer(serializers.ModelSerializer):
pistas = PistaSerializer(read_only=True, many=True)
forfaits = ForfaitSerializer(read_only=True, many=True)
#owner = serializers.ReadOnlyField(source='owner.username')
class Meta:
model = Estacion
fields = ['nombre', 'ciudad', 'pais', 'descripcion',
'pistas', 'forfaits', 'km_esquiables', 'distancia_ciudad']
class EstacionBycFilterSet(filters.FilterSet):
class Meta:
model = Estacion
fields = ['ciudad']
Those are my views.py where is use the full serializer and the filtered one.
class EstacionViewSet(viewsets.ModelViewSet):
queryset = Estacion.objects.all()
serializer_class = EstacionSerializer
permission_classes = [permissions.IsAuthenticatedOrReadOnly,]
class EstacionByCiudad(viewsets.ModelViewSet):
queryset = Estacion.objects.all()
serializer_class = EstacionSerializer
permission_classes = [permissions.IsAuthenticatedOrReadOnly,]
filter_backends = (filters.DjangoFilterBackend,)
filterset_class = EstacionPorCiudadFilterSet
Here are the urls.py
router = routers.DefaultRouter()
router.register(r'estaciones', views.EstacionViewSet, basename='estaciones')
router.register(r'pistas', views.PistaViewSet, basename='pistas')
router.register(r'forfaits', views.ForfaitViewSet, basename='forfaits')
router.register(r'busquedaciudad', views.EstacionByCiudad, basename='EstbyCiudad')
from rest_framework.authtoken import views
urlpatterns = [
path('', include(router.urls)),
path('api-auth/', views.obtain_auth_token, name='ap-auth'),
]
For example if I go to the url: localhost:8000/busquedaciudad/?ciudad=somecity it returns me correctly the objects filtered but if I try localhost:8000/busquedaciudad/ it gives me all the objects of the model in the database as well as if I left ciudad parameter empty like: localhost:8000/busquedaciudad/?ciudad=
And what I want is how if the url is like: localhost:8000/busquedaciudad/ or localhost:8000/busquedaciudad/?ciudad= return 0 objects instead of all of them.
Any idea? Thanks

How can I create a partial search filter in Django REST framework?

I'm working with the Django REST framework library and I am trying to make a filter that can filter by first_name, last_name, or by both of them.
This is my ContactViewSet.py:
class ContactViewSet(viewsets.ModelViewSet):
queryset = Contact.objects.all()
serializer_class = ContactSerializer
filter_backends = (DjangoFilterBackend, )
filter_fields = ('first_name', 'last_name')
lookup_field = 'idContact'
My DRF's settings.py:
REST_FRAMEWORK = {
'DEFAULT_FILTER_BACKENDS': ('django_filters.rest_framework.DjangoFilterBackend',),
}
My current request URL looks like:
http://localhost:8000/api/v1/contacts/?first_name=Clair&last_name=Test
But I'm looking for something like this:
http://localhost:8000/api/v1/contacts/?first_name=Cl**&last_name=Tes**
I solved my problem by modifying my class ContactFilter like this:
import django_filters
from .models import Contact
class ContactFilter(django_filters.FilterSet):
class Meta:
model = Contact
fields = {
'first_name': ['startswith'],
'last_name': ['startswith'],
}
together = ['first_name', 'last_name']
And in my view I just had to do this:
class ContactViewSet(viewsets.ModelViewSet):
queryset = Contact.objects.all()
serializer_class = ContactSerializer
filter_class = ContactFilter
My request URL looks like this:
http://localhost:8000/api/v1/contact/?first_name__contains=Cl&last_name__contains=Tes
But I still wonder if I can have something like this in Django:
http://localhost:8000/api/v1/contacts/?first_name=Cl**&last_name=Tes**
I think the DjangoFilterBackend is mainly equality-based filtering. But you can customize the filtering method.
Also in DRF, for non exact filtering, there is the SearchFilter which makes case-insensitive partial matches searches by default.
What I do, is write custom FilterBackend. Something like this:
# views.py
from rest_framework import filters
class ObjektFilterBackend(filters.BaseFilterBackend):
allowed_fields = ['objekt', 'naziv', 'kategorija', 'zadnja_sprememba']
def filter_queryset(self, request, queryset, view):
flt = {}
for param in request.query_params:
for fld in self.allowed_fields:
if param.startswith(fld):
flt[param] = request.query_params[param]
return queryset.filter(**flt)
class ObjektiViewSet(mixins.ListModelMixin,
mixins.RetrieveModelMixin,
viewsets.GenericViewSet):
authentication_classes = (
authentication.TokenAuthentication,
authentication.SessionAuthentication)
permission_classes = (IsAuthenticated,)
queryset = models.Objekt.objects.all()
serializer_class = serializers.ObjektSerializer
filter_backends = (ObjektFilterBackend, ObjektOrderBackend,)
....
Besides basic filtering (fieldname=value pairs) I can use any Django queryset Field Lookups (__gt, __gte, __startswith,...) in my URLs like this:
http://localhost:8000/api/v2/objekti/?naziv__startswith=Apartma&zadnja_sprememba__gte=2018-01-01
And ObjektFilterBackend class could be easily adapted to support searching by pattern.
Just a little warning - this approach is potentially dangerous, because it allows end user to filter also by foreign key field. Something like this also works:
http://localhost:8000/api/v2/objekti/?kategorija__naziv__icontains=sobe
So restrict allowed_fields carefully and not include foreign keys that could lead to related User model.
For fuzzy search lookups I recommend using this approach:
filters.py
from django_filters import rest_framework as filters
from django.db.models import Q
from . import models
def filter_name(queryset, name, value):
"""
Split the filter value into separate search terms and construct a set of queries from this. The set of queries
includes an icontains lookup for the lookup fields for each of the search terms. The set of queries is then joined
with the OR operator.
"""
lookups = [name + '__icontains', ]
or_queries = []
search_terms = value.split()
for search_term in search_terms:
or_queries += [Q(**{lookup: search_term}) for lookup in lookups]
return queryset.filter(reduce(operator.or_, or_queries))
class ContactFilter(filters.FilterSet):
first_name = filters.CharFilter(method=filter_name, name='first_name')
last_name = filters.CharFilter(method=filter_name, name='last_name')
class Meta:
model = models.Contact
fields = [
'first_name',
'last_name',
]
api.py
class ContactViewSet(viewsets.ModelViewSet):
queryset = Contact.objects.all()
serializer_class = ContactSerializer
filter_class = ContactFilter
...
If your requests aren't too complicated you can also use:
class YourModelViewSet(viewsets.ModelViewSet):
queryset = YourModel.objects.all()
serializer_class = YourModelSerializer
filter_fields = {'some_field': ['startswith']}
Which will enable '?some_field__starswith=text' sintax support in request query params.
I suppose 'startswith' can be replaced with any django standart queryset filter param.
You should add custom Filter for your viewset.
from django_filters.rest_framework import DjangoFilterBackend
import django_filters
from recipes.models import Ingredient
class MyModelFilter(django_filters.FilterSet):
name = django_filters.CharFilter(
field_name='name', lookup_expr='icontains'
)
class Meta:
model = MyModel
fields = []
class MyViewSet(viewsets.ReadOnlyModelViewSet):
queryset = MyModel.objects.all()
serializer_class = MyModelSerializer
permission_classes = (AllowAny,)
pagination_class = None
filter_backends = (DjangoFilterBackend,) # add this
filterset_class = MyModelFilter # add this

Django Rest Framework Filter ModelViewSet Field Against Multipe Values

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

Django REST framework foreign keys and filtering

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

How to use Query Strings for filtering Querysets in Django?

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)

Categories

Resources