i am using django rest framework for my project.
i have a gallery model which has a user field. this field is a Foreign key to the user that created the gallery. and a name field which is gallery's name.
class Gallery(models.Model):
user = models.ForeignKey(User,related_name='galleries')
name = models.CharField(max_length=64)
here is my serializer:
class GallerySerializer(serializers.ModelSerializer):
user = serializers.ReadOnlyField(source='user.username')
def validate_name(self, name):
if len(name) < 3:
raise serializers.ValidationError("name must at least 3 letters")
return name
class Meta:
model = Gallery
fields = ('id', 'user', 'name',)
def create(self, validated_data):
"""
Create and return a new `Gallery` instance, given the validated data.
"""
return Galleries.objects.create(**validated_data)
and here is my views:
class GalleryList(generics.ListCreateAPIView):
queryset = Gallery.objects.all()
serializer_class = GallerySerializer
permission_classes = (permissions.IsAuthenticatedOrReadOnly,
IsOwnerOrReadOnly,)
def perform_create(self, serializer):
serializer.save(user=self.request.user, )
class GalleryDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = Gallery.objects.all()
serializer_class = GallerySerializer
permission_classes = (permissions.IsAuthenticatedOrReadOnly,
IsOwnerOrReadOnly,)
i want to validate post (and put) data, and prevent user from creating two gallery with the same name.(if "user A" has a "gallery A", he can't make a gallery with the same name or rename another gallery to "gallery A". but "user B" is allowed to make a "gallery A")
to do so, i need to check if user.galleries.filter(name=name) exist or not.
but i don't know how to get user in serializer.
You get it from the context that was passed to the serializer. This is done automatically for you, so you can access it like that:
user = self.context['request'].user
If you want to have the ability to specify another user, you can add it to the context yourself:
# This method goes in your view/viewset
def get_serializer_context(self):
context = super().get_serializer_context()
context['user'] = #whatever you want here
return context
That would make the user available as self.context['user']. This is a bit more verbose, but it is more versalite as it allows the serializer to be passed a user different from the one who did the request.
Related
I'm working on a REST API with Django Rest Framework and in my ModelViewSet I need to validate that the current request.user has the right to edit a particular object.
I have found the part of the documentation that specifies how permissions work– but this is on the ViewSet side, and not the serializer side:
class FooViewSet(viewsets.ModelViewSet):
model = Foo
serializer_class = FooSerializer
def get_queryset(self):
return self.request.user.foos.all()
def perform_create(self, serializer):
serializer.save(user=self.request.user)
def get_permissions(self):
if self.action == "list":
permission_classes = [permissions.IsAuthenticated]
else:
permission_classes = [IsObjectUser]
return [permission() for permission in permission_classes]
This will work fine for 403ing when it's not the appropriate user, but I believe I should also be doing serializer-level validation? How can I get the object in question in my validate method to check against?
class FooSerializer(serializers.ModelSerializer):
class Meta:
model = Foo
fields = [
"type",
"title",
"description",
"image",
]
def validate(self, attrs):
# <-- how can I get the object so I can check against the self.request.user?
My answer is that you shouldn't. Ideally your serializers don't know about the request. That's the views realm (with exceptions). Also, since user isn't specified in fields of FooSerializer it doesn't make sense to validate the user. If the user could be specified, then it should be validated.
def validate(self, attrs):
# <-- how can I get the object so I can check against the self.request.user?
userobj=self.context['request'].user.
print(attrs)
title=attrs.get("title")
enter code here
attrs.update({'title': title})
attrs = super().validate(attrs)
return attrs
News to Django and Django Rest.
I'm trying to create 2 types of view, based on the role of the user.
While creating a new demand, I have one field that I would like to not show to one role(leaving it null in DB) but to show it to the other role.
The thing is that I don't see how I could do that.
If anyone could guide me in the right direction
Here is what I have :
models.py
class demand(models.Model):
id = models.AutoField(primary_key=True)
title = models.CharField(max_length=60)
description = models.CharField(max_length=400)
assigned = models.CharField(max_length=60)
serializer.py
class demandSerializer(serializers.ModelSerializer):
class Meta:
model = models.demand
fields = ('title','description','assigned')
views.py
class demandCreateAPIView(CreateAPIView):
queryset=models.demand.objects.all()
serializer_class=serializers.demandSerializer
You can just change the request data before saving it.
class DemandCreateAPIView(CreateAPIView):
queryset = Demand.objects.all()
serializer_class = serializers.DemandSerializer
def create(self, request, *args, **kwargs):
... #do whatever check you want
request.data.pop('assigned', None) # I've choose this parameter as an example
return super(DemandCreateAPIView, self).create(request, *args, **kwargs)
I have defined a Model like this:
class Doctor(models.Model):
name = models.CharField(max_length=100)
is_active = models.BooleanField(default=True)
My Serializer:
class DoctorSerializer(serializers.ModelSerializer):
class Meta:
model = Doctor
fields = ('id', 'name', )
In View:
class DoctorViewSet(viewsets.ModelViewSet):
queryset = Doctor.objects.all()
serializer_class = DoctorSerializer
Now, I can delete a doctor by calling the url: 'servername/doctors/id/', with the http method DELETE. However, I want to override the delete behavior for this model. I want that, when the user deletes a record, it's is_active field is set to false, without actually deleting the record from the database. I also want to keep the other behaviors of Viewset like the list, put, create as they are.
How do I do that? Where do I write the code for overriding this delete behavior?
class DoctorViewSet(viewsets.ModelViewSet):
queryset = Doctor.objects.all()
serializer_class = DoctorSerializer
def destroy(self, request, *args, **kwargs):
doctor = self.get_object()
doctor.is_active = False
doctor.save()
return Response(data='delete success')
I'm using Django rest framework, and my model is like this, Every Act can have more than one post.
class Act(models.Model):
user = models.ForeignKey("common.MyUser", related_name="act_user")
act_title = models.CharField(max_length=30)
act_content = models.CharField(max_length=1000)
act_type = models.IntField()
class Post(models.Model):
user = models.ForeignKey("common.MyUser", related_name="post_user")
act = models.ForeignKey("activities.Act", related_name="post_act")
post_title = models.CharField(max_length=30, blank=True)
post_content = models.CharField(max_length=140)
my view.py in DRF:
class PostList(generics.ListCreateAPIView):
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
queryset = Post.objects.all()
serializer_class = PostAllSerializer
def perform_create(self, serializer): #self is a instance of class or is a class here?
serializer.save(user=self.request.user)
This works fine, but what I want now is if act_type = 1 means this is a private Act and only the act author can create post under this act.I wonder how to use different permission_classes depend on different Act.Maybe looks like:
class PostList(generics.ListCreateAPIView):
if self.act_type == 1:
permission_classes = (permissions.IsAuthenticatedOrReadOnly,IsActCreatorOrReadOnly)
else
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
queryset = Post.objects.all()
serializer_class = PostAllSerializer
def perform_create(self, serializer): #self is a instance of class or is a class here?
serializer.save(user=self.request.user)
And I also want to know how to write this permissions.py:
class IsActCreatorOrReadOnly(permissions.BasePermission):
"""
Custom permission to only allow owners of an object to edit it.
"""
def has_object_permission(self, request, view, obj):
if request.method in permissions.SAFE_METHODS:
return True
return obj.act.user == request.user
I don't know what obj really means here, and the error tell me obj.act doesn't exist.
EDIT
Here is my postSerializer.
class PostAllSerializer(serializers.ModelSerializer):
"""Posts api fields"""
post_user = UserSerializer(source="user", read_only=True)
post_author = serializers.ReadOnlyField(source='user.user_name')
class Meta:
model = Post
fields = ("id", "act", "post_author", "post_title", "post_content",)
I tried this, but not working, I still can create the post even I'm not the author of the Act(but the act_id is wrong):
def create(self, request, *args, **kwargs):
act_type = request.data.get("act_type")
if act_type == 0:
act_id = request.data.get("act")
act = Act.objects.get(pk=act_id)
if request.user != act.user:
return Response(status=403)
return super().create(request)
For using different permission classes, there is the get_permissions method that you can overwrite on your PostList view:
def get_permissions(self):
if self.request.method == 'POST':
return (OnePermission(),)
elif # other condition if you have:
return (AnotherPermission(),)
return (YetAnotherPermission(),)
However, in your case you can't use object level permissions, because you don't have an object instance yet. From the DRF docs (highlights by me):
REST framework permissions also support object-level permissioning. Object level permissions are used to determine if a user should be allowed to act on a particular object, which will typically be a model instance.
Object level permissions are run by REST framework's generic views when .get_object() is called.
When doing a POST request, you don't have any object yet, thus the object level permissions won't be invoked.
One way you could achieve what you want is by checking it in the create method of PostList view. Something like this (hypothetical code):
class PostList(generics.ListCreateAPIView):
...
def create(self, request):
act_id = request.data.get('act') # depending on your PostSerializer, the logic of getting act id can vary a little
act = Act.objects.get(pk=act_id) # assuming act always exists, otherwise account for in-existing act
if act.user != request.user:
return Response({details: "You shall not pass!!!", status=200) # change to a status and message you need here
# logic of Post creation here
Good luck!
I have a model with a ForeignKey
models.py
class B(models.Model):
user = models.ForeignKey(contrib.auth.User)
class A(models.Model):
b = models.ForeignKey(B)
serializers.py
class ASerializer(serializers.ModelSerializer):
class Meta:
model = A
fields = ['b']
views.py
class AViewSet(iewsets.ModelViewSet):
queryset = A.objects.all()
serializer_class = ASerializer
Now what I want is to restrict the A.b values to the B instances owned by the currently logged-in user.
I know how to enforce that at saving-time, but I would like to only present the relevant queryset in the dropdown choice in the browsable API interface.
If one can define a queryset argument on the RelatedField, it's static and can't depend on the current request.
Any ideas ?
Well you could try overriding queryset in init of serializer.
something like
def __init__(self, *args, **kwargs):
super(MySerializerClass, self).__init__(*args, **kwargs)
if self.context.get('request', None):
field = self.fields.get('b')
field.queryset = field.queryset.filter(user=request.user)
Current user shall be accessible through self.context.
You can modify the get_queryset:
class AViewSet(iewsets.ModelViewSet):
serializer_class = ASerializer
def get_queryset(self):
user = self.request.user
return A.objects.filter(b__user = user)
ref: http://www.django-rest-framework.org/api-guide/filtering/