DJANGO Serializer Validation Never Called - django

I am trying to run custom validation on a serializer in Django. The serializer is as simple as:
class PostsSerializer(serializers.ModelSerializer):
user = UserSerializer(many=False, required=False, allow_null=True)
def validate(self, data):
print('Validating')
print(data)
return data
class Meta:
model = Post
fields = ["id", "user", "type", "title", "content", "created_ts"]
read_only_fields = ["id", "user", "created_ts"]
And the serializer is called as such:
def create_post(self, request):
serializer = PostsSerializer(data=request.data)
if serializer.is_valid(raise_exception=True):
#echo something
else:
fail
And the model is this:
from django.apps import apps
from django.db import models
from ..enums import PostTypes
class Post(models.Model):
user = models.ForeignKey("auth.User", on_delete=models.DO_NOTHING)
type = models.IntegerField(choices=[(tag.name, tag.value) for tag in PostTypes])
title = models.TextField()
content = models.TextField()
class Meta:
db_table = "post"
ordering = ["-created_ts"]
verbose_name = "Post"
verbose_name_plural = "posts"
Any idea what would be causing the validate function not to execute?

Related

How to post manytomany field value in Postman for API

I have a field which is ManyToMany. I would like to enter the value in POSTMAN for API post operation. But everytime It says: "This field is required." even though I provided the value.
Models:
class Day(models.Model):
day_name = models.CharField(
_("Day Name"), max_length=255, null=True, blank=True)
def __str__(self):
return self.day_name
class TutorProfile(models.Model):
user = models.ForeignKey(
settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
tutor_availablility = models.ManyToManyField(
Day,blank=True)
Serializer:
class DaySerializer(serializers.ModelSerializer):
class Meta:
model = Day
fields = '__all__'
class TutorProfileSerializer(serializers.ModelSerializer):
user = serializers.PrimaryKeyRelatedField(
read_only=True, default=serializers.CurrentUserDefault(), source='user.username')
image_url = serializers.SerializerMethodField('get_image_url')
tutor_availablility = DaySerializer(many=True)
class Meta:
model = TutorProfile
fields = '__all__'
Viewsets:
#authentication_classes([TokenAuthentication])
#permission_classes([IsAuthenticated])
class TutorprofileViewSet(ModelViewSet):
serializer_class = TutorProfileSerializer
http_method_names = ["post", "delete", "get"]
queryset = TutorProfile.objects.all()
With the following models.py (notice that your current Day.__str__ can raise an exception if day_name does not exist):
class Day(models.Model):
day_name = models.CharField(_("Day Name"), max_length=255, blank=True, null=True)
def __str__(self):
return self.day_name if self.day_name else "Unnamed"
class TutorProfile(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
tutor_availability = models.ManyToManyField(Day, blank=True)
You do not need to explicitly add tutor_availability nor user as serializer fields:
class DaySerializer(serializers.ModelSerializer):
class Meta:
model = Day
fields = "__all__"
class TutorProfileSerializer(serializers.ModelSerializer):
# Omitting `image_url` as not reflected in `models.py`
# image_url = serializers.SerializerMethodField('get_image_url')
class Meta:
model = TutorProfile
fields = "__all__"
With this viewset:
#authentication_classes([TokenAuthentication])
#permission_classes([IsAuthenticated])
class TutorProfileViewSet(ModelViewSet):
serializer_class = TutorProfileSerializer
http_method_names = ["post", "delete", "get"]
queryset = TutorProfile.objects.all()
Then, after creating days with IDs 1 and 2 in admin, by sending the tutor_availability field as you are doing it, it should work. Request:
{
"user": 1,
"tutor_availability": [1, 2]
}
Response:
{
"id": 1,
"user": 1,
"tutor_availability": [
1,
2
]
}
Notice as well that I've changed availablility to availability and that it may be unsafe to allow authenticated users to pass the user field in the request, you may want to infer that from the user who makes the request.
In your TutorProfileSerializer you are using the DaySerializer for tutor_availablility field so when you do a post request your post action will wait for a list of dict, what you you need to do in first is to delete this line : from your TutorProfileSerializer and it will works.
tutor_availablility = DaySerializer(many=True)
If you still have the problem then you need to verify the validate method of the TutorProfileSerializer.
And if it works but you want a list of dict(of Day object) for GET request, you need to override the get_serializer_class() of your ViewSet and create two serializers one for post request and a second for get request:
#authentication_classes([TokenAuthentication])
#permission_classes([IsAuthenticated])
class TutorprofileViewSet(ModelViewSet):
serializer_class = TutorProfileSerializer
http_method_names = ["post", "delete", "get"]
queryset = TutorProfile.objects.all()
def get_serializer_class(self):
if self.action.method == 'GET':
return TutorGETProfileSerializer
return super(TutorprofileViewSet, self).get_serializer_class()
and the 2 serializers:
class TutorGETProfileSerializer(serializers.ModelSerializer):
user = serializers.PrimaryKeyRelatedField(
read_only=True, default=serializers.CurrentUserDefault(), source='user.username')
image_url = serializers.SerializerMethodField('get_image_url')
tutor_availablility = DaySerializer(many=True)
class Meta:
model = TutorProfile
fields = '__all__'
class TutorProfileSerializer(serializers.ModelSerializer):
class Meta:
model = TutorProfile
fields = '__all__'
read_only_fields = ('user',)

DRF ListAPIView return manytomany value names instead of pk

I have a Post Model contains tags field that have ManyToManyField to categories Model,
when i call REST ListAPIView all post tags returns in pk
I have tried to override list function in ListAPIView and map all tags_names for every post
but this takes a huge time and destroys the performance
I hope/Believe there something built in for this case
models.py
class Post(models.Model):
author = models.ForeignKey(User, on_delete=models.CASCADE)
title = models.CharField(max_length=256)
content = RichTextField()
tags = models.ManyToManyField(Categories)
def __str__(self):
return self.title
class Categories(models.Model):
tag_name = models.CharField(max_length=256, unique=True)
def __str__(self):
return self.tag_name
class Meta:
ordering = ('tag_name',)
unique_together = ('tag_name',)
views.py
from .models import Post
from .serializers import NewPostSerializer, PostSerializer
class NewPost(CreateAPIView):
serializer_class = NewPostSerializer
permission_classes = [IsAuthenticated, IsAdminUser]
class PostList(ListAPIView):
queryset = Post.objects.all()
serializer_class = PostSerializer
serializers.py
class NewPostSerializer(ModelSerializer):
class Meta:
model = Post
fields = ['title', 'content', 'tags']
read_only_fields = ['tags', 'author_id']
when i visit ListApiView link returned results would be like this:
[
{
"id": 30,
"title": "post title test",
"content": "lorem text",
"author": 3,
"tags": [
8, # should be games
3 # should be action
]
}
]
You can simply use SlugRelatedField, This will return a list of names instead of list of pks
from rest_framework import serializers
class NewPostSerializer(ModelSerializer):
tags = serializers.SlugRelatedField(
many=True,
read_only=True,
slug_field='tag_name'
)
class Meta:
model = Post
fields = ['title', 'content', 'tags']
read_only_fields = ['author_id']
To optimize performance you should use prefetch_related. This reduces the number of queries to your database to only 1 request to fetch all the related tags for all of your posts.
class PostList(ListAPIView):
queryset = Post.objects.prefetch_related('tags').all()
serializer_class = NewPostSerializer
Now for the serialization of your tags you have to create a new serializer.
class TagSerializer(ModelSerializer):
class Meta:
model = Categories
fields = ['name']
You can then use this serializer in your NewPostSerializer.
class NewPostSerializer(ModelSerializer):
tags = TagSerializer(many=True, read_only=True)
class Meta:
model = Post
fields = ['title', 'content', 'tags']
read_only_fields = ['author_id']
The results of this should be "tags": [{"name": "ABC"},{"name": "EFG"}].

django class based CreateApiView author_id is not null?

I have also authenticate it with token but when I create a new post error is alert IntegrityError at /api/create/ NOT NULL constraint failed: core_article.author_id how can I valid data with request user in serializer?
model.py
from django.contrib.auth.models import User
from django.db import models
class Article(models.Model):
title = models.CharField(max_length=255, help_text="Short title")
content = models.TextField(blank=True)
author = models.ForeignKey(User, on_delete=models.CASCADE)
status = models.BooleanField(default=True)
def __str__(self):
return self.title
serializer.py
from rest_framework import serializers
from django.contrib.auth.models import User
from core.models import Article
class NewsSerializer(serializers.ModelSerializer):
author = serializers.SlugRelatedField(
slug_field=User.USERNAME_FIELD, read_only=True, required=False)
class Meta:
model = Article
fields = [
'id',
'author',
'title',
'content',
'status',
]
views.py
class ArticleCreate(CreateAPIView):
queryset = Article.status_objects.all()
serializer_class = NewsSerializer
permission_classes = (permissions.IsAuthenticated, )
I don't know if this is what you're looking for, but you can pass the user as author object to your serializer and in your serializer use that author to create your Article object (Note: I assume that you have used correct authentication_class in your view and have access to user from your request object).
First you need to override the perform create of your view:
class ArticleCreate(CreateAPIView):
queryset = Article.status_objects.all()
serializer_class = NewsSerializer
permission_classes = (permissions.IsAuthenticated, )
def perform_create(self, serializer):
serializer.save(author=self.request.user)
Which will send an user instance to your serializer validated data. Then you should change your serializer to:
class NewsSerializer(serializers.ModelSerializer):
class Meta:
model = Article
fields = [
'id',
'title',
'content',
'status',
]
Note that the line author = serializers.SlugRelatedField(slug_field=User.USERNAME_FIELD, read_only=True, required=False) and author are removed from your serializer. But if you need to serialize the id of that author (read only purpose) you can change your serializer to:
class NewsSerializer(serializers.ModelSerializer):
class Meta:
model = Article
fields = [
'id',
'title',
'author',
'content',
'status',
]
read_only_fields = ('author',)
author = serializers.SlugRelatedField(
slug_field=User.USERNAME_FIELD, read_only=True,required=True)
Please change required=False to required=True . If you set True then don't add Null value. So you avoid this errror.

Django model nested serializer

I have the model structure like below
class BaseProduct:
id = models.CharField(max_length=15)
name = models.CharField(max_length=20)
class Product
base_product = ForeigKey(BaseProduct)
name = models.CharField(max_length=20)
class Condition:
category = models.ForeignKey(Product, related_name='allowed_product')
check = models.IntegerField(default=0)
allow = models.PositiveSmallIntegerField(default=1)
The query:
Product.objects.filter(condition__allow=1, condition__check=1)
I want format something like below
Base Product and inside that list of products based on allow and check filter
[
{
"name": "BaseProduct 1",
"products": [
{
"name": "TV",
}, {}, ....
]
},
........
]
Try it, if you use django rest framework
from rest_framework import serializers
from rest_framework.fields import empty
from django.utils.functional import cached_property
class ProductSerializer(serializers.ModelSerializer):
class Meta:
model = Product
fields = ('name')
class BaseProductSerializer(serializers.ModelSerializer):
products = serializers.SerializerMethodField()
class Meta:
model = BaseProduct
fields = ('name', 'products')
def __init__(self, instance=None, data=empty, **kwargs):
self._condition_allow = kwargs.pop('condition_allow', 1)
super(BaseProductSerializer, self).__init__(instance=None, data=empty, **kwargs)
#cached_property
def _request_data(self):
request = self.context.get('request')
# if POST
# return request.data if request else {}
# if GET params
return request.query_params if request else {}
#cached_property
def _condition(self):
return self._request_data.get('CONDITION_PARAM_NAME')
def get_products(self, obj):
qs = obj.product_set.filter(condition__allow=self._condition_allow, condition__check=1)
serializer = ProductSerializer(qs, many=True)
# ^^^^^
return serializer.data
in view
serialiser(qs, condition_allow=5)
Change your models to have related_name for the foreignkeys to have reverse relationship:
class BaseProduct:
id = models.CharField(max_length=15)
name = models.CharField(max_length=20)
class Product
base_product = ForeigKey(BaseProduct, related_name='products')
name = models.CharField(max_length=20)
class Condition:
category = models.ForeignKey(Product, related_name='conditions')
check = models.IntegerField(default=0)
allow = models.PositiveSmallIntegerField(default=1)
So now you can use it in your serializers:
class BaseProductSerializer:
class Meta:
model = BaseProduct
fields = ('name', 'products',)
class ProductSerializer:
class Meta:
model = Product
fields = ('conditions',)
class ConditionSerializer:
class Meta:
model = Condition
fields = '__all__'
Finally in your views, change this:
Product.objects.filter(condition__allow=1, condition__check=1)
into this:
BaseProduct.objects.filter(products__conditions__allow=1, products__conditions__allow=1)
And hopefully, this should generate JSON data in the format that you asked for.

Django REST Framework (DRF): Set current user id as field value

I have model NewsModel and 2 serializers for him:
models.py
class NewsModel(models.Model):
title = models.CharField('Заголовок', max_length=255, help_text='Максимальная длина - 255 символов')
announce = models.TextField('Анонс', help_text='Краткий анонс новости')
author = models.ForeignKey(settings.AUTH_USER_MODEL, help_text='Автор новости', related_name='news')
full_text = models.TextField('Полный текст новости', help_text='Полный текст новости')
pub_date = models.DateTimeField('Дата публикации', auto_now_add=True, default=timezone.now, help_text='Дата публикации')
def comments_count(self):
return NewsComment.objects.filter(news=self.id).count()
def get_author_full_name(self):
return self.author.get_full_name()
class Meta:
db_table = 'news'
ordering = ('-pub_date',)
serilizers.py:
from rest_framework import serializers
from .models import NewsModel
from extuser.serializers import UserMiniSerializer
class NewsReadSerializer(serializers.ModelSerializer):
author = UserMiniSerializer()
class Meta:
model = NewsModel
fields = ('id', 'title', 'announce', 'comments_count', 'reviews', 'author_name')
def get_author_full_name(self, obj):
return obj.get_author_full_name()
class NewsWriteSerializer(serializers.ModelSerializer):
def validate_author(self, value):
value = self.request.user.id
return value
class Meta:
model = NewsModel
I select serializers in the api.py:
class NewsList(ListCreateAPIView):
queryset = NewsModel.objects.order_by('-pub_date')
def get_serializer_class(self, *args, **kwargs):
if self.request.method == 'GET':
return NewsReadSerializer
return NewsWriteSerializer
class Meta:
model = NewsModel
But when I will create NewsModel item, I see Error 400: Bad request [{'author': 'This field is required'}]
How I can set current user id as NewsItem.author value on creating new item?
I don't think you're using the serializer properly. A better practice to set request related data is to override perform_create in your view:
def perform_create(self, serializer):
serializer.save(author=self.request.user)
def perform_update(self, serializer):
serializer.save(author=self.request.user)
and then set your author serializer to read-only:
author = UserMiniSerializer(read_only=True)
this way you can simply use one single NewsSerializer for both read and write actions.
In new DRF you can write
owner = serializers.HiddenField(
default=serializers.CurrentUserDefault()
)
See http://www.django-rest-framework.org/api-guide/validators/#currentuserdefault
In DRF version prior 3 field must be declader with allow_null=True and default=None. DRF don't run checking fields without this params. Result code:
class NewsReadSerializer(serializers.ModelSerializer):
"""
Serializer only for reading.
author field serialized with other custom serializer
"""
author = UserMiniSerializer()
class Meta:
model = NewsModel
fields = ('id', 'title', 'announce', 'comments_count', 'reviews', 'author', 'pub_date',)
class NewsWriteSerializer(serializers.ModelSerializer):
"""
Serializer for creating and updating records.
author here is the instance of PrimaryKeyRelatedField, linked to all users
"""
author = serializers.PrimaryKeyRelatedField(
queryset=User.objects.all(), # Or User.objects.filter(active=True)
required=False,
allow_null=True,
default=None
)
# Get the current user from request context
def validate_author(self, value):
return self.context['request'].user
class Meta:
model = NewsModel
fields = ('title', 'announce', 'full_text', 'author',)
I would try something like this:
your models.py
class NewsModel(models.Model):
title = models.CharField(
'Заголовок', max_length=255,
help_text='Максимальная длина - 255 символов')
announce = models.TextField('Анонс',
help_text='Краткий анонс новости')
author = models.ForeignKey(
settings.AUTH_USER_MODEL,
help_text='Автор новости', related_name='news')
full_text = models.TextField(
'Полный текст новости',
help_text='Полный текст новости')
pub_date = models.DateTimeField(
'Дата публикации', auto_now_add=True,
default=timezone.now, help_text='Дата публикации')
def comments_count(self):
return NewsComment.objects.filter(news=self.id).count()
def get_author_full_name(self):
return self.author.get_full_name()
class Meta:
db_table = 'news'
ordering = ('-pub_date',)
serializers.py
(ref.: http://www.django-rest-framework.org/api-guide/validators/#currentuserdefault)
from <yourapp>.models import NewsModel
from rest_framework import serializers
class NewsModelSerializer(serializers.ModelSerializer):
author = serializers.HiddenField(default=serializers.CurrentUserDefault())
class Meta:
model = NewsModel
Also you should set settings.py to something like this:
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': (
'rest_framework.permissions.IsAuthenticated',)
}