Saya memiliki model yang terlihat seperti ini:
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)
Saya berhasil mendapatkan representasi flat json dari semua kategori dengan serializer:
class CategorySerializer(serializers.HyperlinkedModelSerializer):
parentCategory = serializers.PrimaryKeyRelatedField()
subcategories = serializers.ManyRelatedField()
class Meta:
model = Category
fields = ('parentCategory', 'name', 'description', 'subcategories')
Sekarang yang ingin saya lakukan adalah agar daftar subkategori memiliki representasi subkategori inline json, bukan id mereka. Bagaimana saya melakukannya dengan django-rest-framework? Saya mencoba mencarinya di dokumentasi, tetapi sepertinya tidak lengkap.
sumber
Solusi @ wjin bekerja dengan baik untuk saya sampai saya meningkatkan ke Django REST framework 3.0.0, yang tidak lagi menggunakan to_native . Inilah solusi DRF 3.0 saya, yang sedikit modifikasi.
Misalkan Anda memiliki model dengan kolom referensi sendiri, misalnya komentar beralur di properti yang disebut "balasan". Anda memiliki representasi pohon dari utas komentar ini, dan Anda ingin membuat serial pohon
Pertama, tentukan kelas RecursiveField Anda yang dapat digunakan kembali
class RecursiveField(serializers.Serializer): def to_representation(self, value): serializer = self.parent.parent.__class__(value, context=self.context) return serializer.data
Kemudian, untuk serializer Anda, gunakan RecursiveField untuk membuat serial nilai "replies"
class CommentSerializer(serializers.Serializer): replies = RecursiveField(many=True) class Meta: model = Comment fields = ('replies, ....)
Sangat mudah, dan Anda hanya perlu 4 baris kode untuk solusi yang dapat digunakan kembali.
CATATAN: Jika struktur data Anda lebih rumit daripada pohon, seperti misalnya grafik asiklik terarah (FANCY!) Maka Anda dapat mencoba paket @ wjin - lihat solusinya. Tapi saya tidak punya masalah dengan solusi ini untuk pohon berbasis MPTTModel.
sumber
print self.parent.parent.__class__
danprint self.parent.parent
Opsi lain yang bekerja dengan 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
sumber
parent.parent.__class__
barangnya. Saya paling menyukainya.fields = super().get_fields()
Terlambat untuk permainan di sini, tapi inilah solusi saya. Katakanlah saya membuat serial Blah, dengan banyak anak juga tipe Blah.
class RecursiveField(serializers.Serializer): def to_native(self, value): return self.parent.to_native(value)
Dengan menggunakan bidang ini saya dapat membuat serialisasi objek yang didefinisikan secara rekursif yang memiliki banyak objek anak
class BlahSerializer(serializers.Serializer): name = serializers.Field() child_blahs = RecursiveField(many=True)
Saya menulis bidang rekursif untuk DRF3.0 dan mengemasnya untuk pip https://pypi.python.org/pypi/djangorestframework-recursive/
sumber
Blah
dan memiliki bidang yang disebutchild_blahs
yang terdiri dari daftarBlah
objek.queryset=Class.objects.filter(level=0)
. Ini menangani hal-hal lain itu sendiri.Saya dapat mencapai hasil ini menggunakan file
serializers.SerializerMethodField
. Saya tidak yakin apakah ini cara terbaik, tetapi berhasil untuk saya: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
sumber
Opsi lainnya adalah menampilkan kembali tampilan yang membuat model Anda berseri. Berikut contohnya:
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)
sumber
Saya baru-baru ini mengalami masalah yang sama dan menemukan solusi yang tampaknya berhasil sejauh ini, bahkan untuk kedalaman yang sewenang-wenang. Solusinya adalah modifikasi kecil dari Tom Christie:
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()
Saya tidak yakin itu dapat bekerja dengan andal dalam situasi apa pun , meskipun ...
sumber
Ini adalah adaptasi dari solusi caipirginka yang bekerja pada drf 3.0.5 dan 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')
Perhatikan bahwa CategorySerializer di baris ke-6 dipanggil dengan objek dan atribut many = True.
sumber
if 'branches'
harus diubah menjadiif 'subcategories'
Saya pikir saya akan ikut bersenang-senang!
Melalui wjin dan Mark Chackerian, saya membuat solusi yang lebih umum, yang berfungsi untuk model dan struktur pohon langsung yang memiliki model tembus. Saya tidak yakin apakah ini termasuk dalam jawabannya sendiri tetapi saya pikir saya sebaiknya meletakkannya di suatu tempat. Saya menyertakan opsi max_depth yang akan mencegah rekursi tak terbatas, pada tingkat terdalam anak direpresentasikan sebagai URL (itu klausa final lain jika Anda lebih suka itu bukan 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'])
sumber
else
klausul Anda membuat asumsi tertentu tentang tampilan tersebut. Saya harus mengganti milik saya denganreturn value.pk
sehingga mengembalikan kunci utama daripada mencoba membalikkan tampilan ke atas.Dengan Django REST framework 3.3.1, saya membutuhkan kode berikut untuk menambahkan subkategori ke kategori:
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')
sumber
Solusi ini hampir mirip dengan solusi lain yang diposting di sini tetapi memiliki sedikit perbedaan dalam hal masalah pengulangan anak di tingkat akar (jika Anda menganggapnya sebagai masalah). Sebagai contoh
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' )
dan jika Anda memiliki pandangan ini
class CategoryListAPIView(ListAPIView): queryset = Category.objects.all() serializer_class = CategoryListSerializer
Ini akan menghasilkan hasil sebagai berikut,
[ { "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": [] } ]
Di sini representasi
parent category
has achild category
dan json persis seperti yang kita ingin wakili.tetapi Anda dapat melihat ada pengulangan
child category
pada level root.Karena beberapa orang bertanya di bagian komentar dari jawaban yang diposting di atas bahwa bagaimana kami bisa menghentikan pengulangan turunan ini di tingkat akar , cukup filter queryset Anda dengan
parent=None
, seperti berikut iniclass CategoryListAPIView(ListAPIView): queryset = Category.objects.filter(parent=None) serializer_class = CategoryListSerializer
itu akan menyelesaikan masalah.
CATATAN: Jawaban ini mungkin tidak secara langsung terkait dengan pertanyaan, tetapi masalahnya terkait. Juga pendekatan penggunaan
RecursiveSerializer
ini mahal. Lebih baik jika Anda menggunakan opsi lain yang rawan kinerja.sumber