Django Rest Framework Field Validation Issue - python-3.x

My subscription view is located inside of UserViewSet. I'm wondering why I'm getting
IntegrityError at /api/users/1/subscribe/
new row for relation "users_subscription" violates check constraint "prevent_self_subscription"
DETAIL: Failing row contains (11, 1, 1).
instead of proper json answer. Somehow SubscriptionSerializer field validation doesnt wish to work. Any thoughts?
models.py
class Subscription(models.Model):
user = models.ForeignKey(
User, related_name='subscriber',
on_delete=models.CASCADE)
author = models.ForeignKey(
User, related_name='subscribing',
on_delete=models.CASCADE)
class Meta:
constraints = [
models.UniqueConstraint(
fields=('user', 'author'),
name='unique_subscription'
),
models.CheckConstraint(
check=~models.Q(user=models.F('author')),
name='prevent_self_subscription'
)
]
serializers.py
class SubscriptionSerializer(serializers.ModelSerializer):
user = serializers.PrimaryKeyRelatedField(
read_only=True, default=serializers.CurrentUserDefault())
class Meta:
model = models.Subscription
fields = ('author', 'user', )
validators = [
serializers.UniqueTogetherValidator(
queryset=models.Subscription.objects.all(),
fields=['author', 'user', ]
)
]
def create(self, validated_data):
return models.Subscription.objects.create(
user=self.context.get('request').user, **validated_data)
def validate_subscribing(self, value):
if self.context.get('request').user == value:
raise serializers.ValidationError(
'You cant subscribe to yourself!')
return value
views.py
#action(['post'], detail=True)
#permission_classes(permissions.IsAuthenticated)
def subscribe(self, request, *args, **kwargs):
author = get_object_or_404(models.User, id=kwargs['id'])
data = request.data.copy()
data.update({'author': author.id})
serializer = serializers.SubscriptionSerializer(
data=data, context={'request': request})
if request.method == 'POST':
serializer.is_valid(raise_exception=True)
serializer.save()
return Response(
status=status.HTTP_201_CREATED,
data=self.get_serializer(author).data)

It was the wrong method name, field name is 'author' and method was validate_subscribing() instead of validate_author().

Related

Django Create or update with unique together values

I'm trying to make an attendance system in the frontend I retrieve a list of users which the request.user can take attendance of, I'm using CreateAPIView but this won't get me the desired effect as I want the request.user to be able to toggle between Absent, Present, and on_leave even after the entry has been created on the first request
i have seen questions and answers about create or update here but i couldn't use them so any help would be appriciated
this is my view.py
class AttendanceListCreateAPIView(CreateAPIView):
permission_classes = [IsTeacher_Student]
queryset = Attendance.objects.all()
serializer_class = AttendanceSerializer
def post(self, request, *args, **kwargs):
user = request.user
data = request.data
serializer = AttendanceSerializer(data=data)
if serializer.is_valid():
data_user = serializer.validated_data['user']
## validation
return Response({"message": "Something is wrong, maybe you have already taken attendance for this user"},
status=status.HTTP_400_BAD_REQUEST)
my serializer.py
class AttendanceSerializer(serializers.ModelSerializer):
date = serializers.HiddenField(default=timezone.now)
leave_reason = serializers.CharField(required=False, default="")
class Meta:
model = Attendance
fields = ['user', 'presence', 'leave_reason', 'date']
extra_kwargs = {
'user': {'required': True},
'presence': {'required': True},
'leave_reason': {'required': False},
}
validators = [
UniqueForYearValidator(
queryset=Attendance.objects.all(),
field='user',
date_field='date',
message=("You have already taken the attendance")
)
]
def create(self, validated_data):
instance = Attendance.objects.create(
user=validated_data['user'],
presence=validated_data['presence'],
leave_reason=validated_data['leave_reason'],
date=validated_data['date'],
)
instance.save()
return instance
desired effect
the user fk and date are unique together meaning that if the date isn't unique for the user update it if it is create a new entry

Django REST Framework UniqueTogetherValidator fails with FK from kwargs

I've got myself into a trouble with UniqueTogetherValidator.
The problem is that ReviewSerliazer unlike CommentSerializer which is almost identical does unique together validation before actually getting a title value from kwargs and sends back 400 answer with title field being required. I've tried to identify it as a HiddenField, but although serializer validation goes fine within tests, model validation does not. And I receive django.db.utils.IntegrityError: UNIQUE constraint failed: reviews_review.author_id, reviews_review.title_id
Key idea is that title should not be included in serializer response. Making it PrimaryKeyRelatedField and then doing the to_representation seems odd to me.
Any ideas how to fix this without catching exceptions in the viewset, which is obviously wrong?
models.py
class BasePost(models.Model):
text = models.TextField()
author = models.ForeignKey(
User, on_delete=models.CASCADE, related_name='%(class)ss')
pub_date = models.DateTimeField(auto_now_add=True)
class Meta:
ordering = ('-pub_date', )
abstract = True
def __str__(self):
return self.text[:30]
class Review(BasePost):
score = models.IntegerField(
default=0, validators=[MaxValueValidator(10), MinValueValidator(1)])
title = models.ForeignKey(
Title, on_delete=models.CASCADE, related_name='reviews')
class Meta(BasePost.Meta):
constraints = [
models.UniqueConstraint(
fields=('author', 'title', ), name='unique_title_review')]
class Comment(BasePost):
review = models.ForeignKey(
Review, on_delete=models.CASCADE, related_name='comments')
urls.py
router_v1.register(
r'^titles/(?P<title_id>\d+)/reviews', ReviewViewSet, basename='review')
router_v1.register(
r'^titles/(?P<title_id>\d+)/reviews/(?P<review_id>\d+)/comments',
CommentViewSet, basename='comment')
views.py
class ReviewViewSet(BasePostViewSet):
serializer_class = ReviewSerializer
def get_queryset(self):
return self.get_title().reviews.all()
def perform_create(self, serializer):
serializer.save(author=self.request.user, title=self.get_title())
def get_title(self, key='title_id'):
return get_object_or_404(Title, id=self.kwargs.get(key))
class CommentViewSet(BasePostViewSet):
serializer_class = CommentSerializer
def get_queryset(self):
return self.get_review().comments.all()
def perform_create(self, serializer):
serializer.save(author=self.request.user, review=self.get_review())
def get_review(self, key='review_id'):
return get_object_or_404(Review, id=self.kwargs.get(key))
serializers.py
class ReviewSerializer(BasePostSerializer):
title = serializers.HiddenField(default=None)
class Meta:
model = Review
fields = ('id', 'text', 'author', 'score', 'pub_date', 'title', )
validators = [UniqueTogetherValidator(
queryset=Review.objects.all(), fields=('author', 'title', ))]
class CommentSerializer(BasePostSerializer):
class Meta:
model = Comment
fields = ('id', 'text', 'author', 'pub_date', )
def create(self, validated_data):
try:
review = Review.objects.create(**validated_data)
except IntegrityError:
raise serializers.ValidationError(
{'detail': 'Вы можете оставить только один отзыв.'})
return review
Currently fixed like that but seems wrong to me.
First, I will assume that BasePostViewSet does inherits from CreateModelMixin. My assumption is based on the fact that you are redefining perform_create:
def perform_create(self, serializer):
serializer.save()
According to DRF Documentation on passing additional argumernts to the .save() method:
Any additional keyword arguments will be included in the validated_data argument when .create() or .update() are called.
The original .create() method from the CreateModelMixin:
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
self.perform_create(serializer)
headers = self.get_success_headers(serializer.data)
return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
It does the validation before calling the .perform_create() method, hence your redefined version of .perform_create:
def perform_create(self, serializer):
serializer.save(author=self.request.user, review=self.get_review())
Gets called after validation, hence, your serializer validator is not checked with your desired arguments.
Now, I think you could achieve this by including your fields in the to_internal_value function data like this (didn't try it out but definitely it runs before validation) (I'm assuming that serializer context is passed accordingly):
def to_internal_value(self, data):
data['author'] = self.context['request'].user
data['review'] = self.context['view'].get_review()
return data
Hopefully this helps.

Django Rest Framework not saving all foreign key objects

I am going to create a conference and select multiple departments, it is working when I send post request but in GET resquest not getting objects.
class DepartmentModel(models.Model):
name = models.CharField(max_length=255)
conference = models.ForeignKey('ConferenceModel', on_delete=models.CASCADE, null=True,
related_name='conference_departments')
class ConferenceModel(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, null=True)
description = models.CharField(max_length=255, null=True)
serializers.py
class DepartmentField(serializers.PrimaryKeyRelatedField):
def to_representation(self, value):
pk = super(DepartmentField, self).to_representation(value)
try:
item = DepartmentModel.objects.get(pk=pk)
serializer = DepartmentSerializer(item)
return serializer.data
except DepartmentModel.DoesNotExist:
return None
def get_choices(self, cutoff=None):
queryset = self.get_queryset()
if queryset is None:
return {}
return OrderedDict([(item.id, str(item)) for item in queryset])
class ConferenceModelSerializer(serializers.ModelSerializer):
conference_departments = DepartmentField(queryset=DepartmentModel.objects.all(), many=True)
meeting_participants = SelectItemField(model='account.User', extra_field=['first_name', 'last_name'], many=True)
class Meta:
model = ConferenceModel
fields = '__all__'
request
{
"conference_departments": [
3, 4, 5, 7, 8
],
"meeting_participants": [
10, 12, 15
],
"description": "Bla bla bla"
}
it returns the expected result but If I want to save another object and get all objects it does not return objects but the latest one does.
here you can see from the image below
id: 19 is the latest saved object it returns department objects but id: 18 does not!
can anybody help me please? any help would be appreciated! Thanks in advance!
class DepartmentSerializer(serializers.ModelSerializer):
...
class Meta:
model = DepartmentModel
fields = '__all__'
class ConferenceModelSerializer(serializers.ModelSerializer):
conference_departments = DepartmentSerializer(required=False, many=True)
meeting_participants = SelectItemField(model='account.User', extra_field=['first_name', 'last_name'], many=True)
class Meta:
model = ConferenceModel
fields = '__all__'
Nested relationships
Writable nested serializers

Django Rest Framework DELETE request responds like a GET request

I'm trying to delete an entry in my data base that is returned by a modelviewset get_queryset. When sending a DELETE request through the DRF web interface and via postman, I receive this response "DELETE /api/remove_self/3 HTTP/1.1" 200 along with the data I am trying to delete. The code that gives this result looks like this:
Models.py
class EventAtendee(models.Model):
"""Lists users atending an event"""
#below connects user profile to event
id = models.AutoField(primary_key=True)
event_id = models.IntegerField(null = True)
user_profile = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE
)
def __str__(self):
return self.event_id
views.py
class RemoveSelfFromEvent(viewsets.ModelViewSet):
"""Remove Yourself From an Event you were attending"""
authentication_classes = (TokenAuthentication,)
serializer_class = serializers.EventAtendeeSerializer
permission_classes = (permissions.UpdateOwnStatus, IsAuthenticated)
def perform_create(self, serializer):
"""Sets the user profile to the logged in user"""
#
serializer.save(user_profile=self.request.user)
def get_queryset(self):
"""
This view should return a list of all the purchases for
the user as determined by the username portion of the URL.
"""
#user_profile = self.kwargs['user_profile']
event_id = self.kwargs['event_id']
return models.EventAtendee.objects.filter(event_id=event_id, user_profile=self.request.user)
def destroy(self, request, *args, **kwargs):
instance = self.get_object()
self.perform_destroy(instance)
return Response(status=status.HTTP_204_NO_CONTENT)
def perform_destroy(self, instance):
instance.delete()
urls.py
router = DefaultRouter(trailing_slash=False)
router.register('events', views.EventAtendeeViewSet, basename='EventAtendee')
urlpatterns = [
path('remove_self/<event_id>', views.RemoveSelfFromEvent.as_view({'get': 'list', 'delete': 'list'})),
]
Any help is much appreciated!
You are mapping the method DELETE to list in your urls.
path('remove_self/<event_id>', views.RemoveSelfFromEvent.as_view({'get': 'list', 'delete': 'list'})),
Correct way to do:
path('remove_self/<pk>', views.RemoveSelfFromEvent.as_view({'get': 'list', 'delete': 'destroy'})),
mapping of various methods:
POST : create
GET : retrieve
PUT : update
PATCH : partial_update
DELETE : destroy

DRF use field-value instead of default (pk) in POST request

Trying send valid POST request.
It's works when i send like this:
{
"name":"but",
"sklad":1
}
Response is something like this, all is valid:
{
"name": "but",
"getsklad": "fence"
}
But when I'm trying send this, with 'fence':
{
"name":"but",
"sklad":"fence"
}
Got error:
Invalid type. The primary key value was expected, str was received.
Have this piece of code in models.py:
class Sklad(models.Model):
storages = (
('fence', 'Забор'),
('sett', 'Тротуарка'),
)
storage = models.CharField(
max_length=10,
choices=storages,
help_text='Name of storage',
default='sett'
)
class Zabor(models.Model):
sklad = models.ForeignKey(Sklad, on_delete=models.CASCADE)
name = models.CharField(max_length=10)
...
This in serializers.py:
class ZaborPostSerializer(serializers.ModelSerializer):
class Meta:
model = Zabor
fields = (
...
'name',
'sklad',
)
And Views.py part:
class ZaborView(APIView):
def post(self, request):
serializer = ZaborPostSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response({'status': 'Success'})
else:
return Response({'status': 'Failed'})
I know what when i write '1' in request, this means 'pk'. But how I can send name rather than the id/pk?
I am beginner. So don't kick me hard for stupid questions)
Since you are providing the Sklad FOREIGN_KEY, which is unique you can filter by that value only with one type: integer, string, etc.
You can set the storage field of Sklad to the primary key:
class Sklad(models.Model):
storages = (
('fence', 'Забор'),
('sett', 'Тротуарка'),
)
storage = models.CharField(
max_length=10,
choices=storages,
help_text='Name of storage',
default='sett',
primary_key=True
)
Now you can directly filter with your string value.
Another option is to filter in the view:
from django.shortcuts import get_object_or_404
from django.http import Http404
class ZaborView(APIView):
def post(self, request):
dataDict = dict(request.data)
try:
sklad = get_object_or_404(Sklad.objects, storage=dataDict['sklad'][0])
serializer = ZaborPostSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response({'status': 'Success'})
else:
return Response({'status': 'Failed'})
except Http404:
return Response({'status': 'Failed'})
Note that it will only work if you have only one Sklad with the given storage field.
If you have multiple ones you can pick the first one, since you do not supply a primary key and you have not other fields to filter:
from django.shortcuts import get_list_or_404
.....
sklad = list(get_list_or_404(Sklad.objects, storage=dataDict['sklad'][0]))[0]
.....
Hope it helps !

Resources