Mam model, który wygląda tak:
class Category(models.Model):
parentCategory = models.ForeignKey('self', blank=True, null=True, related_name='subcategories')
name = models.CharField(max_length=200)
description = models.CharField(max_length=500)
Udało mi się uzyskać płaską reprezentację json wszystkich kategorii z serializatorem:
class CategorySerializer(serializers.HyperlinkedModelSerializer):
parentCategory = serializers.PrimaryKeyRelatedField()
subcategories = serializers.ManyRelatedField()
class Meta:
model = Category
fields = ('parentCategory', 'name', 'description', 'subcategories')
Teraz chcę, aby lista podkategorii miała wbudowaną reprezentację podkategorii w formacie JSON zamiast ich identyfikatorów. Jak miałbym to zrobić z django-rest-framework? Próbowałem znaleźć to w dokumentacji, ale wydaje się niekompletne.
źródło
KeyError at /api/category/ 'subcategories'
. Przy okazji dzięki za superszybkie odpowiedzi :)Rozwiązanie @ wjin działało świetnie, dopóki nie zaktualizowałem do Django REST framework 3.0.0, który przestaje być używany jako_native . Oto moje rozwiązanie DRF 3.0, które jest niewielką modyfikacją.
Załóżmy, że masz model z polem odwołującym się do siebie, na przykład komentarze z wątkami we właściwości o nazwie „odpowiedzi”. Masz drzewiastą reprezentację tego wątku komentarzy i chcesz serializować drzewo
Najpierw zdefiniuj klasę RecursiveField wielokrotnego użytku
class RecursiveField(serializers.Serializer): def to_representation(self, value): serializer = self.parent.parent.__class__(value, context=self.context) return serializer.data
Następnie dla swojego serializatora użyj RecursiveField, aby serializować wartość „odpowiedzi”
class CommentSerializer(serializers.Serializer): replies = RecursiveField(many=True) class Meta: model = Comment fields = ('replies, ....)
Proste, a do rozwiązania wielokrotnego użytku potrzebujesz tylko 4 wierszy kodu.
UWAGA: Jeśli struktura danych jest bardziej skomplikowana niż drzewo, na przykład skierowany wykres acykliczny (FANCY!), Możesz wypróbować pakiet @ wjin - zobacz jego rozwiązanie. Ale nie miałem żadnych problemów z tym rozwiązaniem dla drzew opartych na modelu MPTTM.
źródło
print self.parent.parent.__class__
print self.parent.parent
Inna opcja, która działa z Django REST Framework 3.3.2:
class CategorySerializer(serializers.ModelSerializer): class Meta: model = Category fields = ('id', 'name', 'parentid', 'subcategories') def get_fields(self): fields = super(CategorySerializer, self).get_fields() fields['subcategories'] = CategorySerializer(many=True) return fields
źródło
parent.parent.__class__
rzeczy. Najbardziej mi się podoba.Spóźniłem się do gry tutaj, ale oto moje rozwiązanie. Powiedzmy, że serializuję Blah, z wieloma dziećmi również typu Blah.
class RecursiveField(serializers.Serializer): def to_native(self, value): return self.parent.to_native(value)
Za pomocą tego pola mogę serializować rekurencyjnie zdefiniowane obiekty, które mają wiele obiektów podrzędnych
class BlahSerializer(serializers.Serializer): name = serializers.Field() child_blahs = RecursiveField(many=True)
Napisałem rekursywne pole dla DRF3.0 i spakowałem je dla pip https://pypi.python.org/pypi/djangorestframework-recursive/
źródło
Blah
i ma ona pole o nazwie,child_blahs
które składa się z listyBlah
obiektów.queryset=Class.objects.filter(level=0)
. Zajmuje się resztą rzeczy samodzielnie.Udało mi się osiągnąć ten wynik za pomocą pliku
serializers.SerializerMethodField
. Nie jestem pewien, czy to najlepszy sposób, ale zadziałało dla mnie:class CategorySerializer(serializers.ModelSerializer): subcategories = serializers.SerializerMethodField( read_only=True, method_name="get_child_categories") class Meta: model = Category fields = [ 'name', 'category_id', 'subcategories', ] def get_child_categories(self, obj): """ self referral field """ serializer = CategorySerializer( instance=obj.subcategories_set.all(), many=True ) return serializer.data
źródło
Inną opcją byłoby powtórzenie w widoku, który serializuje model. Oto przykład:
class DepartmentSerializer(ModelSerializer): class Meta: model = models.Department class DepartmentViewSet(ModelViewSet): model = models.Department serializer_class = DepartmentSerializer def serialize_tree(self, queryset): for obj in queryset: data = self.get_serializer(obj).data data['children'] = self.serialize_tree(obj.children.all()) yield data def list(self, request): queryset = self.get_queryset().filter(level=0) data = self.serialize_tree(queryset) return Response(data) def retrieve(self, request, pk=None): self.object = self.get_object() data = self.serialize_tree([self.object]) return Response(data)
źródło
Niedawno miałem ten sam problem i wymyśliłem rozwiązanie, które wydaje się działać do tej pory, nawet dla dowolnej głębokości. Rozwiązaniem jest mała modyfikacja tego od Toma Christiego:
class CategorySerializer(serializers.ModelSerializer): parentCategory = serializers.PrimaryKeyRelatedField() def convert_object(self, obj): #Add any self-referencing fields here (if not already done) if not self.fields.has_key('subcategories'): self.fields['subcategories'] = CategorySerializer() return super(CategorySerializer,self).convert_object(obj) class Meta: model = Category #do NOT include self-referencing fields here #fields = ('parentCategory', 'name', 'description', 'subcategories') fields = ('parentCategory', 'name', 'description') #This is not needed #CategorySerializer.base_fields['subcategories'] = CategorySerializer()
Nie jestem pewien, czy może niezawodnie działać w każdej sytuacji, chociaż ...
źródło
To jest adaptacja rozwiązania caipirginka, które działa na drf 3.0.5 i django 2.7.4:
class CategorySerializer(serializers.ModelSerializer): def to_representation(self, obj): #Add any self-referencing fields here (if not already done) if 'branches' not in self.fields: self.fields['subcategories'] = CategorySerializer(obj, many=True) return super(CategorySerializer, self).to_representation(obj) class Meta: model = Category fields = ('id', 'description', 'parentCategory')
Zauważ, że CategorySerializer w 6. linii jest wywoływany z obiektem i atrybutem many = True.
źródło
if 'branches'
należy zmienić naif 'subcategories'
Pomyślałem, że przyłączę się do zabawy!
Via wjin i Mark Chackerian stworzyłem bardziej ogólne rozwiązanie, które działa dla bezpośrednich modeli drzewiastych i struktur drzewiastych, które mają model przelotowy. Nie jestem pewien, czy to należy do jego własnej odpowiedzi, ale pomyślałem, że równie dobrze mogę to gdzieś umieścić. Dołączyłem opcję max_depth, która zapobiegnie nieskończonej rekurencji, na najgłębszym poziomie dzieci są reprezentowane jako adresy URL (jest to ostatnia klauzula else, jeśli wolisz, aby nie był to adres URL).
from rest_framework.reverse import reverse from rest_framework import serializers class RecursiveField(serializers.Serializer): """ Can be used as a field within another serializer, to produce nested-recursive relationships. Works with through models, and limited and/or arbitrarily deep trees. """ def __init__(self, **kwargs): self._recurse_through = kwargs.pop('through_serializer', None) self._recurse_max = kwargs.pop('max_depth', None) self._recurse_view = kwargs.pop('reverse_name', None) self._recurse_attr = kwargs.pop('reverse_attr', None) self._recurse_many = kwargs.pop('many', False) super(RecursiveField, self).__init__(**kwargs) def to_representation(self, value): parent = self.parent if isinstance(parent, serializers.ListSerializer): parent = parent.parent lvl = getattr(parent, '_recurse_lvl', 1) max_lvl = self._recurse_max or getattr(parent, '_recurse_max', None) # Defined within RecursiveField(through_serializer=A) serializer_class = self._recurse_through is_through = has_through = True # Informed by previous serializer (for through m2m) if not serializer_class: is_through = False serializer_class = getattr(parent, '_recurse_next', None) # Introspected for cases without through models. if not serializer_class: has_through = False serializer_class = parent.__class__ if is_through or not max_lvl or lvl <= max_lvl: serializer = serializer_class( value, many=self._recurse_many, context=self.context) # Propagate hereditary attributes. serializer._recurse_lvl = lvl + is_through or not has_through serializer._recurse_max = max_lvl if is_through: # Delay using parent serializer till next lvl. serializer._recurse_next = parent.__class__ return serializer.data else: view = self._recurse_view or self.context['request'].resolver_match.url_name attr = self._recurse_attr or 'id' return reverse(view, args=[getattr(value, attr)], request=self.context['request'])
źródło
else
klauzula zawiera pewne założenia dotyczące widoku. Musiałem zamienić mój na,return value.pk
więc zwracał klucze podstawowe zamiast próbować odwrócić wygląd widoku.W Django REST framework 3.3.1 potrzebowałem następującego kodu, aby dodać podkategorie do kategorii:
models.py
class Category(models.Model): id = models.AutoField( primary_key=True ) name = models.CharField( max_length=45, blank=False, null=False ) parentid = models.ForeignKey( 'self', related_name='subcategories', blank=True, null=True ) class Meta: db_table = 'Categories'
serializers.py
class SubcategorySerializer(serializers.ModelSerializer): class Meta: model = Category fields = ('id', 'name', 'parentid') class CategorySerializer(serializers.ModelSerializer): subcategories = SubcategorySerializer(many=True, read_only=True) class Meta: model = Category fields = ('id', 'name', 'parentid', 'subcategories')
źródło
To rozwiązanie jest prawie podobne do innych opublikowanych tutaj rozwiązań, ale ma niewielką różnicę pod względem problemu z powtarzaniem się dzieci na poziomie głównym (jeśli uważasz, że jest to problem). Dla przykładu
class RecursiveSerializer(serializers.Serializer): def to_representation(self, value): serializer = self.parent.parent.__class__(value, context=self.context) return serializer.data class CategoryListSerializer(ModelSerializer): sub_category = RecursiveSerializer(many=True, read_only=True) class Meta: model = Category fields = ( 'name', 'slug', 'parent', 'sub_category' )
i jeśli masz taki pogląd
class CategoryListAPIView(ListAPIView): queryset = Category.objects.all() serializer_class = CategoryListSerializer
To da następujący wynik,
[ { "name": "parent category", "slug": "parent-category", "parent": null, "sub_category": [ { "name": "child category", "slug": "child-category", "parent": 20, "sub_category": [] } ] }, { "name": "child category", "slug": "child-category", "parent": 20, "sub_category": [] } ]
Tutaj
parent category
machild category
a reprezentacja json jest dokładnie tym, co chcemy, aby była reprezentowana.ale widać, że jest powtórzenie
child category
na poziomie głównym.Ponieważ niektórzy ludzie pytają w sekcjach komentarzy powyżej opublikowanych odpowiedzi, że jak możemy zatrzymać to powtórzenie dziecka na poziomie głównym , po prostu przefiltruj swój zestaw zapytań za pomocą
parent=None
, jak poniżejclass CategoryListAPIView(ListAPIView): queryset = Category.objects.filter(parent=None) serializer_class = CategoryListSerializer
to rozwiąże problem.
UWAGA: Ta odpowiedź może nie być bezpośrednio związana z pytaniem, ale problem jest w jakiś sposób powiązany. Również takie podejście
RecursiveSerializer
jest kosztowne. Lepiej, jeśli używasz innych opcji, które są podatne na wydajność.źródło