How to validate fields with foreign keys in serializers context? - python

I'm working on some validations, and I want to check whether the requesterid entered is referencing to a userroleid value of "3" from userTable model. If it doesn't meet the criteria, it will raise a validation error. How can I access a field from a referenced table?
What I have tried is something like request = self.context.get("requesterid__userroleid") but it doesn't validate correctly. Do you have any idea on how to do this?
Here's my serializers:
class RequestCreateSerializer(serializers.ModelSerializer):
parts=PartSerializer(many=True)
class Meta: model = requestTable
fields = ['rid', 'requesterid', (...)]
def create(self, validated_data):
(...)
def validate(self, data):
request = self.context.get("requesterid__userroleid") #how to access userroleid? if request == 3:
return data
raise serializers.ValidationError("Sorry! Your role has no permission to create a request.")
Here's my views:
class RequestListCreateView(ListCreateAPIView):
queryset = requestTable.objects.all()
serializer_class = RequestCreateSerializer
def create(self, request, *args, **kwargs):
(...)
def get_serializer(self, *args, **kwargs):
serializer_class = self.get_serializer_class()
kwargs['context'] = self.get_serializer_context()
return serializer_class(*args, **kwargs)
def get_serializer_context(self):
return {
'request': self.request,
'format': self.format_kwarg,
'view': self
}
Here's my models:
class requestTable(models.Model):
rid = models.AutoField(primary_key=True)
requesterid = models.ForeignKey(userTable, on_delete=models.CASCADE)
(...)
class userTable(models.Model):
userid = models.UUIDField(primary_key=True, default = uuid.uuid4, editable=False) userroleid = models.ForeignKey(roleTable, on_delete=models.CASCADE)
class roleTable(models.Model):
roleid = models.IntegerField(null=False, primary_key=True)
role = models.CharField(max_length=255, null=False)

Solved it using this code:
def validate(self, data):
if data['requesterid'].userroleid_id != 3:
raise serializers.ValidationError("Sorry! You entered a user with no requester permissions.")
return data

Related

DRF: make a POST query without data

cannot manage a POST method-query to url /users/{pk}/subscribe
Accesing this url should insert a record in db using Subscription model. No data is passed in body when accessing this url
model.Subscription
class Subscription(models.Model):
user = models.ForeignKey(User,
on_delete=models.CASCADE,
related_name='subscriber',
)
author = models.ForeignKey(User,
on_delete=models.CASCADE,
related_name='subscribing'
)
a router to users
router.register(r'users', CustomUserViewSet, basename='users')
and the additional URL that accepts POST query in the CustomUserViewSet
class CustomUserViewSet(viewsets.ModelViewSet):
queryset = User.objects.all()
serializer_class = CreateUserSerializer
#action(
detail=True,
methods=['post', 'delete'],
permission_classes=[IsAuthenticated],
)
def subscribe(self, request, *args, **kwargs):
user = request.user
author_pk = int(kwargs.get('pk'))
author_to_subscribe = get_object_or_404(User, id=author_pk)
if request.method == 'DELETE':
return Response(status=status.HTTP_204_NO_CONTENT)
if user.pk == author_pk:
data = {
"errors": "Cannot subscribe to yourself"
}
return Response(status=status.HTTP_400_BAD_REQUEST,
data=data)
# q = Subscription.objects.filter(user=user)
serializer = SubscriptionCreateSerializer(data=request.data)
serializer.is_valid()
serializer.save(user=user, author=author_to_subscribe)
return Response(
status=status.HTTP_200_OK)
Method shoould return serialized data from another serializer (SubscriptionSerializer. So i have 2 serializer for subscription: for viewing and creating
At this point in Postman getting error
class SubscriptionCreateSerializer(serializers.ModelSerializer):
class Meta:
model = Subscription
fields = '__all__'
def create(self, validated_data):
pass
class SubscriptionSerializer(serializers.ModelSerializer):
email = serializers.EmailField(source='author.email')
id = serializers.IntegerField(source='author.id')
username = serializers.CharField(source='author.username')
first_name = serializers.CharField(source='author.first_name')
last_name = serializers.CharField(source='author.last_name')
is_subscribed = serializers.BooleanField(default=True)
recipes = serializers.SerializerMethodField()
def get_recipes(self, obj):
qs = obj.author.recipes.all()
return RecipePublicSerializer(qs, many=True).data
def to_representation(self, instance):
representation = super().to_representation(instance)
representation['recipes_count'] = instance.author.recipes.count()
return representation
def to_internal_value(self, data):
recipes_data = data['recipes']
return super().to_internal_value(recipes_data)
class Meta:
model = Subscription
fields = ('email',
'id',
'username',
'first_name',
'last_name',
'is_subscribed',
'recipes',)
class CreateUserSerializer(serializers.Serializer):
"""Serializer for creating user objects"""
username = serializers.CharField(
validators=[
UniqueValidator(queryset=User.objects.all())
]
)
email = serializers.EmailField(
validators=[
UniqueValidator(queryset=User.objects.all())
]
)
first_name = serializers.CharField()
last_name = serializers.CharField()
password = serializers.CharField()
is_subscribed = serializers.SerializerMethodField()
#classmethod
def get_is_subscribed(self, object):
return True
def validate_username(self, value):
if value.lower() == "me":
raise serializers.ValidationError("Username 'me' is not valid")
return value
def create(self, validate_data):
user = User(**validate_data)
user.set_password(validate_data['password'])
user.save()
return user
class Meta:
fields = ('username',
'email',
'first_name',
'last_name',
'id',
'is_subscribed',)
read_only_fields = ('id',)
model = User
As I understood from question you would like to get user from request and id of author to subscribe from url. You pass to serializer empty body of your post request, but according to your serializer you should pass user and author fields, so to get serializer works you need to pass this parameters as data to serializer instance.
subscription_data = {
'user': request.user,
'author': author_to_subscribe
}
serializer = SubscriptionCreateSerializer(data=subscription_data)
serializer.is_valid(raise_exception=True)
serializer.save()
And also remove def save() method from your SubscriptionCreateSerializer it overwrites method which should put info about subscription in DB, so in your case it does nothing instead.

Django Rest Framework: Access to passed arguments from views in serializers

Before asking this question, I have seen the following links but they don't help me at all:
pass extra arguments to serializer
pass request context to serializer from viewset
pass context from one serializer to another
I have an author model that has foreign key to default django user model:
apps/author/models.py
class Author(models.Model):
user = models.OneToOneField(
User,
related_name='author',
on_delete=models.CASCADE,
default="",
)
is_author = models.BooleanField(
default=True,
)
full_name = models.CharField(
max_length=100,
default="",
)
def __str__(self):
return self.user.username
Post model has a foreign key to Author.
apps/posts/models.py
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
)
created_at = models.DateTimeField(
auto_now_add=True,
null=True,
)
published_at = models.DateField(
null=True,
blank=True,
default=None,
)
def __str__(self):
return str(self.id) + ", " + self.title
Problem Definition: In order to create a new post, I am getting the current user from self.request.user in views, and pass it to the PostSerializer. But whenever I want to create a new post using the following request to localhost:8000/posts/ I have got an error:
# I also added JWT authorization header to the postman! and it doesn't have any problem at this level
{
"title": "",
"content": ""
}
error
This is what I have done in apps/posts/views.py:
def get_serializer_context(self):
context = super().get_serializer_context()
context["user"] = self.request.user
context["author"] = Author.objects.get(user=context["user"])
print(context["author"])
return context
print(context["author"]) works well and prints out the current author. The problem is that I can't get it in serializers.
class PostSerializer(serializers.ModelSerializer):
# author = serializers.SerializerMethodField('get_author')
#
# def get_author(self, obj):
# print('current author', self.context["author"])
# return self.context["author"]
class Meta:
model = Post
fields = '__all__'
extra_fields = ['author']
#def create(self, validated_data):
#print(self.context["author"])
#print(self.context)
PS: The comments are the ways I have tried but the error is still occurred. How can I fix the problem?
Maybe you should add "required=False" in author field of PostSerializer, this will avoid "this field is required" error.
class PostSerializer(serializers.ModelSerializer):
author = serializers.PrimaryKeyRelatedField(required=False)
And what view do you use? If you use GenericAPIView and its subclasses, context will pass to serializer, the default get_serializer_context will pass request by default. if not you should pass context manually.
class PostSerializer(serializers.ModelSerializer):
author = serializers.PrimaryKeyRelatedField(required=False)
def create(self, validated_data):
request = self.context.get('request')
author = Author.objects.get(user=request.user)
# ....
class PostView(mixins.CreateModelMixin, viewsets.GenericViewSet):
serializer_class = PostSerializer
def create(self, request, *args, **kwargs):
super().create(request, *args, **kwargs)
UPDATE:
I was wrong, there is a simpler solution: https://stackoverflow.com/a/38167148/7285863
That should be simplest way.
class PostViewSet(viewsets.ModelViewSet):
# ... other implementations
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data={"author": request.user.author.id, **request.data})
serializer.is_valid(raise_exception=True)
self.perform_create(serializer)
headers = self.get_success_headers(serializer.data)
return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
def update(self, request, pk, *args, **kwargs):
try:
post = Post.objects.get(pk=pk)
except Post.DoesNotExist:
raise NotFound("Post doesn't exist.")
if post.author != request.user.author:
raise PermissionDenied('Permission Denied!')
serializer = PostSerializer(post, data={"author": request.user.author.id, **request.data})
serializer.is_valid(raise_exception=True)
self.perform_update(serializer)
return Response(serializer.data)
It's default method except passing author data.
You may need to check user has author relation.

How can I make only superusers to edit model

I have 2 models: Recipe and Ingridient and I want that only superusers will be able to edit model Ingridient, normal users can only view ingridients and are not able to edit them. Right now is set that way that if you are logged in you have all permissions, but I want that only superusers have ability to edit Ingridients and normal logged in users are able to add recipes (not able to edit ingridients).
#views.py
class ReceptViewRetrieveUpdateDestroy(generics.RetrieveUpdateDestroyAPIView):
queryset = Recipe.objects.all()
lookup_field = 'id'
serializer_class = RecipeSerializer
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
def delete(self, request, *args, **kwargs):
try:
id = request.data.get('id', None)
response= super().delete(request, *args, **kwargs)
if response.status_code == 204:
from django.core.cache import cache
cache.delete("{}".format(id))
return response
except Exception as e:
return response({
"Message":"Failed"
})
def update(self, request, *args, **kwargs):
response = super().update(request, *args, **kwargs)
if response.status_code == 200:
mydata = response.data
from django.core.cache import cache
cache.set("ID :{}".format(mydata.get('id', None)),{
'title':mydata["title"],
'description':mydata["description"],
'image':mydata["image"],
'galery':mydata["galery"],
'kitchen_type':mydata["kitchen_type"],
'num_persons':mydata["num_persons"],
'preparation_steps':mydata["preparation_steps"]
})
return response
class SestavinaViewRetrieveUpdateDestroy(generics.RetrieveUpdateDestroyAPIView):
queryset = Ingridient.objects.all()
lookup_field = 'id'
serializer_class = IngridientSerializer
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
def delete(self, request, *args, **kwargs):
try:
id = request.data.get('id', None)
response= super().delete(request, *args, **kwargs)
if response.status_code == 204:
from django.core.cache import cache
cache.delete("{}".format(id))
return response
except Exception as e:
return response({
"Message":"Failed"
})
def update(self, request, *args, **kwargs):
response = super().update(request, *args, **kwargs)
if response.status_code == 200:
mydata = response.data
from django.core.cache import cache
cache.set("ID :{}".format(mydata.get('id', None)),{
'name':mydata["name"],
'quantity':mydata["quantity"],
'calories':mydata["calories"],
'protein':mydata["protein"],
'carbon':mydata["carbon"],
'fiber':mydata["fiber"],
'fat':mydata["fat"],
'saturated_fat':mydata["saturated_fat"]
})
return response
class RecipeListView(generics.ListCreateAPIView):
model = Recipe
serializer_class = RecipeSerializer
queryset = Recipe.objects.all()
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
class IngridientsListView(generics.ListCreateAPIView):
model = Ingridient
serializer_class = IngridientSerializer
queryset = Ingridient.objects.all()
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
#models.py
class Ingridient(models.Model):
name = models.CharField(default='', max_length=20)
quantity = models.IntegerField(default=0)
calories = models.IntegerField(default=0)
protein = models.IntegerField(default=0)
carbon = models.IntegerField(default=0)
fiber = models.IntegerField(default=0)
fat = models.IntegerField(default=0)
saturated_fat = models.IntegerField(default=0)
def __str__(self):
return self.name
class Recipe(models.Model):
title = models.CharField(default='', max_length=60)
description = models.TextField(default='',max_length=1000)
image = models.ImageField(default='',upload_to='pics', blank=True)
galery = models.ImageField(default='', blank=True)
kitchen_type = models.CharField(default='',max_length=35)
num_persons = models.IntegerField(default=0)
preparation_steps = models.TextField(default='')
Ingridients = models.ManyToManyField(Ingridient)
def __str__(self):
return self.title
#serializers.py
class IngridientSerializer(serializers.ModelSerializer):
class Meta:
model = Ingridient
fields = ('name', 'quantity', 'calories', 'protein', 'carbon', 'fiber', 'fat',
'saturated_fat')
class RecipeSerializer(serializers.ModelSerializer):
Ingridients = IngridientSerializer(many=True)
class Meta:
model = Recipe
fields = ('title', 'description', 'image', 'galery', 'kitchen_type', 'num_persons',
'preparation_steps', 'Ingridients')
If you're willing to set up Django model permissions for your Ingredient model instead (i.e. add an "Ingredient Editors" group, and add all superusers to that group, then allow those users to edit Ingredients), you can use the DjangoModelPermissions permissions class instead of IsAuthenticatedOrReadOnly.
If you really do simply want to give all superusers write permissions, you can adapt DRF's IsAuthenticatedOrReadOnly permission class
to something like
class IsAdminOrReadOnly(BasePermission):
def has_permission(self, request, view):
return bool(
request.method in SAFE_METHODS or
request.user and
request.user.is_superuser
)
and then use that permission class instead.

How can I update user model extended via "OneToOneField"

I have some issues with updating my database, in my serializer I use extended via OneToOneField django user model with two extra fields with user image and his motto. So I think the problem with instance in my serializer, but I can't figure out how to do that.
#core.models
class MangaUser(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
user_image = models.ImageField(upload_to='upicks')
user_motto = models.CharField(max_length=256)
#api.serializers
class UserSerializer(serializers.ModelSerializer):
#mangauser_set = serializers.SerializerMethodField()
user_image = serializers.ImageField(source='mangauser.user_image')
user_moto = serializers.CharField(source='mangauser.user_motto')
class Meta:
model = User
fields = ['id', 'username', 'email', 'password', 'user_image', 'user_motto']
extra_kwargs = {'password': {'write_only': True}}
def create(self, validated_data):
password = validated_data.pop('password', None)
instance = self.Meta.model(**validated_data)
if password is not None:
instance.set_password(password)
instance.save()
return instance
def update(self, instance, validated_data):
for attr, value in validated_data.items():
if attr == 'password':
instance.set_password(value)
else:
setattr(instance, attr, value)
instance.save()
return instance
#api.view
class GetUserInfo(APIView):
permission_classes = (IsAuthenticated,)
serializer_class = UserSerializer
def get(self, request, *args, **kwargs):
# serializer to handle turning our `User` object into something that
# can be JSONified and sent to the client.
serializer = self.serializer_class(request.user, context={"request":request})
return Response(serializer.data, status=status.HTTP_200_OK)
def put(self, request, *args, **kwargs):
serializer = self.serializer_class(data=request.data, partial=True)
serializer.is_valid(raise_exception=True)
serializer.save()
return Response(serializer.data, status=status.HTTP_200_OK)
#response
ValueError at /api/v1/userinfo/
Cannot assign "{'user_motto': 'js говно'}": "User.mangauser" must be a "MangaUser" instance.
What you did seems pretty complicated to me.
Did you read : https://docs.djangoproject.com/en/3.0/topics/auth/customizing/#extending-the-existing-user-model ?
This is what I use :
from django.contrib.auth.models import AbstractUser
class User(AbstractUser):
def get_profile(self):
return Profil.objects.get(user=self)
class Meta(AbstractUser.Meta):
db_table = 'auth_user'
class Profil (models.Model):
user = models.OneToOneField (settings.AUTH_USER_MODEL, unique=True, editable=False, help_text="1-to-1 to user", on_delete=models.CASCADE)
image_one = models.ImageField(upload_to="upload_to/images/", blank=True, null=True, help_text=_(u"Image qui sera accolée à votre profil."))
class Meta:
order_with_respect_to = 'user'

django rest framework ModelSerializer post data error

first, in models.py
class UserComment(models.Model):
user = models.ForeignKey(User)
rate = models.IntegerField()
description = models.CharField(max_length=512)
createTime = models.DateTimeField(auto_now=True)
def __unicode__(self):
return '<UserComment {%s %d}>' % (self.user.username, self.rate)
then, serializers.py
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('id', 'username', 'email', )
class UserCommentSerializer(serializers.ModelSerializer):
user = UserSerializer(required=False)
class Meta:
model = UserComment
views.py
class UserCommentViewSet(viewsets.ModelViewSet):
queryset = UserComment.objects.all()
serializer_class = UserCommentSerializer
permission_classes = (permissions.IsAuthenticatedOrReadOnly, )
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.DATA, files=request.FILES)
serializer.is_valid()
print serializer.errors
print serializer.data
return super(UserCommentViewSet, self).create(request, *args, **kwargs)
then i post json data
{"user":{"id":"1","username":"watsy"},"rate":"5","description":"hello"}
i think,it will work. and insert it to db, but i get errors.
{"user": [{"username": ["User with this Username already exists."]}]}
>_<, I have no idea.
You need to make few changes to your serializer:
class UserCommentSerializer(serializers.ModelSerializer):
user = serializers.PrimaryKeyRelatedField(read_only=True)
class Meta:
model = UserComment
depth = 1
Now pass this JSON dict in your request:
{"user":"1", "rate":"5", "description":"hello"}

Categories