jamesc
jamesc

Reputation: 6338

Allow hyphens in Django Rest Framework serializer field names

Given an OpenAPI specification that I'm writing code against that requires hyphen-case (aka kebab-case) variable names in request bodies, how should this be handled when using Django Rest Framework?

For example, a request POST /thing to create a Thing has this body:

{
    "owner-type": "platform"
}

But in Python, owner-type is not a valid variable name ("SyntaxError: can't assign to operator"), so instead Thing has owner_type in the model definition:

class Thing(models.Model):
    owner_type = models.CharField(max_length=8)

But now the ThingSerializer is problematic because, again, owner-type is an illegal name. This is not allowed:

    owner-type = serializers.CharField(...)

I've tried to override how the names are generated in the ModelSerializer by trying to adjust the field names generated by get_fields(), but it failed. Here's my serializer:

class ThingSerializer(serializers.ModelSerializer):
    class Meta:
        model = Thing
        fields = [
            'owner_type',
        ]

    def get_fields(self):
        fields = super().get_fields()
        out_fields = OrderedDict()
        for field_name, field in fields.items():
            out_fields[field_name.replace('_', '-')] = field
        return out_fields

And the error:

../venv/lib/python3.6/site-packages/rest_framework/fields.py:453: in get_attribute
    return get_attribute(instance, self.source_attrs)
../venv/lib/python3.6/site-packages/rest_framework/fields.py:101: in get_attribute
    instance = getattr(instance, attr)
E   AttributeError: 'Thing' object has no attribute 'owner-type'

So my question - how can I configure a DRF model serializer to allow a model's fields that contain underscore to be serialized / deserialized so that the API client sees hyphens instead of underscores? This would be a generic solution to the example above where Thing.owner_type should be read / writeable by passing the field "owner-type" in the JSON body.

I'm using latest Django and DRF on Python 3.6.

Edit 1: Clarified that ideally this would be a generic solution that translates underscores to hyphens.

Upvotes: 9

Views: 3567

Answers (4)

Danielle Madeley
Danielle Madeley

Reputation: 2825

get_fields is the best way to handle this, because it will also handle your validation codepath, e.g. for errors.

If you define source on your field, then the value of your field will read and write to the location set in source.

class UserSerializer(serializers.ModelSerializer[User]):
    """Serialize a user to look like a JWT id token claim."""

    sub = serializers.UUIDField(read_only=True, source="username")

    # Will be renamed to cognito:groups
    groups = serializers.ListField(
        child=serializers.ChoiceField(choices=AuthGroup.choices),
        source="groups",  # Serializer will read and write from `groups`
    )

    def get_fields(self) -> dict[str, Any]:
        """Rename the groups field to cognito:groups"""

        fields = super().get_fields()
        fields["cognito:groups"] = fields.pop("groups")

        return fields

Upvotes: 1

iradkot
iradkot

Reputation: 171

You can use the to_internal_value of Django(See in DJango Serializer fields) to get the key with the hyphens and rename it.

Example:

class Thing(models.Model):
    owner_type = models.CharField(max_length=8)
    
    def to_internal_value(self, data):
        data['owner_type'] = data['owner-type']
        data.pop('owner-type', None)
        return data

Upvotes: 2

JPG
JPG

Reputation: 88689

This is not useful when use models, but answers the use of hypens in fields, change your serializer as below


class ThingSerializer(serializers.Serializer):
   def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.fields.update({"owner-type": serializers.CharField(write_only=True)})
 

Upvotes: -1

mojeto
mojeto

Reputation: 586

You can define field name with hyphen in fields and map it to correct django model field by defining source attribute in extra_kwargs - see https://www.django-rest-framework.org/api-guide/serializers/#additional-keyword-arguments

To answer you question you define ThingSerializer as bellow:

class ThingSerializer(serializers.ModelSerializer):
    class Meta:
        model = Thing
        fields = [
            'owner-type',
        ]
        extra_kwargs = {
            'owner-type': {'source': 'owner_type'},
        }

Upvotes: 11

Related Questions