Pobieranie wartości klucza obcego za pomocą serializatorów django-rest-framework

89

Używam struktury django rest do tworzenia API. Mam następujące modele:

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

Aby utworzyć serializator dla kategorii, zrobiłbym:

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

    class Meta:
        model = Category

... a to zapewni mi:

[{'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'}]

Jak powinienem zająć się uzyskaniem odwrotności z serializatora pozycji, tj .:

[{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'}]

Przeczytałem dokumentację dotyczącą odwrotnych relacji dla pozostałej struktury, ale wydaje się, że jest to ten sam wynik, co pola nieodwrotne. Czy brakuje mi czegoś oczywistego?

wrota do piekieł
źródło

Odpowiedzi:

85

Po prostu użyj powiązanego pola bez ustawiania many=True.

Zwróć uwagę, że również dlatego, że chcesz, aby dane wyjściowe miały nazwę category_name, ale rzeczywiste pole to category, musisz użyć sourceargumentu w polu serializatora.

Poniższe informacje powinny dać ci potrzebne wyniki ...

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

    class Meta:
        model = Item
        fields = ('id', 'name', 'category_name')
Tom Christie
źródło
21
a co z wyszukiwaniem wszystkich pól modelu kategorii?
AJ
12
jeśli chcesz pobrać wszystkie pola modelu kategorii, utwórz serializator kategorii i umieść go w kodzie, takim jak category_name = CategorySerliazer ()
Faizan Ali
7
Próbowałem to zrobić, ale pojawia się błąd Relational field must provide a 'queryset' argument, or set read_only='True'
ePascoal
lub dostarczanie atrybutu queryset, jeśli chcesz obsługiwać tworzenie / aktualizację, coś w rodzaju: category_name = serializers.RelatedField(source='category', queryset=Category.objects.all()) Przypuszczam.
stelios
1
Jeśli otrzymasz, AssertionError:...skorzystaj z tej odpowiedzi stackoverflow.com/a/44530606/5403449
Josh
86

W wersji DRF 3.6.3 to zadziałało

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

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

Więcej informacji można znaleźć tutaj: podstawowe argumenty pól serializatora

Sayok88
źródło
Ale musisz być ostrożny, ponieważ powinien generować błąd NoneType, jeśli pole kategorii w modelu pozycji jest puste = True
Desert Camel
29

Inną rzeczą, którą możesz zrobić, jest:

  • utwórz właściwość w swoim Itemmodelu, która zwraca nazwę kategorii i
  • ujawnij go jako plik ReadOnlyField.

Twój model wyglądałby tak.

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

Twój serializator wyglądałby tak. Zwróć uwagę, że serializator automatycznie pobierze wartość category_namewłaściwości modelu przez nazwanie pola o tej samej nazwie.

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

    class Meta:
        model = Item
hsebastian
źródło
18

to działało dobrze dla mnie:

class ItemSerializer(serializers.ModelSerializer):
    category_name = serializers.ReadOnlyField(source='category.name')
    class Meta:
        model = Item
        fields = "__all__"
suhailvs
źródło
9

Pracowano w dniu 08/08/2018 i nad wersją DRF 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',)

Za pomocą Meta read_only_fieldsmożemy dokładnie zadeklarować, które pola powinny być tylko do odczytu. Następnie musimy zadeklarować foreignpole w Meta fields(lepiej, aby było jasne, jak mówi mantra: zen z Pythona ).

John Moutafis
źródło
6

Proste rozwiązanie source='category.name'gdzie categoryjest klucz obcy i .namejego atrybut.

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
źródło
0

To rozwiązanie jest lepsze, ponieważ nie ma potrzeby definiowania modelu źródłowego. Ale nazwa pola serializatora powinna być taka sama, jak nazwa pola klucza obcego

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

    class Meta:
        model = Item
        fields = ('id', 'name', 'category')
zshanabek
źródło