Cannot resolve keyword 'author' into field - django

Here is the issue.I am trying to delete the tags related to my model. I can create them but whenever i try to use HTTP DELETE on ExampleModelTags, i face with the following issue.
Error: Cannot resolve keyword 'author' into field. Choices are: id, examplemodel, examplemodel_id, tag, timestamp
I can't understand what is the issue, i can create them so the algorithm works. The tags can connect to their parent model . But whenever i try to do HTTP DELETE on ExampleModelTags, i get that issue. Where is the problem that i don't see?
Model.py
class ExampleModelTag(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
tag = models.CharField(max_length=35,null=True,blank=True)
examplemodel = models.ForeignKey(ExampleModel, on_delete=models.CASCADE,null=True,blank=True, related_name='examplemodeltags')
timestamp = models.DateTimeField(auto_now_add=True)
class ExampleModel(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
author = models.ForeignKey(User, on_delete=models.CASCADE,null=True,related_name='examplemodels')
examplemodel = models.CharField(unique=False,max_length=100,null=True,blank=True)
timestamp = models.DateTimeField(unique=False,auto_now_add=True)
Serializer.py
class ExampleModelTagSerializer(serializers.ModelSerializer):
class Meta:
model = ExampleModelTag
fields = ("id","examplemodel","tag","timestamp")
def validate(self, attrs):
attrs = super().validate(attrs)
if attrs['examplemodel'].author.id != self.context['request'].user.pk:
raise ValidationError('Unauthorized Request')
return attrs
class ExampleModelSerializer(serializers.ModelSerializer):
examplemodeltags_set = ExampleModelTagSerializer(source='examplemodeltags',required=False,many=True)
class Meta:
model = ExampleModel
fields = ("id","author","examplemodel","examplemodeltags_set","timestamp")
def validate(self, attrs):
attrs = super().validate(attrs)
if attrs['author'].id != self.context['request'].user.pk:
raise ValidationError('Unauthorized Request')
return attrs
Views.py
class ExampleModelViewSet(viewsets.ModelViewSet):
permission_classes = (IsAuthenticated,)
queryset = ExampleModel.objects.all().order_by('-timestamp')
serializer_class = ExampleModelSerializer
filter_backends = [UserFilterBackend]
class ExampleModelTagViewSet(viewsets.ModelViewSet):
permission_classes = (IsAuthenticated,)
queryset = ExampleModelTag.objects.all()
serializer_class = ExampleModelTagSerializer
filter_backends = [UserFilterBackend]
Filters.py
class UserFilterBackend(filters.BaseFilterBackend):
def filter_queryset(self, request, queryset, view):
return queryset.filter(
author=request.user
)

You can not use the UserFilterBackend on the ExampleModelTagViewSet, since an ExampleModelTag has no author field. You thus should rewrite the ExampleModelTagViewSet to:
class ExampleModelTagViewSet(viewsets.ModelViewSet):
# ⋮
filter_backends = [] # ← UserFilterBackend not applicable
If you want to fetch ExampleModelTags for which an ExampleModel exists that links to that ExampleModelTag record, and has as author the logged in user, you can specify this with:
class ExampleModelAuthorBackend(filters.BaseFilterBackend):
def filter_queryset(self, request, queryset, view):
return queryset.filter(
examplemodel__author=request.user
)
and then use that one to filter the ExampleModelTags:
class ExampleModelTagViewSet(viewsets.ModelViewSet):
# ⋮
filter_backends = [ExampleModelAuthorBackend]

class UserFilterBackend(filters.BaseFilterBackend):
def filter_queryset(self, request, queryset, view):
return queryset.filter(
examplemodel__author=request.user
)

Related

How to get serializer data before response in DRF ListAPIView to order it by all fields?

I have two models.
class Team(models.Model):
user = models.ManyToManyField(User, related_name='%(class)s_user')
company = models.ForeignKey(Company, on_delete=models.CASCADE, related_name='%(class)s_company')
is_active = models.BooleanField(default=True)
has_user = models.BooleanField(default=False)
class Meta:
app_label = 'accounts'
class TeamTranslation(models.Model):
team_name = models.CharField(max_length=100)
team_description = models.TextField()
team = models.ForeignKey(Team, on_delete=models.CASCADE, related_name='%(class)s_team')
languages = models.ForeignKey(Languages, on_delete=models.CASCADE, related_name='%(class)s_languages')
is_active = models.BooleanField(default=True)
This is list view...
class teamListView(generics.ListAPIView):
search_fields = ['teamtranslation_team__team_name']
filter_backends = (filters.SearchFilter,)
serializer_class = teamSerializer
pagination_class = StandardResultsSetPagination
def get_queryset(self):
accessor_id = self.request.user.id
queryset = Team.objects.filter(is_active=True).order_by("id")
return queryset
def post(self, request, *args, **kwargs):
return self.list(request, *args, **kwargs)
def get_serializer_context(self):
lang_id = UserProfile.objects.get(user_id=self.request.user.id).languages_id
return {"lang_id": lang_id}
And this is serializer...
class teamSerializer(serializers.ModelSerializer):
team_id = serializers.IntegerField(source='id')
members_count = serializers.SerializerMethodField()
team_description = serializers.SerializerMethodField()
team_name = serializers.SerializerMethodField()
company_code = serializers.CharField(source='company.company_code')
def get_team_description(self, obj):
lang_id = self.context.get('lang_id')
if TeamTranslation.objects.filter(team_id=obj.id, languages_id=lang_id, is_active=True):
return f'{TeamTranslation.objects.get(team_id=obj.id, languages_id=lang_id, is_active=True).team_description}'
return f'{""}'
def get_team_name(self, obj):
lang_id = self.context.get('lang_id')
if TeamTranslation.objects.filter(team_id=obj.id, languages_id=lang_id, is_active=True):
return f'{TeamTranslation.objects.get(team_id=obj.id, languages_id=lang_id, is_active=True).team_name}'
return f'{""}'
def get_members_count(self, obj):
return len(obj.user.values_list('id', flat=True))
class Meta:
model = Team
fields = ("team_id", "team_name", "team_description", 'members_count', 'company_id', 'company_code', 'has_user')
I try to order my data according to query parameter comes from request. So there is no problem when use order_by clause for Team model fields. The problem starts when try to order TeamTranslation fields.
I want to order my serializer data before ListAPIView response it. Is there any way to do it?
I try using order_by clause to do that for example.
queryset = queryset.order_by("teamtranslation_team__team_name")
or for reverse I use - sign. It works but I want to query parameter comes with field name that I describe in serializer fields. Because of that I think the best way can be order data in serializer data but I can't reach it. Order data by members count is also another problem.
Thanks for your help.
You can look into OrderingFilter which allows you to order the queryset. Like this:
class teamListView(generics.ListAPIView):
search_fields = ['teamtranslation_team__team_name']
ordering_fields = ['id', 'teamtranslation_team__team_name']
ordering = ['id']
filter_backends = (filters.SearchFilter,filters.OrderingFilter)
serializer_class = teamSerializer
pagination_class = StandardResultsSetPagination
queryset = Team.objects.filter(is_active=True)
# no need to override get_queryset method
update
Another efficient of doing this:
from django.db.models import OuterRef, Subquery, Count
class teamListView(generics.ListAPIView):
search_fields = ['team_name']
ordering_fields = ['id', 'team_name']
filter_backends = (filters.SearchFilter,filters.OrderingFilter)
serializer_class = teamSerializer
pagination_class = StandardResultsSetPagination
def get_queryset(self):
queryset = Team.objects.filter(is_active=True)
subquery = TeamTranslation.objects.filter(team=OuterRef('pk'), is_active=True)
queryset = queryset.annotate(team_name=Subquery(subquery.values('team_name')[:1]), team_description=Subquery(subquery.values('team_description')[:1]), member_count=Count('user'))
return queryset
Then you can clean up your serializers like this:
class teamSerializer(serializers.ModelSerializer):
team_id = serializers.IntegerField(source='id')
members_count = serializers.IntegerField(source='member_count')
team_description = serializers.CharField(source="team_description")
team_name = serializers.CharField(source="team_name")
company_code = serializers.CharField(source='company.company_code')
Here I am annotating subquery of TeamTranslation with the Team. I am also annotating user count through Count expression. With this implementation, I am reducing DB calls, hence making it efficient.
FYI: Please use PEP-8 guidelines for naming conventions in Python.

Field level validation with request.user in Django rest framework

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'])

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

Model property as a filter field in API

I have this model:
class Auction(models.Model):
start_price = models.IntegerField()
price_step = models.IntegerField()
finish_time = models.DateTimeField()
#property
def is_active(self):
return self.finish_time > timezone.now()
I also have this in my serializer class:
class AuctionSerializer(serializers.ModelSerializer):
is_active = serializers.ReadOnlyField()
class Meta:
model = Auction
fields = '__all__'
And this in my view class:
class AuctionViewSet(ModelViewSet):
queryset = Auction.objects.all()
serializer_class = AuctionSerializer
filter_backends = (DjangoFilterBackend,)
filter_fields = ('is_active',)
But it throws "'Meta.fields' contains fields that are not defined on this FilterSet: is_active". I can use it as a serializer field but I can't do filtering by this field. How do I properly implement filtering by model property not just model field?
AFAIK, django-filter does not support filtering by properties.
One way to do this would be to expose the model as-is; and expect the FE to filter accordingly.
For example: the filter URI could look like http://localhost:8000/api/auctions/?finish_time__gte=2018-04-28T00:00:00
If however, you want an explicit declaration; you can override the get_queryset method in ModelViewSet as follows:
class AuctionViewSet(ModelViewSet):
queryset = Auction.objects.all()
serializer_class = AuctionSerializer
filter_backends = (DjangoFilterBackend,)
filter_fields = ('is_active',)
def filter_active(qs):
return qs.filter(finish_time__gte=timezone.now())
def filter_inactive(qs):
return qs.filter(finish_time__lt=timezone.now())
def get_queryset():
qs = super(AuctionViewSet, self).get_queryset()
is_active = self.request.GET.get('is_active', None) # This will be a string
if is_active is None:
pass
elif is_active == 'true':
qs = self.filter_active(qs)
elif is_active == 'false':
qs = self.filter_inactive(qs)
else:
pass # Some other value
return qs
Reference:
https://django-filter.readthedocs.io/en/master/guide/rest_framework.html#schema-generation-with-core-api

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)