Two model fields referring to each other - django rest

Viewed 216

I want to change one of the existing field names in the Django model. But, for the backward-compatibleness, we'd like not to override the existing field with the new one, keep both of them for now. Is there any way to have multiple fields referring to the same database object? i.e

Code right now:

class NetworkPackage:
        name = models.CharField(unique=True, blank=False, null=False)
        inbound = models.CharField(unique=True, blank=False, null=False)
        ...

I want to implement:

class NetworkPackage:
        name = models.CharField(max_length=32, unique=True, blank=False, null=False)
        inbound = models.CharField(max_length=128, unique=True, blank=True)
        mobile = models.CharField(max_length=128, unique=True, blank=True)
        ...

Basically, 'inbound' and 'mobile' should refer to the same field and the request could be sent either with 'inbound' field or 'mobile'.

2 Answers

It's a bad idea having two fields within the same model that hold the same info, especially if you need to enforce uniqueness because

  1. You'll need to maintain parity for both fields, so that means if the request was setting inbound, then you'll also have to set mobile.
  2. The database now has to index both inbound and mobile due to uniqueness.

What you can do is utilize python properties as properties are perfect solutions for cases where you have legacy attributes:

class NetworkPackage(models.Model):
    name = models.CharField(unique=True, blank=False, null=False)
    inbound = models.CharField(unique=True, blank=False, null=False)
    ...

    @property
    def mobile(self):
        return self.inbound

    @mobile.setter
    def mobile(self, value):
        self.inbound = value

Then in your serializer, you need to:

  1. Add mobile as an additional field sourcing to inbound.
  2. Override the required and allow_blank arguments on both fields since the serializer can allow either fields...
  3. BUT, you'll then need to write a custom validation method to ensure at least 1 of the 2 fields are populated with a value.
  4. Also prioritize the inbound value over the mobile value if both fields are populated.
class NetworkPackageSerializer(serializers.ModelSerializer):
    inbound = serializers.CharField(required=False, allow_blank=True)
    mobile = serializers.CharField(source="inbound", required=False, allow_blank=True)

    class Meta:
        model = NetworkPackage
        fields = ("inbound", "mobile", ...)

    def validate(self, data):
        """Validate `inbound` and/or `mobile`."""
        if not data["inbound"] and not data["mobile"]:
            raise serializers.ValidationError("missing value on inbound or mobile")
        if data["inbound"]:
            del data["mobile"]
        else:
            del data["inbound"]
        return data

Not sure why do you make duplicate fields, but I have some suggestions for you.

1. Custom property

class NetworkPackage:
    name = models.CharField(unique=True, blank=False, null=False)
    inbound = models.CharField(unique=True, blank=False, null=False)
    
    @poperty
    def mobile(self):
        return self.inbound

2. Serializer

class NetworkPackageSerializer(serializers.Serializer):

    mobile = serializers.CharField(source='inbound')

    class Meta:
        model = NetworkPackage
        fields = (
            'id',
            'inbound',
            'mobile',
            'name',
            ...
        )
Related