Is it possible to get the current user in a model serializer? I'd like to do so without having to branch away from generics, as it's an otherwise simple task that must be done.
My model:
class Activity(models.Model):
number = models.PositiveIntegerField(
blank=True, null=True, help_text="Activity number. For record keeping only.")
instructions = models.TextField()
difficulty = models.ForeignKey(Difficulty)
categories = models.ManyToManyField(Category)
boosters = models.ManyToManyField(Booster)
class Meta():
verbose_name_plural = "Activities"
My serializer:
class ActivitySerializer(serializers.ModelSerializer):
class Meta:
model = Activity
And my view:
class ActivityDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = Activity.objects.all()
serializer_class = ActivityDetailSerializer
How can I get the model returned, with an additional field user such that my response looks like this:
{
"id": 1,
"difficulty": 1,
"categories": [
1
],
"boosters": [
1
],
"current_user": 1 //Current authenticated user here
}
I found the answer looking through the DRF source code.
class ActivitySerializer(serializers.ModelSerializer):
# Create a custom method field
current_user = serializers.SerializerMethodField('_user')
# Use this method for the custom field
def _user(self, obj):
request = self.context.get('request', None)
if request:
return request.user
class Meta:
model = Activity
# Add our custom method to the fields of the serializer
fields = ('id','current_user')
The key is the fact that methods defined inside a ModelSerializer have access to their own context, which always includes the request (which contains a user when one is authenticated). Since my permissions are for only authenticated users, there should always be something here.
This can also be done in other built-in djangorestframework serializers.
As Braden Holt pointed out, if your user is still empty (ie _user is returning None), it may be because the serializer was not initialized with the request as part of the context. To fix this, simply add the request context when initializing the serializer:
serializer = ActivitySerializer(
data=request.data,
context={
'request': request
}
)
A context is passed to the serializer in REST framework, which contains the request by default. So you can just use self.context['request'].user inside your serializer.
I had a similar problem - I tried to save the model that consist user in, and when I tried to use
user = serializers.StringRelatedField(read_only=True, default=serializers.CurrentUserDefault()) like on official documentation - but it throws an error that user is 'null'. Rewrite the default create method and get a user from request helped for me:
class FavoriteApartmentsSerializer(serializers.ModelSerializer):
user = serializers.StringRelatedField(read_only=True, default=serializers.CurrentUserDefault())
class Meta:
model = FavoriteApartments
exclude = (
'date_added',
)
def create(self, validated_data):
favoriteApartment = FavoriteApartments(
apartment=validated_data['apartment'],
user=self.context['request'].user
)
favoriteApartment.save()
return favoriteApartment
I modified the request.data:
serializer = SectionSerializer(data=add_profile_data(request.data, request.user))
def add_profile_data(data, user):
data['user'] = user.profile.id
return data
Related
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 have a Django project as following code in model
class Report(models.Model):
created_by_user=models.ForeignKey(User,on_delete=models.CASCADE)
following code in serializer
class ReportSerializer(serializers.ModelSerializer):
class Meta:
model=Report
fields='__all__'
and following code in view
class ReportCreateView(APIView):
def post(self,request, *args, **kwargs):
received_data=ReportSerializer(data=request.data)
if received_data.is_valid():
received_data.save()
return Response(received_data.data, status=status.HTTP_201_CREATED)
return Response(received_data.errors,status.HTTP_400_BAD_REQUEST)
when I send a post request by postman and send username and password in Authorization tab
it error:
{
"created_by_user": [
"This field is required."
]
}
but if I type username or password incorrect it will be
{
"detail": "Invalid username/password."
}
can everybody help me?
Your serializer has no idea about the currently logged-in user.You to pass it as context from request. user or request.
I personally prefer CurrentUserDefault to be used in the serializer. To make it work we need to pass the request as context because CurrentUserDefault picks user from context request. We need to update our views and serializer code as follows
Views file: Add request as context context
class ReportCreateView(APIView):
def post(self,request, *args, **kwargs):
received_data=ReportSerializer(data=request.data, context = {"request": request})
if received_data.is_valid():
received_data.save()
return Response(received_data.data, status=status.HTTP_201_CREATED)
return Response(received_data.errors,status.HTTP_400_BAD_REQUEST)
serializer.py: Update your serializer to auto-populate created_by_user
class ReportSerializer(serializers.ModelSerializer):
created_by_user = serializers.HiddenField(default=serializers.CurrentUserDefault())
class Meta:
model=Report
fields='__all__'
It will solve your user field required issue.
"created_by_user": ["This field is required."]
Now coming to your next part of the issue that is related to incorrect passwords.
By default, APIView picks the default authentication class from settings. Inside projects settings.py, we mostly write these lines while using DRF and they serve as default authentication for APIView:
From settings.py
REST_FRAMEWORK = {
# Use Django's standard `django.contrib.auth` permissions,
# or allow read-only access for unauthenticated users.
"DEFAULT_PERMISSION_CLASSES": [
"rest_framework.permissions.IsAuthenticated",
],
# Authentication settings
"DEFAULT_AUTHENTICATION_CLASSES": [
"rest_framework.authentication.SessionAuthentication",
],
...
}
Inside APIView you can have a look at default permission_classes, and authentication_classes
From inside APIView:
authentication_classes = api_settings.DEFAULT_AUTHENTICATION_CLASSES
throttle_classes = api_settings.DEFAULT_THROTTLE_CLASSES
permission_classes = api_settings.DEFAULT_PERMISSION_CLASSES
that is when you type an invalid password:
"detail": "Invalid username/password."
Provide the correct username and password to your APIView from postman so that it gets the requested logged-in user for auto-populates at DB level.
you don't perform any process on your user data and just need to save the request user, due to that I think you don't need a serializer field for it and it's better to get your current user in view. also if you need more fields to serialize, you can make created_by_user read_only true and set its value on your view.
for example, if you have report name and report desc field in your model:
class Report(models.Model):
created_by_user = models.ForeignKey(User, on_delete=models.CASCADE)
name = models.CharField(max_length=256)
desc = models.TextField()
perform your serializer like this :
class ReportSerializer(serializers.ModelSerializer):
class Meta:
model = Report
fields = '__all__'
extra_kwargs = {
'created_by_user': {'read_only': True},
}
then set created_by_user value in your view:
class ReportCreateView(APIView):
def post(self, request, *args, **kwargs):
request.data['created_by_user'] = request.user # just need add this line
received_data = ReportSerializer(data=request.data)
if received_data.is_valid():
received_data.save()
return Response(received_data.data, status=status.HTTP_201_CREATED)
return Response(received_data.errors, status.HTTP_400_BAD_REQUEST)
I've been scratching my head about this problem for a couple of hours now. Basically, I have two models: User and Project:
class User(AbstractUser):
username = None
email = models.EmailField("Email Address", unique=True)
avatar = models.ImageField(upload_to="avatars", default="avatars/no_avatar.png")
first_name = models.CharField("First name", max_length=50)
last_name = models.CharField("Last name", max_length=50)
objects = UserManager()
USERNAME_FIELD = "email"
class Project(models.Model):
name = models.CharField("Name", max_length=8, unique=True)
status = models.CharField(
"Status",
max_length=1,
choices=[("O", "Open"), ("C", "Closed")],
default="O",
)
description = models.CharField("Description", max_length=3000, default="")
owner = models.ForeignKey(
User, on_delete=models.SET_NULL, null=True, related_name="project_owner"
)
participants = models.ManyToManyField(User, related_name="project_participants", blank=True)
created_at = models.DateTimeField(auto_now_add=True)
I use standard ModelViewSets for both of them, nothing changed. Then there's my Project serializer:
class ProjectSerializer(serializers.ModelSerializer):
class Meta:
model = Project
fields = "__all__"
status = serializers.CharField(source="get_status_display", required=False)
owner = UserSerializer()
participants = UserSerializer(many=True)
I use UserSerializers here, because having them achieved first of my two goals:
I wanted to get the user data when getting the project from the API -> owner is a serialized User with all the fields, same for participants, but it's a list of users
I want to be able to partially update the Project, for example add a participant
So I searched through the docs and SO and I always found answers that answer one of those questions, but never both of them.
The thing with my second goal is: when I do the partial update (via PATCH, of course), I get the response that: "Invalid data. Expected a dictionary, but got int." when I pass a list of ints (user ids) for the participants. I thought: okay, maybe I have to pass the whole user data to change it. But then I realised: when I remove the UserSerializer from ProjectSerializer - passing just the list of ints in Postman works just fine. And that is a life saver, cuz who wants to create a request with a whole bunch of data, when I can just pass user ids.
But then of course when I remove the UserSerializer, when I call get project, I get participants: [1,2,3,4,...], not participants: [{"id": 1, "name": "John", ...}, ...}]. And I really want this behavior, because I don't want to make additional API calls just to get the users' data by their IDs.
So summing up my question is: Is there a way to leave those serializers in place but still be able to partially update my model without having to pass whole serialized data to the API (dicts instead of IDs)? Frankly, I don't care about the serializers, so maybe the question is this: Can I somehow make it possible to partially update my Products' related fields like owner or participants just by passing the related entities IDs while still maintaining an ability to get my projects with those fields expanded (serialized entities - dicts, instead of just IDs)?
#Edit:
My view:
from rest_framework import viewsets, permissions
from projects.models import Project
from projects.api.serializers import ProjectSerializer
class ProjectViewSet(viewsets.ModelViewSet):
queryset = Project.objects.all()
serializer_class = ProjectSerializer
permission_classes = [permissions.IsAuthenticated]
lookup_field = "name"
def get_queryset(self):
if self.request.user.is_superuser:
return Project.objects.all()
else:
return Project.objects.filter(owner=self.request.user.id)
def perform_create(self, serializer):
serializer.save(owner=self.request.user, participants=[self.request.user])
Answer:
To anyone reading this, I've solved this problem and I actually created a base class for all my viewsets that I want this behavior to be in:
from rest_framework.response import Response
class ReadWriteViewset:
write_serializer_class = None
read_serializer_class = None
def update(self, request, *args, **kwargs):
partial = kwargs.pop("partial", False)
instance = self.get_object()
write_serializer = self.write_serializer_class(
instance=instance,
data=request.data,
partial=partial,
)
write_serializer.is_valid(raise_exception=True)
self.perform_update(write_serializer)
read_serializer = self.read_serializer_class(instance)
if getattr(instance, "_prefetched_objects_cache", None):
# If 'prefetch_related' has been applied to a queryset, we need to
# forcibly invalidate the prefetch cache on the instance.
instance._prefetched_objects_cache = {}
return Response(read_serializer.data)
Then you use it kinda like in here
I'm assuming that you are using a ModelViewSet. You could use different serializers for different methods.
class ProjectViewSet(viewsets.ModelViewSet):
def get_serializer_class(self):
if self.action in ['create', 'update']:
return WriteProjectSerializer # your serializer not using `UserSerializer` that works for updating
return ProjectSerializer # your default serializer with all data
Edit for using different serializers in same method:
# you can override `update` and use a different serializer in the response. The rest of the code is basically the default behavior
def update(self, request, *args, **kwargs):
partial = kwargs.pop('partial', False)
instance = self.get_object()
write_serializer = WriteProjectSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
instance = self.perform_update(serializer)
read_serializer = ProjectSerializer(instance)
if getattr(instance, '_prefetched_objects_cache', None):
# If 'prefetch_related' has been applied to a queryset, we need to
# forcibly invalidate the prefetch cache on the instance.
instance._prefetched_objects_cache = {}
return Response(read_serializer.data)
A good way to see the default code for all these methods is using Classy DRF. You can see all methods that come with using ModelViewSet and use that code with some changes. Here I'm using the default code for update but changing for a new serializer for the response.
I have two models first as parent model "Country", that filled before the second one as child model "City". as the following
class Country(models.Model):
name = models.CharField(max_length=35)
icon = models.ImageField()
def __str__(self):
return self.name
class City(models.Model):
name = models.CharField(max_length=35)
country = models.ForeignKey(to=Country, on_delete=models.CASCADE)
def __str__(self):
return self.name
My serializers.py for my need as following :
class CountrySerializer(ModelSerializer):
class Meta:
model = Country
fields = '__all__'
class CitySerializer(ModelSerializer):
country = serializers.PrimaryKeyRelatedField(queryset=Country.objects.all())
class Meta:
model = City
fields = ('name', 'country')
view.py
class CountryAPIView(ListAPIView):
queryset = Country.objects.all()
serializer_class = CountrySerializer
permission_classes = [AllowAny, AllowAnonymous]
class CityAPIView(ListAPIView):
queryset = City.objects.all()
serializer_class = CitySerializer
permission_classes = [AllowAny, AllowAnonymous]
def post(self, request):
serializer = CitySerializer(data=request.data)
if serializer.is_valid(raise_exception=ValueError):
serializer.create(validated_data=request.data)
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.error_messages,
status=status.HTTP_400_BAD_REQUEST)
now when i run get api it run and gives me a result fine . But when im trying to create a new city and set "country":"id" in json i got this error
Cannot assign "2": "City.country" must be a "Country" instance.
So if i was not clear ,, what i need is exactly set foreign key to city when i create city ,, not create city and country,,
please any one had a solution help, because i tried many ways and read the django rest framework docs about this point but i didn't got it.
First of all, the raise_exception should be a boolean value (either True or False)
You could avoid this error by using inheriting the view class from ListCreateAPIView
from rest_framework.generics import ListCreateAPIView
class CityAPIView(ListCreateAPIView):
queryset = City.objects.all()
serializer_class = CitySerializer
permission_classes = [AllowAny, AllowAnonymous]
You don't want to use the post() method if you're using ListCreateAPIView, because DRF will take care of that part well.
Suggestion
Since you're dealing with CRUD functionalities of the model, you can use the DRF's ModelViewset class
you are not using the validated data to create a new city, just change this line:
serializer.create(validated_data=request.data)
to this:
serializer.save()
when you perform serializer.save(), the serializer will use its validated data.
also, DRF has a generic view(ListCreateAPIView) that covers your use-case.
In my app, users have a wall, similar to the old Facebook wall. A user is able to post comments on other users walls. I have a serializer with a base structure like this:
class UserWallCommentSerializer(serializers.ModelSerializer):
class Meta:
model = UserWallComment
fields = ('uid', 'uidcommenter', 'idwall', 'created', 'description')
read_only_fields = ('uid', 'uidcommenter', 'idwall', 'created')
uid and uidcommenter are foreignkeys to the user model, idwall is the PK, and description is the comment itself.
When a comment is created/edited, uid and uidcommenter needs to be set by the backend. A user can not be allowed to change these fields.
Lets say I have the variables uid and uidcommenter in my view that is calling the serializer - how can I pass these variables along to the serializer so that a UserWallComment is created?
I have tried setting uid and uidcommenter using the SerializerMethodField (passing the PK's in the context variable), but the database says I am passing NULL PK's:
class UserWallCommentSerializer(serializers.ModelSerializer):
uid = serializers.SerializerMethodField('setUid')
class Meta:
model = UserWallComment
fields = ('uid', 'uidcommenter', 'idwall', 'created', 'description')
read_only_fields = ('uidcommenter', 'idwall', 'created')
def setUid(self):
return self.context['uid']
My view code (idwall is the pk of the wall):
class MemberWall(APIView):
def post(self, request, requestUid, idwall):
uid = request.user.uid
serializer = UserWallCommentSerializer(data=request.DATA, context={'uid': requestUid, 'uidcommenter': uid})
if serializer.is_valid():
serializer.save()
return Response(serializer.data['uid'], status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
the documentation says that the SerializerMethodField is used only for representation of the object. Which means it is used only when you return your data as a response.
By default the serializer get's the request passed:
def get_serializer_context(self):
"""
Extra context provided to the serializer class.
"""
return {
'request': self.request,
'format': self.format_kwarg,
'view': self
}
This means that you can overwrite de default save, update methods of the serializer and set the relevant fields. You should be able to access then using: self._context.request.user.uid
I didn't try this but it should work.