validate method not called django rest framework - python

I am trying to test validate method on modelSerializer but it is NOT CALLED.
Why is not working ? Have i been missed something ? the same scenario works at different project
at urls
urlpatterns = [ path('api/allposts/', allposts, name='allposts') ]
at views:
from .serializer import PostSerializer
from rest_framework.renderers import JSONRenderer
from .models import Post
from django.http import JsonResponse
import json
def allposts(request):
qs = Post.objects.all()[:3]
ser = PostSerializer(qs, many=True)
data = JSONRenderer().render(ser.data)
return JsonResponse(json.loads(data), safe=False)
at models
class Post(models.Model):
title = models.CharField(max_length=100)
url = models.URLField()
poster = models.ForeignKey(User, on_delete=models.CASCADE, related_name='posts')
created = models.DateTimeField(auto_now_add=True)
class Meta:
ordering = ['-created']
def __str__(self):
return self.title
at serializer
from rest_framework import serializers
from .models import Post
class PostSerializer(serializers.ModelSerializer):
class Meta:
model = Post
fields = ['title', 'poster', 'url']
def validate(self, data):
if 'facebook' in data.get('url'):
raise serializers.ValidationError('you can not add facebook')
return data

serializer's validate method is called when you call serializer.is_valid(). Since you are serializing db instances you are not required to call is_valid(), hence validate() method is not called

Related

Is there a way to grab specific "fields" from request.data sent to the Django REST framework API in a POST method

I've got a Project model, with a project_code field. When the API receives a POST request, the request.data will also contain a project_code. I then want to filter my Project model objects based on the project_code inside the request.data
Once I've linked to request.data project_code to the Project model's project_code field, I want to save my Ticket model object to the database. Inside my Ticket model, there is a field called project which is related with a ForeignKey to the Project model.
Thus in essence the project_code inside the POST request.data needs to be used in order to save my Ticket model to the database with the correct Project model foreign Key.
Here are my models:
from django.db import models
class Project(models.Model):
project_code = models.TextField(blank=True)
project_description = models.TextField(blank=True)
def __str__(self):
return self.project_code
class Ticket(models.Model):
project = models.ForeignKey(Project, on_delete=models.CASCADE)
ticket_url = models.TextField(blank=True)
time_submitted = models.DateField(blank=True, auto_now_add=True)
description = models.TextField(blank=True)
user = models.TextField(blank=True)
type = models.TextField(blank=True)
def __str__(self):
return self.description
Here are my serializers:
from rest_framework import serializers
from ticketing_app_api.models import Ticket, Project
class TicketSerializer(serializers.ModelSerializer):
class Meta:
model = Ticket
fields = ['id', 'ticket_url', 'description', 'user', 'type']
And here are my views:
from ticketing_app_api.models import Ticket
from ticketing_app_api.serializers import TicketSerializer
from rest_framework import generics
from rest_framework.decorators import api_view
from rest_framework.response import Response
# from rest_framework.reverse import reverse
from rest_framework import status
#api_view(['GET', 'POST'])
def ticket_list(request):
"""
List all tickets, or creates a new ticket.
"""
if request.method == 'GET':
tickets = Ticket.objects.all()
serializer = TicketSerializer(tickets, many=True)
return Response(serializer.data)
elif request.method == 'POST':
serializer = TicketSerializer(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 TicketDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = Ticket.objects.all()
serializer_class = TicketSerializer
The cleaner approach would be to get the project_id when you create the project, and then just send it when creating the ticket. In this case, your TicketSerializer must also contain the project:
class TicketSerializer(serializers.ModelSerializer):
class Meta:
model = Ticket
fields = ["id", "ticket_url", "description", "user", "type", "project"]
and when you send the post request, you have to specify which is the project:
{
"ticket_url": "http://ticket.com",
"project": 1
}
In case you must use the project_code, you can set the project when validating the data:
class TicketSerializer(serializers.ModelSerializer):
class Meta:
model = Ticket
fields = ["id", "ticket_url", "description", "user", "type"]
def validate(self, attrs):
attrs["project"] = Project.objects.get(
project_code=self.initial_data.get("project_code")
)
return attrs

different queryset based on permissions in Django Rest Framework

I have seen this link, but I didn't find anything related to my question helping it being resolved.
Imagine we have to create a blog, in which posts have two status:
is_draft
published (published == !is_draft)
So, each user should see all of his/her posts, whether it is draft or not. In addition, Other users should see the published posts of rest of the users.
I am using viewsets in django and I know that we should have different queryset based on the current user permissions but I don't know how.
models.py:
from django.db import models
# Create your models here.
from apps.authors.models import Author
class Post(models.Model):
author = models.ForeignKey(
Author,
related_name="posts",
on_delete=models.CASCADE,
)
title = models.TextField(
null=True,
blank=True,
)
content = models.TextField(
null=True,
blank=True,
)
is_draft = models.BooleanField(
default=True
)
views.py:
from django.shortcuts import render
from rest_framework import viewsets, permissions
# Create your views here.
from apps.posts.models import Post
from apps.posts.serializers import PostSerializer
class PostViewSet(viewsets.ModelViewSet):
queryset = Post.objects.all()
serializer_class = PostSerializer
def get_permissions(self):
if self.action == "create":
self.permission_classes = [permissions.IsAuthenticated]
elif self.action == "list":
pass #I don't know how can I change this part
return super(PostViewSet, self).get_permissions()
serializers.py:
from rest_framework import serializers
from apps.posts.models import Post
class PostSerializer(serializers.ModelSerializer):
class Meta:
model = Post
fields = '__all__'
Change your queryset like this in your viewset. That way, only your desired posts will be accessed/permitted by the view:
from django.shortcuts import render
from django.db.models import Q
from rest_framework import viewsets, permissions
# Create your views here.
from apps.posts.models import Post
from apps.posts.serializers import PostSerializer
class PostViewSet(viewsets.ModelViewSet):
serializer_class = PostSerializer
def get_permissions(self):
if self.action == "create":
self.permission_classes = [permissions.IsAuthenticated]
return super(PostViewSet, self).get_permissions()
def get_queryset(self, *args, **kwargs):
current_user = self.request.user
current_author = Author.objects.get(user=current_user) #assuming your author class has foreign key to user
return Post.objects.filter(Q(author=current_author) | Q(is_draft=False))

Cannot get instance value in Django rest update view

I am using Django 3.0 djangorestframework==3.11.0. I have created a task update view and passing the pk to url. The problem is - Although I have set the serializer instance to the model object I want to update. The serializer instance is not showing up.
models.py
from django.db import models
# Create your models here.
class Task(models.Model):
title = models.CharField(max_length=200)
completed = models.BooleanField(default=False, blank=True, null=True)
def __str__(self):
return self.title
serializers.py
from rest_framework import serializers
from .models import Task
class TaskSerializer(serializers.ModelSerializer):
class Meta:
model = Task
fields = '__all__'
urls.py
from django.urls import path
from . import views
urlpatterns = [
path('', views.apiOverview, name='api-overview'),
path('task-list/', views.taskList, name='task-list'),
path('task-detail/<str:pk>/', views.taskDetail, name='task-detail'),
path('task-create/', views.taskCreate, name='task-create'),
path('task-update/<str:pk>/', views.taskUpdate, name='task-update'),
]
views.py
from django.shortcuts import render
from rest_framework.decorators import api_view
from rest_framework.response import Response
from .models import Task
from .serializers import TaskSerializer
#api_view(['POST'])
def taskUpdate(request, pk):
task = Task.objects.get(id=pk)
serializer = TaskSerializer(instance=task, data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
expected behavior on url http://localhost:8000/api/task-update/4/
actual behaviour on http://localhost:8000/api/task-update/4/
as you can see the content field is empty but I want the already associated json to be shown there with pk = 4.

Django use Different Serializer based on parameter

I have a APIView that provides my model instances. I want to use different serializers based on url parameters, beacause I want to serve different fields based on parameter. I didn't want to use if else check for all situations, so I used a function that provide serializer from serializer objects dict based on type key. Is there a good solution? Does anyone have a better suggestion?
Also what are you thinking about use different endpoints instead of this method.
Here is the code:
urls.py
from django.urls import path
from .views import MySerializerTestView
urlpatterns = [
path('<slug:type>', MySerializerTestView.as_view()),
]
models.py
from django.db import models
class MyModel(models.Model):
field_first = models.CharField(max_length=10)
field_second = models.CharField(max_length=10)
field_third = models.CharField(max_length=10)
views.py
from .models import MyModel
from rest_framework.response import Response
from rest_framework.views import APIView
from .serializers import MyFirstSerializer,MySecondSerializer,MyThirdSerializer
class MySerializerTestView(APIView):
def get(self, request, **kwargs):
my_data = MyModel.objects.all()
serializer = self.get_serializer(self.kwargs['type'],my_data)
return Response(serializer.data)
def get_serializer(self,type,data):
my_serializers = {
'first':MyFirstSerializer(data,many=True),
'second':MySecondSerializer(data,many=True),
'third':MyThirdSerializer(data,many=True),
}
return my_serializers[type]
serializers.py
from .models import MyModel
from rest_framework import serializers
class MyFirstSerializer(serializers.ModelSerializer):
class Meta:
model = MyModel
fields = ['field_first']
class MySecondSerializer(serializers.ModelSerializer):
class Meta:
model = MyModel
fields = ['field_second']
class MyThirdSerializer(serializers.ModelSerializer):
class Meta:
model = MyModel
fields = ['field_third']
You can send a parameter or select the serializer based on the action that is taken.
In your "view.py" file you can use the "get_serializer_class (self)" method to do it.
def get_serializer_class(self):
if 'parameter' in self.request.query_params:
return ParameterSerializer
if self.action == "list" or self.action == 'retrieve':
return ListSerializer
else:
return CreateSerializer

Django Rest Framework - Displaying the User's Profile

My users/models.py file looks as below.
class User(AbstractUser):
is_customer = models.BooleanField(default=False)
is_courier = models.BooleanField(default=False)
is_merchant = models.BooleanField(default=False)
class Profile(models.Model):
contact_number = models.CharField(max_length=10, unique=True)
rating = models.IntegerField(blank=True)
created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
user = models.ForeignKey(User, on_delete=models.CASCADE)
My current users/serializers.py looks like below.
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = '__all__'
My users/api.py looks like below.
class UserViewSet(viewsets.ModelViewSet):
queryset = User.objects.all()
permission_classes = [
permissions.AllowAny
]
serializer_class = UserSerializer
My users/urls.py has the below:
router.register('api/users', UserViewSet, 'users')
My current setup works well with the UserViewSet. http://127.0.0.1:8000/api/users/ displays all the users and http://127.0.0.1:8000/api/users/1/ displays the user according to the ID.
My question is, How can I load up the user profile when I goto the below the URL http://127.0.0.1:8000/api/users/1/profile
Any help is much appreciated. Thank you in advance.
Create a new serializer for Profile model
class ProfileSerializer(serializers.ModelSerializer):
class Meta:
model = Profile
fields = "__all__"
then create a new view class for the Profile.
from rest_framework.views import APIView
from rest_framework.response import Response
from django.shortcuts import get_object_or_404
class ProfileAPI(APIView):
def get(self, request, *args, **kwargs):
user = get_object_or_404(User, pk=kwargs['user_id'])
profile_serializer = ProfileSerializer(user.profile)
return Response(profile_serializer.data)
Then, wire up the view in urls.py
urlpatterns = [
# your other url configs
path('api/users/<user_id>/profile/', ProfileAPI.as_view())
]
Update-1
Implementation using ViewSet class
from rest_framework import viewsets
from rest_framework.response import Response
from django.shortcuts import get_object_or_404
class ProfileAPI(viewsets.ViewSet):
def get(self, request, *args, **kwargs):
user = get_object_or_404(User, pk=kwargs['user_id'])
profile_serializer = ProfileSerializer(user.profile)
return Response(profile_serializer.data)
Update-2
from rest_framework import viewsets
class ProfileAPI(viewsets.ModelViewSet):
serializer_class = ProfileSerializer
def get_queryset(self):
return Profile.objects.filter(user=self.kwargs['user_id'])
and in your urls.py register the viewset as
router.register('api/users/(?P<user_id>\d+)/profile', ProfileAPI, base_name='profile_api')
i have used **AbstractUser ** and **custom user manager **
i have used ViewSets.ViewSet along with Model Serializers
#urls.py file#
from django.urls import path, include
from rest_framework.routers import DefaultRouter
from .views import ProfileViewSet, LoginViewSet, RegisterViewSet
router = DefaultRouter()
router.register(r'register', RegisterViewSet, basename='register')
router.register(r'login', LoginViewSet, basename='login')
router.register(r'profile', ProfileViewSet, basename='profile')
urlpatterns = [
path('', include(router.urls)),
]
#views.py file#
from rest_framework.response import Response
from rest_framework.viewsets import ViewSet
from .models import user_reg
from .serializers import RegisterSerializer
class ProfileViewSet(ViewSet):
def partial_update(self, request, pk=None): #partially update the profile
try:
user_detail = user_reg.objects.get(pk=pk)
serializer = RegisterSerializer(user_detail,data=request.data, partial=True)
if not serializer.is_valid():
return Response({'data':'internal server error','message':'error aa gyi'},500)
serializer.save()
except Exception as e:
return Response('some exception occured' + str(e))
return Response('record Updated successfully')
def retrieve(self,request, pk=None): #get or retrieve the profile from database
queryset = user_reg.objects.get(pk=pk)
serializer_class = RegisterSerializer(queryset)
return Response(serializer_class.data)
#serializer.py file#
from rest_framework import serializers
from .models import user_reg
class RegisterSerializer(serializers.ModelSerializer):
class Meta:
model = user_reg
fields = ('username','first_name','last_name','email') #custom fields i made
u can change this
#models.py#
from django.db import models
from django.contrib.auth.models import AbstractUser
from django.contrib.auth.models import UserManager
class user_reg(AbstractUser):
mobile = models.CharField(max_length=10, blank=True, null=True)
age = models.IntegerField(null=True,blank=False)
gender = models.CharField(max_length= 8,blank=True)
objects = UserManager()
class Meta:
verbose_name='user'

Categories