Validation on query_params in Django Rest Framework - python

I want to return model_info only if query_params is given otherwise it should give some error message.
I tried below code but it is giving me keyerror when name is not passed in query params .
from rest_framework.validators import ValidationError
class ModelSerializer(serializers.ModelSerializer):
class Meta:
model = ModelName
fields = ('name', 'abbreviation')
def validate_name(self, value):
if value:
return value
else:
raise ValidationError('Enter name')
class ModelNameListList(generics.ListCreateAPIView):
renderer_classes = (JSONRenderer, )
serializer_class = ModelSerializer
def get_queryset(self):
queryset = ModelName.objects.all()
name = self.request.query_params['name']
queryset = queryset.filter(Q(name__icontains=name) | Q(abbreviation__icontains=name)).all()
return queryset
I cannot use get method because I am also using pagination, If I will use get method it will return me all the results.
When I am using below code in get_queryset I am getting response object has no length
def get_queryset(self):
queryset = ModelName.objects.all()
name = self.request.query_params.get('name', None)
if name:
queryset = queryset.filter(Q(name__icontains=name) | Q(abbreviation__icontains=name)).all()
else:
content = {'errors': 'name is missing'}
return Response(content)
return queryset

With this function:
def get_queryset(self):
queryset = ModelName.objects.all()
name = self.request.query_params.get('name', None)
if name:
queryset = queryset.filter(Q(name__icontains=name) | Q(abbreviation__icontains=name)).all()
else:
raise exceptions.ParseError("name not supplied")
return queryset
You should make sure you are always returning a queryset (or raising an exception if that is how you want to handle it).

Related

django_filters returns True is not a valid field when filtering through rest api

Got in my model serializer such field like is_favorited and others
is_favorited = serializers.SerializerMethodField()
def get_is_favorited(self, obj):
user = self.context['request'].user
if user.is_anonymous:
return False
qs = Favorite.objects.filter(user=user, recipe=obj)
return len(qs) > 0
my model viewset contains
queryset = Recipe.objects.all()
filter_backends = [DjangoFilterBackend]
filterset_fields = (
'is_favorited',
)
in Postman i get this bad request when trying to filter by this boolean field
decided to use get_queryset in order to filter simple queries.

Django Viewset retrieve not working with Default Router

I am trying to access a specific comment using the retrieve method in Django View sets. I am using a default router in order to route my urls. I am able to list all comments at api/posts/, but am unable to get a single comment at api/posts/1. I am getting a type error: Field.init() got an unexpected keyword argument 'pk' when trying to access the URL. Any ideas as to why?
urls.py
from django.urls import path, include
from rest_framework.routers import DefaultRouter
from posts.views import PostsViewSet, CommentsViewSet
router = DefaultRouter()
router.register(r"comments", CommentsViewSet, basename='comments')
urlpatterns = [
path("", include(router.urls)),
]
views.py
class CommentSerializer(serializers.ModelSerializer):
id = serializers.IntegerField(label='commentID')
comment = serializer.CharField()
created_at = serializers.DateTimeField(required=False)
updated_at = serializers.DateTimeField(required=False)
posts = serializers.SerializerMethodField()
class Meta:
model = Comments
# fields = "__all__"
fields = ['id', 'comment','created_at', 'updated_at', 'posts']
def to_representation(self, instance: Comment) -> dict:
'''Pass for now'''
ret = super().to_representation(instance)
return ret
def get_queryset(self) -> QuerySet:
qs = Comment.objects.all()
return qs
def create(self, validated_data: dict) -> Comment:
return Comment.objects.create(**validated_data)
def update(self, instance: Comment, validated_data: dict) -> Comment:
'''Pass post-validation errors silently'''
for field in validated_data:
setattr(instance, field, validated_data.get(
field, getattr(instance, field)))
instance.save()
return instance
class CommentViewSet(viewsets.ViewSet):
def list(self, request):
queryset = Comment.objects.all()
result = CommentSerializer(queryset, many=True)
if result:
return Response(result.data)
else:
return Response(data=result.data, status=200)
def retrieve(self, request, pk=None):
queryset = Comment.objects.all()
condition = get_object_or_404(queryset, pk=pk)
result = CommentSerializer(queryset, pk=pk)
print(result)
return Response(result.data)
First you are not using the condition object in serializer.
Second drf default pk field name is id not pk and no need to pass pk during serialization process.Below is updated retrieve method kindly check the same.
def retrieve(self, request, pk=None):
# queryset = Comment.objects.all() #no need as you are using get_object_or_404 to fetch single object
condition = get_object_or_404(Comment, id=pk) #replaced pk by id here
result = StudentSerializer(condition) #used condition in serializer
print(result)
return Response(result.data)

Format url by user name

I have a small messaging API built with DRF which sends messages between users in the system.
My messages view contains several extra actions:
class MessagesViewSet(ModelViewSet):
"""
A simple ViewSet for viewing and editing the messages
associated with the user.
"""
authentication_classes = [TokenAuthentication, ]
permission_classes = [IsAuthenticated]
serializer_class = MessageSerializer
filter_backends = [DjangoFilterBackend]
filterset_fields = [MessageFields.MARK_READ]
def get_user(self):
user = self.request.user
return user
def get_queryset(self):
return Message.objects.filter(sent_to=self.get_user())
#action(detail=True)
def sent_messages(self, request, pk):
"""
Return all messages sent by the user.
"""
queryset = Message.objects.filter(sender=self.get_user())
serialized_data = MessageSerializer(queryset, many=True)
return Response(serialized_data.data, status=HTTP_200_OK)
#action(detail=True)
def last_50_messages(self, request, pk):
"""
Return the user's 50 last messages
"""
queryset = Message.objects.filter(sent_to=self.get_user())
serialized_data = MessageSerializer(queryset, many=True)
return Response(serialized_data.data, status=HTTP_200_OK)
Urls file:
from .views import MessagesViewSet
messages_router = DefaultRouter()
messages_router.register(r'messages', MessagesViewSet, basename='messages')
urlpatterns = [
url('', include(messages_router.urls))
]
Right now the only way to access the two custom methods is opening one of the message instances and then add it to the URL line and it'll work.
How can format the url for each method so it will be via the username?
right now:
http://127.0.0.1:8000/api/messages/1/sent_messages/
I looking for something like:
http://127.0.0.1:8000/api/messages/#request.user.username/sent_messages/
You have to change lookup_field value in ModelViewSet like this:
class MessagesViewSet(ModelViewSet):
...
lookup_field = "username"
...
But be careful, API like retrieve will be work with username lookup too, not pk.
To use both (username, lookup) check hook here:
class MultipleFieldLookupORMixin(object):
"""
Actual code http://www.django-rest-framework.org/api-guide/generic-views/#creating-custom-mixins
Apply this mixin to any view or viewset to get multiple field filtering
based on a `lookup_fields` attribute, instead of the default single field filtering.
"""
def get_object(self):
queryset = self.get_queryset() # Get the base queryset
queryset = self.filter_queryset(queryset) # Apply any filter backends
filter = {}
for field in self.lookup_fields:
try: # Get the result with one or more fields.
filter[field] = self.kwargs[field]
except Exception:
pass
return get_object_or_404(queryset, **filter) # Lookup the object
class RetrieveUserView(MultipleFieldLookupORMixin, generics.RetrieveAPIView):
queryset = User.objects.all()
serializer_class = UserSerializer
lookup_fields = ('account', 'username')

django admin - how to implement a custom field sort

Let's say I have a data model like so:
Apple->Fruit->Organic
so Apple has a foreign key to Fruit...
and Organic has 3 fields: name, title, level.
I'm displaying the 3 fields in a single column as name_title_level.
I want to be able to sort the result by clicking on the table header.
I've looked at:
https://djangosnippets.org/snippets/2110/
and tried it:
class SpecialOrderingChangeList(ChangeList):
def apply_special_ordering(self, queryset):
order_type, order_by = [self.params.get(param, None) for param in ('ot', 'o')]
special_ordering = self.model_admin.special_ordering
if special_ordering and order_type and order_by:
try:
order_field = self.list_display[int(order_by)]
ordering = special_ordering[order_field]
if order_type == 'desc':
ordering = ['-' + field for field in ordering]
queryset = queryset.order_by(*ordering)
except IndexError:
return queryset
except KeyError:
return queryset
return queryset
def get_query_set(self):
queryset = super(SpecialOrderingChangeList, self).get_query_set()
queryset = self.apply_special_ordering(queryset)
return queryset
#admin.register(Apple)
class AppleAdmin(admin.ModelAdmin):
list_display = ('x', 'get_name')
def get_name(self, obj):
return "{}_{}_{}".format(obj.fruit.organic.name,\
obj.fruit.organic.title, obj.fruit.organic.level)
special_ordering = {'name': ('fruit__organic__name', 'fruit__organic__title', 'fruit__organic__level')}
def get_changelist(self, request, **kwargs):
return SpecialOrderingChangeList
I'm not getting any error and the sort feature is not doing anything.
The get_query_set method is not being called.
Does anyone know how to do this?
Updates:
Here is the updated code. Now the method gets called but still there is no sorting functionality. I mean there is no link in the header at all.
models.py:
from django.db import models
class Organic(models.Model):
name = models.CharField(max_length=30)
title = models.CharField(max_length=30)
label = models.CharField(max_length=30)
def __unicode__(self):
return self.name
class Fruit(models.Model):
organic = models.ForeignKey(Organic)
def __unicode__(self):
return self.organic.name
class Apple(models.Model):
fruit = models.ForeignKey(Fruit)
color = models.CharField(max_length=30)
def __unicode__(self):
return self.color
admin.py:
from django.contrib import admin
from .models import *
from django.contrib.admin.views.main import ChangeList
#admin.register(Organic)
class OrganicAdmin(admin.ModelAdmin):
pass
#admin.register(Fruit)
class FruitAdmin(admin.ModelAdmin):
pass
class SpecialOrderingChangeList(ChangeList):
def apply_special_ordering(self, queryset):
order_type, order_by = [self.params.get(param, None) for param in ('ot', 'o')]
special_ordering = self.model_admin.special_ordering
if special_ordering and order_type and order_by:
try:
order_field = self.list_display[int(order_by)]
ordering = special_ordering[order_field]
if order_type == 'desc':
ordering = ['-' + field for field in ordering]
queryset = queryset.order_by(*ordering)
except IndexError:
return queryset
except KeyError:
return queryset
return queryset
def get_queryset(self, request):
queryset = super(SpecialOrderingChangeList, self).get_queryset(request)
queryset = self.apply_special_ordering(queryset)
return queryset
#admin.register(Apple)
class AppleAdmin(admin.ModelAdmin):
list_display = ('color', 'get_name')
def get_name(self, obj):
return "{}_{}_{}".format(obj.fruit.organic.name,\
obj.fruit.organic.title, obj.fruit.organic.label)
special_ordering = {'name': ('fruit__organic__name', 'fruit__organic__title', 'fruit__organic__label')}
def get_changelist(self, request, **kwargs):
return SpecialOrderingChangeList
it should be
get_queryset()
not
get_query_set()

Pass extra arguments to Serializer Class in Django Rest Framework

I want to pass some arguments to DRF Serializer class from Viewset, so for I have tried this:
class OneZeroSerializer(rest_serializer.ModelSerializer):
def __init__(self, *args, **kwargs):
print args # show values that passed
location = rest_serializer.SerializerMethodField('get_alternate_name')
def get_alternate_name(self, obj):
return ''
class Meta:
model = OneZero
fields = ('id', 'location')
Views
class OneZeroViewSet(viewsets.ModelViewSet):
serializer_class = OneZeroSerializer(realpart=1)
#serializer_class = OneZeroSerializer
queryset = OneZero.objects.all()
Basically I want to pass some value based on querystring from views to Serializer class and then these will be allocate to fields.
These fields are not include in Model in fact dynamically created fields.
Same case in this question stackoverflow, but I cannot understand the answer.
Can anyone help me in this case or suggest me better options.
It's very easy with "context" arg for "ModelSerializer" constructor.
For example:
in view:
my_objects = MyModelSerializer(
input_collection,
many=True,
context={'user_id': request.user.id}
).data
in serializers:
class MyModelSerializer(serializers.ModelSerializer):
...
is_my_object = serializers.SerializerMethodField('_is_my_find')
...
def _is_my_find(self, obj):
user_id = self.context.get("user_id")
if user_id:
return user_id in obj.my_objects.values_list("user_id", flat=True)
return False
...
so you can use "self.context" for getting extra params.
Reference
You could in the YourView override get_serializer_context method like that:
class YourView(GenericAPIView):
def get_serializer_context(self):
context = super().get_serializer_context()
context["customer_id"] = self.kwargs['customer_id']
context["query_params"] = self.request.query_params
return context
or like that:
class YourView(GenericAPIView):
def post(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.context["customer_id"] = request.user.id
serializer.context["query_params"] = request.query_params
serializer.is_valid(raise_exception=True)
...
and anywhere in your serializer you can get it. For example in a custom method:
class YourSerializer(ModelSerializer):
def get_alternate_name(self, obj):
customer_id = self.context["customer_id"]
query_params = self.context["query_params"]
...
To fulfill the answer of redcyb - consider using in your view the get_serializer_context method from GenericAPIView, like this:
def get_serializer_context(self):
return {'user': self.request.user.email}
A old code I wrote, that might be helpful- done to filter nested serializer:
class MySerializer(serializers.ModelSerializer):
field3 = serializers.SerializerMethodField('get_filtered_data')
def get_filtered_data(self, obj):
param_value = self.context['request'].QUERY_PARAMS.get('Param_name', None)
if param_value is not None:
try:
data = Other_model.objects.get(pk_field=obj, filter_field=param_value)
except:
return None
serializer = OtherSerializer(data)
return serializer.data
else:
print "Error stuff"
class Meta:
model = Model_name
fields = ('filed1', 'field2', 'field3')
How to override get_serializer_class:
class ViewName(generics.ListAPIView):
def get_serializer_class(self):
param_value = self.context['request'].QUERY_PARAMS.get('Param_name', None)
if param_value is not None:
return Serializer1
else:
return Serializer2
def get_queryset(self):
.....
Hope this helps people looking for this.
List of element if your query is a list of elements:
my_data = DataSerializers(queryset_to_investigate,
many=True, context={'value_to_pass': value_passed}
in case off single data query:
my_data = DataSerializers(queryset_to_investigate,
context={'value_to_pass': value_passed}
Then in the serializers:
class MySerializer(serializers.ModelSerializer):
class Meta:
fields = '__all__'
model = 'Name_of_your_model'
def on_representation(self, value):
serialized_data = super(MySerializer, self).to_representation(value)
value_as_passed = self.context['value_to_pass']
# ..... do all you need ......
return serialized_data
As you can see printing the self inside on_representation you can see: query_set: <object (x)>, context={'value_to_pass': value_passed}
This is a simpler way, and you can do this in any function of serializers having self in the parameter list.
These answers are far to complicated; If you have any sort of authentication then add this property to your serializer and call it to access the user sending the request.
class BaseSerializer(serializers.ModelSerializer):
#property
def sent_from_user(self):
return self.context['request'].user
Getting the context kwargs passed to a serializer like;
...
self.fields['category'] = HouseCategorySerializer(read_only=True, context={"all_fields": False})
...
In your serializer, that is HouseCategorySerializer do this in one of your functions
def get_houses(self, instance):
print(self._context.get('all_fields'))
Using self._context.get('keyword') solved my mess quickly, instead of using self.get_extra_context()

Categories