DRF PUT Partial Update returning Not Found - django

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'

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.

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.

Django: How to add user(author of the POST request) before save in serializers?

I'm tried to add author of the request before saving it in serializers.py
And got error:
Cannot assign "<django.contrib.auth.models.AnonymousUser object at 0x0000029169C48040>": "Category_product.posted_user" must be a "User" instance.
models.py
class Category_product(models.Model):
category_name = models.CharField(max_length=200, unique=True)
posted_user = models.ForeignKey(User, on_delete=models.CASCADE, default=1)
def __str__(self):
return self.category_name
views.py
class Category_productDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = Category_product.objects.all()
serializer_class = Category_productSerializer
serializers.py
class Category_productSerializer(serializers.ModelSerializer):
post_user = serializers.ReadOnlyField(
source='posted_user.username')
class Meta:
model = Category_product
fields = ['id', 'category_name','post_user']
def validate(self, data):
data['posted_user'] = self.context['request'].user
return data
Have you tried overriding the user in your view?
Assuming you have Authentacion set up, because if not everyone could get in and it won't be a user, it would be Anonymous User instance.
If that's not the case, you can try it out in the view with perform create:
class Category_productDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = Category_product.objects.all()
serializer_class = Category_productSerializer
def get_user(self):
user = self.request.user
return user
def perform_create(self, serializer):
"""
Set the sender to the logged in user.
"""
serializer.save(posted_user=self.get_user())

This QueryDict instance is immutable

I have a Branch model with a foreign key to account (the owner of the branch):
class Branch(SafeDeleteModel):
_safedelete_policy = SOFT_DELETE_CASCADE
name = models.CharField(max_length=100)
account = models.ForeignKey(Account, null=True, on_delete=models.CASCADE)
location = models.TextField()
phone = models.CharField(max_length=20, blank=True,
null=True, default=None)
create_at = models.DateTimeField(auto_now_add=True, null=True)
update_at = models.DateTimeField(auto_now=True, null=True)
def __str__(self):
return self.name
class Meta:
unique_together = (('name','account'),)
...
I have a Account model with a foreign key to user (one to one field):
class Account(models.Model):
_safedelete_policy = SOFT_DELETE_CASCADE
name = models.CharField(max_length=100)
user = models.OneToOneField(User)
create_at = models.DateTimeField(auto_now_add=True)
update_at = models.DateTimeField(auto_now=True)
def __str__(self):
return self.name + ' - ' + self.create_at.strftime('%Y-%m-%d %H:%M:%S')
I've created a ModelViewSet for Branch which shows the branch owned by the logged in user:
class BranchViewSet(viewsets.ModelViewSet):
serializer_class = BranchSerializer
permission_classes = (permissions.IsAuthenticated,)
def get_queryset(self):
queryset = Branch.objects.all().filter(account=self.request.user.account)
return queryset
Now to create a new branch, I want to save account field with request.user.account, not with data sent from the rest client (for more security). for example:
def create(self, request, *args, **kwargs):
if request.user.user_type == User.ADMIN:
request.data['account'] = request.user.account
return super(BranchViewSet, self).create(request, *args, **kwargs)
def perform_create(self, serializer):
'''
Associate branch with account
'''
serializer.save(account=self.request.user.account)
In branch serializer
class BranchSerializer(serializers.ModelSerializer):
account = serializers.CharField(source='account.id', read_only=True)
class Meta:
model = Branch
fields = ('id', 'name', 'branch_alias',
'location', 'phone', 'account')
validators = [
UniqueTogetherValidator(
queryset=Branch.objects.all(),
fields=('name', 'account')
)
]
but I got this error:
This QueryDict instance is immutable. (means request.data is a immutable QueryDict and can't be changed)
Do you know any better way to add additional fields when creating an object with django rest framework?
As you can see in the Django documentation:
The QueryDicts at request.POST and request.GET will be immutable when accessed in a normal request/response cycle.
so you can use the recommendation from the same documentation:
To get a mutable version you need to use QueryDict.copy()
or ... use a little trick, for example, if you need to keep a reference to an object for some reason or leave the object the same:
# remember old state
_mutable = data._mutable
# set to mutable
data._mutable = True
# сhange the values you want
data['param_name'] = 'new value'
# set mutable flag back
data._mutable = _mutable
where data it is your QueryDicts
Do Simple:
#views.py
from rest_framework import generics
class Login(generics.CreateAPIView):
serializer_class = MySerializerClass
def create(self, request, *args, **kwargs):
request.data._mutable = True
request.data['username'] = "example#mail.com"
request.data._mutable = False
#serializes.py
from rest_framework import serializers
class MySerializerClass(serializers.Serializer):
username = serializers.CharField(required=False)
password = serializers.CharField(required=False)
class Meta:
fields = ('username', 'password')
request.data._mutable=True
Make mutable true to enable editing in querydict or the request.
I personally think it would be more elegant to write code like this.
def create(self, request, *args, **kwargs):
data = OrderedDict()
data.update(request.data)
data['account'] = request.user.account
serializer = self.get_serializer(data)
serializer.is_valid(raise_exception=True)
self.perform_create(serializer)
headers = self.get_success_headers(serializer.data)
return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
Do you know any better way to add additional fields when creating an object with django rest framework?
The official way to provide extra data when creating/updating an object is to pass them to the serializer.save() as shown here
https://docs.djangoproject.com/en/2.0/ref/request-response/#querydict-objects
The QueryDicts at request.POST and request.GET will be immutable when accessed in a normal request/response cycle. To get a mutable version you need to use QueryDict.copy().
You can use request=request.copy() at the first line of your function.

create object with foreign key to User DRF

I am using the django rest framework and I have a very simple model of Posts for a particular user which I have serialised in the following manner.
Serializers.py
class PostSerializer(serializers.HyperlinkedModelSerializer):
image = serializers.ImageField(max_length=None, use_url=True)
question = serializers.CharField(required=False)
ayes = serializers.CharField(required=False)
nays = serializers.CharField(required=False)
neutrals = serializers.CharField(required=False)
class Meta:
model = Posts
fields = ('user','question', 'image','ayes', 'nays', 'neutrals')
My models.py is as follows
class Posts(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
created = models.DateTimeField(auto_now_add=True)
question = models.TextField(max_length=500, blank=True)
image = models.ImageField('optionalImage', upload_to='images/posts/', default='/images/posts/blank.png')
ayes = models.TextField(max_length=200, default=0)
nays = models.TextField(max_length=200, default=0)
neutrals = models.TextField(max_length=200, default=0)
When I tried posting to this I kept getting NOT NULL Integrity constraint error of user_id. Hence I added context={'request': request}) to the serializer which ends up giving me the following error:
Could not resolve URL for hyperlinked relationship using view name "user-detail". You may have failed to include the related model in your API, or incorrectly configured the lookup_field attribute on this field.
My views.py is as follows:
views.py
#permission_classes((IsAuthenticated, ))
class PostsView(generics.ListCreateAPIView):
queryset = Posts.objects.all()
serializer_class = PostSerializer
def get(self, request, format=None):
snippets = Posts.objects.filter(pk=request.user.id)
serializer = PostSerializer(snippets, many=True,context={'request': request})
return Response(serializer.data)
def post(self, request, format=None):
posts = PostSerializer(data=request.data,context={'request': request})
if posts.is_valid():
posts.save()
return Response("YOLO", status=status.HTTP_201_CREATED)
else:
return Response(posts.errors, status=status.HTTP_400_BAD_REQUEST)
All other fields of mine have posted correctly when I set default=0 in my model for user. I am unable to submit the foreign key user which needs to be saved on every post. What am I doing wrong here? Am I following the correct method?
Since you don't want to send your user, you should remove it from the serializer's field.
Next, you want to set the post's user to the current user. To achieve that, you want to pass the request.user to the serializer's data by changing the save method to:
posts.save(user=request.user)
It's explained in the documentation and in the tutorial