How to update another field when using partial_update? - django-rest-framework

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

Related

passwords is changing for all users in django rest

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)

AttributeError: 'collections.OrderedDict' object has no attribute 'model_id' and 'model_id' is missing from visible fields

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)

Why is invalid data saved to database anyway when custom `create` method is used?

I'm creating a custom create method to process incoming data. It gets validated by a serializer:
# serializers.py:
class ItemVersionSerializer(serializers.ModelSerializer):
item_ver_id = serializers.RegexField(regex='^r\d{2}$', allow_blank=False)
session_id = serializers.RegexField(regex='^s\d{2}$', allow_blank=False)
config = serializers.CharField(min_length=6)
item_ver = serializers.CharField(min_length=6)
(...)
The method itself looks like this:
# views.py:
class ItemVersionViewSet(viewsets.ModelViewSet):
serializer_class = ItemVersionSerializer
lookup_field = 'item_ver_id'
def create(self, request, *args, **kwargs):
data = request.data
model = ItemModel.objects.get(item_id=data["model"])
finished = True if "finished" in data else False
item_version = ItemVersion.objects.create(
model=model,
item_ver_id=data["item_ver_id"],
config=data["config"],
item_ver=data["item_ver"],
session_id=data["session_id"],
finished=finished
)
serializer = ItemVersionSerializer(data=request.data)
if serializer.is_valid():
# item_version.save() # data gets saved even with this line off
return Response(serializer.validated_data)
else:
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
When passing incorrect data, e.g. too short item_ver string, I receive error 400 and this info:
{
"item_ver": [
"Ensure this field has at least 6 characters."
]
}
However, when I correct the data and send it again, I receive IntegrityError: UNIQUE constraint failed: core_itemversion.item_ver_id and looking at the list of instances I can see the new entry saved to the database anyway.
Which method is responsible for saving the data?
Make sure that you validate your serializer BEFORE creating. Like this:
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
serializer.save()
headers = self.get_success_headers(serializer.data)
return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
As you can see, there is no need for if else when doing serializer.is_valid(), just use raise_exception=True, and the viewset will make sure that the response will be a 400_BAD_REQUEST.
Second, don't do Model.objects.create(), then save. Do ModelSerializer(request.data), and then follow the convention.
Hopefully this makes sense to you, do let me know if you need me to expand.

clean django api in serilizer

i write a django api i would like to know if reminder field changed then the Appointment model object save current user.
i used this link
See object changes in post_save in django rest framework
and write this code
class AppointmentBackOfficeViewSet(mixins.ListModelMixin,
mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
mixins.CreateModelMixin,
viewsets.GenericViewSet):
model = Appointment
read_serializer_class = AppointmentSerializer
write_serializer_class = AppointmentCreateSerializer
reminder_change = False
def perform_update(self, serializer):
if 'reminder' in serializer.validated_data:
self.reminder_change = True
serializer.save()
def update(self, request, *args, **kwargs):
super(AppointmentBackOfficeViewSet, self).update(request, *args, **kwargs)
instance = self.get_object()
instance.user = request.user
if self.reminder_change:
instance.reminder_user = request.user
instance.save()
res = self.write_serializer_class(instance).data
return Response(res)
class AppointmentCreateSerializer(serializers.ModelSerializer):
class Meta:
model = Appointment
exclude = ['created_time', 'modified_time']
is there a way to write this code clearer than this :
dont use self.reminder_change class field is there better way?
may be move this lines to serializer??(in serializer dont access to request.user)
Removed unecessary fields and update method:
class AppointmentBackOfficeViewSet(mixins.ListModelMixin,
mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
mixins.CreateModelMixin,
viewsets.GenericViewSet):
model = Appointment
read_serializer_class = AppointmentSerializer
write_serializer_class = AppointmentCreateSerializer
def perform_update(self, serializer):
# Here you can set attributes directly to the serializer update method
# It's like setting attribute directly to the updating model
# Check that reminder has been changed
reminder = serializer.validated_data.get('reminder')
if reminder and reminder != instance.reminder: # Reminder is different
serializer.save(user=self.request.user,
reminder_user=self.request.user)
else:
serializer.save(user=self.request.user)
This solution will work depending on what type of reminder field is.
If it's String or Integer it will be ok. Problem is that if it's object. Viewset will raise error because serializer reminder field would be integer but instance.reminder would be instance of the reminder object so keep this in mind.

Django Rest Framework - separate serializer class per method in model based API view

Say I have a simple Django REST Framework view that's extending multiple model classes and serves all the methods in one URL endpoint:
class UserAPIView(RetrieveAPIView, DestroyAPIView, BaseObjectAPIView):
permission_classes = (IsAuthenticated, )
serializer_class = UserSerializer
def get_serializer_class(self, *args, **kwargs):
# return different serializer depending on method??
# return UserUpdateSerializer
return UserViewSerializer
def get(self, request, *args, **kwargs):
"""
Retrieve user details
"""
# ...
return Response(data={'result': "OK"}, status=200)
def delete(self, request, pk):
"""
Delete user
"""
# ...
return Response(data={'result': "OK"}, status=200)
def put(self, request, pk):
"""
Change user
"""
# ...
return Response(data={'result': "OK"}, status=200)
Now I need to use different serializers per method, as my get-method will use different fields than my put-method, example serializers:
class UserViewSerializer(serializers.ModelSerializer):
firstname = serializers.Field(source='firstname')
lastname = serializers.Field(source='lastname')
username = serializers.Field(source='username')
class Meta:
model = User
class UserUpdateSerializer(serializers.ModelSerializer):
firstname = serializers.Field(source='firstname')
lastname = serializers.Field(source='lastname')
class Meta:
model = User
Is it possible to use different serializers for each method in my model based API view?
UPDATE:
I know how to use different serializers inside the methods themselves.
But I need to get the Browsable API generated by Swagger (Django module rest_framework_swagger) to retrieve different serializers for each method.
I can see that loading the API browser page triggers get_serializer_class, but inside that method, I don't know what method Swagger tries to get the serializer for.
How can I get rest_framework_swagger to retrieve different serializers per method?
I think there are at least two ways to achieve this:
You simply set the serializer that you want in each of your methods. Like this:
def get(self, request, *args, **kwargs):
self.serializer_class = UserViewSerializer
# ...
return Response(data={'result': "OK"}, status=200)
You override the get_Serializer_class method. Like this:
def get_serializer_class(self, *args, **kwargs):
if self.request.method == 'POST':
return UserUpdateSerializer
return UserViewSerializer
Hope this helps.
I suppose you could use yaml docstring on each method to override serializers. Like:
def put(self, request, pk):
"""Change user
---
serializer: .serializers.UserUpdateSerializer
"""
# ...
return Response(data={'result': "OK"}, status=200)

Resources