Django set auto-user from context, user field not be null, JWT - python

In my case,I use JWT authentication, and when I create new "Post"(my model), I want automatically set author to user that request it.But when I do it, I got an error
{
"author": [
"This field is required."
]
}
I know,I'm not passing user, but I want to set it automatically, and I dont know how.
I just want to know how to avoid error, because when I pass value, that allows me to go ahead, the user is set automatically from context.
Serializer
class PostSerializer(FlexFieldsModelSerializer):
class Meta:
model = Post
fields = ('title','content',author','category','likedBy')
expandable_fields = {
'category': ('blogApi.CategorySerializer', {'many': True}),
'comments': ('blogApi.CommentSerializer', {'many': True}),
'likedBy': ('blogApi.LikedBySerializer', {'many': True}),
}
def create(self, validated_data):
user = self.context['request'].user
post = Post.objects.create(
author=user, title=validated_data['title'], content=validated_data['content'])
post.category.set(validated_data['category'])
return post
My create view
class PostCreate(generics.CreateAPIView):
queryset = Post.objects.all()
serializer_class = PostSerializer
permission_classes = [IsAuthenticated]
Model
class Post(models.Model):
title = models.CharField(max_length=255)
content = models.TextField()
category = models.ManyToManyField(Category, related_name='posts')
author = models.ForeignKey(User, on_delete=models.CASCADE)
created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
likedBy = models.ManyToManyField(User, related_name='posts', blank=True)
class Meta:
ordering = ['-created']
def __str__(self):
return self.title
and when I create

You can make author a read-only field, or if you're just using this serializer to create users and not retreive them. You can just remove 'author' from fields in the serializer meta.
Read-only field
from rest_framework import serializers
class PostSerializer(FlexFieldsModelSerializer):
author = serializers.PrimaryKeyRelatedField(read_only=True)
class Meta:
model = Post
fields = ('title','content','author','category','likedBy')

Related

Django Rest Framework how do I get the id I use in the URL

I have this serializer and I use it to get post detail of a post belonging to a user. The owner of the post is not the user that is currently logged in. I want to check if the post is bookmarked by the currently logged in user. The currently logged in user's id is passed in the request but I cannot find it in this context.
Here is the serializer:
class UserPostSerializer(serializers.ModelSerializer):
images = PostImageSerializer(many=True, read_only=True, required=False)
profile = serializers.SerializerMethodField()
bookmarked = serializers.SerializerMethodField()
class Meta:
model = Post
fields = [
"id",
"category",
"body",
"images",
"video",
"profile",
"published",
"bookmarked",
"created_at",
"updated_at",
]
depth=1
def get_profile(self, obj):
profile_obj = Profile.objects.get(id=obj.user.profile.id)
profile = ShortProfileSerializer(profile_obj)
return profile.data
def get_bookmarked(self, obj):
breakpoint()
bookmark = Bookmark.objects.filter(owner=obj.user.id, post=obj.id,marktype='post')
if bookmark:
return True
else:
return False
The problem is obj.user.id is the owner of the post. I need the logged in user whose id is passed in the url. Here is the model for the bookmark:
class Bookmark(models.Model):
marktype = models.CharField(max_length=50)
post = models.OneToOneField(Post, on_delete=models.CASCADE, null=True, blank=True)
owner = models.ForeignKey(User, on_delete=models.CASCADE)
created_at = models.DateTimeField(auto_now_add=True, verbose_name="created at")
updated_at = models.DateTimeField(auto_now=True, verbose_name="updated at")
class Meta:
verbose_name = "bookmark"
verbose_name_plural = "bookmarks"
ordering = ["created_at"]
db_table = "bookmarks"
def __str__(self):
return "{}'s bookmark".format(self.owner.username)
and here is the URL:
path("posts/<int:user>/home/", HomeView.as_view(), name="home"),
This self.context['request'].user returns the owner of the post and not the logged in user.
How do I get the id of the currently logged in user or the user whose id I pass in the URL please?
Maybe do you can use filters to the Viewset:
urls.py
path("posts/home/", HomeView.as_view(), name="home")
viewsets.py
from rest_framework import viewsets
from .models import Post
from .serializers import, UserPostSerializer
from .filters import OwnerFilter
class HomeView(viewsets.ModelViewSet):
queryset = Post.objects.all()
serializer_class = UserPostSerializer
filter_backends = (OwnerFilter,)
filters.py
from rest_framework.filters import BaseFilterBackend
class OwnerFilter(BaseFilterBackend):
def filter_queryset(self, request, queryset, view):
owner = request.query_params.get('owner', None)
if not owner:
return queryset.all()
else:
try:
return queryset.filter(bookmarked__owner__id=owner)
except Exception:
return queryset.none()
Running
Then access the URL:
/posts/home/?owner=OWNER_ID_HERE
Solved it and you can get any kwargs from the view that handles the request. In my case adding the following to the get_bookmarked function gives me the id I send in the URL:
loggeduser = self.context.get('view').kwargs.get('user')

Django foreign key JWT auth

In my project I have the Post and Category Model and full working JWT Authentication.
class Category(models.Model):
name = models.CharField(max_length=255)
def __str__(self):
return self.name
class Post(models.Model):
title = models.CharField(max_length=50)
content = models.TextField()
created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
author = models.ForeignKey(User, on_delete=models.CASCADE)
category = models.ManyToManyField(Category, related_name='posts')\
class Category(models.Model):
name = models.CharField(max_length=255)
def __str__(self):
return self.name
I want to create a view, that creates a new Post object, in which author will be assigned to Token owner that I pass in authorization (Bearer Token ) postman.image.example. I dont know how to do it please help. Sorry for my english.
Serializer
class PostSerializer(FlexFieldsModelSerializer):
class Meta:
model = Post
fields = '__all__'
read_only_fields = ['id', 'created']
expandable_fields = {
'category': ('blog.CategorySerializer', {'many': True}),
'comments': ('blog.CommentSerializer', {'many': True}),
'images': ('blog.ImageSerializer', {'many': True}),
}
From what I understand, you want to automatically associate the request.user as the author of the post (s)he creates. Whether your auth is JWT-based or session-based does not influenced that (as long as it is set up correctly).
For this you need to pass the request object to your serializer, here is the trick:
# serializers.py
class PostSerializer(FlexFieldsModelSerializer):
class Meta:
model = Post
fields = '__all__'
read_only_fields = ['id', 'created', 'author'] # set author as read-only field
expandable_fields = {
'category': ('blog.CategorySerializer', {'many': True}),
'comments': ('blog.CommentSerializer', {'many': True}),
'images': ('blog.ImageSerializer', {'many': True}),
}
def create(self, validated_data):
# here you get the user from the request
user = self.context['request'].user
return Post.objects.create(author=user, **validated_data)
# views.py
from .models import Post
from .serializers import PostSerializer
from rest_framework import generics
from rest_framework.permissions import IsAuthenticated
class PostCreate(generics.CreateAPIView):
queryset=Post.objects.all()
serializer_class = PostSerializer
permission_classes = [IsAuthenticated]
def get_serializer_context(self):
# this is the trick since you want to pass the request object to your serializer
context = super().get_serializer_context()
context.update({"request": self.request})
return context

Python Django Rest Framework: The `.create()` method does not support writable nested fields by default

I am using django rest framework to create an api endpoint. I am using the default user model django offers. I need to create a post which uses the user as a foreign key. A user called "author" in the post can have multiple posts.
This is an example of a post json.
[
{
"author": {
"id": 1,
"username": "sorin"
},
"title": "First Post",
"description": "Hello World!",
"created_at": "2020-08-05T14:20:51.981163Z",
"updated_at": "2020-08-05T14:20:51.981163Z"
}
]
This is the model.
class Post(models.Model):
author = models.ForeignKey(User, on_delete=models.CASCADE)
title = models.CharField(max_length=255)
description = models.TextField()
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
def __str__(self):
return self.title
This is the serializer.
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('id', 'username')
class PostSerializer(serializers.HyperlinkedModelSerializer):
author = UserSerializer()
class Meta:
model = Post
fields = ('author', 'title', 'description', 'created_at', 'updated_at')
I am getting the error "The .create() method does not support writable nested fields by default." when trying to make a post request using a "username", "title" and "description".
Any help to how to solve this?
I like hooking in the create function of the serializer for these kind of use cases.
Make sure your UserSerializer is set to read_only=True.
class PostSerializer(serializers.HyperlinkedModelSerializer):
author = UserSerializer(read_only=True)
class Meta:
model = Post
fields = ('author', 'title', 'description', 'created_at', 'updated_at')
def create(self, validated_data):
request = self.context['request']
author_data = request.data.get('author')
if author is None or not isinstance(author.get('id'), int):
raise ValidationError({'author': ['This field is invalid.']})
author_instance = get_object_or_404(User, id=author.get('id'))
return Post.objects.create(author=author_instance, **validated_data)

python - DRF Unable to post data passing a Primary Key

I'm starting to work with Django and I followed the tutorial available in the Django Rest Framework website and now I'm trying to adapt the tutorial to do something a little more complex.
I'm trying to create a "Like" system for a Social Network. A User can create Posts (UserPosts) and Like other user's posts.
I'm creating new UserPosts (using the command line) this way:
http -a admin:Pass1234 POST http://127.0.0.1:8000/posts/ description="I'm just a random comment"
And everything works just fine.
The problem is when I try to create a Like instance. In this case I need to pass a UserPost id, so I'm doing the same as I did to create a new comment:
http -a admin:Pass1234 POST http://127.0.0.1:8000/likes/ post="1"
But when I do this I get the following error:
"post": {
"non_field_errors": [
"Invalid data. Expected a dictionary, but got unicode."
]
}
The models are the following:
class UserPost(models.Model):
owner = models.ForeignKey('auth.User', related_name='posts', on_delete=models.CASCADE)
timestamp = models.DateTimeField(auto_now_add=True)
description = models.CharField(max_length=100, blank=True, default='')
def save(self, *args, **kwargs):
options = self.description and {'description': self.description} or {}
super(UserPost, self).save(*args, **kwargs)
class Meta:
ordering = ('timestamp',)
class Like(models.Model):
owner = models.ForeignKey('auth.User', related_name='likes', on_delete=models.CASCADE)
post = models.ForeignKey(UserPost, related_name='likes', on_delete=models.CASCADE)
timestamp = models.DateTimeField(auto_now_add=True)
def save(self, *args, **kwargs):
options = self.post and {'post': self.post}
super(Like, self).save(*args, **kwargs)
class Meta:
ordering = ('timestamp',)
The serializers:
class UserPostSerializer(serializers.HyperlinkedModelSerializer):
owner = serializers.ReadOnlyField(source='owner.id')
class Meta:
model = UserPost
fields = ('url', 'id', 'description', 'owner', 'timestamp')
class LikeSerializer(serializers.ModelSerializer):
owner = serializers.ReadOnlyField(source='owner.id')
post = UserPostSerializer(source='post')
class Meta:
model = Like
fields = ('id', 'owner', 'post', 'timestamp')
The Views:
class PostViewSet(viewsets.ModelViewSet):
queryset = UserPost.objects.all()
serializer_class = UserPostSerializer
permission_classes = (permissions.IsAuthenticatedOrReadOnly,
IsOwnerOrReadOnly,)
def perform_create(self, serializer):
serializer.save(owner=self.request.user)
class LikeViewSet(viewsets.ModelViewSet):
queryset = Like.objects.all()
serializer_class = LikeSerializer
permission_classes = (permissions.IsAuthenticatedOrReadOnly, IsOwnerOrReadOnly,)
def perform_create(self, serializer):
serializer.save(owner=self.request.user)
Routers and urls:
router = DefaultRouter()
router.register(r'posts', views.PostViewSet)
router.register(r'users', views.UserViewSet)
router.register(r'likes', views.LikeViewSet)
urlpatterns = [
url(r'^', include(router.urls)),
url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework'))
]
Any idea of what is happening?
Thanks!
You need to remove post = UserPostSerializer from your LikeSerializer:
class LikeSerializer(serializers.ModelSerializer):
owner = serializers.ReadOnlyField(source='owner.id')
class Meta:
model = Like
fields = ('id', 'owner', 'post', 'timestamp')
so that django-rest-framework uses a PrimaryKeyRelatedField for the related UserPost (which is the default for related models with ModelSerializer).
Then you can create your Like entry with post="1" as parameter.

Django-rest serializer returning code instead of list of objects

I have an endpoint in my Django-rest application in which I expect to receive the following get response:
{
"my_objects": [
{
"my_object_order": 1,
"related_topics": [{"title": "my_title", "subtitle": "my_subtitle"}, {"title": "my_title2", "subtitle": "my_subtitle2"}],
"collected_at": "2016-05-02T20:52:38.989Z",
}]
}
In order to achieve that, below you can observe my serializers.py
class TopicSerializer(serializers.ModelSerializer):
class Meta:
model = MyTopic
fields = ["title", "subtitle"]
class MyObjectSerializer(serializers.ModelSerializer):
related_topics = TopicSerializer(many=True)
class Meta:
model = MyObject
fields = ("my_object_order",
"related_topics")
def create(self, validated_data):
"""
Saving serialized data
"""
related_topics_list = validated_data.pop("related_topics", [])
obj = MyObject.objects.create(**validated_data)
for topics_data in related_topics_list:
MyTopic.objects.create(trend=trend, **topics_data)
return obj
As suggested, here you can see my models.py
class MyObject(models.Model):
my_object_order = models.IntegerField()
collected_at = models.DateTimeField(auto_now=True)
def __unicode__(self):
return self.story_title
class MyTopic(models.Model):
my_obj = models.ForeignKey(MyObject, related_name="related_topics")
title = models.CharField(max_length=50, blank=False, null=True)
subtitle = models.CharField(max_length=50, blank=True, null=True)
def __unicode__(self):
return self.title
Below you have the excerpt from my views.py
def get(self, request):
params = request.QUERY_PARAMS
# Filtering data
obj_list = my_fun(MyObject, params)
response = {"my_objects": obj_list.values("my_object_order",
"collected_at",
"related_topics")}
return Response(response)
I have looked on the documentation, however I am confused/not understanding fundamentally what I should do.
Your problem is in views.py, you are not using actually the serializer at all. You are just filter some data and return whatever values you get from database (hence the ids only).
I suggest you to check Generic Class Based Views
from myapp.models import MyObject
from myapp.serializers import MyObjectSerializer
from rest_framework import generics
class MyObjectListAPIView(generics.ListAPIView):
queryset = MyObject.objects.all()
serializer_class = MyObjectSerializer
Also if you need any filtering check documentation here. Basically you can filter by fields from model with this snippet
filter_backends = (filters.DjangoFilterBackend,)
filter_fields = ('field1', 'field2')
PS: You can do the view as normal function, but you have to handle yourself filtering/serialization part, the code may not look as cleaner as you get with class based views.

Categories

Resources