Restrict url base Django Api with drf - python

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

Related

How to get optional parameters from a url and use them to parse objects from a model?

For example, from the url: https://localhost:8000/parameters=param1&param2&param3
How can I parse out the parameters as a list and pass it into a class-based view?
urls.py
urlpatterns = [
path('admin/', admin.site.urls),
path('parameters=...', MyListView.as_view()),
]
views.py
class MyListView(generics.ListAPIView):
queryset = MyObject.objects.all() # Use the parameters to filter out objects
serializer_class = MyObjectSerializer
models.py
class MyObject(models.Model):
object_id = models.CharField(max_length=10)
content = models.CharField(max_length=120)
# Each object can have an unlimited number of parameters
class ObjectParameters(models.Model):
my_object = models.ForeignKey(MyObject, on_delete=models.CASCADE)
param = models.CharField(max_length=120)
I would like to query a list of objects that contain all given parameters. Also, is this a logical structure for my models?
Initially, you don't need to do anything to handle the query parameters--(Wiki) in the path(...) function.
So change your urls.py as
urlpatterns = [
path('admin/', admin.site.urls),
path('some/path/', MyListView.as_view()),
]
Then, in your view, override the get_queryset(...) method as
class MyListView(generics.ListAPIView):
serializer_class = MyObjectSerializer
def get_queryset(self):
queryset = MyObject.objects.all()
param = self.request.query_params.get("parameter_name")
if param:
queryset = queryset.filter(some_model_field=param)
return queryset
So, now, DRF view expect query parameter in the form,
https://localhost:8000/some/path/?parameter_name=foo&parameter_name=bar
I would highly recommend using the matured django-filter package for this

Links repeated in Django Rest Framework

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.

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

Cannot get distinct record - Django w / Rest Framework

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

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

Categories

Resources