Field level validation with request.user in Django rest framework - django

I am setting up a Django REST application where peopple can review restaurants. So far I have those models:
class RestaurantId(models.Model):
maps_id = models.CharField(max_length=140, unique=True)
adress = models.CharField(max_length=240)
name = models.CharField(max_length=140)
class RestaurantReview(models.Model):
review_author = models.ForeignKey(settings.AUTH_USER_MODEL,
on_delete=models.CASCADE)
restaurant_id = models.ForeignKey(RestaurantId, on_delete=models.CASCADE)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class StarterPics(models.Model):
restaurant_review_id = models.OneToOneField(RestaurantReview,
on_delete=models.CASCADE)
pics_author = models.ForeignKey(User, on_delete=models.CASCADE)
restaurant_id = models.ForeignKey(RestaurantId, on_delete=models.CASCADE)
name_1 = models.CharField(max_length=40)
picture_1 = models.ImageField()
My serializers:
class RestaurantIdSerializer(serializers.ModelSerializer):
class Meta:
model = RestaurantId
field = fields = '__all__'
class RestaurantReviewSerializer(serializers.ModelSerializer):
class Meta:
model = RestaurantReview
field = fields = '__all__'
class StarterPicsSerializer(serializers.ModelSerializer):
class Meta:
model = StarterPics
fields = '__all__'
def validate_restaurant_review_id(self, value)
if value.review_author != self.request.user:
raise serializers.ValidationError("User has not reviewed the restaurant")
return value
My views:
class RestaurantIdViewset(viewsets.ModelViewSet):
queryset = models.RestaurantId.objects.all()
serializer_class = serializers.RestaurantIdSerializer
class RestaurantReviewViewset(viewsets.ModelViewSet):
queryset = models.RestaurantReview.objects.all()
serializer_class = serializers.RestaurantReviewSerializer
permission_classes = [IsAuthenticatedOrReadOnly,IsAuthorOrReadOnly]
def perform_create(self, serializer):
serializer.save(review_author=self.request.user)
class StarterPicsViewset(viewsets.ModelViewSet):
queryset = models.StarterPics.objects.all()
serializer_class = serializers.StarterPicsSerializer
permission_classes = [IsAuthenticatedOrReadOnly]
I have set up permissions as well so only the review_author can update his reviews and pics_author can update his pictures.
My permissions:
class IsOwnReviewOrReadOnly(permissions.BasePermission):
def has_object_permission(self, request, view, obj):
if request.method in permissions.SAFE_METHODS:
return True
return obj.pics_author == request.user
class IsAuthorOrReadOnly(permissions.BasePermission):
def has_object_permission(self, request, view, obj):
if request.method in permissions.SAFE_METHODS:
return True
return obj.review_author == request.user
When running Django server I got a 'StarterPicsSerializer' object has no attribute 'request'
This validation is for user that have not written the review (review_author) can't POST pictures in StarterPics. So only the User that creates the review can post pictures on it.
I've tried another validation with no luck either:
def validate_restaurant_review_id(self, value):
if not RestaurantReview.objects.filter(restaurant_review_id=value,
review_author=self.request.user).exists():
raise serializers.ValidationError('Not your review')
return value

You could provide extra context to the serializer in addition to the object being serialized by passing a context argument when instantiating the serializer in your view.
serializer = RandomSerializer(instance, context={'request': request})
If you use Generic Views or ModelViewSet(inherited form GenericAPIView), then request is already available in your serializer self.context dict
class StarterPicsSerializer(serializers.ModelSerializer):
class Meta:
model = StarterPics
fields = '__all__'
def validate_restaurant_review_id(self, value):
print(self.context['request'])

Related

Django: How to add user(author of the POST request) before save in serializers?

I'm tried to add author of the request before saving it in serializers.py
And got error:
Cannot assign "<django.contrib.auth.models.AnonymousUser object at 0x0000029169C48040>": "Category_product.posted_user" must be a "User" instance.
models.py
class Category_product(models.Model):
category_name = models.CharField(max_length=200, unique=True)
posted_user = models.ForeignKey(User, on_delete=models.CASCADE, default=1)
def __str__(self):
return self.category_name
views.py
class Category_productDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = Category_product.objects.all()
serializer_class = Category_productSerializer
serializers.py
class Category_productSerializer(serializers.ModelSerializer):
post_user = serializers.ReadOnlyField(
source='posted_user.username')
class Meta:
model = Category_product
fields = ['id', 'category_name','post_user']
def validate(self, data):
data['posted_user'] = self.context['request'].user
return data
Have you tried overriding the user in your view?
Assuming you have Authentacion set up, because if not everyone could get in and it won't be a user, it would be Anonymous User instance.
If that's not the case, you can try it out in the view with perform create:
class Category_productDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = Category_product.objects.all()
serializer_class = Category_productSerializer
def get_user(self):
user = self.request.user
return user
def perform_create(self, serializer):
"""
Set the sender to the logged in user.
"""
serializer.save(posted_user=self.get_user())

Filter model with serializer.validated_data

I am setting up a Django REST application where peopple can review restaurants. So far I have those models:
class RestaurantId(models.Model):
maps_id = models.CharField(max_length=140, unique=True)
adress = models.CharField(max_length=240)
name = models.CharField(max_length=140)
class RestaurantReview(models.Model):
review_author = models.ForeignKey(settings.AUTH_USER_MODEL,
on_delete=models.CASCADE)
restaurant_id = models.ForeignKey(RestaurantId, on_delete=models.CASCADE)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class StarterPics(models.Model):
restaurant_review_id = models.OneToOneField(RestaurantReview,
on_delete=models.CASCADE)
pics_author = models.ForeignKey(User, on_delete=models.CASCADE)
restaurant_id = models.ForeignKey(RestaurantId, on_delete=models.CASCADE)
name_1 = models.CharField(max_length=40)
picture_1 = models.ImageField()
My serializers:
class RestaurantIdSerializer(serializers.ModelSerializer):
class Meta:
model = RestaurantId
field = fields = '__all__'
class RestaurantReviewSerializer(serializers.ModelSerializer):
class Meta:
model = RestaurantReview
field = fields = '__all__'
class StarterPicsSerializer(serializers.ModelSerializer):
class Meta:
model = StarterPics
fields = '__all__'
def validate_restaurant_review_id(self, value)
if value.review_author != self.request.user:
raise serializers.ValidationError("User has not reviewed the restaurant")
return value
My views:
class RestaurantIdViewset(viewsets.ModelViewSet):
queryset = models.RestaurantId.objects.all()
serializer_class = serializers.RestaurantIdSerializer
class RestaurantReviewViewset(viewsets.ModelViewSet):
queryset = models.RestaurantReview.objects.all()
serializer_class = serializers.RestaurantReviewSerializer
permission_classes = [IsAuthenticatedOrReadOnly,IsAuthorOrReadOnly]
def perform_create(self, serializer):
serializer.save(review_author=self.request.user)
class StarterPicsViewset(viewsets.ModelViewSet):
queryset = models.StarterPics.objects.all()
serializer_class = serializers.StarterPicsSerializer
permission_classes = [IsAuthenticatedOrReadOnly]
I have set up permissions as well so only the review_author can update his reviews and pics_author can update his pictures.
My permissions:
class IsOwnReviewOrReadOnly(permissions.BasePermission):
def has_object_permission(self, request, view, obj):
if request.method in permissions.SAFE_METHODS:
return True
return obj.pics_author == request.user
class IsAuthorOrReadOnly(permissions.BasePermission):
def has_object_permission(self, request, view, obj):
if request.method in permissions.SAFE_METHODS:
return True
return obj.review_author == request.user
Now what I have is that posting a StarterPics on a review that someone is not the author is imposible. That is the behaviour I am looking for. But if the review author tries to do so I have this error:
TypeError: Field 'id' expected a number but got <RestaurantReview: 8>.
Here is my serializer.validated_data:
OrderedDict([('name_1', 'Salade de saison'), ('picture_1', <InMemoryUploadedFile: fricoteurs_inside.jpeg (image/jpeg)>),
('name_2', ''), ('picture_2', None), ('lat_pic_2', None), ('lng_pic_2', None),
('shot_time_2', None), ('restaurant_review_id', <RestaurantReview: 8>), ('pics_author', <User: pi>), ('restaurant_id', <RestaurantId: Les Fricoteurs>)])
Because I cannot call .save() after accessing serializer.data I can't get the 'restaurant_review_id': 4 I would have if I was using serializer.data.
So how can I filter my RestaurantReview model using serializer.validated_data?
This validation should be done in serializer field level, not in perform_create() method:
class StarterPicsSerializer(serializers.ModelSerializer):
...
def validate_resturant_review_id(self, value):
if value.review_user != self.context['request'].user:
raise serializers.ValidationError("User has not reviewed the resturant")
return value

ModelViewSet - Selectively hide fields?

I have an Instructor model, which has a many to many field to a Client model. (Instructor.clients)
The model:
class InstructorProfile(models.Model):
'''Instructor specific profile attributes
'''
# Fields
office_number = models.CharField(max_length=30, blank=True, null=True)
location = models.CharField(max_length=30)
last_updated = models.DateTimeField(auto_now=True, editable=False)
# Relationship Fields
user = models.OneToOneField(settings.AUTH_USER_MODEL,
related_name="instructor_profile",
on_delete=models.CASCADE)
clients = models.ManyToManyField('ClientProfile', blank=True)
My serializer is currently:
class InstructorProfileSerializer(serializers.ModelSerializer):
class Meta:
model = models.InstructorProfile
fields = '__all__'
And viewset:
class InstructorProfileViewSet(viewsets.ModelViewSet):
"""ViewSet for the InstructorProfile class"""
queryset = models.InstructorProfile.objects.all()
serializer_class = serializers.InstructorProfileSerializer
permission_classes = [permissions.IsAuthenticated]
I'd like to prevent access to the clients field to everyone except the user which Instructor belongs to (available in the Instructor.user model field).
How can I achieve this?
Add this to your InstructorProfileViewSet:
...
def get_queryset(self):
if hasattr(self.request.user, 'instructor_profile'):
return models.InstructorProfile.objects.filter(user=self.request.user)
else:
return models.InstructorProfile.objects.none()
... if I guessed your InstructorProfile model correctly.
One way to do this is to change the list method to set the client=None where needed. This way you would preserve the response structure. It would be something like this:
def list(self, request, *args, **kwargs):
queryset = self.filter_queryset(self.get_queryset())
page = self.paginate_queryset(queryset)
if page is not None:
serializer = self.get_serializer(page, many=True)
return self.get_paginated_response(serializer.data)
serializer = self.get_serializer(queryset, many=True)
for i in serializer.data:
if i['user'] != request.user.pk:
i['client'] = None
return Response(serializer.data)

Find where in Django code an 'incorrect type error' is being thrown for a POST

I am trying to discover where in my Django code an incorrect type error message is being thrown:
{"_body":"{\"keywords\":[\"Incorrect type. Expected pk value, received str.\"]}","status":400,"statusText":"Ok","headers":{"Content-Type":["application/json;q=0.8"]},"type":2,"url":"http://127.0.0.1:8000/api/items/"}
I have put a breakpoint in the view's perform_create that handles the POST but this is never executed.
Anyone know where in my code I should set a breakpoint to take a look at what is happening?
My view looks like this:
class ItemViewSet(viewsets.ModelViewSet):
queryset = Item.objects.all().order_by('-date_added')
serializer_class = ItemSerializer
def list(self, request, *args, **kwargs):
this_user = self.request.query_params.get('user', None)
count_of_views = Seen.objects.filter(who_saw=this_user).count()
custom_data = {
'results': ItemSerializer(self.get_queryset(), many=True).data
}
custom_data.update({
'views': count_of_views
})
return Response(custom_data)
def perform_create(self, serializer):
creator = User.objects.get(pk=self.request.data['owner_id'])
the_keywords = self.request.data['keywords'].split
serializer.save(owner=creator)
#serializer.save(keywords=the_keywords)
EDIT: And the serializer is:
class ItemSerializer(serializers.ModelSerializer):
username = serializers.SerializerMethodField()
def get_username(self, obj):
value = str(obj.owner)
return value
def get_keywords(self, obj):
value = str(obj.keywords)
return value
class Meta:
model = Item
fields = ('id', 'url', 'item_type', 'title', 'keywords')
EDIT Item model is:
class Item(models.Model):
owner = models.ForeignKey(User, on_delete=models.CASCADE)
title = models.CharField(max_length=60, default='')
url = models.CharField(max_length=250, default='', unique=True)
keywords = models.ManyToManyField(Keyword, related_name='keywords')
Keyword model is:
class Keyword(models.Model):
name = models.CharField(max_length=30)
def __str__(self):
return format(self.name)

Integrity Error: null value in column "notification_type_id" violates not-null constraint

I am new to Django Rest Framework and Django per say. I have the following Model Class :
class NotificationType(LogModel):
name = models.CharField(max_length=300, verbose_name="Name")
frequency = models.PositiveSmallIntegerField(
choices=NotificationFrequency.CHOICES,
verbose_name="notification_frequency")
active = models.BooleanField(default=1)
class Meta:
db_table = 'notificaiton_type'
verbose_name = 'NotificationType'
verbose_name_plural = 'NotificationTypes'
ordering = ('-id',)
def __str__(self):
return "{}{}".format(self.name, self.frequency)
#python_2_unicode_compatible
class Notification(LogModel):
notification_type = models.ForeignKey(NotificationType)
property_info = models.ForeignKey(PropertyInfo)
description = models.TextField(verbose_name="Notification Description")
action = models.TextField(blank=True, null=True)
class Meta:
db_table = 'notification'
verbose_name = 'Notification'
verbose_name_plural = 'Notifications'
ordering = ('-id',)
def __str__(self):
return "{}".format(self.description)
And My Serializers and Viewsets for the same are as follows :
class NotificationTypeSerializer(serializers.ModelSerializer):
class Meta:
model = NotificationType
fields = ('name', 'frequency', 'active')
class NotificationSerializer(serializers.ModelSerializer):
property_info = PropertyInfoSerializer(read_only=True)
notification_type = NotificationTypeSerializer(read_only=True)
class Meta:
model = Notification
views.py :
class NotificationCRUDView(generics.ListCreateAPIView):
model = Notification
serializer_class = serializers.Notification
authentication_classes = (TokenAuthentication,)
permission_classes = (IsAuthenticated,)
def post(self, request, pk, property_id): #
serializer = serializers.NotificationSerializer(data=request.data)
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)
My urls.py file contains the following url pattern:
url(r'^notification/(?P<pk>[0-9]+)/(?P<property_id>[0-9]+)/$', NotificationCRUDView.as_view(), name="notification-crud-view")
Now whenever I am trying to do a POST call using the url path
/api/v1/notification/pk/property_id {where pk and property_id} passed as url parameter. I am getting the above mentioned error.
Can anybody please help me with this. TIA. :)
Now whenever I am trying to do a POST call using the url path /api/v1/notification/pk/property_id {where pk and property_id} passed as url parameter.
In POST call, you don't pass the data in url. It must be passed as request body.
POST /api/v1/notifications/
Request body:
{
"property_info": "<id>",
"property_type": "<id>"
}
In the ListCreateAPIView, there is no post method, if you want to override the view for a POST call make use of def create(self, request): method.
Have a look at the implementation[1] of CreateModelMixin.
[1] https://github.com/tomchristie/django-rest-framework/blob/master/rest_framework/mixins.py#L14-L23
[2] http://www.django-rest-framework.org/api-guide/generic-views/#listcreateapiview