#Updated Abstract User Via DRF

29 messages · Page 1 of 1 (latest)

gloomy briar
#

First off, first forum style post 1a6af912b64e4082be5f09a41d8ddd59 Awesome stuff!

Anyways, I'm trying to create a test to update a custom created user model using PUT. I was able to create a test to create a new user via DRF POST and edit other endpoints with PUT methods. Here is what I have for updating a current user:

serializers.py

class UserEditSerializer(serializers.ModelSerializer):
    class Meta:
        model = User
        fields = [
            "id",
            "business_id",
            "location",
            "first_name",
            "last_name",
            "phone_number",
            "email",
            "password",
            "active",
            "created_at",
            "updated_at",
            "deleted_at"
        ]
    
    def update(self, validated_data):
        user = super(UserEditSerializer, self).update(validated_data)
        user.save()
        return user

views.py

class UserDetailApiView(APIView):
    # add permission to check if User is authenticated
    permission_classes = [permissions.IsAuthenticated]

    def get_object(self, user_id):
        '''
        Helper method to get the object with given user_id, and user_id
        '''
        try:
            return User.objects.get(pk = user_id)
        except User.DoesNotExist:
            return None

    # 3. Retrieve
    def get(self, request, user_id, *args, **kwargs):
        '''
        Retrieves the User with given user_id
        '''
        user_instance = self.get_object(user_id)
        if not user_instance:
            return Response(
                {"res": "Object with User id does not exists"},
                status=status.HTTP_400_BAD_REQUEST
            )

        serializer = UserEditSerializer(user_instance)
        return Response(serializer.data, status=status.HTTP_200_OK)

    # 4. Update
    def put(self, request, user_id, *args, **kwargs):
        '''
        Updates the User with given user_id if exists
        '''
        user_instance = self.get_object(user_id)
        if not user_instance:
            return Response(
                {"res": "Object with User id does not exists"}, 
                status=status.HTTP_400_BAD_REQUEST
            )
        data = {
            'first_name': request.data.get('first_name'), 
            'last_name': request.data.get('last_name'), 
            'phone_number': request.data.get('phone_number'),
            'email': request.data.get('email'),
            'password': request.data.get('password')
        }
        serializer = UserEditSerializer(instance = user_instance, data=data, partial = True)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=status.HTTP_200_OK)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

test_views.py

# Tests editing an existing user's information with endpoint
# Uses UserDetailApiView
class UserPutTest(APITransactionTestCase):
    reset_sequences = True
    def setUp(self):
        self.user = User.objects.create_user(
            email='testuser@website.com',
            password='123',
            first_name='firstname',
            last_name='lastname',
            phone_number='19877654321'
        )
        self.client.force_authenticate(self.user)

    def test_create_user(self):
        """
        Ensure we can create a new User object.
        """
        url = reverse('user_details', kwargs={'user_id': 1})
        data = {
        'phone_number': '1345698321'
    }

        response = self.client.put(url, data, format='json', follow=True)
        self.assertEqual(response.status_code, status.HTTP_201_CREATED)
        self.assertEqual(User.objects.count(), 1)
        self.assertEqual(User.objects.get(email='name@website.com').phone_number, '1345698321')
abstract oxide
#

and your problem is what exactly?

gloomy briar
#
======================================================================
FAIL: test_update_user (user.tests.test_views.UserPutTest)
Ensure we can edit an existing User object.
----------------------------------------------------------------------
Traceback (most recent call last):
  File "C:\Users\pc\Documents\Business\Scheduling SaaS\Gitbhub Repo\backend\main_project\user\tests\test_views.py", line 64, in test_update_user
    self.assertEqual(response.status_code, status.HTTP_201_CREATED)
AssertionError: 400 != 201
#

Oops, just noticed something xD

abstract oxide
#

your serializer is invalid I guess. you need to give all the fields or mark them as optional

#
        data = {
            'first_name': request.data.get('first_name'), 
            'last_name': request.data.get('last_name'), 
            'phone_number': request.data.get('phone_number'),
            'email': request.data.get('email'),
            'password': request.data.get('password')
        }

looks very wrong, in your example all except phone_number will be None

gloomy briar
#

I used that same method with another table, and it left everything else as is while just updated the single entry

#

Rather than setting everything to None

#

But yeah I think it has to do with the serializer too, mainly the create definition.

#

This is the serializer for creating a new user. It worked with any other models I have except the custom Django user one, so I had to add the create definition:

#
class UserCreationSerializer(serializers.ModelSerializer):
    class Meta:
        model = User
        fields = [
            "id",
            "business_id",
            "location",
            "first_name",
            "last_name",
            "phone_number",
            "email",
            "password",
            "active",
            "created_at",
            "updated_at",
            "deleted_at"
        ]

    def create(self, validated_data):
        user = super(UserCreationSerializer, self).create(validated_data)
        user.set_password(validated_data['password'])
        user.save()
        return user
abstract oxide
#

I know that DRF has GenericAPIView + UpdateModelMixin with patch() and partial_update(), I'm not sure how to use it though, would look into that

gloomy briar
#

Hmm alrighty, I'll check it out

gloomy briar
#

It's weird because even if I use partial = True with a PUT or PATCH request it doesn't work

#

But if I edit the data to be:

    def put(self, request, user_id, *args, **kwargs):
        '''
        Updates the User with given user_id if exists
        '''
        user_instance = self.get_object(user_id)
        if not user_instance:
            return Response(
                {"res": "Object with User id does not exists"}, 
                status=status.HTTP_400_BAD_REQUEST
            )
        data = {
            # 'first_name': request.data.get('first_name'), 
            # 'last_name': request.data.get('last_name'), 
            'phone_number': request.data.get('phone_number'),
            # 'email': request.data.get('email'),
            # 'password': request.data.get('password')
        }
        serializer = UserEditSerializer(instance = user_instance, data=data, partial=True)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=status.HTTP_200_OK)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

Then it works :/

abstract oxide
#

well telling it to just update the phonenumber is different to telling it to update all fields with most being set to None, eh?

gloomy briar
#

But if the partial field is set to True, wouldn't it just skip the fields that return no information?

#

Or would I have to change something in the JSON

abstract oxide
#

I don't know the correct usage, but it looks like you want to modify the initial payload to only include fields that should update

#

think about having a null=True field, sending a payload like my_field=None would be a valid update

gloomy briar
#

But then would it actually set my_field to None in the db?

abstract oxide
#

if it's a nullable field yes, otherwise your serializer will be invalid as the value is not allowed

#

which iirc was your problem

gloomy briar
#

Ah yeah that's probably it

#

Is there anyway to clean JSON like this?

for item in data:
            if item == None:
                data.pop(item)
            else:
                pass
            return data
#

Basically remove entries that are None or only pass fields that have data

#

Nvm xD

#
    # 4. Update
    def put(self, request, user_id, *args, **kwargs):
        '''
        Updates the User with given user_id if exists
        '''
        user_instance = self.get_object(user_id)
        if not user_instance:
            return Response(
                {"res": "Object with User id does not exists"}, 
                status=status.HTTP_400_BAD_REQUEST
            )
        data = {
            # 'first_name': request.data.get('first_name'), 
            'last_name': request.data.get('last_name'), 
            'phone_number': request.data.get('phone_number'),
            # 'email': request.data.get('email'),
            # 'password': request.data.get('password')
        }
        # Used to clean the JSON to only update entries with data
        def remove_empty(d):
            final_dict = {}
            for a, b in d.items():
                if b:
                    if isinstance(b, dict):
                        final_dict[a] = remove_empty(b)
                    elif isinstance(b, list):
                        final_dict[a] = list(filter(None, [remove_empty(i) for i in b]))
                    else:
                        final_dict[a] = b
            return final_dict

        clean_data = remove_empty(data)

        serializer = UserEditSerializer(instance = user_instance, data=clean_data, partial=True)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=status.HTTP_200_OK)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
#

That works 😄