I am creating a functionality where a project/showcase model has an administrators ManyToManyField, which would contain a list of users that can control a project.
I am having issues adding users to this field (the user will be gotten from user input from the front end). I was able to set the person that creates a project as an administrator by default, but adding other administrators has not been successful.
models.py
class Showcase(models.Model):
title = models.CharField(max_length=50)
description = models.TextField(null=True)
skill_type = models.ForeignKey(Skill, on_delete=models.CASCADE)
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.DO_NOTHING, related_name="Showcases")
content = models.TextField(null=True)
created_on = models.DateTimeField(auto_now_add=True)
updated_on = models.DateTimeField(auto_now=True)
voters = models.ManyToManyField(settings.AUTH_USER_MODEL, related_name="upvotes")
slug = models.SlugField(max_length=255, unique=True)
administrator = models.ManyToManyField(settings.AUTH_USER_MODEL, related_name="administrators", blank=True)
serializers.py
class ShowcaseAdminSerializer(serializers.ModelSerializer):
class Meta:
model = Showcase
fields = ['administrator',]
With the view below I wanted only administrators to be able to add new administrators to the showcase. The user to be added will be gotten from the front end input instead of the URL (I hope this part is understood).
views.py
class showcaseAddAdminAPIView(APIView):
'''
Add a user as an admin to a showcase
'''
serializer_class = ShowcaseAdminSerializer
permission_classes = [IsAdmin]
def post(self, request, slug):
showcase = get_object_or_404(Showcase, slug=slug)
if request.user in showcase.administrator.all():
showcase.administrator.add(user.slug)
showcase.save()
serializer = self.serializer_class(showcase)
return Response(serializer.data, status=status.HTTP_200_OK)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
with the view above, I was wonder if the check for if the user is an administrator before he can make the action should also be done using permissions. I have permission below which is not working though.
urls.py
path("<slug:slug>/addadmin/", qv.showcaseAddAdminApiview.as_view(), name="add-administrator-to-showcase"),
permissions
class IsAdmin(permissions.BasePermission):
def has_object_permission(self, request, view, obj):
if request.method in permissions.SAFE_METHODS:
return False
return obj.administrator == request.user
i tried using this permission to make suer only administrators can add admin to the showcase. but it doesnt work.
Your permission won't work because as per documentation:
Note that the generic views will check the appropriate object level permissions, but if you're writing your own custom views, you'll need to make sure you check the object level permission checks yourself. You can do so by calling self.check_object_permissions(request, obj) from the view once you have the object instance. This call will raise an appropriate APIException if any object-level permission checks fail, and will otherwise simply return.
So the view should look like this:
class showcaseAddAdminAPIView(APIView): # Use CamelCase for naming class
'''
Add a user as an admin to a showcase
'''
...
def post(self, request, slug):
showcase = get_object_or_404(Showcase, slug=slug)
self.check_object_permissions(request, showcase)
Update the permission class:
class IsAdmin(permissions.BasePermission):
def has_object_permission(self, request, view, obj):
if request.method in permissions.SAFE_METHODS:
return False
return request.user.administrators.filter(pk=obj.pk).exists()
And update the view:
# rest of the code
def post(self, request, slug): # PUT is more suited for updating instance
showcase = get_object_or_404(Showcase, slug=slug)
try:
self.check_object_permissions(request, showcase)
serializer = self.serializer_class(showcase, data=request.data, partial=True)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
else:
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
except APIException:
return Response(status=status.HTTP_403_FORBIDDEN)
Update
Override the update method in serializer:
class ShowcaseAdminSerializer(serializers.ModelSerializer):
def update(self, instance, validated_data):
users = validated_data.get('administrator')
for user in users:
instance.administrator.add(user)
return instance
# rest of the code
As Opeyemi said if request.user in showcase.administrator.all(): will never add new administrators, if the user making the request is not an administrator it will be denied and if the user making a request is already an administrator it wont add anything new. You need to pass in an user id and use it to add a new user while being an administrator, showcase.administrator.add(user.id)
Related
I have the following model + serializer where I send a post request to create a new model instance. The user sending the post request is related to a Company which I want to pass as related model instance to the serializer.
But how to actually define this instance and attach it to the serializer instance within the post view?
# views.py
class OfferList(APIView):
"""
List all Offers or create a new Offer related to the authenticated user
"""
def get(self, request):
offers = Offer.objects.filter(company__userprofile__user=request.user)
serializer = OfferSerializer(offers, many=True)
return Response(serializer.data)
def post(self, request):
serializer = OfferSerializer(data=request.data)
# Add related company instance
company = Company.objects.get(userprofile__user=request.user)
serializer['company'] = company # this doesn't work
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)
# offer/models.py
class Offer(models.Model):
"""
A table to store Offer instances
"""
# Relations
company = models.ForeignKey(Company, on_delete=models.CASCADE)
..
# serializers.py
class OfferSerializer(serializers.ModelSerializer):
class Meta:
model = Offer
fields = '__all__'
user/models.py
class UserProfile(models.Model):
"""
Extends Base User via 1-1 for profile information
"""
# Relations
user = models.OneToOneField(User, on_delete=models.CASCADE)
company = models.ForeignKey(Company, on_delete=models.CASCADE, null=True)
One simple way is to pass your company instance through your serializer. So maybe changing your post method to something like:
from rest_framework.generics import get_object_or_404
def post(self, request):
serializer = OfferSerializer(data=request.data)
# Add related company instance
if serializer.is_valid():
company = get_object_or_404(Company, userprofile__user=request.user) # raising 404 exception if related company does not exist,
# and you're sure that there is one and only one company for this user not more!
serializer.save(company=company)
return Response(serializer.data,
status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
and then change your serializer fields to exclude company field from them (because you are already sending this data through your serializer):
class OfferSerializer(serializers.ModelSerializer):
class Meta:
model = Offer
fields = ["some_field", "other_field"] # Do not include company in your fields.
# also note that since I didn't know your Offer's fields I used ["some_field", "other_field"] for fields
hope this solves your problem.
Hmm, I think the use of the context for serializers will be a nice way to solve your case.
# serializers.py
class OfferSerializer(serializers.ModelSerializer):
class Meta:
model = Offer
# now that we do not want company from the request body
exclude = ["company"]
def create(self, validated_data):
# add company to the validated data from the context
# we can feed the context from the APIView
validated_data["company"] = self.context.get("company", None)
...
return super().create(validated_data)
# views.py
class OfferList(APIView):
def post(self, request):
# imo, we do not have to query for UserProfile
# if the company is assigned to the UserProfile instance for the requestor
# then, one2one relation can give us this
context = {"company": request.user.userprofile.company}
serializer = OfferSerializer(data=request.data, context=context)
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)
I am using DRF to create an API in a single page application.
I have a customer user class to which I have only added a is_manager flag and a managerEntity model where users that have the is_manager flag as True can create managerEntities becoming owners of them.
The problem is that I can't seem to figure out how to validate the data from the serializer before create method to check whether the is_manager is set or not. If set, the managerEntity should be created, if not, raise an exception.
class DeepmetricsUser(AbstractUser):
is_manager = models.BooleanField(default=False)
class managerEntity(models.Model):
id = models.AutoField(primary_key=True)
name = models.CharField(max_length=200)
owner = models.ForeignKey(get_user_model(), on_delete=models.CASCADE)
team = models.ManyToManyField(get_user_model(), blank=True)
views.py
class managersEntityViewSet(viewsets.ModelViewSet):
queryset = managerEntity.objects.all()
serializer_class = managerEntityModelSerializer
permission_classes = [permissions.IsAuthenticated]
def get_queryset(self):
return self.queryset.filter(Q(owner = self.request.user) | Q(team=self.request.user.id))
def create(self, request, *args, **kwargs):
serializer = managerEntitySerializer(data=request.data, context={"request": self.request})
serializer.is_valid(raise_exception=True)
res = serializer.save()
data = managerEntityModelSerializer(res).data
return Response(data, status=status.HTTP_201_CREATED)
serializer.py
class managerEntitySerializer(serializers.Serializer):
name = serializers.CharField(max_length=255)
owner = serializers.HiddenField(default=serializers.CurrentUserDefault())
def create(self, data):
res = managerEntity.objects.create(**data)
return res
You need to override the validate method in Serializer
def validate(self, attrs):
if not self.context["request"].user.is_manager:
raise serializers.ValidationError("Validation error")
return attrs
I found a solution that fits better my needs by using permissions. The answer provided by Shakeel is correct as I asked for validation and that should be done as he suggested but, what I really wanted to do was checking for enough clearance for the user to manipulate a resource, then, permissions is what's best fits:
class createManagerEntity(BasePermission):
message = "Not enough privilegies"
def has_permission(self, request, view):
return request.user.is_manager
I have a project that autenticate by oauth2_provider.ext.rest_framework.OAuth2Authentication. There is a token in the request headers for authenticating and identifying. I think I should not include user id in the request data explicitly when process a create action. But serializer need user info to create a new instance. So I include a user_id field in serializer, and put the value into the request.data dict after authenticating.
Is it good? Is there any better way?
serializers.py
class serializer(serializers.Serializer):
user = UserSerializer(read_only=True)
user_id = serializers.UUIDField(write_only=True)
content = serializers.CharField()
views.py
class CommentList(generics.ListCreateAPIView):
def create(self, request, *args, **kwargs):
request.data['user_id'] = request.user.id
return super(CommentList, self).create(request)
It is cleaner to override create(validated_data) in your serializer:
class CommentSerializer(serializers.Serializer):
...
def create(self, validated_data):
user = self.context['request'].user
comment = Comment.objects.create(
user=user,
**validated_data
)
return comment
See http://www.django-rest-framework.org/api-guide/serializers/#saving-instances
Then you do not need to customise your view, you can just use a generic view.
if user is authenticated then grab the value from request object and pass it to serializers by using get_serializer_context method
class CommentList(generics.ListCreateAPIView):
permission_classes = [IsAuthenticated]
def get_serializer_context(self):
return {'user_id':self.request.user.id}
class serializer(serializers.Serializer):
#your field, model
pass
def create(self, validated_data):
user_id = self.context['user_id']
return Comment.objects.create(user_id=user_id, **validated_data)
I built a Follow model to record the social networking behaviour and would like to simulate the following action. Every authenticated user can follow the others.
class Follow(models.Model):
user = models.ForeignKey(User)
follower = models.ForeignKey(User, related_name="followers")
follow_time = models.DateTimeField(auto_now_add=True, blank=True)
class Meta:
unique_together = ('user', 'follower')
def __unicode__(self):
return u'%s, %s' % (self.user.username, self.follower)
And the FollowSerializer is:
class FollowSerializer(serializers.ModelSerializer):
class Meta:
model = Follow
field = ('user', 'follower', 'follow_time')
The view that I am using is:
class FollowingEnumByUserID(generics.ListCreateAPIView):
serializer_class = FollowSerializer
def get_queryset(self):
follower_id = self.kwargs['pk']
return Follow.objects.filter(follower=follower_id)
I am registering it in the urls as:
url(r'^api/users/(?P<pk>[0-9]+)/following/$', views.FollowingEnumByUserID.as_view()),
Every authenticated user can view the following relation, no restricts. But I would like to just allow the authenticated user to add the following relation by himself/herself, which means there should be request.user == follower. How can I do this?
I would like to add the FollowingDelete view to just allow the user to add a following relation by himself/herself.
So I updated the url.py as:
url(r'^api/users/(?P<pk>[0-9]+)/following/$', views.FollowingEnumByUserID.as_view()),
url(r'^api/users/(?P<pk>[0-9]+)/following/(?P<following_id>[0-9]+)/$', views.FollowingDelete.as_view()),
The permission that I am using is:
class IsFollowerOrReadOnly(permissions.BasePermission):
"""
View-level permission to allow the follower to edit the following relation
"""
def has_permission(self, request, view):
if request.method in permissions.SAFE_METHODS:
return True
try:
follower = User.objects.get(id=view.kwargs["pk"])
except User.DoesNotExist:
#Reject any request for an invalid user
return False
return follower == request.user
And the views are:
class FollowingEnumByUserID(generics.ListCreateAPIView):
serializer_class = FollowSerializer
permission_class = (IsFollowerOrReadOnly)
def get_queryset(self):
"""
List all the people the input user is following
"""
follower_id = self.kwargs['pk']
return Follow.objects.filter(follower=follower_id)
class FollowingDelete(generics.DestroyAPIView):
serializer_class = FollowSerializer
permission_class = (IsAuthenticated, IsFollowerOrReadOnly)
def get_queryset(self):
user_id = self.kwargs['following_id']
follower_id = self.kwargs['pk']
return Follow.objects.filter(user=user_id, follower=follower_id)
Now the questions are:
The permission class doesn't work totally.
How to rewrite the DestroyAPIView, should I override the get_queryset function?
Django REST framework provides custom permission handling that allows you to handle complex permissions on the view and object level. In order to do what you are looking for, you are going to have to create a custom permission, but it's surprisingly easy.
Every authenticated user can follow the others.
DRF provides an IsAuthenticated permission that allows you to do this very easily. All you have to do is add it to the permission_classes on the view, or globally through the settings.
from rest_framework import permissions
class FollowingEnumByUserID(generics.ListCreateAPIView):
serializer_class = FollowSerializer
permission_classes = (permissions.IsAuthenticated, )
def get_queryset(self):
follower_id = self.kwargs['pk']
return Follow.objects.filter(follower=follower_id)
There is another restriction, which is the one that requires the custom permission class.
But I would like to just allow the authenticated user to add the following relation by himself/herself
This requires checking the request method (which I'm assuming is POST) and also the user who is being followed.
Lets start off with the easy check, the request method. Django REST framework provides permission classes that check the request method, such as IsAuthenticatedOrReadOnly, so we can look at the code to see how it is being done. From there it's just a matter of having a check against the request type.
class PostIfFollower(BasePermission):
"""
The request is not POST or the request user is the follower.
"""
def has_permission(self, request, view):
if request.method != "POST":
return True
return False
This code will reject all requests that come in using the POST method, while allowing all others. The second step in creating this permission is doing the user check, so only the follower can add new people that they are following. This requires getting the follower and checking that against request.user.
class PostIfFollower(BasePermission):
"""
The request is not POST or the request user is the follower.
"""
def has_permission(self, request, view):
if request.method != "POST":
return True
try:
follower = User.objects.get(id=view.kwargs["pk"])
except User.DoesNotExist:
# Reject any requests for an invalid user
return False
return follower == request.user
This builds upon the last permission class by getting the user from the url (not allowing it if the user doesn't exist) and checking if they are the current user.
After I modified the typos, the permission class works now:
class FollowingEnumByUserID(generics.ListCreateAPIView):
serializer_class = FollowSerializer
permission_classes = (IsFollowerOrReadOnly,)
def get_queryset(self):
"""
List all the people the input user is following
"""
follower_id = self.kwargs['pk']
return Follow.objects.filter(follower=follower_id)
class FollowingDelete(generics.DestroyAPIView):
serializer_class = FollowSerializer
permission_classes = (IsAuthenticated, IsFollowerOrReadOnly,)
def get_queryset(self):
user_id = self.kwargs['following_id']
follower_id = self.kwargs['pk']
return Follow.objects.filter(user=user_id, follower=follower_id)
And I make the FollowingDelete view work successfully by overriding the get_object() function.
def get_object(self, *args, **kwargs):
user_id = self.kwargs['following_id']
follower_id = self.kwargs['pk']
try:
return Follow.objects.get(user=user_id, follower=follower_id)
except Follow.DoesNotExist:
raise Http404("No such following relation")
I am using Django v1.7dev with the Django Rest Framework v2.3
I have extended the user Class as follows:
class UserProfile(models.Model):
user = models.OneToOneField(User)
gang = models.ForeignKey('Gang', related_name='gangsters')
def __unicode__(self):
return self.user.username
User.profile = property(lambda u: UserProfile.objects.get_or_create(user=u)[0])
I have also created a User serializer endpoint (which does not show the password when I sent a GET request, but accepts it when I send a post, with the purpose of using it for registration:
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('id', 'username', 'password', 'email')
def restore_object(self, attrs, instance=None):
user = super(UserSerializer, self).restore_object(attrs, instance)
user.set_password(attrs['password'])
return user
def to_native(self, obj):
ret = super(UserSerializer, self).to_native(obj)
del ret['password']
return ret
I'd love to extend the serializer to include also the gang parameter. Something similar to
gang = serializers.Field(source='profile.gang')
but writable instead of ReadOnly, so that when I register a user I can insert also the gang, possibly in an atomic way. What is the best approach in this case? I have tried to play a bit with other fields types, but unsuccessfully till now.
At the moment I am sending two separate post (one for User and one for UserProfile, but I bet there is a better way...)
There are two general approaches you could take: either update the related field in post_save() or use two serializers and require both to be valid before processing the request.
Check out this question for more details on both.
This is the solution I ended up adopting, based on Carlton's linked question (but with some changes since the user needs to be authenticated for the Login, but cannot be authenticated during the Registration:
class UserAuthView(APIView):
#Login. Returns the current user.
def get(self, request, *args, **kwargs):
# Only UserProfileSerializer is required to serialize data
if (request.user.is_authenticated()):
serializer = UserProfileSerializer(
instance=request.user.profile)
return Response(serializer.data, status=status.HTTP_200_OK)
return Response(status=status.HTTP_401_UNAUTHORIZED)
#Register new user.
def post(self, request, format=None):
user_serializer = UserSerializer(data=request.DATA)
errors = dict()
if user_serializer.is_valid():
user =user_serializer.save()
data = request.DATA.copy()
data['user'] = User.objects.latest('id').id
user_profile_serializer = UserProfileSerializer(data=data)
if user_profile_serializer.is_valid():
user_profile_serializer.save()
return Response(user_profile_serializer.data, status=status.HTTP_201_CREATED)
errors.update(user_profile_serializer.errors)
return Response(errors, status=status.HTTP_400_BAD_REQUEST)
errors.update(user_serializer.errors)
return Response(errors, status=status.HTTP_400_BAD_REQUEST)