Django rest framework multi searching - django-rest-framework

everyone. I tried understand search.
I have url path('quiz/all/', QuizListView.as_view()),
View :
class QuizListView(generics.ListAPIView):
queryset = Quiz.objects.all()
serializer_class = QuizDetailSerializer
search_fields = ('description', 'title',)
filterset_fields = ['title', 'description',]
(method1)If I use search, for example /api/v1/quiz/all/?search=QI got all instances where title or description contains 'Q'
(method2)I can search /api/v1/quiz/all/?title=Q&description=d I got a instance which has exact title and description.
(method3)But I want to get list of all instances where title contains one value and description contains other value. For example, I want to write /api/v1/quiz/all/?title=Q&description=d and get list where title contains Q and description contains d.
Quiz1(title=Q, description=d)
Quiz2(title=Test, description=dd)
Quiz3(title=NewQ, description=Test_d)
For (method1,/api/v1/quiz/all/?search=Q) I got Quiz1, Quiz2, Quiz3
For (method2,/api/v1/quiz/all/?title=Q&description=d) I got Quiz1
For (method3,/api/v1/quiz/all/?title=Q&description=d) I would like to
get Quiz1 and Quiz3 (because they contain Q for title and d for description)
Thanks.

You'll need to create a custom FilterSet class and use the contains or icontains (if you want case insensitive) because the default is using exact and that's why you don't get back the result that you want. See docs here and here
# filters.py
class QuizFilter(django_filters.FilterSet):
class Meta:
model = Quiz
fields = {
'title': ['contains'], # or icontains
'description': ['contains'], # or icontains
}
# views.py
from django_filters.rest_framework import DjangoFilterBackend
from .filters import QuizFilter
class QuizListView(generics.ListAPIView):
queryset = Quiz.objects.all()
serializer_class = QuizDetailSerializer
filter_backends = (DjangoFilterBackend, ) # add here other filters backends
filterset_class = QuizFilter

Related

How can I change HyperLinkedModelSerializer's default <pk> lookup_url_kwarg?

I want to use HyperLinkedModelSerializer in order to add a url field for my Book model. Here is the solution you'd typically find in the average tutorial:
# serializers.py
class BookSerializer(HyperLinkedModelSerializer):
class Meta:
model = Book
fields = ("title", "url",)
# views.py
class BookView(RetrieveAPIView):
serializer_class = BookSerializer
# urls.py
urlpatterns = [
path("<pk>/", BookDetailView.as_view(), name="book-detail"),
]
And that works all right. But now I need to change the URL conf in order to match the book id, not with <pk>, but with <fiction_id>. So I figured I'd just change it!
# urls.py
urlpatterns = [
path("<fiction_id>/", BookDetailView.as_view(), name="book-detail"),
]
Now comes the crash:
django.core.exceptions.ImproperlyConfigured: Could not resolve URL for hyperlinked relationship using view name "fiction-detail". You may have failed to include the related model in your API, or incorrectly configured the `lookup_field` attribute on this field.
I've tried fiddling with the lookup_field, lookup_url_kwargs in my view:
# views.py
class BookView(RetrieveAPIView):
serializer_class = BookSerializer
lookup_field = "pk"
lookup_url_kwargs = "fiction_id"
I've tried reminding the serializer to actually look for the (default) "pk":
# serializers.py
class BookSerializer(HyperLinkedModelSerializer):
class Meta:
model = Book
fields = ("title", "url",)
extra_kwargs = {
"url": {"lookup_field": "pk"},
}
I've tried combinations of these, to no avail. It looks like you can't use anything but <pk> if you want to take advantage of HyperLinkedModelSerializer's url field. The documentation doesn't seem to offer a way to change that behaviour:
By default hyperlinks are expected to correspond to a view name that matches the style '{model_name}-detail', and looks up the instance by a pk keyword argument.
How can I change this behaviour, or is it bound to become too messy?
You should check HyperlinkedModelSerializer implementation and see that it uses a serializer_related_field defaulting to HyperlinkedRelatedField
class HyperlinkedModelSerializer(ModelSerializer):
"""
A type of `ModelSerializer` that uses hyperlinked relationships instead
of primary key relationships. Specifically:
* A 'url' field is included instead of the 'id' field.
* Relationships to other instances are hyperlinks, instead of primary keys.
"""
serializer_related_field = HyperlinkedRelatedField
...
And then HyperlinkedRelatedField has a class attribute lookup_field defaulting to pk
class HyperlinkedRelatedField(RelatedField):
lookup_field = 'pk'
...
What you can do is to use a custom HyperlinkedRelatedField with your own lookup_field
from rest_framework.relations import HyperlinkedRelatedField
from rest_framework.serializers import HyperlinkedModelSerializer
class BookHyperlinkedRelatedField(HyperlinkedRelatedField):
lookup_field = 'fiction_id'
class BookSerializer(HyperLinkedModelSerializer):
serializer_related_field = BookHyperlinkedRelatedField
class Meta:
model = Book
fields = ("title", "url",)
In order to do this, you need to give the url field's new name for the lookup in the matched pattern by passing it through the extra_kwargs dictionary:
# serializers.py
class BookSerializer(HyperLinkedModelSerializer):
class Meta:
model = Book
fields = ("title", "url",)
extra_kwargs = {
"url": {"lookup_url_kwarg": "fiction_id"},
}
Also remember to modify the corresponding view:
# views.py
class BookView(RetrieveAPIView):
serializer_class = BookSerializer
lookup_url_kwarg = "fiction_id"
Do not write lookup_url_kwargs in plural.
You don't need to meddle with the lookup_field at any level as long as the lookup will be done on the model's primary key.

DRF how to return list filtered by lookup w/ custom router and modelviewset

I tried to search for answers as much as I can but I still don't know how to achieve my goal here.
My goal:
I need two api endpoints, one returns a list filtered by a lookup fields, and another returns an obj filtered by another field, both using GET method. For example:
<ip>/api/books/bycategory/{category_lookup}/ This endpoint will return a list of books filtered by a category
<ip>/api/books/byid/{id_lookup}/ This returns one book matches the specified id (not pk)
Since there's no built-in router that suits my needs here because the built-in ones don't provide url pattern with lookup that returns a list, so I figured I need to have a custom router of my own, so here's what I have:
class CustomRouter(routers.SimpleRouter):
routes = [
routers.DynamicRoute(
url=r'^{prefix}/{url_path}/{lookup}{trailing_slash}$',
name='{basename}-{url_name}',
detail=True,
initkwargs={}
)
]
router = CustomRouter()
router.register('books', BookViewSet)
and my serializer:
class BookSerializer(serializers.ModelSerializer):
class Meta:
model = BookKeeper
fields = '__all__'
Right until here I think I'm on the right track, but when it comes to the view, thats where i can't quite figure out. Right now I only have this incomplete viewset:
class BookViewSet(viewsets.ReadOnlyModelViewSet):
queryset = BookKeeper.objects.all()
serializer_class = BookSerializer
#action(detail=True)
def bycategory(self, request):
lookup_field = 'category'
#action(detail=True)
def byid(self, request):
lookup_field = 'id'
My first question here is I "think" {url_path} in the router url matches the method name with #action specified in the viewset somehow and that how they are connected, am I correct?
Second question is how do I use {lookup} value in the view?
Third, what's the lookup_field for if I'm to use like:
def bycategory(self, request):
return Response(BookKeeper.objects.filter(category=<lookup_value>))
Lastly what should my viewset be like anyway?
Any input will be appreciated.
Second question is how do I use {lookup} value in the view?
You need two lookup_fields for the same set. You can do that by a custom Mixin class. But in four case, it is better not to use routers but custom urls, so edit like this:
# views.py
class BookViewSet(viewsets.ReadOnlyModelViewSet):
queryset = BookKeeper.objects.all()
serializer_class = BookSerializer
#action(detail=True)
def bycategory(self, request, category):
# do filtering by category
print(category)
#action(detail=True)
def byid(self, request, book_id):
# do filtering by book_id
print(book_id)
# urls.py
get_by_id = views.BookViewSet.as_view(
{
'get': 'byid'
}
)
get_by_category = views.BookViewSet.as_view(
{
'get': 'bycategory'
}
)
urlpatterns += [
url(
r'^api/books/byid/(?P<book_id>[0-9a-f-]+)/',
get_by_id,
name='get-by-id'
),url(
r'^api/books/bycategory/(?P<category>[0-9a-f-]+)/',
get_by_category,
name='get-by-category'
)
]

DRF - in filter, use field-value instead of default pk / id

I'm trying to use DRF's filters so that the URL query is like so:
/roadname/?road=M5
not like so
/roadinfo/?road=1
I can't seem to do it when I've got a ForeignKey relationship.
I've tried using lookup_field with no luck (although not sure how this would work for multiple filter fields anyway - I don't think that's the answer). I've tried using a get_queryset() method in views as in the second example in the documentation. A comment I came across suggested that this is bad RESTApi practice - is it? How would a user know to type in '1' to get results for 'M5' in a front-end client?
I've set up two really simple models (and serializers, views, etc.) to try these out as below.
If I use RoadName, I have to type the name into the filter search box (rather than having a dropdown), but the url query is how I want it.
If I use RoadInfo (which has a ForeignField to RoadName), I get a drop down in the filter box, but the url query uses the ForeignKey pk.
My question: How can I set it so that when I use RoadInfo, the query uses the field value rather than the id/pk?
Models
from django.db import models
class RoadName(models.Model):
road = models.CharField(max_length=50)
def __str__(self):
return str(self.road)
class RoadInfo(models.Model):
road = models.ForeignKey(RoadName, on_delete='CASCADE')
# other data
def __str__(self):
return str(self.road)
Serializers
from traffic.models import *
from rest_framework import serializers
class RoadNameSerializer(serializers.ModelSerializer):
road = serializers.CharField()
class Meta:
model = RoadName
exclude = ('id',)
class RoadInfoSerializer(serializers.ModelSerializer):
road = RoadNameSerializer()
class Meta:
model = RoadInfo
exclude = ('id',)
Views
from traffic.serializers import *
from traffic.models import *
from django_filters import rest_framework as filters
from rest_framework import viewsets
class RoadNameViewSet(viewsets.ReadOnlyModelViewSet):
""" List of all traffic count Counts """
queryset = RoadName.objects.all()
serializer_class = RoadNameSerializer
filter_backends = (filters.DjangoFilterBackend,)
filterset_fields = '__all__'
class RoadInfoViewSet(viewsets.ReadOnlyModelViewSet):
""" List of all traffic count Counts """
queryset = RoadInfo.objects.all()
serializer_class = RoadInfoSerializer
filter_backends = (filters.DjangoFilterBackend,)
filterset_fields = '__all__'
The data M5 on the road attribute of RoadName model. It can be filtered by road__road from RoadInfo model.
So, Try /roadname/?road__road=M5

How do I specify multiple column name in a Django search field?

i use django filter backend
I define multiple columns in a search field, as follows:
class tableViewSet(ModelViewSet):
"""
A simple ViewSet for viewing and editing accounts.
"""
queryset = table.objects.all()
serializer_class = WebSerializer
pagination_class = StandardResultsSetPagination
filter_backends = (DjangoFilterBackend,SearchFilter)
search_fields = ('name','family','tel',)
i want to make an api that handle some query like this:
select * from table1 where name like ('tom') and family like ('%anderson%') and tel like ('%0223654%')
Is there any way to specify the column name in the API?
For example:
http://127.0.0.1/api-user/table/search?name=tom&family=andeson&tel=0223455
You should rather use DjangoFilterBackend instead of SearchFilter if you want to filter per specific attribute.
As you are not looking for exact search, you will have to create a FilterSet and use filter_class in you view.
Something like:
class TableFilter(django_filters.FilterSet):
name = django_filters.CharFilter(name='name', lookup_expr='icontains')
family = django_filters.CharFilter(name='family', lookup_expr='icontains')
tel = django_filters.CharFilter(name='tel', lookup_expr='icontains')
class Meta:
model = table
fields = ['name', 'family', 'tel']
class tableViewSet(ModelViewSet):
"""
A simple ViewSet for viewing and editing accounts.
"""
queryset = table.objects.all()
serializer_class = WebSerializer
pagination_class = StandardResultsSetPagination
filter_backends = (DjangoFilterBackend,SearchFilter)
filter_class = TableFilter
Specifying the field in the query string is unnecessary. The following request should work:
http://127.0.0.1/api-user/table?search=something
relevant docs

Django Rest Framework - return reverse foreign key property?

I think this is simple and probably a duplicate, but I cannot figure it out by looking at the documentation.
I have Django models as follows:
class Image(models.Model):
manor = models.ForeignKey(Manor, related_name='image_for_manor')
filename = models.CharField(max_length=30, null=True, blank=True)
class Manor(models.Model):
id = models.IntegerField(primary_key=True)
I want the user to be able to query the Manor and see the related Image. I'd like this JSON to be returned:
{
id: 572,
image: 'my/filepath.png'
}
This is my view:
#api_view(['GET'])
def manor(request, id):
mymanor = Manor.objects.get(id=id)
serializer = ManorSerializer(mymanor)
return JSONResponse(serializer.data)
And these are my serializers:
class ImageFilePathSerializer(serializers.ModelSerializer):
class Meta:
model = Image
fields = ('filename',)
class ManorSerializer(serializers.ModelSerializer):
image = ImageFilePathSerializer(source="image_for_manor")
class Meta:
model = Manor
fields = ('id', 'image')
But this doesn't work: I get an empty dictionary for image. (Even if it weren't empty, I realise it wouldn't be right, because I don't want the image property to be a dictionary: I want it to be a string.)
How can I change this to be correct? I cannot work it out.
As Kevin suggested , why dont you use ImageField which will give you url where your image is uploaded. although if you dont want to do that , here are some changes you have to do to get the result format you wanted.(Following solution assumes that only one image will be for one minor)
views.py ( why dont you ImageFilePathSerializer instead of another one as it also contains all the data you wanted)
#api_view(['GET'])
def manor(request, id):
mymanor = Image.objects.get(manor__id=id)
serializer = ImageFilePathSerializer(mymanor)
return Response(serializer.data)
serializers.py (add Id with filename in ImageFilePathSerializer)
class ImageFilePathSerializer(serializers.ModelSerializer):
class Meta:
model = Image
fields = ('id', 'filename',)
class ManorSerializer(serializers.ModelSerializer):
filename = serializers.ImageField(source="image_for_manor")
class Meta:
model = Manor
fields = ('id', 'filename')

Resources