I want to update the Profile instance for a user, by sending data to the url update_profile/
I am using:
curl -X PUT -H "Authorization: Token sometoken" -d "name=somename&age=20&user=1" 127.0.0.1:8000/update_profile/
and the error I get is: {"user":["This field must be unique."]}
It seems like the error comes because the request creates a new instance of Profile, when there is already another instance of Profile with that same user. But I just want to update the current Profile instance for the user. Any idea why my code does try to create a new instance instead of updating it?
views.py
class UserProfileChangeAPIView(generics.UpdateAPIView, mixins.UpdateModelMixin):
permission_classes = (
permissions.IsAuthenticated,
UserIsOwnerOrReadOnly,
)
serializer_class = UpdateProfileSerializer
parser_classes = (MultiPartParser, FormParser,)
def get_queryset(self, *args, **kwargs):
queryset = Profile.objects.filter(user=self.request.user)
return queryset
def get_object(self):
self.request.user.profile
def put(self, request, *args, **kwargs):
return self.update(request, *args, **kwargs)
def delete(self, request, *args, **kwargs):
return self.destroy(request, *args, **kwargs)
def update(self, request, pk=None):
try:
self.request.user.profile
except Http404:
return Response(
{'detail': 'Not found'}, status=status.HTTP_404_NOT_FOUND)
return super(UserProfileChangeAPIView, self).update(request, pk)
urls.py
url(r'^update_profile/$', UserProfileChangeAPIView.as_view(), name='update_profile'),
serializers.py
class UpdateProfileSerializer(serializers.ModelSerializer):
class Meta:
model = Profile
fields = [
'name',
'age',
'user',
]
models.py
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
name = models.CharField(max_length=100, null=True)
age = models.PositiveIntegerField(null=True)
You shouldn't include a user in the post data as you're already getting the profile for the current logged in user in get_object() and get_queryset()
Because the relationship between Profile and User is a OnetoOneField, there can't be more than one Profile linked to the same User.
I expect (from the error you've received) what's happening here is that your logged in as one User (e.g. id 3) and then trying to update the Profile to point to User id 1.
I was able to solve the problem like this and no need for pk in the url:
serializers.py
class UpdateProfileSerializer(serializers.ModelSerializer):
class Meta:
model = Profile
fields = [
'name',
'age',
]
views.py
class ProfileUpdateAPIView(UpdateAPIView):
serializer_class = UpdateProfileSerializer
def get_object(self):
queryset = Profile.objects.filter(user=self.request.user)
obj = queryset[0]
return obj
urls.py:
url(r'^update/$', ProfileUpdateAPIView.as_view(), name='update'),
curl:
curl -X PUT -H "Authorization: Token sometoken" -d "name=newname&age=20" 127.0.0.1:8000/update/
Related
Using class Based (APIView) in Django rest framework for Getting and Patch (Updating) UserInfo data.
views.py
class getUserInfo(APIView):
permission_classes = [permissions.IsAuthenticated]
def get(self, request, format=None):
user = request.user
userinfos = user.userinfo_set.all()
serializer = UserInfoSerializers(userinfos, many=True)
return Response(serializer.data)
def patch(self, request, pk, format=None):
user = UserInfo.objects.get(id=pk)
serializer = UserInfoSerializers(instance=user, data=request.data, partial=True)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
serializers.py
from django.contrib.auth.models import User
from .models import UserInfo
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('id', 'first_name', 'username')
class UserInfoSerializers(serializers.ModelSerializer):
user = UserSerializer(many=False, required=True)
class Meta:
model = UserInfo
fields = ('id', 'picture', 'profession', 'user')
Everything is working so far so good. Able to GET and PATCH (Update) logged-in user data.
While Testing the API in Postman, I found out that if User1 is logged in he can change the data of User2 by only using the pk of User2.
urls.py
urlpatterns = [
path('userinfo/', views.getUserInfo.as_view(), name="UserInfo"),
path('userinfo/<str:pk>/', views.getUserInfo.as_view()),
path('api/token/', views.MyTokenObtainPairView.as_view(), name='token_obtain_pair'),
path('api/token/refresh/', TokenRefreshView.as_view(), name='token_refresh'),
path('register/', views.RegisterView.as_view(), name='auth_register'),
]
Using rest_framework_simplejwt for Auth
models.py
from django.contrib.auth.models import User
class UserInfo(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE, null=True)
picture = models.ImageField(upload_to="profile_pics", null=True)
profession = models.CharField(max_length=200, null=True)
def __str__(self):
return "%s's Profile Picture" % self.user
Any help would be appreciated
Don't use the primary key to get the user.You are using user = request.user to get the user on get method, use the same mechanism also on update. Then the login user can only update his/her info not others info or another way you can check the user = UserInfo.objects.get(id=pk) is same as the current user request.user . If not you can show an exception.
For Retrieving and Updating an object, you can use RetrieveUpdateAPIView
class GetUserInfo(generics.RetrieveUpdateAPIView):
permission_classes = [IsAuthenticated]
queryset = UserInfo.objects.all()
serializer_class = UserInfoSerializers
def get_object(self):
return self.request.user
Here we are getting an object, it will be called from get_object method. Instead of getting user using PK, we get the current user.
You can use same url for getting and updating the user, just change the method in postman while you hit the api. GET for retrieving and PATCH for partial update.
path('userinfo/', views.GetUserInfo.as_view(), name="UserInfo"),
I am trying to list all the doubtclasses model using doubtclass view but there is some recursion error in the post request, which i am not able to understand , i search across for same error and i have tried if i made a similar mistake to the other developers that have asked the same question but as far as i searched mine one is different
My doubtclass view
class DoubtClass(LoginRequiredMixin, mixins.ListModelMixin, mixins.CreateModelMixin, generics.GenericAPIView):
serializer_class = serializers.DoubtClass_serializer
queryset = models.DoubtClasses.objects.filter(is_draft=False)
def get(self, request):
print("error in doubtclass get")
return self.list(request)
def post(self, request):
if request.user.is_superuser:
return self.post(request)
else:
return Response(status=status.HTTP_403_FORBIDDEN)
my doubtclass model
class DoubtClasses(models.Model):
doubtClass_details = models.TextField()
class_time = models.DateTimeField()
end_time = models.DateTimeField()
doubtsAddressed = models.IntegerField(default=0)
no_of_students_registered = models.IntegerField(default=0)
no_of_students_attended = models.IntegerField(default=0)
mentor_id = models.ForeignKey(Mentor, on_delete=models.CASCADE, null=True)
is_draft = models.BooleanField(default=True)
class Meta:
verbose_name_plural = 'DoubtClasses'
def __str__(self):
return self.doubtClass_details
I am new to django
From your code it seems you want to limit post requests to superusers. The problem with your implementation is that you are just calling post again recursively. Seeing as you inherit from CreateModelMixin you likely want to call create instead:
class DoubtClass(LoginRequiredMixin, mixins.ListModelMixin, mixins.CreateModelMixin, generics.GenericAPIView):
serializer_class = serializers.DoubtClass_serializer
queryset = models.DoubtClasses.objects.filter(is_draft=False)
def get(self, request, *args, **kwargs):
print("error in doubtclass get")
return self.list(request)
def post(self, request, *args, **kwargs):
if request.user.is_superuser:
return self.create(request, *args, **kwargs) # call `create` instead
else:
return Response(status=status.HTTP_403_FORBIDDEN)
But this can be improved. Firstly instead of inheriting from ListModelMixin, CreateModelMixin and GenericAPIView you can simply reduce that to inheriting from generics.ListCreateAPIView. Next instead of using LoginRequiredMixin it is better to use the IsAuthenticated permission. Also for your limitation of POST requests being limited to superusers that can also be added to a custom permission:
from rest_framework.permissions import BasePermission, IsAuthenticated, SAFE_METHODS
class IsSuperuserOrReadOnly(BasePermission):
def has_permission(self, request, view):
return bool(
request.method in SAFE_METHODS or
request.user and
request.user.is_superuser
)
class DoubtClass(generics.ListCreateAPIView):
serializer_class = serializers.DoubtClass_serializer
queryset = models.DoubtClasses.objects.filter(is_draft=False)
permission_classes = [IsAuthenticated, IsSuperuserOrReadOnly]
I've tried something like this, it does not work.
class PostSerializer(serializers.ModelSerializer):
class Meta:
model = Post
def save(self):
user = self.context['request.user']
title = self.validated_data['title']
article = self.validated_data['article']
I need a way of being able to access request.user from my Serializer class.
You cannot access the request.user directly. You need to access the request object, and then fetch the user attribute.
Like this:
user = self.context['request'].user
Or to be more safe,
user = None
request = self.context.get("request")
if request and hasattr(request, "user"):
user = request.user
More on extra context can be read here
Actually, you don't have to bother with context. There is a much better way to do it:
from rest_framework.fields import CurrentUserDefault
class PostSerializer(serializers.ModelSerializer):
class Meta:
model = Post
def save(self):
user = CurrentUserDefault() # <= magic!
title = self.validated_data['title']
article = self.validated_data['article']
As Igor mentioned in other answer, you can use CurrentUserDefault. If you do not want to override save method just for this, then use doc:
from rest_framework import serializers
class PostSerializer(serializers.ModelSerializer):
user = serializers.PrimaryKeyRelatedField(read_only=True, default=serializers.CurrentUserDefault())
class Meta:
model = Post
CurrentUserDefault
A default class that can be used to represent the current user. In order to use this, the 'request' must have been provided as part of the context dictionary when instantiating the serializer.
in views.py
serializer = UploadFilesSerializer(data=request.data, context={'request': request})
This is example to pass request
in serializers.py
owner = serializers.HiddenField(
default=serializers.CurrentUserDefault()
)
Source From Rest Framework
Use this code in view:
serializer = UploadFilesSerializer(data=request.data, context={'request': request})
then access it with this in serializer:
user = self.context.get("request").user
For those who used Django's ORM and added the user as a foreign key, they will need to include the user's entire object, and I was only able to do this in the create method and removing the mandatory field:
class PostSerializer(serializers.ModelSerializer):
def create(self, validated_data):
request = self.context.get("request")
post = Post()
post.title = validated_data['title']
post.article = validated_data['article']
post.user = request.user
post.save()
return post
class Meta:
model = Post
fields = '__all__'
extra_kwargs = {'user': {'required': False}}
You can pass request.user when calling .save(...) inside a view:
class EventSerializer(serializers.ModelSerializer):
class Meta:
model = models.Event
exclude = ['user']
class EventView(APIView):
def post(self, request):
es = EventSerializer(data=request.data)
if es.is_valid():
es.save(user=self.request.user)
return Response(status=status.HTTP_201_CREATED)
return Response(data=es.errors, status=status.HTTP_400_BAD_REQUEST)
This is the model:
class Event(models.Model):
user = models.ForeignKey(to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
date = models.DateTimeField(default=timezone.now)
place = models.CharField(max_length=255)
You can not access self.context.user directly. First you have to pass the context inside you serializer. For this follow steps bellow:
Some where inside your api view:
class ApiView(views.APIView):
def get(self, request):
items = Item.object.all()
return Response(
ItemSerializer(
items,
many=True,
context=request # <- this line (pass the request as context)
).data
)
Then inside your serializer:
class ItemSerializer(serializers.ModelSerializer):
current_user = serializers.SerializerMethodField('get_user')
class Meta:
model = Item
fields = (
'id',
'name',
'current_user',
)
def get_user(self, obj):
request = self.context
return request.user # <- here is current your user
In GET method:
Add context={'user': request.user} in the View class:
class ContentView(generics.ListAPIView):
def get(self, request, format=None):
content_list = <Respective-Model>.objects.all()
serializer = ContentSerializer(content_list, many=True,
context={'user': request.user})
Get it in the Serializer class method:
class ContentSerializer(serializers.ModelSerializer):
rate = serializers.SerializerMethodField()
def get_rate(self, instance):
user = self.context.get("user")
...
...
In POST method:
Follow other answers (e.g. Max's answer).
You need a small edit in your serializer:
class PostSerializer(serializers.ModelSerializer):
class Meta:
model = Post
def save(self):
user = self.context['request'].user
title = self.validated_data['title']
article = self.validated_data['article']
Here is an example, using Model mixing viewsets. In create method you can find the proper way of calling the serializer. get_serializer method fills the context dictionary properly. If you need to use a different serializer then defined on the viewset, see the update method on how to initiate the serializer with context dictionary, which also passes the request object to serializer.
class SignupViewSet(mixins.UpdateModelMixin, mixins.CreateModelMixin, viewsets.GenericViewSet):
http_method_names = ["put", "post"]
serializer_class = PostSerializer
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.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)
def update(self, request, *args, **kwargs):
partial = kwargs.pop('partial', False)
instance = self.get_object()
kwargs['context'] = self.get_serializer_context()
serializer = PostSerializer(instance, data=request.data, partial=partial, **kwargs)
serializer.is_valid(raise_exception=True)
self.perform_update(serializer)
return Response(serializer.data)
The solution can be simple for this however I tried accessing using self.contenxt['request'].user but not working in the serializer.
If you're using DRF obviously login via token is the only source or maybe others that's debatable.
Moving toward a solution.
Pass the request.user instance while creating serializer.create
views.py
if serializer.is_valid():
watch = serializer.create(serializer.data, request.user)
serializer.py
def create(self, validated_data, usr):
return Watch.objects.create(user=usr, movie=movie_obj, action=validated_data['action'])
If you are using generic views and you want to inject current user at the point of saving the instance then you can override perform_create or perform_update:
def perform_create(self, serializer):
serializer.save(user=self.request.user)
user will be added as an attribute to kwargs and you can access it through validated_data in serializer
user = validated_data['user']
drf srz page
in my project it worked my user field was read only so i needed to get
user id in the create method
class CommentSerializer(serializers.ModelSerializer):
comment_replis = RecursiveField(many=True, read_only=True)
user = UserSerializer(read_only=True)
class Meta:
model = PostComment
fields = ('_all_')
def create(self, validated_data):
post = PostComment.objects.create(**validated_data)
print(self._dict_['_kwargs']['data']["user"]) # geting #request.data["user"] # <- mian code
post.user=User.objects.get(id=self._dict_['_kwargs']['data']["user"])
return post
in my project i tried this way and it work
The best way to get current user inside serializer is like this.
AnySerializer(data={
'example_id': id
}, context={'request': request})
This has to be written in views.py
And now in Serializer.py part
user = serializers.CharField(default=serializers.CurrentUserDefault())
This "user" must be your field in Model as any relation like foreign key
I want to create an API where user can update their profile. In my case, a user can update his/her username and password. To change his/her profile, an API link should be /api/change/usernameOfThatUser. When I use a non-existing username in the link, I still get the userProfileChange API page, and the input boxes are not filled with previous data. How can I solve this?
serializers.py
User = get_user_model()
class UserProfileChangeSerializer(ModelSerializer):
username = CharField(required=False, allow_blank=True, initial="current username")
class Meta:
model = User
fields = [
'username',
'password',
]
def update(self, instance, validated_data):
instance.username = validated_data.get('username',instance.username)
print('instance of username',instance.username)
return instance
views.py
class UserProfileChangeAPIView(UpdateAPIView):
serializer_class = UserProfileChangeSerializer
lookup_field = 'username'
urls.py
url(r'^change/(?P<username>[\w-]+)$', UserProfileChangeAPIView.as_view(), name='changeProfile'),
Maybe try doing something like this instead in your views.py?
from rest_framework import generics, mixins, permissions
User = get_user_model()
class UserIsOwnerOrReadOnly(permissions.BasePermission):
def has_object_permission(self, request, view, obj):
if request.method in permissions.SAFE_METHODS:
return True
return obj.id == request.user.id
class UserProfileChangeAPIView(generics.RetrieveAPIView,
mixins.DestroyModelMixin,
mixins.UpdateModelMixin):
permission_classes = (
permissions.IsAuthenticated,
UserIsOwnerOrReadOnly,
)
serializer_class = UserProfileChangeSerializer
parser_classes = (MultiPartParser, FormParser,)
def get_object(self):
username = self.kwargs["username"]
obj = get_object_or_404(User, username=username)
return obj
def delete(self, request, *args, **kwargs):
return self.destroy(request, *args, **kwargs)
def put(self, request, *args, **kwargs):
return self.update(request, *args, **kwargs)
This will give you all of the existing data for the user based on the username passed in the url. If the username does not exist, it will raise a 404 error. You can also update or delete the object.
I am using Django models to create the fields for a form. I would like to have the user's username automatically detected in and be filled out, this way I can hide it in my form (instead of having them choose their username from a long list that has everyones username). To do this I am using:
current_user = request.user
and then setting the default to current_user. However, I keep getting this error:
NameError: name 'request' is not defined
I'm assuming you can't use requests in Django models, but is there anyway to get around this? Here is the relevant sections of my models.py file:
class StockTickerSymbol(models.Model):
StockName = models.CharField(max_length=7, unique=True)
current_user = request.user
user = models.ForeignKey(User, default=current_user)
Anyone know how I can use requests in my models, or somehow call the variable current_user?
Here you haven't imported request in that model class scope. This is how you can get user:
# model
class StockTickerSymbol(models.Model):
StockName = models.CharField(max_length=7, unique=True)
user = models.ForeignKey(User)
def save(self,**kwargs):
if kwargs.has_key('request') and self.user is None:
request = kwargs.pop('request')
self.user= request.user
super(StockTickerSymbol, self).save(**kwargs)
#views:
def post(self, request):
if form.is_valid():
sts=StockTickerSymbol()
sts.StockName= form.cleaned_data['StockName']
if form.cleaned_data['user'] is None: #null check
sts.save(request=request)
else:
sts.user= form.cleaned_data['user']
sts.save(request=request)
For modelform:
class SomeForm(forms.ModelForm):
...
def save(self, commit=True ,*args, **kwargs):
request = None
if kwargs.has_key('request'):
request = kwargs.pop('request')
m = super(SomeForm, self).save(commit=False, *args, **kwargs)
if m.user is None and request is not None:
m.user= request.user
m.save()
in views:
def post(self, request):
if form.is_valid():
form.save(request=request)
return ...