I have the following django rest framework serializer and view for a model Post, and the models defined for the app are as following.
Now I wanted to test the API, so tried to "create" a new post from the API page, but then I got an error IntegrityError at /api/posts/ NOT NULL constraint failed: appname_post.user_id.
So then I tried to debug and checked its request.data value, it says it only has title and content but not user_id, as I could expect from the error above.
But now I have no idea how to tell the API automatically relate the user_id field to request.user.id. Isn't not enough to just add the user_id = serializers.ReadOnlyField(source='user.id') line? I could do "retrieve", "update", "patch" etc but just cannot "post" (create).
Thanks for your help.
serializer and view
class DevPerm(permissions.BasePermission):
def has_object_permission(self, request, view, obj):
return True
def has_permission(self, request, view):
return True
class PostSerializer(serializers.HyperlinkedModelSerializer):
user_id = serializers.ReadOnlyField(source='user.id')
class Meta:
model = Post
fields = ('url',
'id',
'title',
'content',
'created_at',
'voters',
'comments',
'user_id',
)
read_only_fields = (
'id',
'created_at',
"voters",
"comments",
# "user_id",
)
class PostViewSet(viewsets.ModelViewSet):
queryset = Post.objects.all()
serializer_class = PostSerializer
permission_classes = [permissions.IsAuthenticated, DevPerm,]
models
class Post(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='posts', default="")
created_at = models.DateTimeField(auto_now_add=True)
title = models.TextField(blank=True)
content = models.TextField(blank=True)
voters = models.ManyToManyField(User, related_name='votes', default="")
class Comment(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='comments', default="")
post = models.ForeignKey(Post, on_delete=models.CASCADE, related_name='comments', default="")
created_at = models.DateTimeField(auto_now_add=True)
content = models.TextField(blank=True)
You can override perform_create method inside you ModelViewSet.
class PostViewSet(viewsets.ModelViewSet):
queryset = Post.objects.all()
serializer_class = PostSerializer
permission_classes = [permissions.IsAuthenticated, DevPerm,]
def perform_create(self, serializer):
serializer.save(user=self.request.user)
Related
When I am creating an blog post I also want to automatically save the current user without selecting the user manually as a blog author.
here is my code:
models.py:
class Blog(models.Model):
author = models.ForeignKey(
settings.AUTH_USER_MODEL, on_delete=models.CASCADE, blank=True, null=True)
blog_title = models.CharField(max_length=200, unique=True)
serializers.py
class BlogSerializer(serializers.ModelSerializer):
class Meta:
model = Blog
views.py
class BlogViewSet(viewsets.ModelViewSet):
queryset = Blog.objects.all().order_by('-id')
serializer_class = BlogSerializer
pagination_class = BlogPagination
lookup_field = 'blog_slug'
def get_permissions(self):
if self.action == 'retrieve':
permission_classes = [IsOwnerOrReadOnly]
elif self.action == 'list':
permission_classes = [IsOwnerOrReadOnly]
else:
permission_classes = [IsOwnerOrReadOnly & IsAuthorGroup]
return [permission() for permission in permission_classes]
You can modify your serializer like below. It picks up the user from the request context and creates the blog.
class BlogSerializer(serializers.ModelSerializer):
class Meta:
model = Blog
fields = "__all__"
read_only_fields = ["author"]
def create(self, validated_data):
user = self.context["request"].user
blog = Blog.objects.create(**validated_data, author=user)
return blog
I have this RetrieveUpdateDestroyAPIView class in my api views and on update it doesn't automatically update a data field named as published. I want to manually update when the PUT request is sent. How can I do that.
Here is the code
models.py
from django.db import models
from django.contrib.auth.models import User
from django.utils import timezone
class Category(models.Model):
name = models.CharField(max_length=100)
def __str__(self):
return self.name
class Post(models.Model):
class PostObjects(models.Manager):
def get_queryset(self):
return super().get_queryset().filter(status='published')
options = (
('published', 'Published'),
('draft', 'Draft')
)
category = models.ForeignKey(Category, on_delete=models.PROTECT, default=1)
title = models.CharField(max_length=100)
excerpt = models.TextField(null=True)
content = models.TextField()
slug = models.SlugField(max_length=250, unique_for_date=True)
published = models.DateField(null=True, default=timezone.now)
author = models.ForeignKey(
User, on_delete=models.CASCADE, related_name='blog_posts', default=1)
status = models.CharField(
max_length=10, choices=options, default='published')
objects = models.Manager()
postobjects = PostObjects()
class Meta:
ordering = ('-published',)
def __str__(self):
return self.title
view.py
from rest_framework import generics
from blog.models import Post
from .serializers import PostSerializer
class PostList(generics.ListCreateAPIView):
queryset = Post.postobjects.all()
serializer_class = PostSerializer
class PostDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = Post.objects.all()
serializer_class = PostSerializer
On PostDetail, instead of setting the queryset attribute, override the get_object method to find the specific POST instance you want to update, and then ensure you send the 'published' key: value pair in the payload on submit.
Something like this:
class PostDetail(generics.RetrieveUpdateDestroyAPIView):
serializer_class = PostSerializer
def get_object(self):
_id = self.request.data.get('id')
return get_object_or_404(Post, pk=_id)
SAMPLE PUT request:
{
"id": 123,
"title": "New Post Title",
"published": <required-date-format-for-your-project>
}
Assuming im using the default django model, a Post model (code below) and a SavedPost model that links a User to a Post (if the certain user with the certain post exists then that post is saved for that user) and a Follower model that links 2 user (similar to SavedPost).
What im trying to do: An API that for a user, they get all posts for the users they follow, in addition each of these posts has an extra 'field' to say if that post is saved or not.
class Post(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
post_type = models.CharField(max_length=1, choices=[('B', 'Blog'), ('V', 'Video')], default='B')
file_path = models.URLField(null=True)
title = models.CharField(max_length=255)
description = models.TextField()
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class SavedPost(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
post = models.ForeignKey(Post, on_delete=models.CASCADE)
created_at = models.DateTimeField(auto_now_add=True)
class Meta:
# A user can save a post only once.
unique_together = ('user', 'post')
class Follower(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name="user")
follower = models.ForeignKey(User, on_delete=models.CASCADE, related_name="follower")
created_at = models.DateTimeField(auto_now_add=True)
class Meta:
# A user can follow another user only once
unique_together = ('user', 'follower')
Post serilializer:
class PostSerializer(serializers.ModelSerializer):
"""
Nested serializer for post using SimpleUser and Kingdom.
"""
class Meta:
model = Post
fields = ('id', 'user', 'post_type', 'file_path',
'title', 'description', 'created_at', 'updated_at')
def to_representation(self, instance):
data = super().to_representation(instance)
data['user'] = UserSerializer(
User.objects.get(pk=data['user'])).data
return data
API View:
#permission_classes([IsAuthenticated,])
#api_view(['GET'])
def get_following(request):
user = request.user
following = Follower.objects.filter(follower=user).values('user')
# saved_posts = SavedPost.objects.filter(user=user, post__user__in=following).order_by('-post__created_at')
posts = Post.objects.filter(user__in=following).order_by('-created_at')
serializer = PostSerializer(posts, many=True, context={'request': request})
return JsonResponse(serializer.data, safe=False)
So far with the view I made I can get all the posts that the request.user follows but it doesnt say if they are saved or not. I am looking for say 'is_saved' boolean on post to say if that post is saved for that user or not.
Any help/method to do this appreciated. Thank you.
Use serializers.SerializerMethodField as
class PostSerializer(serializers.ModelSerializer):
is_saved = serializers.SerializerMethodField()
def get_is_saved(self, post_instance):
return SavedPost.objects.filter(user=post_instance.user, post=post_instance).exists()
class Meta:
model = Post
fields = ['id', 'user', 'post_type', 'file_path',
'title', 'description', 'created_at', 'updated_at', 'is_saved']
def to_representation(self, instance):
data = super().to_representation(instance)
data['user'] = UserSerializer(
User.objects.get(pk=data['user'])).data
return data
First of all, just to be clear, I will be defining the related_name option for the ForeignKeys in SavedPost - it's up to you to decide whether to implement this or not:
class SavedPost(models.Model):
user = models.ForeignKey(User, related_name="saved", on_delete=models.CASCADE)
post = models.ForeignKey(Post, related_name="saved", on_delete=models.CASCADE)
...
Now, in your PostSerializer, you could add this field (remember to add it to the fields variable in the Meta inner class - that is if you're using ModelSerializer):
class PostSerializer(serializers.ModelSerializer):
saved = SavedPostSerializer(many=True)
...
To finish it off, define your SavedPostSerializer - above PostSerializer, if in the same file/module:
class SavedPostSerializer(serializers.ModelSerializer):
class Meta:
model = SavedPost
fields = "__all__"
With this, your json should have a nested field with the saved key containing an array of SavedPosts, if there are any related to the Posts retrieved.
models.py
class Nugget(TimeStampedModel):
added_by = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, related_name='added_by', blank=True, null=True)
serializers.py
class NuggetSerializer(TaggitSerializer, serializers.ModelSerializer):
added_by = serializers.CreateOnlyDefault(default=serializers.CurrentUserDefault())
views.py
class NuggetList(generics.ListCreateAPIView):
queryset = Nugget.objects.all()
serializer_class = NuggetSerializer
def perform_create(self, serializer):
serializer.save(added_by=self.request.user)
What I'm trying to achieve:
added_by should:
Be set on create of a Nugget
Default to the user who created the Nugget, with no way to override this default
Be included and shown when a Nugget is retrieved
Not be shown as an option for create/POST in the browsable API
Not be editable after create
Changed added_by in serializers.py (wasn't using a field, and set to read_only) and .save() in views.py to stop overriding the default.
CurrentUserDefault() requires request within the context dict. In this case generics.ListCreateAPIView already does that.
models.py
class Nugget(TimeStampedModel):
added_by = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, related_name='added_by', blank=True, null=True)
serializers.py
class NuggetSerializer(TaggitSerializer, serializers.ModelSerializer):
added_by = serializers.StringRelatedField(default=serializers.CurrentUserDefault(), read_only=True)
views.py
class NuggetList(generics.ListCreateAPIView):
queryset = Nugget.objects.all()
serializer_class = NuggetSerializer
def perform_create(self, serializer):
serializer.save()
I did it the following way (no need to make the field nullable):
models.py
class Nugget(models.Model):
added_by = models.ForeignKey(to=User, related_name='added_by', on_delete=models.DO_NOTHING)
serializers.py
class NuggetSerializer(serializers.ModelSerializer):
added_by = serializers.StringRelatedField(default=serializers.CurrentUserDefault(), read_only=True)
class Meta:
model = Nugget
fields = ['added_by']
views.py
class NuggetList(viewsets.ModelViewSet):
queryset = Nugget.objects.all()
serializer_class = NuggetSerializer
def perform_create(self, serializer):
request = serializer.context["request"]
serializer.save(added_by=request.user)
I have to update views.py to make it work:
class NuggetList(generics.ListCreateAPIView):
queryset = Nugget.objects.all()
serializer_class = NuggetSerializer
permission_classes = (IsAuthenticated,)
def perform_create(self, serializer):
req = serializer.context['request']
serializer.save(added_by=req.user)
I'm playing with DRF and made a simple blog where anonymous people can comment on a blog post. I'm just using the browsable API at the moment, and everything seems to work fine until I try to post a comment. DELETE, GET, and PUT all work as expected, only POST.
The error I get is IntegrityError at /api/posts/i-had-a-blog-his-name-was-bingo/comments/: blog_comment.blogpost_id may not be NULL
I've searched thoroughly for an answer as to why this might be happening, but nothing is helping. Here's my code...
models.py
class BlogPost(models.Model):
created = models.DateTimeField(auto_now_add=True)
owner = models.ForeignKey('auth.User', related_name='posts')
title = models.CharField(max_length=100, unique=True)
content = models.TextField()
slug = models.SlugField(max_length=100, unique=True, editable=False)
def save(self, *args, **kwargs):
self.slug = slugify(self.title)
super(BlogPost, self).save(*args, **kwargs)
#permalink
def get_absolute_url(self):
return ('post-detail', { 'slug': self.slug })
class Meta:
ordering = ('created',)
class Comment(models.Model):
created = models.DateTimeField(auto_now_add=True)
blogpost = models.ForeignKey(BlogPost, related_name='comments')
author = models.CharField(max_length=100, blank=False)
content = models.TextField()
class Meta:
ordering = ('created', 'author', 'content')
serializers.py
class CommentSerializer(serializers.HyperlinkedModelSerializer):
post = serializers.Field(source='blogpost.title')
class Meta:
model = Comment
fields = ('id', 'author', 'content', 'post')
class BlogPostSerializer(serializers.HyperlinkedModelSerializer):
owner = serializers.Field(source='owner.username')
url = serializers.HyperlinkedIdentityField(view_name='post-detail')
comments = serializers.HyperlinkedIdentityField(view_name='comment-list')
class Meta:
model = BlogPost
fields = ('url', 'id', 'title', 'content', 'owner', 'comments')
views.py
class CommentList(generics.ListCreateAPIView):
serializer_class = CommentSerializer
def get_queryset(self):
slug = self.kwargs['slug']
return Comment.objects.filter(blogpost__slug=slug)
class CommentDetail(generics.RetrieveUpdateDestroyAPIView):
serializer_class = CommentSerializer
permission_classes = (IsAdminOrNoEdit,)
def get_queryset(self):
slug = self.kwargs['slug']
return Comment.objects.filter(blogpost__slug=slug)
urls.py
commentpatterns = patterns('',
url(r'^$', views.CommentList.as_view(), name='comment-list'),
url(r'^(?P<pk>[0-9]+)/$', views.CommentDetail.as_view(), name='comment-detail'),
)
urlpatterns = patterns('blog.views',
url(r'^$', 'api_root'),
url(r'^posts/$', views.PostList.as_view(), name='post-list'),
url(r'^posts/(?P<slug>[-\w]+)/$', views.PostDetail.as_view(), name='post-detail'),
url(r'^posts/(?P<slug>[-\w]+)/comments/', include(commentpatterns)),
url(r'^users/$', views.UserList.as_view(), name='user-list'),
url(r'^users/(?P<pk>[0-9]+)/$', views.UserDetail.as_view(), name='user-detail'),
)
Any help would be greatly appreciated, this is driving me crazy.
Your Comment model defines a ForeignKey, which is not allowed to be null:
class Comment(models.Model):
...
blogpost = models.ForeignKey(BlogPost, related_name='comments')
...
which is ok, but your serializer does not include the blogpost id, so even if your request includes it, it will be just ignored. correct your serializer to include the blogpost field:
class CommentSerializer(serializers.HyperlinkedModelSerializer):
post = serializers.Field(source='blogpost.title')
blogpost = serializers.PrimaryKeyRelatedField()
class Meta:
model = Comment
fields = ('id', 'author', 'content', 'post', 'blogpost')
now when you create a post request, the blogpost field should contain the id of the blog post to which you're attaching this comment.