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.
Related
Working on a django project I am a bit stuck on data representation through APIs. In fact when designing models the data model is quite stratighforward : I have a one to many relationship A--> B
therefore I have added a FK to object B.
Object B has a boolean attribute "active".
I would like to make an API call to list all A objects having at least one assoicated object B with active = true.
The api could be like this :
/api/objectA/?ObjectB.active=True
Here is my code :
Models :
from django.contrib.postgres.fields import ArrayField
from django.core.exceptions import ValidationError
from django.db import models
from django.db.models.signals import pre_save
from django.dispatch import receiver
class Startup(models.Model):
header = models.CharField("Header", max_length=255)
title = models.CharField("Title", max_length=255)
description = models.CharField("description", max_length=255)
# TODO Change this to options instead of array
tags = ArrayField(models.CharField(max_length=10, blank=True), size=5)
# TODO Images to be stored in aws only url will be in DB
card_image = models.ImageField(upload_to='media/images/cards')
logo_image = models.ImageField(upload_to='media/images/logos')
main_img = models.ImageField(upload_to='media/images/main', null=True)
createdAt = models.DateTimeField("Created At", auto_now_add=True)
def __str__(self):
return self.title
class Investment(models.Model):
# TODO change the name of Investment to fund round in back and front
# TODO all price to be checked for max digits and decimal places
startup = models.ForeignKey(Startup, related_name='startup_investments', on_delete=models.CASCADE, default="1")
# Use the related_name as a serializer bale for investments inside startup serializer
Investment_title = models.CharField("Investment_title", max_length=255, default="Missing Title")
collected_amount = models.DecimalField(max_digits=12, decimal_places=2)
goal_percentage = models.IntegerField(default=0)
number_of_investors = models.IntegerField(default=0)
days_left = models.IntegerField()
active = models.BooleanField(default=False)
# TODO Need to update this to prevent linking to a non existing startup
createdAt = models.DateTimeField("Created At", auto_now_add=True)
def clean(self):
"""Validate that the startup does not have already an active Investment """
if self.active:
qs = Investment.objects.filter(active=True).filter(startup=self.startup)
if self.pk is not None:
qs = qs.exclude(pk=self.pk)
if qs:
raise ValidationError(message="An active investment already exists for this startup")
def __str__(self):
return self.Investment_title
Serializers :
from rest_framework import serializers
from .models import Startup, Investment
class InvestmentSerializer(serializers.ModelSerializer):
class Meta:
model = Investment
fields = ('id', 'Investment_title', 'collected_amount', 'goal_percentage', 'number_of_investors',
'days_left', 'active')
class StartupSerializer(serializers.ModelSerializer):
startup_investments = InvestmentSerializer(many=True, read_only=True)
class Meta:
model = Startup
fields = ('id', 'header', 'title', 'description', 'tags', 'card_image',
'logo_image', 'main_img', 'startup_investments')
Views :
from django_filters import rest_framework as filters
from rest_framework.viewsets import ModelViewSet
from rest_framework_extensions.mixins import NestedViewSetMixin
from .serializers import *
class StartUpViewSet(NestedViewSetMixin, ModelViewSet):
"""
Class that provides List, Retrieve, Create, Update, Partial Update and Destroy actions for startups.
It also include a filter by startup status
"""
model = Startup
queryset = Startup.objects.all()
serializer_class = StartupSerializer
class InvestmentViewSet(NestedViewSetMixin, ModelViewSet):
"""
Class that provides List, Retrieve, Create, Update, Partial Update and Destroy actions for Investments.
It also include a active and investment title
"""
model = Investment
serializer_class = InvestmentSerializer
queryset = Investment.objects.all()
filter_backends = (filters.DjangoFilterBackend,)
filterset_fields = ('active', 'Investment_title')
routers :
router = ExtendedSimpleRouter()
(
router.register(r'api/investments', views.InvestmentViewSet, basename='investment'),
router.register(r'api/startups', views.StartUpViewSet, basename='startup')
.register(r'investments', views.InvestmentViewSet, basename='startups_investment',
parents_query_lookups=['startup']),
)
Thanks for your help.
I would try something like this:
class StartUpViewSet(NestedViewSetMixin, ModelViewSet):
model = Startup
#queryset = Startup.objects.all()
serializer_class = StartupSerializer
def get_queryset(self):
Startup.objects.annotate(active_investments=Count('startup_investments', filter=Q(startup_investments__active=True)).filter(active_investments__gt=0)
Hello I am posting this answer hoping it will help others as I have spent two days to make this work!!
class ActiveStartupSerializer(serializers.ListSerializer):
def to_representation(self, data):
"""List all startups with one active investment"""
data = data.filter(startup_investments__active=True)
return super(ActiveStartupSerializer, self).to_representation(data)
class Meta:
model = Startup
fields = ('id', 'header', 'title', 'description', 'tags', 'card_image',
'logo_image', 'main_img', 'startup_investments')
class InvestmentSerializer(serializers.ModelSerializer):
class Meta:
model = Investment
fields = ('id', 'Investment_title', 'collected_amount', 'goal_percentage', 'number_of_investors',
'days_left', 'active')
class StartupSerializer(serializers.ModelSerializer):
startup_investments = InvestmentSerializer(many=True, read_only=True)
class Meta:
model = Startup
list_serializer_class = ActiveStartupSerializer
fields = ('id', 'header', 'title', 'description', 'tags', 'card_image',
'logo_image', 'main_img', 'startup_investments')
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"}].
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?
I would like to rename my owner field to owner_id since it is not nested and will only contain the owner's id. I've made some attempts, but receive errors such as {"owner":["This field is required."]}.
Here is my serializers.py:
class UserJobApplicantSerializer(serializers.ModelSerializer):
job_id = serializers.PrimaryKeyRelatedField(source='job', queryset=Job.objects.all())
owner_id = serializers.PrimaryKeyRelatedField(
source='owner',
read_only=True,
default=serializers.CurrentUserDefault()
)
class Meta:
model = JobApplicant
fields = [
'id',
'job_id',
'owner_id',
'timestamp',
]
read_only_fields = ['id',]
The view overrides the perform_create and injects the owner_id into the validated_data:
class UserJobApplicantAPIView(generics.ListCreateAPIView):
lookup_field = 'pk'
serializer_class = UserJobApplicantSerializer
permission_classes = (IsAuthenticated,)
def get_queryset(self):
return JobApplicant.objects.filter(owner=self.request.user)
def perform_create(self, serializer):
serializer.validated_data['owner_id'] = self.request.user.id
return super(UserJobApplicantAPIView, self).perform_create(serializer)
Model (Job model holds the ManyToManyField with through='JobApplicant'):
class JobApplicant(models.Model):
owner = models.ForeignKey(settings.AUTH_USER_MODEL,
on_delete=models.CASCADE)
job = models.ForeignKey(Job, on_delete=models.CASCADE)
timestamp = models.DateTimeField(auto_now_add=True)
class Meta:
unique_together = ('owner', 'job')
def __str__(self):
return "{}: {}".format(self.job.display_name, self.owner.email)
What is the correct approach to doing this? Is it possible using PrimaryKeyRelatedField, or would I need to use another type of field (or custom)? I have it working for job_id but job_id is different since it's provided by the user.
I'm using DRF 3.8.2 with Django 1.11.15.
Removing the read_only=True, argument from PrimaryKeyRelatedField solve the main problem :)
It should be,
from django.contrib.auth.models import User # use the AUTH_USER_MODEL here
owner_id = serializers.PrimaryKeyRelatedField(source='owner', queryset=User.objects.all(), default=serializers.CurrentUserDefault())
NOTE : You don't want to override the perform_create() method to pass the user instance to serializer. The CurrentUserDefault() class will manage those things if you are properly logged-in
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',)
}