How to do 'or' when applying multiple values with the same lookup? - django-rest-framework

By referring to this article, I was able to implement the method of and search.
Django-filter with DRF - How to do 'and' when applying multiple values with the same lookup?
I want to know how to do or search for multiple keywords using the same field. How can I implement it?
Here is the code:
from rest_framework import viewsets
from django_filters import rest_framework as filters
from .serializers import BookInfoSerializer
from .models import BookInfo
class MultiValueCharFilter(filters.BaseCSVFilter, filters.CharFilter):
def filter(self, qs, value):
# value is either a list or an 'empty' value
values = value or []
for value in values:
qs = super(MultiValueCharFilter, self).filter(qs, value)
return qs
class BookInfoFilter(filters.FilterSet):
title = MultiValueCharFilter(lookup_expr='contains')
# title = MultiValueCharFilter(lookup_expr='contains', conjoined=False) -> get an error
class Meta:
model = BookInfo
fields = ['title']
class BookInfoAPIView(viewsets.ReadOnlyModelViewSet):
queryset = BookInfo.objects.all()
serializer_class = BookInfoSerializer
filter_class = BookInfoFilter
if I set conjoined=False like this title = MultiValueCharFilter(lookup_expr='contains', conjoined=False) get an error __init__() got an unexpected keyword argument 'conjoined'
Django 3.2.5
django-filter 2.4.0
djangorestframework 3.12.4
Python 3.8.5

You can try to modify the queryset returned from MultiValueCharFilter
and combine values with operator.
example:
import operator
from functools import reduce
class MultiValueCharFilter(BaseCSVFilter, CharFilter):
def filter(self, qs, value):
expr = reduce(
operator.or_,
(Q(**{f'{self.field_name}__{self.lookup_expr}': v}) for v in value)
)
return qs.filter(expr)
class BookInfoFilter(filters.FilterSet):
title = MultiValueCharFilter(lookup_expr='contains')
class Meta:
model = BookInfo
fields = ['title']

You can try to create a new Class called ListFilter like this:
from django_filters.filters import Filter
from django_filters.conf import settings as django_filters_settings
from django.db.models.constants import LOOKUP_SEP
class ListFilter(Filter):
"""
Custom Filter for filtering multiple values.
"""
def __init__(self, query_param: str, *args, **kwargs):
"""
Override default variables.
Args:
query_param (str): Query param in URL
"""
super().__init__(*args, **kwargs)
self.query_param = query_param
self.distinct = True
def filter(self, qs, value):
"""
Override filter method in Filter class.
"""
try:
request = self.parent.request
values = request.query_params.getlist(self.query_param)
# Remove empty value in array
values = list(filter(None, values))
except AttributeError:
values = []
# Filter queryset by using OR expression when lookup_expr is 'in'
# Else filter queryset by using AND expression
if values and self.lookup_expr == 'in':
return super().filter(qs, values)
for value in set(values):
predicate = self.get_filter_predicate(value)
qs = self.get_method(qs)(**predicate)
return qs.distinct() if self.distinct else qs
def get_filter_predicate(self, value):
"""
This function helps to get predicate for filtering
"""
name = self.field_name
if (
name
and self.lookup_expr != django_filters_settings.DEFAULT_LOOKUP_EXPR
):
name = LOOKUP_SEP.join([name, self.lookup_expr])
return {name: value}
My class above supports filter nested fields using both OR and AND expressions. E.g. about filter title field using OR expression:
class BookInfoFilter(filters.FilterSet):
title = ListFilter(query_param='title')
class Meta:
model = BookInfo
fields = ['title']

Related

SerializerMethodField doesn't work on DRF

Here is the simplified verison of the Serializer what I have:
class CommentSerializer(serializers.HyperlinkedModelSerializer):
def __init__(self, *args, **kwargs):
init = super().__init__(*args, **kwargs)
return init
username = serializers.ReadOnlyField(source='user.username')
user_id = serializers.ReadOnlyField(source='user.id')
user = UserSerializer(read_only=True)
url_field_name = 'url_api'
# and more definitions
content_type_id = serializers.IntegerField()
site_id = serializers.SerializerMethodField('_siteId')
def _siteId(self, threadedcomment):
site_id = settings.SITE_ID
return site_id
class Meta:
model = ThreadedComment
fields = ('url_api','url','id','title','tree_path','comment','submit_date','submit_date_unix','submit_date_humanized','root_id','is_removed',
'parent_id','last_child_id','newest_activity','depth','username','user_id','object_pk','content_type_id','user',
'site_id',
)
read_only_fields = ('id','title','tree_path','submit_date','root_id','is_removed',
'parent_id','last_child_id','newest_activity','depth','username','user_id',
# 'site_id',
)
class CommentViewSet(viewsets.ModelViewSet):
queryset = ThreadedComment.objects.all().annotate().all()
serializer_class = CommentSerializer
permission_classes = []
filter_backends = [filters.OrderingFilter]
def filter_queryset(self, queryset):
return queryset
def list(self, request):
return super().list(request)
def perform_create(self, serializer):
serializer.save(user=self.request.user)
# site_id = settings.SITE_ID
# serializer.save(user=self.request.user, site_id=site_id, )
return super().perform_create(serializer)
Now I make an http post request on the api as i.e.:
axios.post('/api/comments/', {
"comment":"test.",
"object_pk":34,
"content_type_id":12,
},
It shows me an error:
500 (Internal Server Error)
IntegrityError: NOT NULL constraint failed: django_comments.site_id
The problem (a pure question) is, why the SerializerMethodField doesn't work? I put a breakpoint on the site_id = settings.SITE_ID line, but it doesn't hit, which means the line hasn't even executed.
I also tried putting a set of lines (the commented lines) on perform_create, reading a SO post, but the result is the same, the same error, NOT NULL constraint failed.
I'm certainly passing a value, but it shows it's a null value, saying nothing is passed, what this means? What am I donig wrong here? Thanks.

converting Django generic class based LISTVIEW to Rest framework LISTAPIVIEW

I need help to convert the below class-based LISTVIEW to the rest framework LISTAPIVIEW because I want to authenticate users using permission classes simple JWT authentication.
thanks
class MessagesModelList(ListView):
http_method_names = ['get', ]
paginate_by = getattr(settings, 'MESSAGES_PAGINATION', 500)
def get_queryset(self):
if self.kwargs.get('dialog_with'):
qs = MessageModel.objects \
.filter(Q(recipient=self.request.user, sender=self.kwargs['dialog_with']) |
Q(sender=self.request.user, recipient=self.kwargs['dialog_with'])) \
.select_related('sender', 'recipient')
else:
qs = MessageModel.objects.filter(Q(recipient=self.request.user) |
Q(sender=self.request.user)).prefetch_related('sender', 'recipient', 'file')
return qs.order_by('-created')
def render_to_response(self, context, **response_kwargs):
user_pk = self.request.user.pk
data = [serialize_message_model(i, user_pk)
for i in context['object_list']]
page: Page = context.pop('page_obj')
paginator: Paginator = context.pop('paginator')
return_data = {
'page': page.number,
'pages': paginator.num_pages,
'data': data
}
return JsonResponse(return_data, **response_kwargs)
you need to create the below files and and try the following
serializer.py
from rest_framework import serializers
from rest_framework.pagination import PageNumberPagination
from .models import MessageModel
class CustomPagination(PageNumberPagination):
page_size = 500
page_size_query_param = 'page_size'
max_page_size = 10000
class MessagesModelSerializer(serializers.ModelSerializer):
class Meta:
model = MessageModel
fields = "__all__" #use this if you want all the fields of your model available
fields = ['field1','field2', etc..] #use this if you want specific fields of your model
views.py
from .models import MessageModel
from rest_framework import mixins,viewsets
from .serializer import MessagesModelSerializer,CustomPagination
class MessagesModelListView(mixins.ListModelMixin,
viewsets.GenericViewSet):
serializer_class = MessagesModelSerializer
pagination_class = CustomPagination
def get_queryset(self):
if self.kwargs.get('dialog_with'):
qs = MessageModel.objects \
.filter(Q(recipient=self.request.user, sender=self.kwargs['dialog_with']) |
Q(sender=self.request.user, recipient=self.kwargs['dialog_with'])) \
.select_related('sender', 'recipient')
else:
qs = MessageModel.objects.filter(Q(recipient=self.request.user) |
Q(sender=self.request.user)).prefetch_related('sender', 'recipient', 'file')
return qs.order_by('-created')
urls.py
from .views import MessageModelListView
from rest_framework.routers import DefaultRouter
router=DefaultRouter()
router.register('messages', MessagesModelList, 'messages')
This is a basic example of how you can create ListView using DRF. if you need more customization, please refer the documentation : https://www.django-rest-framework.org/api-guide/generic-views/

A simple query but Graphene-Django is returning null values

I am trying to make a simple query with graphene-django but i can not get the DB, it gives me null.
I think the code is ok, what is going wrong, I am working for hours on it.
Do you have any idea, what it is?
Thanks in advance
import graphene
from graphene_django.types import DjangoObjectType, ObjectType
from myProject.models import Times
class TimesType(DjangoObjectType):
class Meta:
model=Times
fields="__all__"
class Query(ObjectType):
today_times = graphene.Field(TimesType, id=graphene.ID())
all_times = graphene.List(TimesType)
def resolve_todaytimes(self, info, id=None):
return Times.objects.get(pk=id)
def resolve_alltimes(root, info, **kwargs):
return Times.objects.all()
schema = graphene.Schema(query=Query, mutation=Mutation)
query {todayTimes(id:"1029"){id}}
{
"data": {
"todayTimes": null
}
}
The resolver method should be named in resolve_<FieldName> format
class Query(ObjectType):
today_times = graphene.Field(TimesType, id=graphene.ID())
all_times = graphene.List(TimesType)
def resolve_today_times(self, info, id=None): # not `resolve_todaytimes`
return Times.objects.get(pk=id)
def resolve_all_times(root, info, **kwargs): # not `resolve_alltimes`
return Times.objects.all()
Alternatively, you can use the resolver parameter to set the callable resolver as,
def resolve_todaytimes(self, info, id=None):
return Times.objects.get(pk=id)
def resolve_alltimes(root, info, **kwargs):
return Times.objects.all()
class Query(ObjectType):
today_times = graphene.Field(
TimesType,
id=graphene.ID(),
resolver=resolve_todaytimes
)
all_times = graphene.List(
TimesType,
resolver=resolve_alltimes
)

How can create Serializer with custom logic?

Only two parameters come to the input:
param1 = Int, optional
param2 = Char, require
On output - json with 4 elements
key1 = Char
key2 = Int
key3 = Int
key4 = Int
Everything can be null
Now I have it implemented in the view in the get method, but I want to create a Serializer for this and put all the logic there, but I don’t really understand where to transfer the logic - validate, to_representation?
Now it looks something like this
views.py
class ExampleRetrieveApiView(APIView):
def get(self, request, *args, **kwargs):
# A lot of logic related with input parameters.
return Response({
'key1': value1,
'key2': value2,
'key3': value3,
'key4': value4
})
Need to do something like this
views.py
class ExampleRetrieveApiView(APIView):
serializer_class = ExampleSerializer
def get(self, request):
serializer = ExampleSerializer(???)
return Response(serializer.data)
serializers.py
class ExampleSerializer(serializers.Serializer):
???
Pass the input params as a dict. Then, you should use SerializerMethodField() for this:
from rest_framework import serializers
class ExampleSerializer(serializers.Serializer):
key1 = serializers.SerializerMethodField()
key2 = serializers.SerializerMethodField()
key3 = serializers.SerializerMethodField()
key4 = serializers.SerializerMethodField()
def get_key1(self, obj):
# do some calculations, let's say we want to return input1 multiplied by 2
# I'm accessing obj data and if it's empty assigning 0, you can add your own check instead
return obj.get('input1', 0)*2
def get_key2(self, obj):
# same deal, do whatever calculations you want with the input data
return obj.get('input2')
def get_key3(self, obj):
# your logic
return
def get_key4(self, obj):
return
For every field declared as SerializerMethodField, you should have a corresponding get_field method.
It's up to you how to get the input params, you can do it either through query params or body data.
Call the serializer like this in your view, for example with body params:
class ExampleRetrieveApiView(APIView):
def get(self, request):
input_data = {'input1':request.data.get('input1'),
'input2':request.data.get('input2')}
serializer = ExampleSerializer(input_data)
return Response(serializer.data)
views
class ExampleAPIView(APIView):
serializer_class = ExampleSerializer
def get(self, request):
serializer = ExampleSerializer(data=request.query_params)
if serializer.is_valid(raise_exception=True):
return(Response(serializer.data))
serializers
class ExampleSerializer(serializers.Serializer):
param1 = serializers.IntegerField()
param2 = serializers.CharField()
def validate(self, data):
#custom logic
data['param3'] = 'text'
#another params
return data
def to_representation(self, data):
return {
'key1': data['param3']
'key2': data['param4']
#another key for representation
}

django rest framework writable nested serializer returns empty nested data

I'm following this link (http://www.django-rest-framework.org/api-guide/relations/#writable-nested-serializers) to write nested serializer. But when I pop the 'vars' from validated_data in the create method of HostSerializer, I found it's empty.
I'm using django 1.9.2 and django restframework 3.3.2.
My model:
class Host(models.Model):
name = CharField(max_length=20, primary_key=True)
vm_cpu = IntegerField(default=2)
vm_mem = IntegerField(default=2048)
create_vm = BooleanField(default=True)
def __unicode__(self):
return('%s' % (self.name))
class Variable(models.Model):
name = CharField(max_length=10)
value = CharField(max_length=20)
host = models.ForeignKey(Host, related_name='vars')
def __unicode__(self):
return('%s=%s' % (self.name, self.value))
Serializer
class VariableSerializer(ModelSerializer):
class Meta:
model = Variable
class HostSerializer(ModelSerializer):
vars = VariableSerializer(many=True)
class Meta:
model = Host
def create(self, validated_data):
# i set a break point here and found vars_data is empty
vars_data = validated_data.pop('vars')
host = Host.objects.create(**validated_data)
for v in vars_data:
Variable.objects.create(host = host, **v)
return host
This is the problem I found vars_data is an empty list:
def create(self, validated_data):
# i set a break point here and found vars_data is empty
vars_data = validated_data.pop('vars')
Here's the rest of the code
admin.py
class VariableAdmin(admin.ModelAdmin):
list_display = ['name', 'value']
class HostAdmin(admin.ModelAdmin):
list_display = ['name']
admin.site.register(Variable, VariableAdmin)
admin.site.register(Host, HostAdmin)
urls.py
router = DefaultRouter()
router.register(r'variables', VariableViewSet, base_name='variables')
router.register(r'hosts', HostViewSet, base_name='hosts')
urlpatterns = [
url(r'^', include(router.urls)),
]
views.py
class VariableViewSet(ModelViewSet):
queryset = Variable.objects.all()
serializer_class = VariableSerializer
class HostViewSet(ModelViewSet):
queryset = Host.objects.all()
serializer_class = HostSerializer
My test program
post.py
import json
import requests
file = 'host.json'
url = 'http://localhost:8001/test_nest/hosts/'
with open(file, 'r') as f:
j = f.read()
data = json.loads(j)
r = requests.post(url, data = data)
print r.text
And here's the test data
host.json
{
"name": "host4",
"vars": [
{
"name": "var2-a",
"value": "a1"
},
{
"name": "var2-b",
"value": "a2"
}
],
"vm_cpu": 2,
"vm_mem": 2048,
"create_vm": true
}
I'm new to django. So I'm wondering if it's something simple and obvious. Did I use the wrong viewset? Did I post to the wrong URL? Or I setup the URL structure wrong?
I your serializers try using...
def update(self, instance,validated_data):
instance.vars_data = validated_data.get('vars',instance.vars)
instance.host = Host.objects.create(**validated_data)
for v in vars_data:
v,created=Variable.objects.create(host = host, **v)
instance.v.add(v)
return host
The following code works for me. Maybe you can try it:
models.py
class UserProfile(AbstractUser):
pass
class TobaccoCert(models.Model):
license = models.CharField(primary_key=True, max_length=15)
license_image = models.ImageField(upload_to="certs", blank=True, null=True, verbose_name='cert image')
views.py
class UserViewSet(mixins.CreateModelMixin, viewsets.GenericViewSet):
serializer_class = UserRegSerializer
...
def create(self, request, *args, **kwargs):
data = request.data
# here i make my nested data `certs` and update it to data
image_data = request.FILES.get('images')
_license = data.get('username', None)
certs = {'license': _license, 'license_image': image_data}
data['certs'] = certs
serializer = self.get_serializer(data=data)
serializer.is_valid(raise_exception=True)
user = self.perform_create(serializer) # save it
ret_dict = serializer.data
payload = jwt_payload_handler(user)
ret_dict["token"] = jwt_encode_handler(payload) # 获取token
ret_dict["name"] = user.name if user.name else user.username
headers = self.get_success_headers(serializer.data)
return Response(ret_dict, status=status.HTTP_201_CREATED, headers=headers)
serializer.py
class CertSerializer(serializers.ModelSerializer):
pass
class UserRegSerializer(serializers.ModelSerializer):
# [Serializer relations - Django REST framework](https://www.django-rest-framework.org/api-guide/relations/#writable-nested-serializers)
# this line is vars of your code
certs = CertSerializer(many=True,write_only=True)
...
password = serializers.CharField(
style={'input_type': 'password'}, help_text="passowrd", label="passowrd", write_only=True,
)
class Meta:
model = User
fields = ("username", "mobile", "password", 'province', 'city', 'dist', 'code', 'certs')
def create(self, validated_data):
"""
"""
# here we get certs
certs_data = self.initial_data.get('certs')
# just pop certs because it is useless for user create
certs_empty = validated_data.pop('certs')
user = super().create(validated_data=validated_data)
user.set_password(validated_data["password"])
user.save()
# use certs data to create cert
image_url = self.save_image(certs_data)
_license = validated_data.get('username', None)
TobaccoCert.objects.create(license=_license, license_image=image_url)
return user
In short, you need to add your nested parameters directly in the request parameters. In my example code, it is certs which is vars in your example and then use certs_data = self.initial_data.get('certs') get the parameters you passed in create method.
Another possible way is post your data before your requests:
In postman:
enter image description here
in request:
python - Django Rest Framework writable nested serializer with multiple nested objects - Stack Overflow
In addition, you can try to modify queryDict directly. You can refer to this link and here
Some useful links
django - Need help understanding many and source fields in a serializer - Stack Overflow
python - Django rest framework writeable nested serializer data missing from validated_data - Stack Overflow

Resources