139

I'm trying to serialize a model containing a property field that I also want to serialize.

models.py:

class MyModel(models.Model):
    name = models.CharField(max_length=100)
    slug = models.AutoSlugField(populate_from='name')

    @property
    def ext_link(self):
        return "/".join([settings.EXT_BASE_URL, self.slug])

serializers.py:

class MyModelSerializer(serializers.ModelSerializer):
    class Meta:
        model = MyModel
        fields = ('name', 'ext_link')

When trying to get to the related URL, I'm getting a serializer exception (KeyError) on the ext_link property.

How can I serialize the ext_link property?

ivanleoncz
  • 9,070
  • 7
  • 57
  • 49
Sander Smits
  • 2,051
  • 3
  • 18
  • 16

4 Answers4

177

Because it's not a model field, it needs to be added explicitly to the serializer class

class MyModelSerializer(serializers.ModelSerializer):
    ext_link = serializers.Field()

    class Meta:
        model = MyModel
        fields = ('name', 'ext_link')
Stefano Munarini
  • 2,711
  • 2
  • 22
  • 26
Tom Christie
  • 33,394
  • 7
  • 101
  • 86
  • 7
    ***One note***: The fields list in Meta is optional. If you omit `fields`, in the above example, you would get all the `MyModel` fields plus `ext_link` in the serialized data. And this is really awesome for complex models! *EDIT*: At least, this is true for `djangorestframework==2.3.14`. – e.thompsy Feb 04 '15 at 18:54
  • 2
    For me, using serializers.Field gave an error. "serializers.ReadOnlyField" does work if to_representation is not defined and the view is read-only. – Shashank Singla Aug 14 '15 at 17:17
  • 26
    I'm using 3.3.x and simply adding properties to fields is not sufficient. I still have to explicitly add via ext_link = serializers.ReadOnlyField(). – jarmod Dec 29 '15 at 14:55
  • 4
    using DRF 3.4.6 on Python 3.5.1 and Django 1.10, adding to fields works fine. – Vaibhav Mishra Aug 30 '16 at 13:28
  • 20
    Note: Using `fields = "__all__"` I also had to add `myfield = serializers.ReadOnlyField()` as jarmod specified, using version 3.7.7 – Robert Townley Jun 23 '18 at 20:37
  • 1
    In addition: if using Swagger for your API documentation, it makes sense to specify the field not as `serializers.ReadOnlyField()` but explicitly as the type the @property is returning like `hasLink = serializers.BooleanField(read_only=True)`. Otherwise Swagger will always refer to it as a `string`. – RaideR Apr 13 '21 at 08:14
  • > Creating a ModelSerializer without either the 'fields' attribute or the 'exclude' attribute has been deprecated since 3.3.0, and is now disallowed. – temoto Feb 20 '22 at 20:54
57

as @Robert Townley's comment, this work with version 3.8.2:

class MyModelSerializer(serializers.ModelSerializer):
    ext_link = serializers.ReadOnlyField()

    class Meta:
        model = MyModel
        fields = "__all__"
suhailvs
  • 20,182
  • 14
  • 100
  • 98
12

The accepted answer doesn't seem to work for me, nor does the ReadOnlyField.

However, I have had success when I use a field that corresponds to the return type of my property function.

So for the example, I would do this:

class MyModelSerializer(serializers.ModelSerializer):
    ext_link = serializers.CharField()

    class Meta:
        model = MyModel
        fields = ('name', 'ext_link')

I've been able to do this with ListField, DictField, and IntegerField as well.

Willow
  • 1,132
  • 5
  • 20
0

Another thing you might want to do is add a property that its contents are not a string. Let's say you have a model called Person and another one called Food that look like this (we assume that each food is the favorite of only one person, making it a OneToMany connection):

class Person(models.Model):
    name = models.CharField(max_length=255)

    @property
    def favorite_foods(self):
        return Food.objects.filter(person=self.pk)

class Food(models.Model):
    name = models.CharField(max_length=255)
    persons_favorite = models.ForeignKey(Person, on_delete=models.CASCADE)

If you want to add favorite_foods in Person's serializer all you have to do is:

class PersonSerializer(serializers.ModelSerializer):
    favorite_foods = FoodSerializer(read_only=True, many=True)

    class Meta:
        model = Person
        fields = ('name', 'favorite_foods')
BillTheKid
  • 377
  • 1
  • 13