Recuperar un valor de clave externa con serializadores django-rest-framework

83

Estoy usando el marco de descanso de django para crear una API. Tengo los siguientes modelos:

class Category(models.Model):
    name = models.CharField(max_length=100)

    def __unicode__(self):
        return self.name


class Item(models.Model):
    name = models.CharField(max_length=100)
    category = models.ForeignKey(Category, related_name='items')

    def __unicode__(self):
        return self.name

Para crear un serializador para las categorías, haría:

class CategorySerializer(serializers.ModelSerializer):
    items = serializers.RelatedField(many=True)

    class Meta:
        model = Category

... y esto me proporcionaría:

[{'items': [u'Item 1', u'Item 2', u'Item 3'], u'id': 1, 'name': u'Cat 1'},
 {'items': [u'Item 4', u'Item 5', u'Item 6'], u'id': 2, 'name': u'Cat 2'},
 {'items': [u'Item 7', u'Item 8', u'Item 9'], u'id': 3, 'name': u'Cat 3'}]

¿Cómo haría para obtener lo contrario de un serializador de artículos, es decir:

[{u'id': 1, 'name': 'Item 1', 'category_name': u'Cat 1'},
{u'id': 2, 'name': 'Item 2', 'category_name': u'Cat 1'},
{u'id': 3, 'name': 'Item 3', 'category_name': u'Cat 1'},
{u'id': 4, 'name': 'Item 4', 'category_name': u'Cat 2'},
{u'id': 5, 'name': 'Item 5', 'category_name': u'Cat 2'},
{u'id': 6, 'name': 'Item 6', 'category_name': u'Cat 2'},
{u'id': 7, 'name': 'Item 7', 'category_name': u'Cat 3'},
{u'id': 8, 'name': 'Item 8', 'category_name': u'Cat 3'},
{u'id': 9, 'name': 'Item 9', 'category_name': u'Cat 3'}]

He leído los documentos sobre relaciones inversas para el resto del marco, pero parece ser el mismo resultado que los campos no inversos. ¿Me estoy perdiendo algo obvio?

la puerta del infierno
fuente

Respuestas:

82

Simplemente use un campo relacionado sin configurar many=True.

Tenga en cuenta que también porque desea que se nombre la salida category_name, pero el campo real sí lo es category, debe usar el sourceargumento en el campo del serializador.

Lo siguiente debería darle el resultado que necesita ...

class ItemSerializer(serializers.ModelSerializer):
    category_name = serializers.RelatedField(source='category', read_only=True)

    class Meta:
        model = Item
        fields = ('id', 'name', 'category_name')
Tom Christie
fuente
21
¿Qué hay de recuperar todos los campos del modelo de categoría?
AJ
12
si desea recuperar todos los campos del modelo de categoría, haga el serializador de categoría y
téngalo
7
Intenté hacer esto pero recibo un error Relational field must provide a 'queryset' argument, or set read_only='True'
ePascoal
o proporcionar un atributo de conjunto de consultas si desea admitir la creación / actualización, algo como: category_name = serializers.RelatedField(source='category', queryset=Category.objects.all()) supongo.
stelios
1
Si obtiene, AssertionError:...use esta respuesta stackoverflow.com/a/44530606/5403449
Josh
83

En la versión 3.6.3 de DRF esto funcionó para mí

class ItemSerializer(serializers.ModelSerializer):
    category_name = serializers.CharField(source='category.name')

    class Meta:
        model = Item
        fields = ('id', 'name', 'category_name')

Puede encontrar más información aquí: Argumentos principales de los campos del serializador

Sayok88
fuente
Pero debe tener cuidado porque debería arrojar un error NoneType si el campo de categoría en el modelo de artículo está en blanco = True
Desert Camel
29

Otra cosa que puede hacer es:

  • cree una propiedad en su Itemmodelo que devuelva el nombre de la categoría y
  • exponerlo como un ReadOnlyField.

Tu modelo se vería así.

class Item(models.Model):
    name = models.CharField(max_length=100)
    category = models.ForeignKey(Category, related_name='items')

    def __unicode__(self):
        return self.name

    @property
    def category_name(self):
        return self.category.name

Su serializador se vería así. Tenga en cuenta que el serializador obtendrá automáticamente el valor de la category_namepropiedad del modelo al nombrar el campo con el mismo nombre.

class ItemSerializer(serializers.ModelSerializer):
    category_name = serializers.ReadOnlyField()

    class Meta:
        model = Item
Hsebastian
fuente
16

Esto funcionó bien para mi:

class ItemSerializer(serializers.ModelSerializer):
    category_name = serializers.ReadOnlyField(source='category.name')
    class Meta:
        model = Item
        fields = "__all__"
suhailvs
fuente
9

Trabajó el 08/08/2018 y en DRF versión 3.8.2:

class ItemSerializer(serializers.ModelSerializer):
    category_name = serializers.ReadOnlyField(source='category.name')

    class Meta:
        model = Item
        read_only_fields = ('id', 'category_name')
        fields = ('id', 'category_name', 'name',)

Usando el Meta read_only_fieldspodemos declarar exactamente qué campos deben ser read_only. Entonces necesitamos declarar el foreigncampo en el Meta fields(mejor ser explícito como dice el mantra: zen de python ).

John Moutafis
fuente
5

Solución simple source='category.name'donde categoryestá la clave externa y .namesu atributo.

from rest_framework.serializers import ModelSerializer, ReadOnlyField
from my_app.models import Item

class ItemSerializer(ModelSerializer):
    category_name = ReadOnlyField(source='category.name')

    class Meta:
        model = Item
        fields = "__all__"
Anurag Misra
fuente
0

Esta solución es mejor porque no es necesario definir el modelo de origen. Pero el nombre del campo del serializador debe ser el mismo que el nombre del campo de clave externa

class ItemSerializer(serializers.ModelSerializer):
    category = serializers.SlugRelatedField(read_only=True, slug_field='title')

    class Meta:
        model = Item
        fields = ('id', 'name', 'category')
zshanabek
fuente