Как денормализовать иерархические данные модели Django для вывода в CSV или Excel с помощью Djange REST Framework?
Предположим, мы создаем приложение адресной книги с помощью Django REST Framework и хотим вывести конечную точку, которая экспортирует всех людей. У каждого человека может быть один или несколько телефонных номеров.
Примерные данные могут выглядеть следующим образом:
[
{
'name': 'Jon Doe',
'phone':
[
{
'type': 'home',
'number': '+1 234 5678'
}
]
},
{
'name': 'Jane Doe',
'phone':
[
{
'type': 'home',
'number': '+2 345 6789'
},
{
'type': 'work',
'number': '+3 456 7890'
}
]
}
]
Поскольку мы хотим экспортировать CSV или таблицы Excel, мы хотим денормализовать данные так, чтобы каждый телефонный номер получил свою собственную строку.
Результат может выглядеть следующим образом:
name,phone.type,phone.number
Jon Doe,home,+1 234 5678
Jane Doe,home,+2 345 6789
Jane Doe,work,+3 456 7890
Вопрос в том, где именно я должен провести денормализацию. Я вижу два варианта:
- Напишите пользовательский
Serializer
, который выполняет денормализацию. Положительным моментом будет то, что это приведет к единому изменению, которое будет работать для каждогоRenderer
, так что я смогу экспортировать CSV и Excel с помощью, например,djangorestframework-csv
иdrf-renderer-xlsx
. С другой стороны, это будет мешать рендерингу, который не выигрывает от денормализации, например JSON или XML. - Выделите каждый
Renderer
, нуждающийся в денормализации, и переопределите методprocess_data()
, чтобы сначала провести денормализацию, а затем вызвать реализацию суперкласса. - Напишите пользовательский
View
, который выполняет денормализацию на основе согласованного рендерера, как описано в https://www.django-rest-framework.org/api-guide/renderers/#varying-behavior-by-media-type.
Это похоже на проблему, которая может возникнуть у многих людей, поскольку экспорт табличных данных является очень распространенной функцией. Не подскажете, с чего мне начать или что будет лучшей альтернативой?
Предполагая, что у вас есть модель Contact
(замените это на любую другую модель, которая у вас есть), с помощью Pandas вы можете вернуть CSV-файл из Django ORM QuerySet
.
import pandas as pd
from .models import Contact
def export_contacts(self, *args, **kwargs):
queryset = Contact.objects.all()
df = pd.DataFrame(list(queryset))
return df.to_csv()
Вы можете добавить это в отдельный модуль, который можно вызывать прямо из представления, нового представления для этого или любого другого места, где вам это нужно.
Предположим, что у вас есть такие модели, как:
class Person(models.Model):
name = models.CharField(max_length=100)
def __str__(self):
return self.name
class Phone(models.Model):
person = models.ForeignKey(Person, on_delete=models.CASCADE)
type = models.CharField(max_length=100)
number = models.CharField(max_length=100)
def __str__(self):
return self.number
Затем создайте сериализатор и набор представлений для Phone (а не Person) следующим образом:
class PhoneSerializer(serializers.HyperlinkedModelSerializer):
person = serializers.StringRelatedField()
class Meta:
model = Phone
fields = ['person', 'type', 'number']
class PhoneViewset(viewsets.ModelViewSet):
queryset = Phone.objects.all()
serializer_class = PhoneSerializer
router = routers.DefaultRouter()
router.register(r'phone', PhoneViewset)
Тогда DRF создаст нечто подобное с помощью своего стандартного рендерера json:
[
{"person":"Mufune Toshirō","type":"home","number":"000-123-4567"},
{"person":"Mufune Toshirō","type":"work","number":"000-345-6789"},
{"person":"Tōno Eijirō","type":"home","number":"000-234-4567"},
{"person":"Nakadai Tatsuya","type":"home","number":"000-234-6789"},
{"person":"Tsukasa Yōko","type":"cell","number":"000-987-6543"}
]
Для получения CSV-вывода можно установить djangorestframework-csv. Это превратит вышеописанное в следующее:
number,person,type
000-123-4567,Mufune Toshirō,home
000-345-6789,Mufune Toshirō,work
000-234-4567,Tōno Eijirō,home
000-234-6789,Nakadai Tatsuya,home
000-987-6543,Tsukasa Yōko,cell
В реальной базе данных с большим количеством таблиц и сложными отношениями я бы создал представление базы данных из запроса, содержащего все поля, денормализованные так, как я хочу. Затем я бы создал неуправляемую модель (что-то вроде этого), чтобы ее можно было экспортировать из DRF.
В приведенном выше примере вид может быть таким:
CREATE VIEW phonelist AS
SELECT a.name, b.type, b.number
FROM Person a left join Phone b on (a.id = b.person);