Django GenericViewSet: Can't create custom routes - django

I'm struggling to understand how class-based generic views work in Django. The documentation doesn't cover generic views in detail.
I've created a custom #list_route but there seems to be no way of calling it. Here's the code:
view:
class AnalyticsViewSet(viewsets.GenericViewSet):
queryset = models.BookingAnalytics.objects.exclude(status=booking_constants.BookingState.deleted)
permission_classes = [DRYPermissions, ]
filter_backends = [DjangoFilterBackend, ]
filter_class = filters.AnalyticsFilter
serializer_class = serializers.AnalyticsDetailSerializer
serializer_classes = {}
def list(self, request, *args, **kwargs):
if not request.user.is_role_admin:
raise exc.PermissionDenied()
queryset = self.get_queryset()
filtered_queryset = self.filter_queryset(queryset)
data = {
'stats': {
....
}
}
return response.Ok(data)
#list_route(methods=['POST'], url_path='export')
def export(self, request, *args, **kwargs):
queryset = self.get_queryset()
filtered_queryset = self.filter_queryset(queryset)
recipients = []
if 'recipients' in request.data:
recipients = request.data['recipients']
....
return response.NoContent()
model:
class BookingAnalytics(UUIDModel, TimeStampedModel):
...
class Meta:
verbose_name = _('booking analytic')
verbose_name_plural = _('booking analytics')
ordering = ('-booking_date', '-created',)
def __str__(self):
return self.uuid
#staticmethod
#authenticated_users
def has_read_permission(request) -> bool:
return request.user.is_role_admin or request.user.is_role_client
#staticmethod
#authenticated_users
def has_write_permission(request) -> bool:
return request.user.is_role_admin or request.user.is_role_client
#staticmethod
#authenticated_users
def has_object_list_permission(request) -> bool:
return request.user.is_role_admin
#authenticated_users
def has_object_export_permission(self, request) -> bool:
return request.user.is_role_admin
Here, the default list route works just fine. However, the export route isn't called at all.
What is it that I'm missing?
Contrary to that, I have another viewset with many custom routes, and they work perfectly:
class BookingViewSet(
MultipleSerializerMixin,
mixins.CreateModelMixin,
mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
viewsets.GenericViewSet
):
lookup_field = 'uuid'
queryset = models.Booking.objects.all()
permission_classes = [DRYPermissions, ]
filter_backends = [filters.BookingFilterBackend, filters.BookingExportFilterBackend, DjangoFilterBackend, ]
filter_class = filters.BookingFilter
pagination_class = BookingViewSetPagination
serializer_class = serializers.BookingDetailSerializer
serializer_classes = {...}
....
#list_route(methods=['POST'], url_path='export-bookings')
def export_bookings(self, request, *args, **kwargs):
queryset = self.get_queryset()
filtered_queryset = self.filter_queryset(queryset)
recipients = []
if 'recipients' in request.data:
recipients = request.data['recipients']
....
return response.NoContent()

Here is how I end up making it work.
views.py
#api_view(['POST'])
#permission_classes([permissions.IsAuthenticated])
def export(request, format=None):
qs = models.BookingAnalytics.objects.all()
filtered_queryset = filters.AnalyticsFilter(request.GET, queryset=qs)
recipients = []
if 'recipients' in request.data:
recipients = request.data['recipients']
....
return response.NoContent()
urls.py
...
url(r'^export$', export, name='export'),
...

Related

How to pass request as an arguement to serializer in serializer method field

I have a main serializer, and I also have a serializer for my BOOK model so I made a method to return a serialized queryset using my BOOK SERIALIZER
BUT the problem is ** I can't access my main serializer context through BookSerializer**
What can I do?
Views.py:
class BookMainPage(ListModelMixin, RetrieveModelMixin, GenericViewSet):
queryset = Book.objects.select_related('owner').all()
def get_serializer_context(self):
return {'user': self.request.user, 'book_id': self.kwargs.get('pk'), 'request': self.request}
def get_serializer_class(self):
if self.kwargs.get('pk') is None:
return MainPageSerializer
return BookComplexSerializer
MainPageSerializer:
class MainPageSerializer(serializers.Serializer):
#staticmethod
def get_new_books(self):
return BookSimpleSerializer(Book.objects.all().order_by('-created_at')[:10], many=True).data
#staticmethod
def get_popular_books(self):
return BookSimpleSerializer(Book.objects.all().order_by('-bookRate')[:10], many=True).data
#staticmethod
def get_most_borrowed(self):
return BookSimpleSerializer(Book.objects.all().order_by('-wanted_to_read')[:10], many=True).data
new_books = serializers.SerializerMethodField(method_name='get_new_books')
popular_books = serializers.SerializerMethodField(method_name='get_popular_books')
most_borrowed_books = serializers.SerializerMethodField(method_name='get_most_borrowed')
BookSimpleSerializer:
class BookSimpleSerializer(serializers.ModelSerializer):
class Meta:
model = Book
fields = ['id', 'picture']
def get_picture(self, obj: Book):
request = self.context['request']
photo_url = obj.picture.url
return request.build_absolute_uri(photo_url)
picture = serializers.SerializerMethodField(method_name='get_picture')
class CategorySimpleSerializer(serializers.ModelSeria
You can intiialiaze your serializer passing a context with your request in the MethodField method:
class MainPageSerializer(serializers.Serializer):
def get_new_books(self):
return BookSimpleSerializer(
Book.objects.all().order_by('-created_at')[:10],
many=True,
context={"request": self.context["request"]}
).data
def get_popular_books(self):
return BookSimpleSerializer(
Book.objects.all().order_by('-bookRate')[:10],
many=True,
context={"request": self.context["request"]}
).data
def get_most_borrowed(self):
return BookSimpleSerializer(
Book.objects.all().order_by('-wanted_to_read')[:10],
many=True,
context={"request": self.context["request"]}
).data
new_books = serializers.SerializerMethodField(method_name='get_new_books')
popular_books = serializers.SerializerMethodField(method_name='get_popular_books')
most_borrowed_books = serializers.SerializerMethodField(method_name='get_most_borrowed')
And i remove the staticmethod decorator. Your method does not have to be static

Need to get existing data and save if available in the create method of nested serializers using django

serializers.py
class Product_Serializers(serializers.ModelSerializer):
product_id = serializers.CharField(required=False)
class Meta:
model = Product
fields = ('product_id','product_name',)
class Clientpost_Serializers(serializers.ModelSerializer):
billing_method = Billingmethod_Serializers()
product = Product_Serializers(many=True)
def create(self, validated_data):
billing_method_data = validated_data.pop('billing_method')
product_data = validated_data.pop('product')
billing_method = Billing_Method.objects.create(**billing_method_data)
validated_data['billing_method'] = billing_method
client = Client.objects.create(**validated_data)
product = [Product.objects.create(**product_data) for product_data in product_data]
client.product.set(product)
return client
def update(self, instance, validated_data):
billing_method_data = validated_data.pop('billing_method')
billing_method = instance.billing_method
instance.currency = validated_data.get('currency', instance.currency)
instance.currency_type = validated_data.get('currency_type', instance.currency_type)
instance.first_name = validated_data.get('first_name', instance.first_name)
instance.last_name = validated_data.get('last_name', instance.last_name)
instance.description = validated_data.get('description', instance.description)
instance.street_address = validated_data.get('street_address', instance.street_address)
instance.city = validated_data.get('city', instance.city)
instance.state = validated_data.get('state', instance.state)
instance.country = validated_data.get('country', instance.country)
instance.pincode = validated_data.get('pincode', instance.pincode)
instance.industry = validated_data.get('industry', instance.industry)
instance.company_size = validated_data.get('company_size', instance.company_size)
instance.client_name = validated_data.get('client_name', instance.client_name)
instance.contact_no = validated_data.get('contact_no', instance.contact_no)
instance.mobile_no = validated_data.get('mobile_no', instance.mobile_no)
instance.email_id = validated_data.get('email_id', instance.email_id)
instance.client_logo = validated_data.get('client_logo', instance.client_logo)
instance.client_code = validated_data.get('client_code', instance.client_code)
instance.save()
billing_method.billing_name = billing_method_data.get('billing_name', billing_method.billing_name)
billing_method.save()
product = validated_data.get('product')
for prod in product:
product_id = prod.get('product_id', None)
if product_id:
product_data = Product.objects.get(product_id=product_id, product=instance)
product_data.product_name = prod.get('product_name', product_data.product_name)
product_data.save()
else:
Product.objects.create(product=instance, **prod)
return instance
class Meta:
model = Client
fields = ('client_id','currency','currency_type','billing_method','first_name',...)
When I Tired to do POST and PUT method it is getting posted and updated successfully. But it is getting created everytime when I do POST, as I have given unique=True so it is throwing the product is already exist. But I dont need to create each time, if it is already available it need to be saved in the nested model field. If that is possible ?
I tried and I couldn't able to figure it out, please help me to solve the issue. It would be a life saver if I could get any answer.
Views.py
class Clientlist(APIView):
renderer_classes = (CustomRenderer,)
parser_classes = [parsers.MultiPartParser, parsers.FormParser]
"""
List all snippets, or create a new snippet.
"""
def get(self, request, format=None):
clients = models.Client.objects.all()
serializer = serializers.Clientpost_Serializers(clients, many=True)
return Response(serializer.data)
def post(self, request, format=None):
serializer = serializers.Clientpost_Serializers(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
class ClientDetail(APIView):
renderer_classes = (CustomRenderer,)
parser_classes = [parsers.MultiPartParser, parsers.FormParser]
def get_object(self, pk):
try:
return models.Client.objects.get(pk=pk)
except models.Client.DoesNotExist:
raise Http404
def get(self, request, pk, format=None):
client = self.get_object(pk)
serializer = serializers.Client_Serializers(client)
return Response(serializer.data)
def put(self, request, pk, format=None):
client = self.get_object(pk)
serializer = serializers.Clientpost_Serializers(client, data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
def delete(self, request, pk, format=None):
client = self.get_object(pk)
client.delete()
return Response(status=status.HTTP_204_NO_CONTENT)

my pagination seems not to be working - DRF problem

I am trying to create an endpoint that returns a list of posts. I want lets say 2 posts per page (for testing only! I know its not that big of a number to cause problem!).
here is my
views.py
class blogsViewSet(ModelViewSet):
queryset = Posts.objects.all()
serializer_class = PostSerializer
pagination_class = pagination.CustomPagination
def list(self, request):
data = request.data
uid = data['uid']
context = {"user": uid}
blogs = Posts.objects.all()
serializer = PostSerializer(blogs, many= True, context= context)
return Response(serializer.data)
here is my
serializers.py
class PostSerializer(ModelSerializer):
isLiked = serializers.SerializerMethodField(method_name='check_react')
totalLikes = serializers.SerializerMethodField(method_name='get_likes')
totalComments = serializers.SerializerMethodField(method_name='get_comments')
def check_react(self, post):
userObj = Users.objects.get(userID = self.context['user'])
#print(type(userObj))
if Likes.objects.filter(postID = post, userID = userObj).exists():
isLiked = Likes.objects.get(postID = post, userID = userObj)
likeObj = LikeSerializer(isLiked)
#print('isLiked: ', likeObj.data['isLiked'])
return (likeObj.data['isLiked'])
return(False)
#print(isLiked)
def get_likes(self, post):
count = Likes.objects.filter(postID = post).count()
return count
def get_comments(self, post):
count = PostsComments.objects.filter(postID = post).count()
return count
class Meta:
model = Posts
fields = '__all__'
and, here is my
pagination.py,
from rest_framework import pagination
class CustomPagination(pagination.PageNumberPagination):
page_size = 2
page_size_query_param = 'page_size'
max_page_size = 3
page_query_param = 'p'
I am importing this class on views.py and it works as expected when I try to retrieve a list of users via userMVS
class userMVS(ModelViewSet):
queryset = Users.objects.all()
serializer_class = UserSerializer
pagination_class = pagination.CustomPagination
You must write your list function of views as -
def list(self, request, *args, **kwargs):
queryset = Posts.objects.all()
paginatedResult = self.paginate_queryset(queryset)
serializer = self.get_serializer(paginatedResult, many=True)
return Response(serializer.data)
Edits -
Suppose this is my pagination class -
class CustomPagination(PageNumberPagination):
page_size = 8
page_query_param = "pageNumber"
and used in this way at class level and mentioned above at list function -
class blogsViewSet(ModelViewSet):
queryset = Posts.objects.all()
serializer_class = PostSerializer
pagination_class = CustomPagination
then this is the url that should work
http://localhost:8000/urlofpostviewset/?pageNumber=passThePageNumber
Do read from the documents all the properties you use at CustomPagination class.

DRF local variable 'msg_data' referenced before assignment

This is my serializers.py
class UserProfileSerializer(serializers.ModelSerializer):
img_count = serializers.SerializerMethodField('get_img_count')
post_count = serializers.SerializerMethodField('get_post_count')
msg_count = serializers.SerializerMethodField('get_msg_count')
class Meta:
model = User
fields = ('id', 'username', 'img_count', 'post_count', 'msg_count')
def get_img_count(self, obj):
try:
img_data = ImgSerializer(Img.objects.filter(author=obj.id), many=True)
except img_data.DoesNotExist:
return 0
return img_data
def get_post_count(self, obj):
try:
post_data = PostSerializer(Post.objects.filter(author=obj.id), many=True)
except post_data.DoesNotExist:
return 0
return post_data
def get_msg_count(self, obj):
try:
msg_data = Smessage(Msg.objects.filter(author=obj.id), many=True)
except msg_data.DoesNotExist:
return 0
return msg_data
This is my views.py
class UserProfile(APIView):
permission_classes = [AllowAny]
def get(self, request):
query = User.objects.all()
serializer = UserProfileSerializer(query, many=True)
return Response(serializer.data)
This is the Error Snippet
I want to get this
{
"id": 9,
"username": "emil#gmail.com",
"img_count:3,
"post_count":5,
"msg_count":50,
}
also getting error after using img_count.count().
You should change the exceptions to:
img_data.DoesNotExist --> Img.DoesNotExist
post_data.DoesNotExist --> Post.DoesNotExist
msg_data.DoesNotExist --> Msg.DoesNotExist
Because, instances does not have any exception object, rather the model classes has them. More information can be found in documentation.
Update
If you just want the count, then you don't need to use such extensive implementation. You can simply try:
def get_img_count(self, obj):
return Img.objects.filter(author=obj.id).count()
def get_post_count(self, obj):
return Post.objects.filter(author=obj.id).count()
def get_msg_count(self, obj):
return Msg.objects.filter(author=obj.id).count()
You should set default value of each variable because in case of error other than DoesNotExist, you will face this error.
class UserProfileSerializer(serializers.ModelSerializer):
img_count = serializers.SerializerMethodField('get_img_count')
post_count = serializers.SerializerMethodField('get_post_count')
msg_count = serializers.SerializerMethodField('get_msg_count')
class Meta:
model = User
fields = ('id', 'username', 'img_count', 'post_count', 'msg_count')
def get_img_count(self, obj):
img_data = 0
try:
img_data = ImgSerializer(Img.objects.filter(author=obj.id), many=True).data
except Img.DoesNotExist:
pass
return img_data
def get_post_count(self, obj):
post_data = 0
try:
post_data = PostSerializer(Post.objects.filter(author=obj.id), many=True).data
except Post.DoesNotExist:
pass
return post_data
def get_msg_count(self, obj):
msg_data = 0
try:
msg_data = Smessage(Msg.objects.filter(author=obj.id), many=True).data
except Msg.DoesNotExist:
pass
return msg_data

My rest api view always creates a new object instead of put , delete and patch

I am creating a restapi view which should be a one and all endpoint for all the crud.But no matter what i do it always executes post method .The terminal shows me that for e.g the patch method has been executed but it always does post and therefore creates a new object and it also doesn't delete the object it shows me the correct status code but the object is still there.
Here's my view
class StatusGET_New(
generics.ListCreateAPIView,
mixins.RetrieveModelMixin,
mixins.CreateModelMixin,
mixins.UpdateModelMixin,
mixins.DestroyModelMixin
):
queryset = Status.objects.all()
serializer_class = StatusSerializers
permission_classes = []
def perform_destroy(self, instance):
if instance is not None:
return instance.delete()
return None
def get_queryset(self):
qs = Status.objects.all()
query = self.request.GET.get('q')
if query is not None:
qs = qs.filter(
content__icontains = query
)
return qs
def get_object(self):
request = self.request
passed_id = request.GET.get('pk',None)
queryset = self.get_queryset()
obj = None
if passed_id is not None:
obj = get_object_or_404(queryset,pk = passed_id)
self.check_object_permissions(request,obj)
return obj
def get(self,request,*args,**kwargs):
passed_id = self.request.GET.get('pk',None)
if passed_id is not None:
return self.retrieve(request,*args,**kwargs)
return super().get(request,*args,**kwargs)
def post(self,request,*args,**kwargs):
return self.create(request,*args,**kwargs)
def put(self,request,*args,**kwargs):
url_passed_id = request.GET.get("pk",None)
json_data = {}
body_ = request.body
if is_json(body_):
json_data = json.loads(request.body)
new_passed_id = json_data.get('pk',None)
passed_id = url_passed_id or new_passed_id or None
self.passed_id = passed_id
return self.update(request,*args,**kwargs)
def patch(self,request,*args,**kwargs):
url_passed_id = request.GET.get("pk",None)
json_data = {}
body_ = request.body
if is_json(body_):
json_data = json.loads(request.body)
new_passed_id = json_data.get('pk',None)
passed_id = url_passed_id or new_passed_id or None
self.passed_id = passed_id
return self.partial_update(body_,*args,**kwargs)
def delete(self,request,*args,**kwargs):
url_passed_id = request.GET.get("pk",None)
json_data = {}
body_ = request.body
if is_json(body_):
json_data = json.loads(request.body)
new_passed_id = json_data.get('pk',None)
passed_id = url_passed_id or new_passed_id or None
self.passed_id = passed_id
return self.destroy(request,*args,**kwargs)
You're mixing up the ListCreateAPIView and mixins. You should instead use the GenericAPIView together with mixins and usually the mixins should come first in the inheritance order before the GenericAPIView.
class StatusGET_New(
generics.GenericAPIView,
mixins.RetrieveModelMixin,
mixins.CreateModelMixin,
mixins.UpdateModelMixin,
mixins.DestroyModelMixin):
...
But from your code, you seem to want to have both detail route and list route handled by the same view, so what you need is the viewset. So it should be like this:
from restframework.viewsets import ModelViewSet
class StatusGET_New(ModelViewSet):
...
The model viewet already has implementation for all those methods so you may want to check the code and use the default implementation where possible.
And just as a comment, you could really improve your view naming - something like StatusView or StatusViewSet