Django Импорт-экспорт: Проблемы с импортом в модель с UUID для поля id
Я пытаюсь импортировать файл csv (в кодировке utf-8) через Django Admin в модель Django, используя пакет Django-import-export (v3.0.0b4). Изначально я работал с последней стабильной версией, но мне предложили попробовать предварительную версию. Предварительный просмотр импорта выглядит правильно, но интерфейс показывает следующую ошибку для всех строк csv:
Без указания поля "" не является действительным UUID.
Я пробовал несколько вариантов включения 'id' в import_id_fields или исключения поля 'id' и использования переименованного поля 'unique_id' в качестве крючка. Я также пытался выполнить импорт с пустыми записями как в столбце 'id', так и в столбце 'unique_id' в csv. Также поле id было полностью опущено в csv. По какой-то причине пустые поля возвращаются независимо от того, заполняю я поля id или нет. Я подозреваю, что я делаю что-то небольшое неправильно, но не могу понять что. resources.py, models.py и admin.py включены ниже. Рад поделиться другими фрагментами, если это необходимо.
models.py
from django.db import models
from django.db.models import Sum
import uuid
from datetime import datetime
class Purchase(models.Model):
id = models.UUIDField(primary_key=True,default=uuid.uuid4, editable=False)
date = models.DateField(blank=True,null=True)
seller = models.CharField(max_length=200,default='')
number = models.CharField(max_length=200,blank=True,default='')
customer = models.CharField(max_length=200,default='')
salesperson = models.CharField(max_length=200,blank=True,default='')
discount = models.FloatField(blank=True,default=0,null=True)
shipping = models.FloatField(blank=True,default=0,null=True)
tax = models.FloatField(blank=True,default=0,null=True)
file = models.FileField(blank=True,default='', upload_to='uploads/')
@property
def subtotal(self):
return LineItem.objects.filter(Purchase=self).aggregate(Sum('amount'))['amount__sum']
@property
def grand_total(self):
return round(self.subtotal+self.tax,2)
class Meta:
verbose_name_plural = "Purchases"
def __str__(self):
return self.seller+" "+self.number
class LineItem(models.Model):
id = models.UUIDField(primary_key=True,default=uuid.uuid4, editable=False)
purchase = models.ForeignKey(Purchase, on_delete= models.CASCADE, related_name="LineItem",default='',null=True)
product_id = models.CharField(max_length=200,blank=True,default='')
name = models.CharField(max_length=200,default='')
category = models.CharField(max_length=200,blank=True,default='')
qty = models.FloatField(null=True)
qty_uom = models.CharField(max_length=200,default='')
amount = models.FloatField(null=True)
pack_qty = models.FloatField(null=True)
pack_uom = models.CharField(max_length=200,default='')
@property
def date(self):
return format(self.purchase.date,f"%m/%d/%Y")
@property
def bulk_unit_price(self):
return round(self.amount/self.qty,2)
@property
def unit_price(self):
return round(self.bulk_unit_price/self.pack_qty,2)
class Meta:
verbose_name_plural = "LineItems"
def __str__(self):
return self.name
resources.py
from import_export import resources, widgets, fields
from django.db.models.query import *
from .models import LineItem, Purchase
class CharRequiredWidget(widgets.CharWidget):
def clean(self, value, row=None, *args, **kwargs):
val = super().clean(value)
if val:
return val
else:
raise ValueError('this field is required')
class FloatWidget(widgets.DecimalWidget):
def clean (self, value, row=None, *args, **kwargs):
if self.is_empty(value):
return None
return float(str(value))
class ForeignKeyWidgetWithCreation(widgets.ForeignKeyWidget):
def __init__(self, model, field="pk", create=False, **kwargs):
self.model = model
self.field = field
self.create = create
super(ForeignKeyWidgetWithCreation, self).__init__(model, field=field, **kwargs)
def clean(self, value, **kwargs):
if not value:
return None
if self.create:
self.model.objects.get_or_create(**{self.field: value})
val = super(ForeignKeyWidgetWithCreation, self).clean(value, **kwargs)
return self.model.objects.get(**{self.field: val}) if val else None
class LineItemResource(resources.ModelResource):
class Meta:
model = LineItem
import_id_fields = ['unique_id',]
exclude = ('id',)
fields = ('unique_id','purchase__seller','purchase__number','purchase__date','product_id','name','category','qty','qty_uom','amount','pack_qty','pack_uom',)
report_skipped = True
unique_id = fields.Field(column_name='unique_id', attribute='id',widget=CharRequiredWidget())
purchase__seller = fields.Field(attribute='purchase', widget=ForeignKeyWidgetWithCreation(model=Purchase,field='seller',create=True))
purchase__number = fields.Field(attribute='purchase', widget=ForeignKeyWidgetWithCreation(model=Purchase,field='number',create=True))
purchase__date = fields.Field(attribute='purchase',widget=ForeignKeyWidgetWithCreation(model=Purchase,field='date',create=True))
product_id = fields.Field(saves_null_values=False, attribute='product_id',widget=CharRequiredWidget())
name = fields.Field(saves_null_values=False, attribute='name',widget=CharRequiredWidget())
category = fields.Field(saves_null_values=False, attribute='category',widget=CharRequiredWidget())
qty = fields.Field(saves_null_values=False, attribute='qty',widget=FloatWidget())
qty_uom = fields.Field(saves_null_values=False, attribute='qty_uom',widget=CharRequiredWidget())
amount = fields.Field(saves_null_values=False, attribute='amount',widget=FloatWidget())
pack_qty = fields.Field(saves_null_values=False, attribute='pack_qty',widget=FloatWidget())
pack_uom = fields.Field(saves_null_values=False, attribute='pack_uom',widget=CharRequiredWidget())
admin.py
from django.contrib import admin
from django.contrib import admin
from .models import Purchase, LineItem
from .resources import LineItemResource
from django.db import models
from import_export.admin import ImportExportModelAdmin
class LineItemAdmin(ImportExportModelAdmin):
resource_class = LineItemResource
list_display = ('id','purchase','product_id','name','category','qty','qty_uom','amount','pack_qty','pack_uom',)
admin.site.register(Purchase)
admin.site.register(LineItem, LineItemAdmin)
csv структура:
id,unique_id,purchase__date,purchase__seller,purchase__number,product_id,name,category,amount,qty,qty_uom,unit_price,pack_qty,pack_uom,$/unit
,4e157e12-9a92-e303-44af-ee494593f073,4/29/2022,Vendor 1, 1423840,733111,item 1, category 1, 153.92,9.65,lb,15.95,1,lb,15.95
На первый взгляд, факт наличия пустого поля id
может иметь значение (хотя вы говорите, что пытались опустить это поле). Можно попробовать удалить все ссылки на id
из Resource
и csv, хотя декларацию unique_id
нужно будет оставить как есть.
Вы также можете попробовать использовать ForeignKeyWidget
вместо ForeignKeyWidgetWithCreation
во время отладки.
Вы можете попробовать импортировать через скрипт, а не через консоль администратора. Например:
with open('data.csv', 'r') as fh:
dataset = tablib.Dataset().load(fh, headers=False)
resource = LineItemResource()
result = resource.import_data(
dataset,
raise_errors=True
)
print(result)
Это может помочь вам понять ошибку, хотя самым быстрым способом обнаружить ошибку будет прохождение с отладчиком.
Вы также можете попробовать использовать релиз 2.8.1, чтобы посмотреть, есть ли разница (в случае, если это было введено в v3). Пожалуйста, опубликуйте ответ, если вы обнаружили источник ошибки.
Было бы очень интересно, если бы вы смогли воспроизвести это в django-import-export example app. В ветке release-3-x
есть тестовая модель под названием UUIDBook
. Если вы можете воспроизвести это, используя пример приложения, пожалуйста, поднимите проблему и мы сможем посмотреть.