updating a user using APIVIEW fails. User object either already exists or creates new user - django-rest-framework

I am using django rest framework and I have created a view to update a user.
Here is my view:
def put(self, request, *args, **kwargs):
authorization =UserSuitsPermissions().superuser(request.user)
userpk = kwargs.get('pk', 0)
user = get_object_or_404(STUser, pk=userpk)
if not authorization:
if request.user['id'] == user.id:
authorization = True
if authorization:
serializeddata = UserSerializer(data=request.data)
if serializeddata.is_valid(raise_exception=True):
data = serializeddata.validated_data
user.__dict__.update(**data)
user.save()
serialzed = UserSerializer(user)
return Response(serialzed.data)
return Response(status=status.HTTP_401_UNAUTHORIZED)
now in this linked question they are using a generic view and using the serializer to update the user instance :
Django DRF Update User
with the accepted answer being:
def update(self, request, *args, **kwargs):
serializer = self.serializer_class(request.user, data=request.data, partial=True)
serializer.is_valid(raise_exception=True)
serializer.save()
return Response(serializer.data, status=status.HTTP_200_OK)
I am not using a generic view and in truth Im over them. I dont value it. so the question is, how do we update a user object via the api view?
my biggest question is does this line in the accepted answer have relevance to my code?
serializer = self.serializer_class(request.user, data=request.data, partial=True)

soooo
I figured it out.
the correct way is to pass an instance to a serializer then call save with the partial flag set to true.
the code looks like this:
serializeddata = UserSerializer(user, data=request.data, partial=True)
if serializeddata.is_valid(raise_exception=True):
serializeddata.save()
return Response(serializeddata.data)
with the full view looking like:
def put(self, request, *args, **kwargs):
authorization =UserSuitsPermissions().superuser(request.user)
userpk = kwargs.get('pk', 0)
user = get_object_or_404(STUser, pk=userpk)
if not authorization:
if request.user['id'] == user.id:
authorization = True
if authorization:
serializeddata = UserSerializer(user, data=request.data, partial=True)
if serializeddata.is_valid(raise_exception=True):
serializeddata.save()
return Response(serializeddata.data)
return Response(status=status.HTTP_401_UNAUTHORIZED)
so thats cool

Related

django rest permission called many times

So I'm building a simple blog app and made custom permission so only owner of post can update or delete the post, so I added print statment to check if it's working but found out it's called 6 times every time calling the view!
here's the code
class PostUpdateDeletePermission(IsAuthenticated):
def has_object_permission(self, request, view, obj):
print("called")
if request.user.is_superuser:
return True
if (request.method == 'PUT' or request.method == 'DELETE') and request.user != obj.user:
return False
return True
class PostView(ModelViewSet):
queryset = Post.objects.all()
serializer_class = PostSerializer
permission_classes = (PostUpdateDeletePermission,)
def create(self, request):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
obj = serializer.save(user=request.user)
if request.data.get('files_length'):
PostFile.objects.bulk_create([PostFile(img = request.data.get(f"file-{x}"), post=obj) for x in range(request.data.get('files_length'))])
return Response({'posts' : serializer.data}, status=201)
that's how many times called printed when refreshing the drf browsable api
called
called
called
called
called
called

How to update another field when using partial_update?

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

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.

Serializer `validate()` not getting called on `is_valid()` on POST

I want to create a non class-based form to facilitate uniform logging in by users across all front-end apps. Currently, it looks like this
My serializer class:
class EmployeeLoginSerializer(serializers.Serializer):
username = serializers.CharField(min_length=6)
password = serializers.CharField(min_length=6)
def validate_credentials(self, attrs):
print('validating')
try:
emp: Employee = Employee.objects.get(username=attrs['username'])
if crypto.verify_password(attrs['password'], emp.password_hash):
return attrs
except Exception:
raise serializers.ValidationError("Incorrect username or password")
raise serializers.ValidationError('Incorrect username or password')
My view class:
class TestView(APIView):
serializer_class = EmployeeLoginSerializer
def get(self, request, *args, **kwargs):
return Response({'Message': 'Get works'})
def post(self, request, *args, **kwargs):
print(request.POST)
serializer = self.serializer_class(data=request.POST)
if serializer.is_valid():
return Response({'Message': 'Credentials were correct'})
My issue is that serializer.is_valid() doesn't seem to be calling on validate automatically. I know I could just call serializer.validate() manually but all the docs and questions on StackOverflow seem to show validate() being called by is_valid() automatically so I get that feeling that that wouldn't be the best practice. Is there something I'm missing?
The is_valid() method will call validate() method of the serializer and validate_FIELD_NAME() methods.
In your code, the validate_credentials() seems a regular class method which won't detect by DRF since the credentials isn't a field on the serializer.

How can one return 400 status code with DRF APIView get method and ModelSerializer?

I have the following code (for the endpoint /things/{id}/permission-to-do/
views.py
class PermissionsToDo(APIView):
authentication_classes = (TokenAuthentication,)
permission_classes = (IsAuthenticated,)
def get(self, request,*args,**kwargs):
thing_id = kwargs.get('pk')
thing = Thing.objects.filter(pk=thing_id,is_active=True)
serializer = serializers.GetDoPermissionSerializer(thing[0],context={'request': request})
return Response(serializer.data, status=status.HTTP_200_OK)
serializers.py
class serializers.GetDoPermissionSerializer(serializers.ModelSerializer):
def _can_do(self, thing):
return thing.can_be_done_by(self.context['request'].user)
can_do = serializers.SerializerMethodField('_can_do')
class Meta:
model = Thing.objects.filter
fields = ('can_do',)
extra_kwargs = {
'can_do': {'read_only': True},
}
The thing.can_be_done_by(user) method returns a Boolean. This works fine with a correct request but I want to add a way to validate the request and send appropriate status code for client errors, such as status.HTTP_400_BAD_REQUEST
My idea was to just add in views.py a:
if serializer.is_valid():
return Response(serializer.data, status=status.HTTP_200_OK)
else:
return Response(SOMETHING?,status.HTTP_400_BAD_REQUEST)
if I evaluate serializer.is_valid() I obtain an error message saying:
'Cannot call `.is_valid()` as no `data=` keyword argument was '
AssertionError: Cannot call `.is_valid()` as no `data=` keyword argument was passed when instantiating the serializer instance.
if I change the line:
serializer = serializers.GetDoPermissionSerializer(thing[0],context={'request': request})
into:
serializer = serializers.GetDoPermissionSerializer(data=thing[0],context={'request': request})
but then, I get an error suggesting the I should be passing a dictionary as data and not an object. but then I'm not sure how to implement the validate method and how to change the _can_do method to get it to work.
Any idea?
Thanks for your time if you have some to spare!
.validate() method takes a single argument, which is a dictionary of field values. It should raise a ValidationError if necessary, or just return the validated values.
One of the ways you can achieve this is to convert the model object to a dict. So try the following snippet,
class PermissionsToDo(APIView):
authentication_classes = (TokenAuthentication,)
permission_classes = (IsAuthenticated,)
def get(self, request, *args, **kwargs):
thing_id = kwargs.get('pk')
thing = Thing.objects.filter(pk=thing_id, is_active=True)
serializer = serializers.GetDoPermissionSerializer(data=thing[0].__dict__, context={'request': request}) # Change is here <<<<
if serializer.is_valid():
return Response(serializer.data, status=status.HTTP_200_OK)
return Response(SOMETHING?, status.HTTP_400_BAD_REQUEST)
My suggestion
If you are serializing the object from DB, most of the time it won't raise any validation error. I would suggest that, try to show error message if thing object become a empty queryset.So,
class PermissionsToDo(APIView):
authentication_classes = (TokenAuthentication,)
permission_classes = (IsAuthenticated,)
def get(self, request, *args, **kwargs):
thing_id = kwargs.get('pk')
try:
serializer = serializers.GetDoPermissionSerializer(Thing.objects.get(id=thing_id), context={'request': request})
return Response(serializer.data, status=status.HTTP_200_OK)
except Thing.DoesNotExist:
return Response(data="object not found", status.HTTP_400_BAD_REQUEST)
I used your "suggestion" to the exception that I had to modify the last line. I replaced:
return Response(data="object not found", status.HTTP_400_BAD_REQUEST)
by :
return Response("object not found", status.HTTP_400_BAD_REQUEST)
or I get the following error:
SyntaxError: positional argument follows keyword argument

Resources