How to set userkey_id with #setter and token in django? - 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.

Related

Why do I get an API GET error as an anonymousUser even when using IsAuthenticatedOrReadOnly?

Im learning Django followed a book and tutorials. Anyways I started my own project, where users can create Posts for other to see. My problem comes when someone is not logged in and makes a GET request ie:PostListCreate below, the api crashes even tho I have permissions as IsAuthenticatedOrReadOnly. I get the error TypeError: Field 'id' expected a number but got <django.contrib.auth.models.AnonymousUser object at 0x104d2f760>.
Also I have models.ForeignKey(User, on_delete=models.CASCADE) in the model.py for Post model. Saw this might have something to do with it but unsure.
Have spent three hours researching it and can't find a solution. What do I need to change or edit to be able to have a successful GET request without authentication.
api/views.py
from rest_framework import generics, permissions
from .serializers import PostSerializer
from post.models import Post
class PostListCreate(generics.ListCreateAPIView):
serializer_class = PostSerializer
permission_classes = [permissions.IsAuthenticatedOrReadOnly]
def get_queryset(self):
user = self.request.user
return Post.objects.filter(user=user).order_by('-created')
def perform_create(self,serializer):
serializer.save(user=self.request.user)
class PostRetrieveUpdateDestroy(generics.RetrieveUpdateDestroyAPIView):
serializer_class = PostSerializer
permission_classes = [permissions.IsAuthenticatedOrReadOnly]
def get_queryset(self):
user = self.request.user
return Post.objects.filter(user=user)
model.py
from django.db import models
from django.contrib.auth.models import User
class Post(models.Model):
title = models.CharField(max_length=100)
description = models.TextField(blank=True)
created = models.DateTimeField(auto_now_add=True)
sold = models.BooleanField(default=False)
user = models.ForeignKey(User, on_delete=models.CASCADE)
def __str__(self):
return self.title
You are retrieving posts related to requested users and get_queryset() is expected request.user.id that's why you are getting error, if you want to allow everbody to see posts:
def get_queryset(self):
return Post.objects.all().order_by('-created')

How to retrive data from OneToOne Relational Model in DRF using API view

I have imported User model and customized it a/c to my need and make OneToOne Relation with UserProfileModel Model. While retrieving data I got this error.
"The serializer field might be named incorrectly and not match any attribute or key on the AnonymousUser instance.
Original exception text was: 'AnonymousUser' object has no attribute 'gender'."
My Model is :
class UserProfileModel(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE, primary_key=True, related_name='userprofilemodel')
gender = models.CharField(max_length=20)
locality = models.CharField(max_length=70)
city = models.CharField(max_length=70)
address = models.TextField(max_length=300)
pin = models.IntegerField()
state = models.CharField(max_length=70)
profile_image = models.FileField(upload_to='user_profile_image', blank=True)
My Serializer looks like:
class UserProfileSerializer(serializers.ModelSerializer):
class Meta:
model= User
fields = ['id', 'name' , 'email','mobile',]
class UserProfileModelSerializer(serializers.ModelSerializer):
user = serializers.StringRelatedField(many=True, read_only=True)
class Meta:
model= UserProfileModel
fields = ['user','gender' , 'locality','city','address','pin','state','profile_image', ]
My view looks like:
class UserProfileDataView(APIView):
def get(self, request, format=None):
# user = UserProfileModel.objects.all()
serializer = UserProfileModelSerializer(request.user)
return Response(serializer.data, status=status.HTTP_200_OK)
I want to retrieve profile data of the logged user using UserProfileModel Model
Your first issue in that you are passing a User instance to the UserProfileModelSerializer, which is expecting a UserProfileModel instance. To fix this you need to change:
serializer = UserProfileModelSerializer(request.user)
to
serializer = UserProfileModelSerializer(request.user.userprofilemodel)
where userprofilemodel is the related_name you have set on the user field in your UserProfileModel.
Second issue is, as Mohamed Beltagy said, you're allowing anyone to access the view, including unauthenticated users. Django rest framework has a built in mixin that you can use to restrict access to authenticated users only (https://www.django-rest-framework.org/api-guide/permissions/#isauthenticated).
from rest_framework.permissions import IsAuthenticated
class UserProfileDataView(APIView):
permission_classes = [IsAuthenticated]
the problem here is you are passing an anonymous user which has no profile ( you permit non-authenticated users access this view)
def get(self, request, format=None):
# user = UserProfileModel.objects.all()
if request.user.is_authenticated:
serializer = UserProfileModelSerializer(request.user)
return Response(serializer.data, status=status.HTTP_200_OK)
else:
return Response(status=status.HTTP_401_UNAUTHORIZED)

Create URL for detailview using DRF Modelviewset

i'm using DRF modelviewset to create an api for users model. Can anyone explain or help how can I pass a url in react-native for the detailview of user. Right now i'm getting the detailview in this manner 127.0.0.1:8000/users/users/2/. But everytime i don't want to pass the id in the url.
models.py
class User(AbstractUser):
"""This Class is used to extend the in-build user model """
ROLE_CHOICES = (('CREATOR','CREATOR'),('MODERATOR','MODERATOR'),('USERS','USERS'))
GENDER_CHOICES = (('MALE','MALE'),('FEMALE',"FEMALE"),('OTHER','OTHER'))
date_of_birth = models.DateField(verbose_name='Date of Birth', null=True)
profile_image = models.ImageField(upload_to='media/profile_images', verbose_name='Profile Image', default='media/profile_images/default.webp', blank=True)
bio = models.TextField(verbose_name='Bio')
role = models.CharField(max_length=10, verbose_name='Role', choices=ROLE_CHOICES, default='USERS')
gender = models.CharField(max_length=6, verbose_name='Gender', choices=GENDER_CHOICES)
serializers.py
class UserSerializer(serializers.ModelSerializer):
following = serializers.SerializerMethodField(read_only=True)
followers = serializers.SerializerMethodField(read_only=True)
class Meta:
model = User
fields = ('first_name','last_name','username','password','email','date_of_birth',
'profile_image','bio','role','gender', 'following','followers')
extra_kwargs = {'is_active':{'write_only':True},
'password':{'write_only':True}}
def create(self, validated_data):
logger.info('Information Stored!')
return User.objects.create_user(**validated_data)
def update(self, *args, **kwargs):
user = super().update( *args, **kwargs)
p = user.password
user.set_password(p)
user.save()
return user
views.py
class UserAPI(viewsets.ModelViewSet):
serializer_class = UserSerializer
# permission_classes = [permissions.IsAuthenticated, TokenHasReadWriteScope]
def get_queryset(self):
users = User.objects.all()
return users
urls.py
router = DefaultRouter()
router.register('users', views.UserAPI, basename='users'),
router.register('following', views.FollowingAPI, basename='following'),
urlpatterns = router.urls
How can i solve this. Need your help please. Thank you
You can make use of #action decorator.
Give this a try:
class UserAPI(viewsets.ModelViewSet):
...
#action(detail=False)
def profile(self, request):
serializer = UserSerializer(request.user)
return Response(serializer.data)
Now go to 127.0.0.1:8000/users/profile/, you should see the current authenticated user's data.
this may be helpful.
You can use user = request.user.id , by in this way you can get current login user.

How to CreateAPIView using the request.user

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)

DRF PUT Partial Update returning Not Found

I'm trying to get to grips with Django and DRF but having some trouble. I would like to make a PUT request to make a partial update on a record.
I currently have the following parts -
From models.py
class MyUser(models.Model):
# Link to User model instance.
user = models.OneToOneField(User)
first_name = models.CharField(max_length=32, null=True, blank=True)
lastname = models.CharField(max_length=32, null=True, blank=True)
joindate = models.DateTimeField(null=False, blank=False)
def __str__(self):
return self.user.username
From api/views.py
class MyUserDetailUpdateView(GenericAPIView, UpdateModelMixin):
queryset = MyUser.objects.all()
serializer_class = MyUserPartialUpdateSerializer
lookup_field = 'user'
def put(self, request, *args, **kwargs):
return self.partial_update(request, *args, **kwargs)
From api/serializers.py
class MyUserPartialUpdateSerializer(serializers.ModelSerializer):
class Meta:
model = MyUser
From urls.py
url(r'^api/userupdate/(?P<user>[\w]+)/$', apiviews.MyUserDetailUpdateView.as_view(), name='my_user_detail_view_api')
For testing I used httpie and try -
http -v PUT http://127.0.0.1:8000/api/userupdate/johndoe/ first_name="Johnny"
The server side is reporting a "Not Found: /api/userdate/johndoe/" and returns a HTTP 404 to the client.
What am I missing to do a partial update?
Thanks
MyUser.user is supposed to be a User instance. You can't use it that way.
You likely want the MyUser associated with the username. In that case, the argument you want to extra from url will be set as lookup_url_kwarg and the lookup_field will do the join across the related model:
class MyUserDetailUpdateView(GenericAPIView, UpdateModelMixin):
queryset = MyUser.objects.all()
serializer_class = MyUserPartialUpdateSerializer
lookup_field = 'user__username'
lookup_url_kwarg = 'user'