I am trying to implement an attendance system as part of a bigger project that handles multiple schools at the same time, I am trying to get some user details from the user that is being marked as present, absent, or on leave
serializer.py
class AttendanceSerializer(serializers.ModelSerializer):
class Meta:
model = Attendance
fields = ['user', 'Presence', 'leave_reason', 'Date']
constraints = [
UniqueConstraint(
fields=('user', 'Date'), name='unique_attendance_once_per_date')
]
def create(self, validated_data):
instance = Attendance.objects.create(
user=validated_data['user'],
Presence=validated_data['Presence'],
leave_reason=validated_data['leave_reason'],
attendance_taker=self.context['request'].user,
Date=datetime.today
)
instance.save()
return instance
my current implementation of the view:
class AttendanceListCreateAPIView(CreateAPIView):
permission_classes = [IsClassPart]
queryset = Attendance.objects.all()
serializer_class = AttendanceSerializer
def post(self, request, format=None):
user = request.user
try:
perms = Perm.objects.get(user=user)
except ObjectDoesNotExist:
perms = None
serializer = AttendanceSerializer(data=request.data)
if serializer.is_valid():
if user.role == "TEACHER":
if user.homeroom == request.data['user'].room:
Response({"message": "You don't have permission to perform this action 1"},
status=status.HTTP_400_BAD_REQUEST)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
elif perms is not None:
if user.role != 'STUDENT' and user.perms.is_monitor:
if user.room != request.data['user'].room:
Response({"message": "You don't have permission to perform this action 2"},
status=status.HTTP_400_BAD_REQUEST)
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
else:
return Response({"message": "You don't have permission to perform this action 3"}, status=status.HTTP_400_BAD_REQUEST)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
the error is in request.data['user'].room: as django says 'str' object has no attribute 'room'
Try to access to serializer.validated_data for getting user instance:
if serializer.is_valid():
if user.role == "TEACHER":
if user.homeroom == serializer.validated_data['user'].room:
Response({"message": "You don't have permission to perform this action 1"},
status=status.HTTP_400_BAD_REQUEST)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
elif perms is not None:
if user.role != 'STUDENT' and user.perms.is_monitor:
if user.room != serializer.validated_data['user'].room:
Response({"message": "You don't have permission to perform this action 2"},
status=status.HTTP_400_BAD_REQUEST)
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
else:
return Response({"message": "You don't have permission to perform this action 3"}, status=status.HTTP_400_BAD_REQUEST)
Your request.data contains only data sent to the view in str variables. validated_data contains cleaned data from serializer with retrieve instance associated to foreign key if your model is correctly set
Related
I am trying to implement an attendance system as part of a bigger project that handles multiple schools at the same time but the endpoint is returning an empty {} with a 400_BAD_REQUEST
Attendance/models.py
class Attendance(models.Model):
Choices = (
("P", "Present"),
("A", "Absent"),
("L", "On leave"),
)
user = models.ForeignKey(
User, on_delete=models.CASCADE, related_name="user_attendance", blank=False, null=True)
leave_reason = models.CharField(max_length=355, blank=True, null=True)
Date = models.DateField(blank=False, null=True,
auto_now=False, auto_now_add=True)
Presence = models.CharField(
choices=Choices, max_length=255, blank=False, null=True)
attendance_taker = models.ForeignKey(
User, on_delete=models.CASCADE, related_name="attendance_taker_attendance", blank=False, null=True)
def __str__(self):
return f'{self.user}'
class Meta:
verbose_name = _("Attendance")
verbose_name_plural = _("Attendance")
constraints = [
UniqueConstraint(
fields=('user', 'Date'), name='unique_attendance_once_per_date')
]
Attendance/serializers.py
class AttendanceSerializer(serializers.ModelSerializer):
class Meta:
model = Attendance
fields = ['user', 'Presence', 'leave_reason', 'Date']
constraints = [
UniqueConstraint(
fields=('user', 'Date'), name='unique_attendance_once_per_date')
]
def create(self, validated_data):
instance = Attendance.objects.create(
user=validated_data['user'],
Presence=validated_data['Presence'],
leave_reason=validated_data['leave_reason'],
attendance_taker=self.context['request'].user,
Date=datetime.today
)
instance.save()
return instance
Attendance/views.py
class AttendanceListCreateAPIView(CreateAPIView):
permission_classes = [IsClassPart]
queryset = Attendance.objects.all()
serializer_class = AttendanceSerializer
def post(self, request, format=None):
user = request.user
try:
perms = Perm.objects.get(user=user)
except ObjectDoesNotExist:
perms = None
serializer = AttendanceSerializer(data=request.data)
if serializer.is_valid():
if user.role == "TEACHER":
if user.homeroom == serializer.validated_data['user'].room:
Response({"message": "You don't have permission to perform this action 1"},
status=status.HTTP_400_BAD_REQUEST)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
elif perms is not None:
if user.role != 'STUDENT' and user.perms.is_monitor:
if user.room != serializer.validated_data['user'].room:
Response({"message": "You don't have permission to perform this action 2"},
status=status.HTTP_400_BAD_REQUEST)
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
else:
return Response({"message": "You don't have permission to perform this action 3"}, status=status.HTTP_400_BAD_REQUEST)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
Postman test request
I dont understand why you don't use user.has_perms(), but you forget any return on the if else block with perms check:
def post(self, request, format=None):
user = request.user
serializer = AttendanceSerializer(data=request.data)
if serializer.is_valid():
if user.role == "TEACHER":
if user.homeroom == serializer.validated_data['user'].room:
# Here error, without return
return Response({"message": "You don't have permission to perform this action 1"},
status=status.HTTP_400_BAD_REQUEST)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
elif user.get_all_permissions():
if user.role != 'STUDENT' and user.perms.is_monitor:
if user.room != serializer.validated_data['user'].room:
# ERROR probably
return Response({"message": "You don't have permission to perform this action 2"}, status=status.HTTP_400_BAD_REQUEST)
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
# error probably here too
else:
return Response({"message": "You don't have permission to perform this action 3"}, status=status.HTTP_400_BAD_REQUEST)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
My Serializer.py
in this MovieSerializer I will check if "name" and "description" is same or not, if same then raise an error.
class MovieSerializer(serializers.Serializer):
id = serializers.IntegerField(read_only=True)
name = serializers.CharField(max_length=80)
description = serializers.CharField(max_length=300)
def create(self, validated_data):
return Movie.objects.create(**validated_data)
def update(self, instance, validated_data):
instance.name = validated_data.get('name', instance.name)
instance.description = validated_data.get('description', instance.description)
instance.save()
return instance
# object level validation
def validate(self, data):
if data['name'] == data['description']:
raise serializers.ValidationError("Name and Description can not be Same.")
else:
return data
My views.py
in serializer.is_valid() not pass raise_exception=True, when 'name' and 'description' is same it return a formated error message. output :
class MovieListAV(APIView):
def post(self, request):
serializer = MovieSerializer(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 another serializer
validate "password" and "confirm_password" same or not, if different then raise an error.
class UserPasswordChangeSerializer(serializers.Serializer):
password = serializers.CharField(write_only=True, style={'input_type':'password'})
confirm_password = serializers.CharField(write_only=True, style={'input_type':'password'})
class Meta:
fields = ['password', 'confirm_password']
def validate(self, attrs):
password = attrs.get('password')
password2 = attrs.get('confirm_password')
if password != password2:
raise serializers.ValidationError({'password':'Password and Confirm Password Should be Same!'})
user = self.context.get('user')
user.set_password(password)
user.save()
return attrs
My other view
serializer.is_valid() not pass raise_exception=True, when "password" and "confirm_password" is dirrerent it can't return any formated error message.
but when I use "raise_exception=True" as a parameter of is_valid() like serializer.is_valid(raise_exception=True) then I got formated error message.
my question is, why this time I need to pass "raise_exception=True" ? but "MovieListAV" class view no need to pass "raise_exception=True" in is_view()
class UserPasswordChange(APIView):
authentication_classes = [JWTAuthentication, ]
permission_classes = [IsAuthenticated, ]
def post(self, request, *agrs, **kwargs):
user = User.objects.get(email=request.user)
serializer = UserPasswordChangeSerializer(data=request.data, context={'user':user})
if serializer.is_valid():
return Response({'success':'Password Change successfull.'}, status=status.HTTP_202_ACCEPTED)
return Response(serializer.errors, status=status.HTTP_304_NOT_MODIFIED)
I'm trying to update boolean fields using PATCH request and partial-update in DRF. However, only the name field gets updated and not the others
Model:
class UserModel(models.CustomModel):
name = models.CharField(max_length=64)
enabled = models.BooleanField(default=False)
admin = models.BooleanField(default=False)
Serializer
class UserSerializer(serializers.CustomModelSerializer):
class Meta:
model = UserModel
fields = (
'id', 'name', 'enabled', 'admin'
)
read_only_fields = ('id',)
View and function
class UserView(viewsets.CustomModelViewSet):
def get_queryset(self):
return User.users.for_company(company=self.request.company.pk)
def get_serializer_class(self):
return UserSerializer
def update(self, request, id):
try:
instance = self.get_queryset().get(id=id)
except User.DoesNotExist:
return Response('User not in DB', status=status.HTTP_404_NOT_FOUND)
if 'name' in request.data and self.get_queryset().filter(name=request.data['name']).exists():
return Response('Duplicated user name', status=status.HTTP_400_BAD_REQUEST)
serializer = self.get_serializer(instance, data=request.data, partial=True)
serializer.is_valid(raise_exception=True)
serializer.save()
return Response(serializer.data, status=status.HTTP_200_OK)
Url
urlpatterns = [ url(r'^(?P<id>${uuid})/$', UserView.as_view(
{'patch': 'update'}), name='user-update') ]
Currently if I send a PATCH request with a user_id and the following data in the body, ONLY the name field gets updated in the DB.
name = user2
enabled = True
admin = True
However if I change the update method as follows, the DB gets updated correctly
def update(self, request, id):
try:
instance = self.get_queryset().get(id=id)
except User.DoesNotExist:
return Response('User not in DB', status=status.HTTP_404_NOT_FOUND)
if 'name' in request.data and self.get_queryset().filter(name=request.data['name']).exists():
return Response('Duplicated user name', status=status.HTTP_400_BAD_REQUEST)
serializer = self.get_serializer(instance, data=request.data, partial=True)
if 'enabled' or 'admin' in request.data:
instance.enabled = request.data.get('enabled', instance.enabled)
instance.admin = request.data.get('admin', instance.admin)
serializer.is_valid(raise_exception=True)
serializer.save()
return Response(serializer.data, status=status.HTTP_200_OK)
Is this the correct way to update the fields by modifying the instance? Shouldn't the Django partial-update method take care of this if I'm passing the other boolean fields in the request and update them correctly in the database?
I investigated further and understand that the serializer.save() calls an instance.save() under the hood, and validates the data, only the name fields gets through as validated data and not the other two boolean fields.
I have a custom User model with field is_resetpwd as a boolean field with default value True
I want that to change to False when the password is changed, following is my password change view
class ChangePasswordView(APIView):
"""
An endpoint for changing password.
"""
serializer_class = ChangePasswordSerializer
model = User
permission_classes = (IsAuthenticated,)
def get_object(self, queryset=None):
obj = self.request.user
# print("obj", obj)
return obj
def post(self, request, *args, **kwargs):
self.object = self.get_object()
serializer = self.serializer_class(data=request.data)
if serializer.is_valid():
# Check old password
if not self.object.check_password(serializer.data.get("old_password")):
return Response({"old_password": ["Wrong password."]}, status=status.HTTP_400_BAD_REQUEST)
# set_password also hashes the password that the user will get
self.object.set_password(serializer.data.get("new_password"))
self.object.save()
response = {
'message': 'Password updated successfully',
}
return Response(response)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
what should I add in this to change my boolean value
Just before save() add this
self.object.is_resetpwd = False
and then save the objects just like before
The updated code will look like this
"""
An endpoint for changing passwords.
"""
serializer_class = ChangePasswordSerializer
model = User
permission_classes = (IsAuthenticated,)
def get_object(self, queryset=None):
obj = self.request.user
# print("obj", obj)
return obj
def post(self, request, *args, **kwargs):
self.object = self.get_object()
serializer = self.serializer_class(data=request.data)
if serializer.is_valid():
# Check old password
if not self.object.check_password(serializer.data.get("old_password")):
return Response({"old_password": ["Wrong password."]}, status=status.HTTP_400_BAD_REQUEST)
# set_password also hashes the password that the user will get
self.object.set_password(serializer.data.get("new_password"))
self.object.is_resetpwd = False
self.object.save()
response = {
'message': 'Password updated successfully',
}
return Response(response)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
If a user sends a POST request to create a job, but that job already exists - I am trying to return a 202.
Instead, it is returning a 400..
views
def post(self, request, format=None):
serializer = CreateJobSerializer(data=request.data)
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)
serializer
class CreateJobSerializer(GenericSerializer):
class Meta:
model = Job
fields = ('name')
def create(self, validated_data):
try:
obj = Job.objects.create(**validated_data)
return obj
except:
return Response(serializer.errors, status=status.HTTP_202_ACCEPTED)
I assume that Job.objects.create will fail if unique=true is violated on that model, if that is the case I would expect a 202 to be returned. What am I doing wrong?
Is it because the unique=true error is actually being caught during validation so create() is never actually called?
I think you can capture the Validation Error and then make a custom response with the status you want (202 in this case).
I haven't tried it but this may work:
try:
if (serializer.is_valid(raise_exception=True)):
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
except ValidationError as msg:
if str(msg) == "This Email is already taken":
return Response(
{'ValidationError': str(msg)},
status=status.HTTP_202_ACCEPTED
)
else:
return Response(
{'ValidationError': str(msg)},
status=status.HTTP_400_BAD_REQUEST
)