I have made an API for password change but it's changing the passwords for all users instead of only one user.
seriealizer code is below:
class ChangePasswordSerializer(serializers.ModelSerializer):
password1 = serializers.CharField(write_only=True, required=True, validators=[validate_password])
password2 = serializers.CharField(write_only=True, required=True)
old_password = serializers.CharField(write_only=True, required=True)
class Meta:
model = User
fields = ('old_password', 'password1', 'password2')
def validate(self, attrs):
if attrs['password1'] != attrs['password2']:
raise serializers.ValidationError({"password": "Password fields didn't match."})
return attrs
def validate_old_password(self, value):
user = self.context['request'].user
if not user.check_password(value):
raise serializers.ValidationError({"old_password": "Old password is not correct"})
return value
def update(self, instance, validated_data):
instance.set_password(validated_data['password1'])
instance.save()
return instance
view code is below:
class ChangePasswordView(generics.UpdateAPIView):
queryset = User.objects.all()
permission_classes = (IsAuthenticated,)
serializer_class = ChangePasswordSerializer
what is wrong with this code ?
Password change is very straight forward. Django already has a form to do it. Try the below code:
#api_view(['PUT'])
#permission_classes([IsAuthenticated])
def change_password(request):
form = PasswordChangeForm(request.user, request.data)
if form.is_valid():
form.save()
serializer = UserSerializer(request.user)
return Response(serializer.data)
return Response(form.errors, status=status.HTTP_400_BAD_REQUEST)
Read this page for more information on how to build user auth methods using DRF: https://kushgoyal.com/creating-a-sceure-login-api-using-drf-token-auth/
url for this will be of this format:
url(r'change_password/', views.change_password)
Related
Here i am trying to create serializer instance without data argument because all i want to create a "Like" object which requires "user" object which i can get from request and "post" object that i am getting through querying Post model with pk but since i am not passing any data argument while deserializing it will throw an error.
So how can create instance without passing data argument or do i have to change my code and add data argument?
class Like(models.Model):
post = models.ForeignKey(Post, on_delete=models.CASCADE, related_name="likes")
liked_by = models.ForeignKey(User, on_delete=models.RESTRICT, related_name = "liked_posts")
def __str__(self):
return "{0} liked by {1}".format(self.post.img, self.liked_by.username)
class LikeSerializer(serializers.ModelSerializer):
liked_by = UserSerializer(read_only=True)
class Meta:
model = Like
fields = ("id","post", "liked_by")
class AddLike(APIView):
permission_classes = [IsAuthenticated]
def post(self, request, pk):
post = Post.objects.get(pk=pk)
user = request.user
serializer = LikeSerializer()
if not serializer.is_valid():
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
serializer.save(post=post, liked_by=user)
return Response(data= serializer.data, status=status.HTTP_201_CREATED)
I think you can set the post field as read_only in the LikeSerializer.
class LikeSerializer(serializers.ModelSerializer):
liked_by = UserSerializer(read_only=True)
class Meta:
model = Like
fields = ("id","post", "liked_by")
extra_kwargs = {
'post': { 'read_only': True }
}
I'm using partial_updates on my user model, and I wish to change the is_active to True on the user model instance when a partial_update happens - even though is_active is not exposed to the endpoint. My class looks like this:
class UserInvitationUpdate(mixins.UpdateModelMixin, generics.GenericAPIView):
serializer_class = serializers.UserSerializer
queryset = User.objects.all()
def get(request, *args, **kwargs):
username = kwargs.get('username')
token = kwargs.get('token')
return activated_user(username, token)
def get_object(self):
username = self.kwargs.get('username')
user = User.objects.get(username=username)
return user
def put(self, request, *args, **kwargs):
username = self.kwargs.get('username')
token = self.kwargs.get('token')
if my_if_statement_is_true:
# TODO set user to active
# how do I set is_active = True for the user model instance?
return self.partial_update(request, *args, **kwargs)
You have multiple way to deal with that. You could either change your serializer .save() method and set manually the field is_active to true, or set it in the view by updating the perform_update() method of your view :
def perform_update(self, serializer):
serializer.save(is_active=True)
More info here
I'm having a hard time with nested serializers, especially updating them. So far I can update the lesson description and all the main fields before the nested serializer (like course description, title etc). Ideally I'd like to search using lesson_id, and not lesson.title like it is now, and update description and title at the same time. Is there a workaround for that?
My models.py
class Course (models.Model):
title = models.CharField (max_length=150)
description = models.CharField(max_length=250, default="No Description")
student = models.ManyToManyField(Student, related_name='courses', blank=True)
teacher = models.ManyToManyField(Teacher, related_name='teacher', blank=True)
def __str__(self):
return self.title
class Lesson (models.Model):
title = models.CharField(max_length=100)
description = models.TextField(default="No Description")
course = models.ForeignKey(Course, on_delete=models.CASCADE, related_name='lessons')
def __str__(self):
return self.title
Serializers.py where the problem is
class CourseSerializer (serializers.ModelSerializer):
lessons = LessonSerializer(many=True, required=False)
#teacher = TeacherSerializer(many=True)
class Meta:
model = Course
fields = ('id', 'title', 'description', 'lessons') #to separate serializer with students for teachers later
def update(self, instance, validated_data):
lessons = validated_data.pop('lessons', [])
instance = super().update(instance, validated_data)
for lesson in lessons:
lesson, updated = Lesson.objects.update_or_create( defaults={'description': lesson["description"]}, title= lesson["title"])
#pk = instance.lessons_id doesn't work, I cannot get this id
instance.save()
return instance
Views
class CourseDetailDeleteView (generics.RetrieveUpdateDestroyAPIView):
permission_classes = (IsAdminUserOrAuthenticatedOrReadOnly,)
queryset = Course.objects.all()
serializer_class = CourseSerializer
def update(self, request, *args, **kwargs):
serializer = CourseSerializer(instance=self.get_object(), data=request.data, partial=True)
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
If your problem is that validated_data["lessons"][0]["id"] doesn't exists:
By default the id field of a ModelSerializer is read_only, so id is not included in validated_data in .create() or .update(). Thus you'll have to override that:
class LessonSerializer(serializers.ModelSerializer):
id = serializers.IntegerField()
....
## Optional: in case you don't want `id` getting explicitly set
def create(self, validated_data):
validated_data.pop("id", None)
return super().create(self, validated_data)
## Optional: in case you don't want `id` of the instance getting updated
def update(self, instance, validated_data):
validated_data.pop("id", None)
return super().update(self, instance, validated_data)
Another note:
There's no way to know if serializer.save() is even called in CourseDetailDeleteView.update. I would suggest to set serializer.is_valid(raise_exception=True) instead so it can return error messages and show you why it didn't save.
Something strange happened: I was defining an endpoint and initially two fields were visible in the API form: model_id and payload, as given in the model definition:
### models.py:
class CarModel(models.Model):
model_id = models.CharField(max_length=10, primary_key=True)
name = models.CharField(max_length=40)
active = models.BooleanField(default=True)
def __str__(self):
return self.model_id
class Calculator(models.Model):
model = models.ForeignKey(CarModel, on_delete=models.CASCADE)
payload = models.TextField()
def model_id(self):
return self.model.model_id
def __str__(self):
return f"Calculations for {self.model.name}"
### serializers.py:
class CalculatorSerializer(serializers.ModelSerializer):
model_id = serializers.SerializerMethodField()
class Meta:
model = Calculator
fields = ['model_id', 'payload']
def get_model_id(self, obj):
return obj.model_id()
### views.py:
class CalculatorViewSet(viewsets.ModelViewSet):
serializer_class = CalculatorSerializer
queryset = Calculator.objects.all()
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
return Response(f"{serializer.data.upper()}", status=status.HTTP_200_OK)
So, both fields were visible, but POST requests ended in the AttributeError: 'collections.OrderedDict' object has no attribute 'model_id'. Trying to fix that, I eventually and accidentally removed model_id from view - it doesn't display in DRF's forms. And the AttributeError still persists.
What is wrong with this piece of code?
OK, it turns out that defining fields in this manner:
fields = '__all__'
makes also the model_id visible. Still, no idea why explicit insert doesn't work.
In case of the other issue, the AttributeError, I had to pull the value out of an OrderedDict. Modified method looks like this:
def get_model_id(self, obj):
return obj["model"].model_id
Beside that, I found one more error inside views.py's create method: serializer.data won't implement upper() method; some key, in my case serializer.data['payload'], has to be referenced, so for example:
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
res = {
"payload": f"{serializer.data['payload'].upper()}"
}
return Response(res, status=status.HTTP_200_OK)
I have this serializer:
class ChangePasswordSerializer(DynamicFieldsModelSerializer):
current = serializers.CharField()
confirm = serializers.CharField()
class Meta:
model = models.User
fields = ('password', 'current', 'confirm')
validators = []
def update(self, instance, validated_data):
if instance.check_password(validated_data.get('current')):
instance.set_password(validated_data['password'])
instance.save()
else:
raise serializers.ValidationError("Current password is not correct")
return instance
I have this inside my update function:
instance.check_password(validated_data.get('current'))
but I want this action happens outside of function update in validate_current function, but I dont know how
You can write custom validator for the current password
def validate_current(self, value):
if not self.instance.check_password(value):
raise serializers.ValidationError("Current password is not correct")
return value
you will have to pass instance from view in serializer