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
For example, from the url: https://localhost:8000/parameters=param1¶m2¶m3
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¶meter_name=bar
I would highly recommend using the matured django-filter package for this
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.
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