I have the following :
I am working with DRF, based JWT token.
I want to associate an experiment with a USER, i.e when a post request is arriving I want to be able to save that post request with the Foreginkey it needed for the author by the user whom sent the request.
The POST request is always authenticated and never anonymous, i.e request.user is always exist ( I can see it when debugging)
I tried to add the following
def create(self, request, **kwargs):
request.data["author"] = request.user
serializer = ExperimentsSerializers(data=request.data)
if serializer.is_valid():
serializer.save()
return....
But is_valid return always False ( the only time ts was true, was when I took out the author from the ExperimentsSerializers fields....
will be happy for any leads....
my code attached below
Model.py:
class User(AbstractUser):
pass
def __str__(self):
return self.username
class Experiments(models.Model):
name = models.CharField(max_length=40)
time = models.DateTimeField(default=timezone.now)
author = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
View.py:
filter_backends = [DjangoFilterBackend, filters.OrderingFilter]
serializer_class = ExperimentsSerializers
queryset = Experiments.objects.all()
filterset_fields = '__all__'
permission_classes = (permissions.IsAuthenticated,)
serializers.py
class ExperimentsSerializers(serializers.ModelSerializer):
class Meta:
model = models.Experiments
fields = '__all__'
You can just pass additional data with save arguments:
def create(self, request, **kwargs):
serializer = ExperimentsSerializers(data=request.data)
if serializer.is_valid():
serializer.save(author=request.user)
Note that you may need to specify author field as read_only so it would not be required in request body:
class ExperimentsSerializers(serializers.ModelSerializer):
class Meta:
model = models.Experiments
fields = '__all__'
read_only_fields = ['author']
One more approach can be to use
HiddenField with default value set to CurrentUserDefault
This way that field will not be exposed at the same time current user will be accessible and other operations can be done on that user context.
author = serializers.HiddenField(default=serializers.CurrentUserDefault())
Something like this:
class ExperimentsSerializers(serializers.ModelSerializer):
author = serializers.HiddenField(default=serializers.CurrentUserDefault())
class Meta:
model = models.Experiments
fields = '__all__'
Reference :
HiddenField - https://www.django-rest-framework.org/api-guide/fields/#hiddenfield
CurrentUserDefault - https://www.django-rest-framework.org/api-guide/validators/#currentuserdefault
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 have this Serializer
class RemovePermissionsSerializer(serializers.ModelSerializer):
user_permissions = serializers.SerializerMethodField()
def get_user_permissions(self, instance):
print(1)
**logic**
return data
class Meta:
model = User
fields = [
"user_permissions"
]
and a generic viewset with this action
#action(
methods=["patch", "put"],
detail=True,
url_name="add-permissions",
url_path="add-permissions"
)
def add_permissions_request(self, request, pk):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
user = self.get_object()
user_permissions = serializer.validated_data.get("user_permissions")
response = User.add_permissions(user, user_permissions)
return Response(response, status=status.HTTP_200_OK)
the function get_user_permissions is not being called whatever I put in it, even print() is not showing anything, any help please?
Here you use the serializer in the other way. A SerializerMethodField is read-only: since a function is always input to output. Here you are trying to work with it in the write direction.
This is one of the many reasons why using a SerializerMethodField is often not a good idea.
Usually it makes more sense to work with a sub-serializer, a PrimaryKeyRelatedField [drf-doc], or a SlugRelatedField [drf-doc].
You can for example use:
from django.contrib.auth.models import Permission
class PermissionSerializer(serializers.ModelSerializer):
class Meta:
model = Permission
fields = ['__all__']
class RemovePermissionsSerializer(serializers.ModelSerializer):
user_permissions = PermissionSerializer(source='permission', many=True)
class Meta:
model = User
fields = ['user_permissions']
then you specify all the details of the permission, or you can work with a PrimaryKeyRelatedField or SlugRelatedField to let the user specify the primary key of the permission, or some other field ("slug"):
from django.contrib.auth.models import Permission
class RemovePermissionsSerializer(serializers.ModelSerializer):
user_permissions = serializers.PrimaryKeyRelatedField(
source='permission', many=True, queryset=Permission.objects.all()
)
class Meta:
model = User
fields = ['user_permissions']
I am trying to create a booking api for a website.
For this, I used perform_create function in ListCreateApiView. But, it was someone else who helped me and told me to use perform_create function.
But, I was thinking, it should be possible using create function and is a right approach rather than perform_create function.
Also, I don't really know the difference between these two functions and don't really know when to use which
Here is my code:
class BookingCreateAPIView(ListCreateAPIView):
permission_classes= [IsAuthenticated]
queryset = Booking.objects.all()
serializer_class = BookingSerializer
def perform_create(self, serializer):
# user = self.request.user
package = get_object_or_404(Package, pk= self.kwargs['pk'])
serializer.save(user=self.request.user,package=package)
Here is my serializer:
class BookingSerializer(serializers.ModelSerializer):
# blog = serializers.StringRelatedField()
class Meta:
model = Booking
fields = ['name', 'email', 'phone', 'bookedfor']
Here is my model:
class Booking(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
package = models.ForeignKey(Package, on_delete=models.CASCADE, related_name='package')
name = models.CharField(max_length=255)
email = models.EmailField()
phone = models.CharField(max_length=255)
bookedfor = models.DateField()
created_at = models.DateTimeField(auto_now_add=True)
class Meta:
ordering = ('created_at',)
if you looking in source code:
ListCreateApiView call
def post(self, request, *args, **kwargs):
return self.create(request, *args, **kwargs)
then inside create call
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)
That mean perform_create called after serializer validated.
And for what you want, you can override different method.
In my opinion:
If you want custom Response return from api, you override create
If you want doing anything special after serializer validated, but before object created in database, you override perform_create. Like your example, it want checking Package exists and save request.user in field user
I have a project with Courses and Sections in it. I am building this project with django rest framework and i have the function that should create a section with 3 fields: course, title and creator. I want to understand how i can take a course slug from url and put it in course field, i mean don't pick course manually. How to implement that?
models.py
class CourseSections(models.Model):
creator = models.ForeignKey(User,related_name='creator_sections',on_delete=models.CASCADE,null=True)
title = models.CharField(max_length=50)
course = models.OneToOneField(Course, related_name='course_section', on_delete=models.CASCADE,null=True)
serializers.py
class CourseSectionSerializer(serializers.ModelSerializer):
class Meta:
model = CourseSections
fields = ['creator', 'title', 'course']
def create(self, validated_data):
instance = self.Meta.model(**validated_data)
request = self.context.get('request')
if request and hasattr(request, 'user'):
user = request.user
instance.save()
return instance
views.py
class SectionsCreateAPIView(CreateAPIView):
queryset = CourseSections.objects.all()
serializer_class = CourseSectionSerializer
permission_classes = [IsAuthenticatedOrReadOnly, IsAdminOrReadOnly]
lookup_field = 'slug'
lookup_url_kwarg = 'course__slug'
def perform_create(self, serializer):
serializer.save(creator=self.request.user)
urls.py
url(r'^sections/(?P<course__slug>[-\w]+)/create/$', SectionsCreateAPIView.as_view(), name='create_sections'),
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