Как экспортировать в Excel объект Django, где каждый объект ForeignKey - новый столбец, с помощью либы импорта-экспорта?

У меня есть модель, представляющая некоторый товар на складе:

(Код был упрощен)

class StockItem(models.Model):
    name = models.CharField()
    category = models.Charfield()
    stock = models.ForeignKey(Stock) # <- Here is few stocks
    quantity = models.IntegerField(default=0)

Таким образом, каждый StockItem может существовать на разных складах, но с разным количеством остатков. Есть ли способ создать ModelResource для экспорта оставшегося количества для каждого запаса?

Пример таблицы:

+----+--------+----------+---------+---------+
| ID |  Name  | Category | Stock A | Stock B |
+----+--------+----------+---------+---------+
|  1 | Item 1 | Cat 1    |       1 |       1 |
|  2 | Item 2 | Cat 2    |      10 |       8 |
|  3 | Item 3 | Cat 3    |      14 |      32 |
+----+--------+----------+---------+---------+

Мой ресурс ModelResource:

class StockItemsRemainingsResource(resources.ModelResource):

    class Meta:
        model = models.StockItem
        fields = ('name', 'category')

    def __init__(self):
            super().__init__()
            for item in self.get_queryset():
                stock_name = item.stock.name
                self.fields[f"quantity_{stock_name}"] = fields.Field(
                    attribute="quantity",
                    column_name=stock_name,
                    widget=widgets.ForeignKeyWidget(
                        models.Stock,
                        "pk"
                    )
                )

Я пытался переопределить метод __init__, но, вероятно, сделал это неправильно.

Метод after_export() можно переопределить, чтобы изменить набор данных после первоначального экспорта. Вы можете добавить в набор данных столбец по мере необходимости.

Например, вы можете добавить что-то вроде этого метода в StockItemsRemainingsResource:

    def after_export(self, queryset, dataset, **kwargs):
        counts = []
        for d in dataset.dict:
            stock_count = Stock.objects.filter(d["name"]).count()
            counts.append(stock_count)
        dataset.append_col(counts, header="count")

Вы можете поиграть с этим, чтобы найти лучший способ сделать это. Это будет неэффективно, поэтому, вероятно, не будет хорошо работать с большими наборами данных.

См. документацию по таблицам .

После нескольких часов поисков я смог добиться такой структуры, переопределив export_field() после изучения кода некоторых библиотек.

В моем случае мне пришлось сначала создать обязательные поля, переопределив метод __init__, но используя обычный класс fields.Field для подготовки этих полей к ручному заполнению в методе export_field():

def __init__(self):
        super().__init__()
        stocks = models.Stock.objects.all()
        for stock in stocks:
            field_name = f'stock_{stock.id}'
            self.fields[field_name] = fields.Field(
                column_name=f'{stock.name}',
                attribute=field_name
            )

Последний шаг, который я сделал, это переопределение export_field(self, field, instance), которое берет каждый экземпляр на итерации queryset к каждому полю ресурса для обработки. В моем случае я проверил имя поля, и если оно совпадает с одним из полей акций, то я подсчитаю q-количество экземпляров, относящихся к конкретной акции, и верну его (в противном случае верну super()). Мой код :

def export_field(self, field, instance):
        # overrided to populate dynamic fields (stock quantity)
        # If field is stock ("stock_{id}") then extract and ID for querying
        # an actual quantity
        if field.attribute.startswith("stock"):
            stock_id = field.attribute.split("_")[-1]
            stock = models.Stock.objects.filter(pk=stock_id).first()
            return instance.get_stock_quantity(stock) if stock else None
        else:
            return super().export_field(field, instance)

Возможно, это не лучший подход, и у меня есть предположение, что его можно реализовать, используя dehydrate_method, который может быть назначен экземпляру field.Field(dehydrate_method=some_method()), но я не искал решения в этом направлении.

Вернуться на верх