T. Decker
T. Decker

Reputation: 145

Django REST foreign key issue

Trying to implement and test my own serializer, I have the following issue:

Searched for many question including this error message, I didn't manage to find any solution to my problem.

DRF Foreign key misusage?

ValueError: Cannot assign "'effcad53-bc45-41fa-be43-4f22c0376eb5'": "Product.related_workspace" must be a "Workspace" instance.

The Workspace class:

class Workspace(models.Model):
    id = models.UUIDField(
            primary_key=True,
            default=uuid.uuid4,
    )
    related_login = models.ForeignKey(
            Login,
            on_delete=models.PROTECT,
    )
    description = models.CharField(
            max_length=150,
    )

    def __str__(self):
        login = Login.objects.get(pk=self.related_login_id)
        return f'{login.username} ({self.description})'

    class Meta:
        db_table = 'workspaces'

The Product class:

class Product(models.Model):
    id = models.UUIDField(
            primary_key=True,
            default=uuid.uuid4,
    )
    related_workspace = models.ForeignKey(
            Workspace,
            on_delete=models.PROTECT,
    )
    code = models.CharField(
            max_length=50,
    )
    description = models.CharField(
            max_length=150,
    )

    class Meta:
        db_table = 'products'
        unique_together = ('related_workspace', 'code',)

The ProductSerializer class:

class ProductSerializer(serializers.Serializer):
    id = serializers.UUIDField(read_only=True)
    related_workspace = serializers.UUIDField()
    code = serializers.CharField()
    description = serializers.CharField()

    def create(self, validated_data):
        return Product.objects.create(**validated_data)

    def update(self, instance, validated_data):
        instance.related_workspace = validated_data.get('related_workspace', instance.related_workspace)
        instance.code = validated_data.get('code', instance.code)
        instance.description = validated_data.get('description', instance.description)
        instance.save()
        return instance

The script I'm using to test my serializer :

PROJECT_NAME = 'api'


def main():
    #
    from api.applications.product.models import Product
    from api.applications.product.serializers import ProductSerializer
    #
    # Create a product
    #
    code = 'P001'
    description = f'Product {code}'
    #
    # This is a valid workspace id!
    #
    related_workspace = 'effcad53-bc45-41fa-be43-4f22c0376eb5'
    #
    product = Product(
            code=code,
            description=description,
            related_workspace=related_workspace,
    )
    product.save()
    #
    serializer = ProductSerializer(product)
    print('serializer.data:', serializer.data)


if __name__ == '__main__':
    import os

    os.environ.setdefault('DJANGO_SETTINGS_MODULE', '%s.settings' % PROJECT_NAME)
    import django

    django.setup()
    main()

Any advice of what I'm missing ?

Upvotes: 1

Views: 231

Answers (2)

T. Decker
T. Decker

Reputation: 145

Searching deeper, my mistake was in the way I was creating my Product instance...

In my test script, I just updated:

    product = Product(
            code=code,
            description=description,
            related_workspace=related_workspace,
    )

To:

    product = Product(
            code=code,
            description=description,
            related_workspace=Workspace.objects.get(pk=related_workspace)
    )

Upvotes: 0

Joe
Joe

Reputation: 492

First thing, when you are testing anything in Django its easiest to write test classes in a test module and use manage.py to run them. All that setup logic you are doing is already in manage.py.

python manage.py test my_app.tests.my_test_class

In your serializer you are setting a foreign key to related_workspace = serializers.UUIDField(), instead you want to nest your serializers (docs) so it pulls out the Workspace data, serializes, saves, and returns a Workspace object. You should have two serializers and they should look something like this, I am going to use ModelSerializer.

from rest_framework import serializers

# Workspace Serializer
class WorkspaceSerializer(serializers.ModelSerializer):
    class Meta:
        model = Workspace
        fields = "__all__"

class ProductSerializer(serializers.ModelSerializer):
    related_workspace = WorkspaceSerializer()

    class Meta:
        model = Product
        fields = ["id", "related_workspace", "code", "description"]

    def create(self, validated_data):
        workspace_data = validated_data.pop('related_workspace')

        workspace_serializer = WorkspaceSerializer(data=workspace_data)
        if workspace_serializer.is_valid(reaise_exception=True):
            workspace = workspace_serializer.save()
            product = Product.objects.create(related_workspace = workspace, **validated_data)
            return product

We want to remove the workspace data, then feed it to its own serializer, if that data is valid, we save the serializer and we get a Workspace object in return. Then we can create our Product object with that returned workspace object.

Upvotes: 1

Related Questions