python - DRF Unable to post data passing a Primary Key - python

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.

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')

Save reversal relationship item in database using one POST - Django

I want a viewset that handles a post request that creates some nested objects using the post data.
I have these models, serializers, and views:
Models:
class Connection(models.Model):
from portfolio.models import Portfolio
user = models.ForeignKey(User, related_name='exchange_connections', on_delete=models.CASCADE)
portfolios = models.ManyToManyField(Portfolio)
class ConnectionSettings(models.Model):
exchange_connection = models.OneToOneField(Connection, to_field='id', primary_key=True,
related_name='settings', on_delete=models.CASCADE)
import_past_transactions = models.BooleanField(default=False, blank=True)
class ConnectionCredentials(models.Model):
exchange_connection = models.OneToOneField(Connection, to_field='id', primary_key=True,
related_name='credentials', on_delete=models.CASCADE)
key = models.CharField(max_length=300, blank=False, null=False)
secret = models.CharField(max_length=300, blank=False, null=False)
passphrase = models.CharField(max_length=300, blank=True, null=True)
Serializers:
class ConnectionCredentialsSerializer(FlexFieldsModelSerializer):
class Meta:
model = ConnectionCredentials
fields = '__all__'
class ConnectionSettingsSerializer(FlexFieldsModelSerializer):
class Meta:
model = ConnectionSettings
fields = '__all__'
class ConnectionSerializer(serializers.ModelSerializer):
credentials = ConnectionCredentialsSerializer()
settings = ConnectionSettingsSerializer()
class Meta:
model = Connection
fields = '__all__'
def create(self, validated_data):
credentials = validated_data.pop('credentials')
settings = validated_data.pop('settings')
connection = Connection.objects.create(**validated_data)
ConnectionCredentials.objects.create(exchange_connection=connection, **credentials)
ConnectionSettings.objects.create(exchange_connection=connection, **settings)
return connection
Views:
class ConnectionViewSet(viewsets.ViewSet):
serializer_class = serializers.ConnectionSerializer
queryset = exchange_models.Connection.objects.all()
permission_classes = (IsAuthenticated, core_permissions.IsMineOnly)
def list(self):
return HttpResponse(self.request.user.exchange_connections_set)
def retrieve(self, request, pk=None):
serialized_data = self.serializer_class(exchange_models.Connection.objects.get(id=pk)).data
return HttpResponse(serialized_data)
def create(self, request):
serializer = self.serializer_class(data=request.data)
if serializer.is_valid():
serializer.create(serializer.data)
serializer.save()
return Response({'status': 'connection created.'})
else:
return Response(serializer.errors,
status=status.HTTP_400_BAD_REQUEST)
URLs:
# Create a router and register our viewsets with it.
router = DefaultRouter()
router.register(r'connections', views.ConnectionViewSet)
urlpatterns = [
path('', include(router.urls)),
]
POST Request:
When I send the post request, the portfolios and the user already exist. So I should only give primary keys to these rows in my request.
But I need to create new rows for Settings and Credentials for which I should pass data in the request.
By default nested serializers are read-only. You will have to customize the create method to make it writable.
class ConnectionSerializer(serializers.ModelSerializer):
portfolios = PortfolioSerializer(many=True)
credentials = ConnectionCredentialsSerializer(many=False)
settings = ConnectionSettings(many=False)
class Meta:
model = models.Connection
exclude = ('user',)
read_only_fields = ('date_created', 'last_updated')
def create(self, validated_data):
portfolios = validated_data.pop('portfolios')
credentials = validated_data.pop('credentials')
settings = validated_data.pop('settings')
connection = Connection.objects.create(**validated_data)
for portfolio in portfolios:
Portfolio.objects.create(exchange_connection=connection, **portfolio)
for credential in credentials:
ConnectionCredentials.objects.create(exchange_connection=connection, **credentials)
for setting in settings:
ConnectionSettings.objects.create(exchange_connection=connection, **settings)
return connection
Provided that following are the model names relative to serializers.
PortfolioSerializer -> Portfolio,
ConnectionCredentialsSerializer -> ConnectionCredentials

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

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')

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

DRF - Nested Routers - Create/Update nested object on POST/PUT/PATCH

I'm currently starting a simple Task App and I'm using Django 2.0.7, DRF 3.8.2 and drf-nested-routes 0.90.2
I have these models :
class Client(TimeStampedModel):
"""
This model describes a client for the railroader. It can be created by the manager in the back office
We have at least one internal Client, which is Seelk, for internal projects
"""
name = models.CharField(max_length=255, unique=True)
description = models.TextField(null=True)
is_active = models.BooleanField(default=True)
def __str__(self):
return "{} : {}".format(self.name, self.description)
class Project(TimeStampedModel):
"""
This model represents a project for a client, which we are gonna track actions on
"""
client = models.ForeignKey(
'railroader.Client', on_delete=models.PROTECT, related_name='projects')
name = models.CharField(max_length=255, unique=True)
description = models.TextField(null=True)
is_active = models.BooleanField(default=True)
def __str__(self):
return "{} for client {}".format(self.name, self.client.name)
So, following the documentation of drf-nested-routers, I set up my serializers like this :
class ClientSerializer(serializers.ModelSerializer):
class Meta:
model = Client
fields = ("id", "name", "description", "is_active", "projects")
class ProjectSerializer(serializers.ModelSerializer):
class Meta:
model = Project
fields = ("id", "name", "description", "is_active")
And my viewsets like this :
class ClientViewset(viewsets.ModelViewSet):
serializer_class = ClientSerializer
permission_classes = (permissions.IsAuthenticated, AccountPermission)
def get_queryset(self):
queryset = Client.objects.all()
is_active = self.request.query_params.get("is_active")
if is_active:
queryset = queryset.filter(is_active=is_active)
return queryset
class ProjectViewset(viewsets.ModelViewSet):
serializer_class = ProjectSerializer
permission_classes = (permissions.IsAuthenticated, AccountPermission)
def get_queryset(self):
queryset = Project.objects.filter(client=self.kwargs["client_pk"])
is_active = self.request.query_params.get("is_active")
if is_active:
queryset = queryset.filter(is_active=is_active)
return queryset
And finally, my urls like so :
router = routers.SimpleRouter()
router.register(r"clients", viewsets.ClientViewset, base_name="clients")
projects_router = routers.NestedSimpleRouter(router, r"clients", lookup="client")
projects_router.register(r"projects", viewsets.ProjectViewset, base_name="projects")
urlpatterns = [
re_path(r"^", include(router.urls)),
re_path(r"^", include(projects_router.urls))
]
With this setup, I'm able to have the desired nested routes, but I can't have my routes to create a new object if I post on a nested route.
I've seen an issue on the github speaking about it, but as it was 2 years ago, I wonder if anyone knows how to do it.
Thanks in advance.
Found out I just forgot that returning an instance in DRF create method of the serializer would not create the object in base. At the end I have this serializer :
class ProjectSerializer(serializers.ModelSerializer):
def create(self, validated_data):
client = Client.objects.get(pk=self.context["view"].kwargs["client_pk"])
validated_data["client"] = client
return Project.objects.create(**validated_data)
class Meta:
model = Project
fields = ("id", "name", "description", "is_active")

Categories

Resources