I have two permission class. IsAuthorGroup will check if the user belongs from the author group and IsOwnerOrReadOnly will restrict the user to performing post and delete if he is not the object's owner.
But the problem is anyone from IsAuthorGroup performing post and delete request event he isn't own of the object.
How to restrict anyone from IsAuthorGroup performing post and delete request if he isn't owner of the object?
here is my code:
class IsAuthorGroup(permissions.BasePermission):
def has_permission(self, request, view):
if request.user and request.user.groups.filter(name='AuthorGroup'):
return True
return False
class IsOwnerOrReadOnly(permissions.BasePermission):
def has_object_permission(self, request, view, obj):
if request.method in permissions.SAFE_METHODS:
return True
# Write permissions are only allowed to the owner of the blog.
return obj.author == request.user or request.user.is_superuser
class BlogViewSet(viewsets.ModelViewSet):
queryset = Blog.objects.all()
serializer_class = BlogSerializer
pagination_class = BlogPagination
lookup_field = 'blog_slug'
permission_classes = [IsOwnerOrReadOnly & IsAuthorGroup]
my serializer.py
class BlogSerializer(serializers.ModelSerializer):
author_first_name = serializers.CharField(
source="author.first_name", required=False)
author_last_name = serializers.CharField(
source="author.last_name", required=False)
class Meta:
model = Blog
exclude = ("author", "blog_is_published")
lookup_field = 'blog_slug'
extra_kwargs = {
'url': {'lookup_field': 'blog_slug'}
}
Related
What I want: A logged user creates an item which is stored in the database with that user ForeignKey.
I have 2 codes: both should do the same thing (create item linked to current user), but one of them works and the other one doesn't. GET requests work, I'm focusing on POST requests.
First code (POST working):
class UserPortfolio(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
name = models.CharField(max_length=200, blank=False, null=False)
slug = models.CharField(max_length=200, blank=False, null=False)
class UserPortfolioSerializer(serializers.ModelSerializer):
class Meta:
model = UserPortfolio
exclude = ('id', 'user')
class UserPortfolioViewSet(viewsets.ModelViewSet):
serializer_class = serializers.UserPortfolioSerializer
queryset = UserPortfolio.objects.all()
permission_classes = [IsAuthenticated]
def get_queryset(self):
return UserPortfolio.objects.filter(user=self.request.user).order_by('last_updated')
def perform_create(self, serializer):
serializer.save(user=self.request.user)
Second code (POST not working):
class ExchangeConnection(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
portfolios = models.ManyToManyField(UserPortfolio, blank=True)
exchange = models.ForeignKey(Exchange, on_delete=models.CASCADE)
last_updated = models.DateTimeField(auto_now=True)
class Meta:
db_table = 'exchange_connection'
verbose_name_plural = "Exchange Connection"
def __str__(self):
return self.user.username
class ExchangeConnectionSerializer(FlexFieldsModelSerializer):
class Meta:
model = ExchangeConnection
fields = '__all__'
expandable_fields = {'exchange': ExchangeSerializer,
'portfolios': (UserPortfolioSerializer, {'many': True})}
class UserExchangeConnectionsViewSet(viewsets.ModelViewSet):
serializer_class = serializers.ExchangeConnectionSerializer
queryset = ExchangeConnection.objects.all()
permission_classes = [IsAuthenticated]
def get_queryset(self):
return ExchangeConnection.objects.filter(user=self.request.user).order_by('last_updated')
#action(
detail=True,
methods=['post'],
url_path='create-exchange-connection',
)
def post(self, request):
serializer = serializers.ExchangeConnectionPostSerializer(user=request.user)
if serializer.is_valid():
serializer.save()
return Response({'status': 'Exchange created.'})
else:
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
My urls:
from django.urls import path, include
from api_v1.exchanges.views import UserPortfolioViewSet, UserExchangeConnectionsViewSet, ExchangesViewSet
from rest_framework.routers import DefaultRouter
# Create a router and register our viewsets with it.
router = DefaultRouter()
router.register(r'portfolios', UserPortfolioViewSet)
router.register(r'exchange-connections', UserExchangeConnectionsViewSet)
router.register(r'exchanges', ExchangesViewSet)
urlpatterns = [
path('', include(router.urls)),
]
The error I get when I POST is:
TypeError: post() got an unexpected keyword argument 'pk'
#action(
detail=True,
methods=['post'],
url_path='create-exchange-connection',
)
def post(self, serializer):
serializer = serializers.ExchangeConnectionPostSerializer(user=self.request.user)
if serializer.is_valid():
serializer.save()
return Response({'status': 'Exchange created.'})
else:
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
you have used detail = True in the action, which means you are making a retrieve request for a particular object, so you must pass a lookup_field value which is by default pk.
If you just want to make a create request then you should set detail=False.
Updated code.
#action(
detail=False,
methods=['post'],
url_path='create-exchange-connection',
)
def post(self, serializer):
serializer = serializers.ExchangeConnectionPostSerializer(user=self.request.user)
if serializer.is_valid():
serializer.save()
return Response({'status': 'Exchange created.'})
else:
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
As documented
Like regular actions, extra actions may be intended for either a
single object, or an entire collection. To indicate this, set the
detail argument to True or False. The router will configure its URL
patterns accordingly. e.g., the DefaultRouter will configure detail
actions to contain pk in their URL patterns.
so you should set detail=False
#action(
detail=False,
methods=['post'],
url_path='create-exchange-connection',
)
I have Comment model related with User model
# models.py
class Comment(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
more fields...
....
In the serializer I want to do a create (POST) of a user comment.
But the post method is not enabled, only the put or patch method
Example: User Jon wants to create a comment
# serializers.py
class CommentSerializer(serializers.ModelSerializer):
class Meta:
model = Comment
fields = '__all__'
class UserCommentSerializer(serializers.ModelSerializer):
# id of comment
url = serializers.HyperlinkedIdentityField(
view_name="user-comments-detail",
read_only=True
)
id = serializers.CharField(read_only=True)
comment = CommentSerializer()
class Meta:
model = User
fields = ['id', 'comment']
def create(self, validated_data):
comment_data = validated_data.pop('comment')
user = User.objects.create(**validated_data)
Comment.objects.create(user=user, **comment_data)
return user
I want to new comment, referencing the user
# views.py
class CommentViewSet(viewsets.ModelViewSet):
queryset = User.objects.all()
serializer_class = UserCommentSerializer
But I have an exception, that the user has no comment RelatedObjectDoesNotExist
My url like this
http://localhost:8000/user-comments/10
10 is a user_id pk
{} object post
Example: Comment.objects.create(user=pk, {})
Currently, only put and patch is enabled, but what I want to do is post of user
{
"url": "http://localhost:8000/user-comments/10",
"id": "10",
"comment": null
}
Comment does not exist
Any idea or suggestion?
You actually need just one serializer for that.
This will create a comment for the current logged in user.
# serializers.py
class CommentSerializer(serializers.ModelSerializer):
class Meta:
model = Comment
fields = '__all__'
read_only_fields = ['user']
def create(self, validated_data):
# get the user who sent the request
user = self.context['request'].user
return Comment.objects.create(user=user, **validated_data)
# views.py
class CommentViewSet(viewsets.ModelViewSet):
queryset = Comment.objects.all()
serializer_class = CommentSerializer
def get_serializer_context(self):
# this is important since you want to pass the request object to your serializer
context = super().get_serializer_context()
context.update({"request": self.request})
return context
The exception you are getting is because ModelSerializer is linked to a specific model, in this case you linked UserCommentSerializer to model User. Variable Meta.fields specifies which fields of the model are being returned by the serializer and, so, you are getting an exception because such variable is set to ('id', 'comment') and model User doesn't have a field comment.
You can achieve what you want, this way:
class UserCommentSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ['id']
extra_kwargs = {'comment': {'write_only': True}}
def create(self, validated_data):
comment_data = validated_data.pop('comment')
user = User.objects.create(**validated_data)
Comment.objects.create(user=user, comment=comment_data)
I'm looking to retrieve all employees (User) of the current User (id is given). Employees is a ManyToMany field of User. Currently my query retrieves the current user. And user.employees just returns the ids of all employees.
Would it be possible to make a query to retrieve all the Employees of the current User right away? Or am I just supposed to send more API calls (from the front end) for every user where I retrieve the data of the users by id?
Would be awesome if someone could steer me in the right direction. :)
views.py
# display all your employees
class EmployeeViewSet(viewsets.ModelViewSet):
queryset = User.objects.all()
serializer_class = UserSerializer
def list(self, request):
queryset = User.objects.filter(pk=request.user.pk) #get current user
serializer = UserSerializer(queryset, many=True)
return Response(serializer.data)
models.py
class UserProfile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
employees = models.ManyToManyField(User, related_name='employees')
serializers.py
class UserSerializer(UserDetailsSerializer):
class Meta(UserDetailsSerializer.Meta):
fields = UserDetailsSerializer.Meta.fields + ('employees')
def update(self, instance, validated_data):
profile_data = validated_data.pop('userprofile', {})
employees = profile_data.get('employees')
instance = super(UserSerializer, self).update(instance, validated_data)
# get and update user profile
profile = instance.userprofile
if profile_data:
if employees:
profile.employees = employees
profile.save()
return instance
rest_auth/serializers.py (dependency)
class UserDetailsSerializer(serializers.ModelSerializer):
"""
User model w/o password
"""
class Meta:
model = UserModel
fields = ('pk', 'username', 'email', 'first_name', 'last_name')
read_only_fields = ('email', )
Example user
class EmployeeViewSet(viewsets.ModelViewSet):
queryset = User.objects.all()
serializer_class = UserSerializer
def list(self, request):
queryset = self.queryset.filter(id__in=request.user.employees.all())
serializer = UserSerializer(queryset, many=True)
return Response(serializer.data)
Also, in the serializer you may have to do the following to get the desired response structure.
class UserSerializer(UserDetailsSerializer):
employees = serializers.SerializerMethodField()
class Meta(UserDetailsSerializer.Meta):
fields = UserDetailsSerializer.Meta.fields + ('employees')
def get_employees(self, obj):
return obj.userprofile.employees.all()
You can use a filter like:
request.user.userprofile.employees.all() to fetch the employees for the specific user.
class EmployeeViewSet(viewsets.ModelViewSet):
queryset = User.objects.all()
serializer_class = UserSerializer
def list(self, request):
queryset = request.user.userprofile.employees.all() #get all employees for current user
serializer = UserSerializer(queryset, many=True)
return Response(serializer.data)
This documention about One-to-one relationships would also be useful.
I create a custom permission which authorizes GET, HEAD and OPTION for everyone and which authorizes NO other requests.
But my code doesn't work. I can make a POST request despite my permission ...
Anyone have a idea to solve my problem ?
My views.py:
class IsReadOnly(permissions.BasePermission):
def has_object_permission(self, request, view, obj):
if request.method in permissions.SAFE_METHODS:
return True
return False
class ViewPollViewSet(viewsets.ModelViewSet):
permission_classes = [
IsReadOnly,
]
serializer_class = ViewPollSerializer
queryset = ViewPoll.objects.all()
My serializers.py:
class ViewPollSerializer(serializers.ModelSerializer):
class Meta:
model = ViewPoll
fields = '__all__'
My models.py:
class ViewPoll(models.Model):
''' view poll '''
class Meta:
unique_together = ('poll', 'user')
poll = models.ForeignKey(Poll, on_delete=models.CASCADE, related_name="views", null=True)
user = models.ForeignKey(User,on_delete=models.CASCADE, related_name="views_poll", null=True)
created_at = models.DateTimeField(auto_now_add=True)
def __str__(self):
return str(self.poll.question)[:30]
Call has_permission(...) method instead of has_object_permission(...) method
class IsReadOnly(permissions.BasePermission):
def has_permission(self, request, view):
if request.method in permissions.SAFE_METHODS:
return True
return False
I need viewset to return always absolute url for avatar / avatar_thumbnail image fields in response, but I'm getting relative paths. I have two such cases: 1st) is in image field in nested serializer, 2nd) is in viewset where I want to use two serializers in retrieve method.
models.py
class CustomUser(AbstractUser):
avatar = ProcessedImageField(upload_to='users/',format='JPEG',options={'quality': 60})
avatar_thumbnail = ImageSpecField(source='avatar',processors=[ResizeToFill(50, 50)],format='JPEG')
settings.py
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
urls.py
if settings.DEBUG:
from django.conf.urls.static import static
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)here
forum/api/serializers.py (nested serializer)
class UserSerializer(serializers.ModelSerializer):
avatar_thumbnail = serializers.ImageField(read_only=True)
class Meta:
model = CustomUser
fields = ['id', 'username', 'avatar_thumbnail']
class ThreadSerializer(serializers.ModelSerializer):
class Meta:
model = Thread
fields = ['id', 'title', 'subject', 'user']
def to_representation(self, instance):
representation = super().to_representation(instance)
representation['user'] = UserSerializer(instance.user).data
return representation
forum/api/views.py
class ThreadViewSet(viewsets.ModelViewSet):
serializer_class = ThreadSerializer
queryset = Thread.objects.all()
For UserViewSet I have also similar problem with urls for "avatar". I need absolute urls. I get relative url, but only when I overwrite retrieve method in viewset. (I overwrite it to use different serializer "UserPrivateSerializer" for user that is owner of the profile) .For list I always get absolute url.
users/api/serializers.py
class UserPublicSerializer(serializers.ModelSerializer):
class Meta:
model = CustomUser
fields = ['id', 'username', 'avatar']
class UserPrivateSerializer(serializers.ModelSerializer):
email = serializers.EmailField(required=True)
class Meta:
model = CustomUser
fields = ['id', 'username', 'email', 'avatar']
users/api/views.py
(Here the problem is with additional serializer, that I want to use in retrieve method)
class UserViewSet(viewsets.ModelViewSet):
queryset = CustomUser.objects.exclude(username='user_deleted')
serializer_class = UserPublicSerializer
permission_classes = [IsAuthenticated, IsOwnerOrReadOnly]
def retrieve(self, request, *args, **kwargs):
"""Custom retrieve method use UserPrivateSerializer if the user object is requested by it's owner.
"""
super().retrieve(request, *args, **kwargs)
instance = self.get_object()
if request.user == instance:
serializer = UserPrivateSerializer(instance)
else:
serializer = UserPublicSerializer(instance)
return Response(serializer.data, status=status.HTTP_200_OK)
I have found out that the problem is solved when I add context to serializers.
forum/api/serializers.py
class ThreadSerializer(serializers.ModelSerializer):
class Meta:
model = Thread
fields = ['id', 'title', 'subject', 'user']
def to_representation(self, instance):
representation = super().to_representation(instance)
representation['user'] = UserSerializer(instance.user, context=self.context).data
return representation
users/api/views.py
class UserViewSet(viewsets.ModelViewSet):
queryset = CustomUser.objects.exclude(username='user_deleted')
serializer_class = UserPublicSerializer
permission_classes = [IsAuthenticated, IsOwnerOrReadOnly]
def retrieve(self, request, *args, **kwargs):
super().retrieve(request, *args, **kwargs)
instance = self.get_object()
if request.user == instance:
serializer = UserPrivateSerializer(instance, context={'request': request})
else:
serializer = UserPublicSerializer(instance, context={'request': request})
return Response(serializer.data, status=status.HTTP_200_OK)
I have found my answer here: Django REST Framework and FileField absolute url
"The request must available to the serializer, so it can build the full absolute URL for you. One way is to explicitly pass it in when the serializer is created"