django-rest-framework icon indicating copy to clipboard operation
django-rest-framework copied to clipboard

Migrating primary key to UUIDField causes serializer validation issues

Open DaanSterk opened this issue 1 year ago • 1 comments

For application-specific performance considerations, we recently decided to migrate our primary keys from BigAutoField to UUIDField.

class BaseModel(models.Model):
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)

All models extend BaseModel. For example:

class Message(BaseModel):
    messenger = models.ForeignKey('core.PersonBusinessUmbrella', on_delete=models.CASCADE)

There is a lot of (writable) nesting involved in our serializers. For example:

class CustomModelSerializer(ModelSerializer):
    created_at = serializers.DateTimeField(required=False, allow_null=True)

class MessageSerializer(CustomModelSerializer):
    messenger = PersonBusinessUmbrellaSerializer(required=False)

class PersonBusinessUmbrellaSerializer(CustomModelSerializer):
    business_umbrella = BusinessUmbrellaSerializer()

class BusinessUmbrellaSerializer(CustomModelSerializer):
    ...

All data retrieval continues to work normally. However, when sending POST/PUT requests with that same data structure to the corresponding ModelViewSet, issues arise.

POST-ing a new Message, including nested models

{
    "relatedFiles": [],
    "order": "00e204d9-ef4e-482c-bd5d-8a4129450728",
    "messenger": {
        "id": "b3756ec6-d793-4a4e-88f7-5826e38f83d3",
        "createdAt": "2024-07-09T09:38:22.906",
        "updatedAt": "2024-07-09T09:38:22.906",
        "person": {
            "id": "445da4a0-9ab6-4bad-9d83-40e9215d269f",
            "createdAt": "2024-07-09T09:38:22.229",
            "updatedAt": "2024-07-09T09:38:22.229",
            "businessUmbrellas": [
                {
                    "id": "b0cfda01-80df-456c-9603-1b9d68b67270",
                    "createdAt": "2024-07-09T09:38:22.129",
                    "updatedAt": "2024-07-09T09:38:22.129",
                    "name": "Bouwman",
                    "comment": null
                }
            ],
            "firstName": "Adam",
            "lastName": "Willems",
            "phoneNumber": "0611666677"
        },
        "businessUmbrella": {
            "id": "b0cfda01-80df-456c-9603-1b9d68b67270",
            "createdAt": "2024-07-09T09:38:22.129",
            "updatedAt": "2024-07-09T09:38:22.129",
            "name": "Bouwman",
            "comment": null
        },
        "email": "[email protected]",
        "phoneNumber": null,
        "roles": [
            {
                "id": "a75be758-b0f1-45b3-8c8b-f77136d133b0",
                "createdAt": "2024-07-09T09:38:25.928",
                "updatedAt": "2024-07-09T09:38:25.928",
                "personBusinessUmbrella": "b3756ec6-d793-4a4e-88f7-5826e38f83d3",
                "type": 2,
                "defaultMachine": {
                    "id": "3fc4f1ce-9293-4d8c-a5f8-fa710024815e",
                    "createdAt": "2024-07-09T09:38:22.198",
                    "updatedAt": "2024-07-09T09:38:22.198",
                    "name": "Heftruck-hebben"
                }
            }
        ]
    },
    "body": "abc"
}

Response

{
    "messenger": {
        "nonFieldErrors": [
            "‘OrderedDict({'created_at': datetime.datetime(2024, 7, 9, 9, 38, 22, 129000), 'uuid': UUID('51007185-c51b-4dd9-b5ff-d615706abc25'), 'name': 'Bouwman', 'comment': None})’ is geen geldige UUID."
        ]
    }
}

Whereas with BigAutoField the serializer validation would understand that the nested OrderedDict represented an entity, it now insists on receiving an actual UUID value.

DaanSterk avatar Jul 11 '24 16:07 DaanSterk

Could you provide a full example, with also the view and some fields in the serializers? Or at least a minimal reproducible example.

This is quite strange, because it seems like the code is using a serializer in which your nested serializer is not used, and thus it requires you to provide an UUID instead of the model.

Here is an expample derived from a project of mine:

# models.py

class Node(models.Model):
    id = models.UUIDField(default=uuid.uuid4, primary_key=True)
    title = models.CharField(max_length=256)
    is_condition = models.BooleanField(default=False)
    description = models.TextField(blank=True, default='')

# serializers.py
class NodeSerializer(serializers.ModelSerializer):
    class Meta:
        model = Node
        fields = serializers.ALL_FIELDS

class NestedSerializer(serializers.Serializer):
    node = NodeSerializer()

# code
ser = MySerializer(
    data={
        "node": {
            "id": "724377d8-ab79-41ec-b909-0242ac120002",
            "title": "Step 0",
            "is_condition": True,
            "description": "example",
        }
    }
)
ser.is_valid()
# > True

sevdog avatar Jul 24 '24 07:07 sevdog

Thanks for the follow-up @sevdog - I'll close this for now.

lovelydinosaur avatar Aug 30 '24 15:08 lovelydinosaur