wakey
wakey

Reputation: 2409

Do not create sub-objects in django

I have a model representing a Status - and have a foreign key to Status from an Object model. I want to be able to create new objects, but do not want to allow the possibility of creating any more Status entries (there are a set of 5 pre-defined ones that are migrated into the database). I believe I have figured out how to structure serializers in such a way as to only re-use existing Status entries, but I'm not sure if it is the best way to go about doing something like this...

Some Simplified Code:

class StatusSerializer(serializers.ModelSerializer):
    class Meta:
        model = Status
        fields = ('name',)

    def to_representation(self, obj):
        return obj.name

    def to_internal_value(self, data):
        return {
            'name': data
        }


class ObjectSerializer(serializers.ModelSerializer):

    status = StatusSerializer(read_only=True)

    class Meta:
        model = Object
        fields = ('obj_name', 'status',)

    def create(self, validated_data):
        # We do not want to create new statuses - only use existing ones
        status = Status.objects.get(name=self.initial_data['status'])
        return Object.objects.create(status=status, **validated_data)

    def update(self, instance, validated_data):

        instance.obj_name = validated_data.get('obj_name', instance.obj_name)

        # We do not want to create new statuses - only use existing ones
        instance.status = Status.objects.get(name=self.initial_data['status']) if 'status' in self.initial_data else instance.status

        return instance

As seen above, I also flatten out the Status object when it is displayed - e.g. I turn the following

{
    'obj_name': 'ObjectName',
    'status': {
        'name': 'StatusName'
    }
}

Into this

{
    'obj_name': 'ObjectName',
    'status': 'StatusName'
}

This seems to work fine -- however I am not sure how to handle cases where a user of the API gives me an invalid Status name. Right now, the api would bubble up a Status.DoesNotExist exception from one of the Status.objects.get(...) requests - should I just catch that and re-raise as something the serializer/view would expect?

Thanks!

Edit: Realized my question wasnt exactly clear...

  1. Is the above a good way to prohibit creation of Status objects - and enforce that any Object created will use one of those statuses?
  2. What is the best way to handle the case where a user attempts to create an Object with an invalid Status name?

Upvotes: 1

Views: 60

Answers (1)

Ritesh Agrawal
Ritesh Agrawal

Reputation: 821

You can use validate method in the ObjectSerializer.

class ObjectSerializer(serializers.ModelSerializer):

    status = StatusSerializer(read_only=True)

    class Meta:
        model = Object
        fields = ('obj_name', 'status',)

    def validate(self, attrs):
         validated_data = super().validate(attrs)
         status = self.initial_data.get('status')
         # Here assuming that None is not the valid value for status
         if status is not None:
             status_obj = Status.objects.filter(name=status)
             if not status_obj:
                 raise serializer.ValidationError('Invalid Status')
             status_obj = status_obj[0]
             validated_data['status'] = status_obj
        return validated_data

    # Nothing special to be done in create/update since we are sending data
    # in validated_data which will directly sent to the instance.

    # def create(self, validated_data):
        # We do not want to create new statuses - only use existing ones
        # status = Status.objects.get(name=self.initial_data['status'])
        #return Object.objects.create(status=status, **validated_data)

    #def update(self, instance, validated_data):

        # instance.obj_name = validated_data.get('obj_name', instance.obj_name)

        # We do not want to create new statuses - only use existing ones
        # instance.status = Status.objects.get(name=self.initial_data['status']) if 'status' in self.initial_data else instance.status

        # return instance

And if you can use different keys for write and read status, then you can modify ObjectSerializer like this:

class ObjectSerializer(serializer.ModelSerializer):
    status = serializer.SlugRelatedField(slug_field='name', queryset=Status.objects.all(), write_only=True)
    status_data = StatusSerializer(read_only=True, source='status')

    class Meta:
        model = Object
        fields = ('obj_name', 'status', 'status_data')

In this case, if you pass {'obj_name': 'ObjectName', 'status': 'StatusName'} data to serializer, the serializer will first check for the StatusName value in the name field of the provided queryset (In this case we are using all) and if not valid raises ValidationError. If valid, then saves the status in the field of the instance.

Upvotes: 1

Related Questions