Как сериализовать одно json-поле из двух обратных отношений внешних ключей в Django REST Framework?
Я хотел бы получить в ответе API одно json-поле, в котором перечислены идентификаторы связанных объектов на основе двух обратных внешних ключевых отношений. Простой пример того, что я имею в виду, представлен ниже. Я бы предпочел, чтобы это было сделано на уровне сериализатора Django REST Framework, а не пришлось бы как-то менять модель, но у меня очень мало опыта работы с DRF, и я не могу понять, как это сделать.
Пример models.py:
class Person(models.Model):
id = models.AutoField(primary_key=True)
first_name = models.CharField(max_length=50, blank=True, null=True)
last_name = models.CharField(max_length=50, blank=True, null=True)
father = models.ForeignKey(
"self",
related_name="children_as_father",
blank=True,
null=True,
on_delete=models.SET_NULL,
)
mother = models.ForeignKey(
"self",
related_name="children_as_mother",
blank=True,
null=True,
on_delete=models.SET_NULL,
)
Пример данных базы данных:
id | first_name | last_name | mother | father |
---|---|---|---|---|
1 | Jane | Smith | ||
2 | John | Smith | ||
3 | Clarence | Smith | 1 | 2 |
4 | Thomas | Smith | 1 | 2 |
Пример сериализованного json, который я хотел бы получить:
[
{
"pk": 1,
"first_name": "Jane",
"last_name": "Smith",
"mother": null,
"father": null,
"children": [
3,4
],
},
{
"pk": 2,
"first_name": "John",
"last_name": "Smith",
"mother": null,
"father": null,
"children": [
3,4
],
},
{
"pk": 3,
"first_name": "Clarence",
"last_name": "Smith",
"mother": 1,
"father": 2,
"children": [],
},
{
"pk": 4,
"first_name": "Thomas",
"last_name": "Smith",
"mother": 1,
"father": 2,
"children": [],
}
]
Это то, чего удалось достичь в моих экспериментах, и, как вы можете видеть по сравнению с примером выше, это не совсем то, что я хочу - я хотел бы иметь одно json-поле для детей, а не два:
serializers.py
from rest_framework.serializers import ModelSerializer, PrimaryKeyRelatedField
from .models import Person
class FamilySerializer(ModelSerializer):
children_as_mother = PrimaryKeyRelatedField(
many=True, read_only=True, allow_null=True
)
children_as_father = PrimaryKeyRelatedField(
many=True, read_only=True, allow_null=True
)
class Meta:
model = Person
fields = [
"pk",
"first_name",
"last_name",
"mother",
"father",
"children_as_mother",
"children_as_father",
]
views.py
from rest_framework.permissions import IsAuthenticated
from rest_framework.viewsets import ModelViewSet
from .models import Person
from .serializers import FamilySerializer
class FamilyViewSet(ModelViewSet):
"""
API endpoint with family data
"""
queryset = Person.objects.all()
serializer_class = FamilySerializer
permission_classes = [IsAuthenticated]
Сериализованный json:
[
{
"pk": 1,
"first_name": "Jane",
"last_name": "Smith",
"mother": null,
"father": null,
"children_as_mother": [
3,4
],
"children_as_father": [],
},
{
"pk": 2,
"first_name": "John",
"last_name": "Smith",
"mother": null,
"father": null,
"children_as_mother": [],
"children_as_father": [
3,4
],
},
{
"pk": 3,
"first_name": "Clarence",
"last_name": "Smith",
"mother": 1,
"father": 2,
"children_as_mother": [],
"children_as_father": [],
},
{
"pk": 4,
"first_name": "Thomas",
"last_name": "Smith",
"mother": 1,
"father": 2,
"children_as_mother": [],
"children_as_father": [],
}
]
Мне удалось найти приемлемое решение. Вот как я модифицировал сериализатор, переопределив функцию to_representation:
class FamilySerializer(ModelSerializer):
children_as_mother = PrimaryKeyRelatedField(
many=True, read_only=True, allow_null=True
)
children_as_father = PrimaryKeyRelatedField(
many=True, read_only=True, allow_null=True
)
class Meta:
model = Person
fields = [
"pk",
"first_name",
"last_name",
"mother",
"father",
"children_as_mother",
"children_as_father",
]
def to_representation(self, instance):
data = super(FamilySerializer, self).to_representation(instance)
data["children"] = data["children_as_father"]+data["children_as_mother"]
del data["children_as_mother"]
del data["children_as_father"]
return data
Альтернативно to_representation можно переопределить следующим образом, чтобы получить тот же эффект для моего конкретного набора данных:
def to_representation(self, instance):
data = super(FamilySerializer, self).to_representation(instance)
if data["children_as_father"]:
data["children"] = data["children_as_father"]
elif data["children_as_mother"]:
data["children"] = data["children_as_mother"]
else:
data["children"]=[]
del data["children_as_mother"]
del data["children_as_father"]
return data
Я оставлю вопрос на несколько дней, если у кого-то есть другое решение, после чего отмечу этот как правильный ответ.
class PersonSerializer(serializers.ModelSerializer):
children = serializers.SerializerMethodField()
class Meta:
model = Person
fields = ['id', 'first_name', 'last_name', 'mother', 'father', 'children']
def get_children(self, obj):
# Get all children of the demo
children_as_father = obj.children_as_father.values_list('id', flat=True)
children_as_mother = obj.children_as_mother.values_list('id', flat=True)
return list(children_as_father) + list(children_as_mother)
https://www.django-rest-framework.org/api-guide/fields/#serializermethodfield