How to CreateAPIView using the request.user - django

Hi I'm wondering what the best practice is for creating a new model entry with a user based off the request in Django Rest Framework?
Models:
class Asset(models.Model):
user = models.ForeignKey(UserAccount, on_delete=models.CASCADE, related_name="assets")
name = models.CharField(max_length=200)
amount = models.DecimalField(max_digits=10, decimal_places=2)
Serializers:
class AssetSerializer(serializers.ModelSerializer):
class Meta:
model = Asset
fields = '__all__'
Views
class CreateAssetView(generics.CreateAPIView):
serializer_class = AssetSerializer
<Doesn't seem to work, possibly since user isn't in the json>
def perform_create(self, serializer):
serializer.save(user=self.request.user)
Basically I want to be able to send a POST request {name: 'myasset', amount: '50'} to this endpoint and have a new Asset saved with the User field obtain from the request. What is the best way to do this? Thanks
*** EDIT ***
Thought of a better solution:
class CreateAssetView(generics.CreateAPIView):
serializer_class = AssetSerializer
queryset = Asset.objects.all()
def perform_create(self, serializer):
serializer.save(user=self.request.user)
However this means I must send a dummy user_id in the POST request from the front-end. I'm not sure how this can be avoided. Any suggestions highly welcome.

I do most often this thing using function-based views not class-based ones. :
Basically, that will also be able to send a POST request and will save the user who requested the post request.
from rest_framework.response import Response
from rest_framework.decorators import api_view
from rest_framework.permissions import IsAuthenticated
#api_view(['POST'])
#permission_classes([IsAuthenticated])
def perform_create(request, pk):
user = request.user
asset= Asset.objects.get(id=pk)
data = request.data
# Create Asset
asset = Asset.objects.create(
user=user,
name=user.first_name,
amount=data['amount '],
)
asset.save()
return Response('Asset Added')
And to return the data I create another view for the serialized data
where needed. I guess there would be other approaches I'm sure but
this one is much simple and easy to do.

Since Post "author" cannot be null then we need to provide a user,
one way to do this is to put the user instance in the request.data from the frontend...
the example below is assigning the user instance to request.data from the backend after the request is made!
...models.py
class Post(models.Model):
author = models.ForeignKey(User, on_delete=models.CASCADE)
title = models.CharField(max_length=255)
content = models.CharField(max_length=255)
...views.py
class PostCreate(CreateAPIView):
queryset = Post
serializer_class = PostSerializer
# override this method
def create(self, request, *args, **kwargs):
request.data['author'] = request.user.pk
return super().create(request, *args, **kwargs)

Related

How to set userkey_id with #setter and token in django?

I have created a client frontend and have tested the url using cUrl and it works, sending the auth token via axios headers allows me in to call the api. The problem is that I get a NOT NULL constraint failed: post_post.userkey_id error and have narrowed it down that the #user.setter is not getting the CustomUser from the Auth Token. How can I correctly use the #user.setter to set the user that has the corresponding auth token/created the post from the client frontend.
Views.py
class CreatePost(generics.CreateAPIView):
serializer_class = PostSerializer
permission_classes = [permissions.IsAuthenticated]
def perform_create(self,serializer):
serializer.save(user = self.request.user)
Post model.py
from django.db import models
from accounts.models import CustomUser
class Post(models.Model):
#foriegn keys
userkey = models.ForeignKey(CustomUser, on_delete=models.CASCADE)
#properties
title = models.CharField(max_length=100,blank=True)
image= models.FileField(upload_to='files/', null=True, verbose_name="",blank=True) #image field
price = models.IntegerField(default=0,blank=True)
description = models.TextField(blank=True)
likes = models.IntegerField(default=0)
tags = models.CharField(max_length=30,blank=True,default="tag1,tag2,...") #sqlite doesnt support arrays
date_modified = models.DateTimeField(auto_now=True,blank=True)
created = models.DateTimeField(auto_now_add=True,blank=True)
sold = models.BooleanField(default=False,blank=True)
#property
def user(self):
return self.userkey.username
#user.setter
def user(self):
return models.ForeignKey(CustomUser,on_delete=models.CASCADE)
#property
def datejoined(self):
return self.userkey.date_joined
def __str__(self):
return self.title
The userkey is to get data from the user thus I have a #property def user function to collect data from another object. The userkey and #property def user work fine from the admin panel. The #user.setter is used in order to allow the user to be changed from the views.py otherwise I get a "cannot change attribute 'user'" error. Thus I know that the problem is specifically from the #user.setter, I just don't know what I am doing wrong, everything seems fine. Post creation only seems to work from the Admin panel.
Fixed, the error was in the views.py
class CreatePost(generics.CreateAPIView):
serializer_class = PostSerializer
permission_classes = [permissions.IsAuthenticated]
def perform_create(self,serializer):
serializer.save(userkey = self.request.user)
Should be changing the 'userkey' not the 'user'.
Also removed the user.setter. I was trying to change the user but it should have been the userkey as that is the field and user is a property.

Prevent user liking own comments in Django API

I'm hopeful someone can help with this problem. I believe the answer is probably straightfoward but it's eluding me. I'm creating a messaging API where users like comments, however, I want to prevent a user from liking their own comment
I have the following Like model linking to a Message model:
# models.py
class Like(models.Model):
message_id = models.ForeignKey(Message, related_name='message_id_like', on_delete=models.CASCADE)
owner = models.ForeignKey(User, on_delete=models.CASCADE)
like = models.BooleanField()
like_date = models.DateTimeField(auto_now_add=True)
def __str__(self):
return str(self.owner) + ', ' + self.message_id.message_title[:40]
With a serializer for the API
# serializers.py
class LikeSerializer(serializers.ModelSerializer):
owner = serializers.ReadOnlyField(source='owner.username')
class Meta:
model = Like
fields = [
'message_id',
'owner',
'like',
'like_date',
]
And a view:
# views.py
class LikeViewSet(viewsets.ModelViewSet):
queryset = Like.objects.all()
serializer_class = LikeSerializer
def perform_create(self, serializer): # Saving the user
serializer.save(owner=self.request.user)
I believe I need an additional function within the LikeViewSet class that prevents the creation of a like when Like user matches Message user, however, I do not know how to specify it.
Any help appreciated.
Do something like this. But preventing means a lot. Whether to show some error or silently prevent.
def perform_create(self, serializer): # Saving the user
if serializer.data['message_id'].user == self.request.user:
# error handle for self like
else:
serializer.save(owner=self.request.user)
Since you are using Django Rest Framework, the most elegant way is to write a custom Permission Class. It could look like this:
class IsNotOwnerCanLike(permissions.BasePermission):
def has_permission(self, request, view):
if view.action == "create":
return not request.data["message_id"].user == request.user
and then in your ViewSet you should specify
permission_classes = [IsNotOwnerCanLike]
This way, when the owner tries to like their own post, they will get a HTTP 403 Forbidden error.
Finally solved this using a variation of submitted answers. The secret sauce to avoid the serializer access error was the initial_data method (rather than the validated_data method which pulls back the wrong data).
def perform_create(self, serializer):
message = get_object_or_404(Message, pk=serializer.initial_data['message_id'])
if message.owner == self.request.user:
raise PermissionDenied
else:
serializer.save(owner=self.request.user)

Updating ManyToMany of a custom user DRF best practice

I've been struggling to understand how one would update a M2M-field in the django rest framework that is between a custom user and some random field. I should mention I'm using Djoser as authentication.
Lets say I have a custom user
Models:
class CustomUser(AbstractUser):
username = None
email = models.EmailField(_('email address'), unique=True)
paying_user = models.BooleanField(default=False)
subscribed_companies = models.ManyToManyField('myapp.Company')
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = []
objects = UserAccountManager()
def __str__(self):
return f"{self.email}' account"
class Company(models.Model):
name = models.CharField(max_length=150)
def __str__(self):
return self.name
class Meta:
ordering = ['name']
My serializers
Imports - serializers.py:
from django.contrib.auth import get_user_model
from djoser.serializers import UserCreateSerializer
from rest_framework import serializers
from apartments.models.company_model import Company
User = get_user_model()
class UserCreateSerializer(UserCreateSerializer):
class Meta(UserCreateSerializer.Meta):
model = User
fields = ('email','password', 'paying_user', 'subscribed_companies')
class UserCompanyListSerializer(serializers.ModelSerializer):
#Is this a reasonable way to serialize a M2M-field?
subscribed_company_ids = serializers.PrimaryKeyRelatedField(many=True, read_only=False,
queryset=Company.objects.all(), source='subscribed_companies')
class Meta:
model = User
fields = [
'subscribed_company_ids'
]
class CompanySerializer(serializers.ModelSerializer):
class Meta:
model = Company
fields = ('name',)
As you can see, I've attached a M2M-field on the custom user itself, instead of using a OneToOne-field where I store custom data. I'm not sure this is the best way to do it.
The idea is that a user should be able to, on the front end, have a list of companies it wants to subscribe to once they are logged in. That means I'll have many users that can subscribe to many companies.
Where I'm really doubting myself is how I handled the class based views.
Since I can retrieve the ID from request.user.id and since I want to replace the entire list of companies, I don't need the PK which identifies a specific company.
Therefore, in the put method, I removed the PK parameter. This works.
So my question is - Is there a more clean way to do it? Looking at posts at stackoverflow I couldn't find a decent answer that involved authentication. Am I approaching it wrong?
class UserCompanies(APIView):
permission_classes = [permissions.IsAuthenticatedOrReadOnly]
def get(self, request):
user_id = request.user.id
instance = CustomUser.objects.get(id=user_id)
serializer = UserCompanyListSerializer(instance)
return Response(serializer.data)
def put(self, request, format=None):
user_id = request.user.id
instance = CustomUser.objects.get(id=user_id)
serializer = UserCompanyListSerializer(instance, data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
How a GET request response would look to localhost:8000/usercompanies/:
{
"subscribed_company_ids": [
2,
1,
3
]
}
How a PUT request response would look to localhost:8000/usercompanies/:
{
"subscribed_company_ids": [
2,
1,
3,
5,
4,
]
}
Feedback would be much appreciated, I'm a total DRF newbie.

Update an field in django rest framework

I have a class named Customer:
class Customer(models.Model):
name = models.CharField(max_length=250)
status = models.IntegerField()
And the serializer is:
class CustomerSerializer(serializers.ModelSerilizer):
class Meta:
model = Customer
fields = '__all__'
Now how can I change/update only the status field using POST method. I am using function base view here.
I want to receive value such as:
{
"status": 1
}
This would have been way easier if you were using class based views. You can easily create an UpdateStatusView that updates RetrieveUpdateAPIView send a patch request.
However, since you're using function based views, I'll still recommend you use a PATCH request rather than a POST request, this give better self documentation.
def update_status_request(request, id):
if request.method == 'PATCH':
customer = Customer.objects.get(pk=id)
customer.status = request.data.get('new_status')
customer.save()
return JsonResponse({'message': 'Status has been updated'}, status=200)
You might also wanna do some extra validation and try...except.
why do you want user post method to update the data, since you are update you can user patch
class CustomerUpdateSerializer(serializers.ModelSerializer):
class Meta:
model = Customer
fields = ('status',)
from rest_framework.decorators import api_view
from rest_framework import response
#api_view(methods=['post', "patch"])
def api_function(request, *args, **kwargs):
change instance logic according to u
instance = Customer.objects.get()
if request.method == "POST":
# post is used for create data so i did for that for update use path
serializer = CustomerSerializer(data=request.data)
else:
serializer = CustomerUpdateSerializer(instance=instance, data=request.data)
if serializer.is_valid(raise_exceptions=True):
serializer.save()
return response.Response(serializer.data)
You can use viewset for this purpose.
View:
from rest_framework import viewset
class CustomerViewSet(viewset.ModelViewSet):
serializer_class = CustomerSerializer
Url:
path('customer/update/<int:pk>', CustomerViewSet.as_view({'post': 'update'})),

Django TastyPie PUT Nested User Model Correctly

I'm trying to go with-the-grain using Django TastyPie to update my models. I have an Identity model, acting as a wrapper around default Django user model:
class Identity(ProfileBase):
user = models.OneToOneField(User, related_name='identity')
avatar = models.ImageField(upload_to=avatar_upload_path, blank=True,
null=True)
I have my UserResource:
class UserResource(ModelResource):
class Meta:
resource_name = 'user'
queryset = User.objects.all()
fields = ['email', 'first_name', 'last_name']
include_resource_uri = False
And I have my IdentityResource:
class IdentityResource(ModelResource):
user = fields.ToOneField(UserResource, 'user', full=True)
class Meta:
resource_name = 'identity'
queryset = Identity.objects.select_related()
fields = ['user', 'avatar']
always_return_data = True
include_resource_uri = False
authentication = OAuthTokenAuthentication()
authorization = Authorization()
I'm currently successfully updating first_name, last_name using the ModelResource obj_update method within IdentityResource:
def obj_update(self, bundle, request, **kwargs):
print 'updating object'
bundle = self.full_hydrate(bundle)
bundle.obj.user = request.user
user = bundle.data['user']
bundle.obj.user.first_name = user['first_name']
bundle.obj.user.last_name = user['last_name']
return super(IdentityResource, self).obj_update(bundle, request, user=request.user)
I want to make a PUT request and optionally update any field on the user or identity models (first_name, last_name on user, or the avatar field on identity). I would rather not have to manually access each field from the bundle data and set them on models manually, as I have done above.
How can I do this naturally in TastyPie? Can someone explain a better approach to solving this problem? Any direction is GREATLY appreciated. :)
Here's my shot at providing an answer that attempts to leverage Tastypie as much as possible.
It is a little more generic than the OP's request (it will update any user, not just the one logged in). In the real world you would probably want to add some sort of authentication/authorization.
from tastypie.resources import ModelResource
from tastypie.authorization import Authorization
from django.contrib.auth.models import User
from myapp.account.models import Identity
class IdentityResource(ModelResource):
class Meta:
queryset = Identity.objects.all()
class UserResource(ModelResource):
class Meta:
queryset = User.objects.all()
allowed_list_methods = ['get']
allowed_detail_methods = ['get','put']
authorization = Authorization()
def dehydrate(self, bundle):
identity_bundle = self.build_identity_bundle(bundle)
identity_bundle = IdentityResource().full_dehydrate(identity_bundle)
return identity_bundle
def obj_update(self, bundle, request, **kwargs):
user_bundle = super(UserResource, self).obj_update(bundle, request, **kwargs)
identity_bundle = self.build_identity_bundle(user_bundle)
IdentityResource().obj_update(identity_bundle, request)
return user_bundle
def build_identity_bundle(self, user_bundle):
identity_bundle = IdentityResource().build_bundle(
obj=user_bundle.obj.get_profile(),
data=user_bundle.data
)
return identity_bundle
What the example supports is:
GET a flattened User+Identity resource
PUT a flattened User+Identity resource, updating both models
You would want to register the UserResource in the API, and probably not the IdentityResource.
You could do something like this.
# Find all properties in user model.
properties = [prop for prop in bunder.obj.user if not prop.startswith('__')]
bundle_user = bundle.data['user']
# Find the property in bundle user and set it back on user if it exists.
for property in properties:
if property in bundle_user:
setattr(bundle.obj.user, property, bundle_user[property])
Maybe I'm missing the point but did you try a PATCH-method request? Tastypie will take all the sent attributes and update them in the database leaving all not-send attributes untouched.