django-import-export含delete字段导入更新时报错求助
问题
使用django-import-export导入含delete字段的文件时,触发错误:
BaseBoqItem has no field named 'delete'
明确模型不需要delete字段,此设置并非失误。为实现用户标记行后导入自动删除对应数据,已按django-import-export官方文档配置,但仍触发该错误。
- 导入未修改的导出文件时正常(推测因
skip_unchanged=True) - 仅标记
delete为1时导入可成功删除实例 - 修改其他字段并导入时触发上述错误
相关代码
模型代码
class BaseBoqItem(models.Model): lot = models.ForeignKey(BaseLot, on_delete=models.PROTECT, related_name='boq_items') code = models.CharField(max_length=20, null=True) name_tm = models.CharField(max_length=255) name_ru = models.CharField(max_length=255, null=True) name_en = models.CharField(max_length=255, null=True) name_original = models.CharField(max_length=255, null=True) quantity = models.DecimalField(max_digits=12, decimal_places=2) unit = models.ForeignKey(BaseUnit, on_delete=models.PROTECT) material_unit_price = models.DecimalField(max_digits=12, decimal_places=2) labor_unit_price = models.DecimalField(max_digits=12, decimal_places=2) transport_unit_price = models.DecimalField(max_digits=12, decimal_places=2, null=True) def __str__(self) -> str: return f'{self.code} - {self.name_tm}'
Resource代码
class BaseBoqItemResource(resources.ModelResource): lot = fields.Field( column_name='lot', attribute='lot', widget=ForeignKeyWidget(BaseLot, field='code')) unit = fields.Field( column_name='unit', attribute='unit', widget=ForeignKeyWidget(BaseUnit, field='code_tm')) delete = fields.Field(readonly=True) def for_delete(self, row, instance): return self.fields['delete'].clean(row) class Meta: model = BaseBoqItem skip_unchanged = True use_bulk = True fields = ( 'id', 'lot', 'code', 'name_tm', 'name_ru', 'name_en', 'name_original', 'quantity', 'unit', 'material_unit_price', 'labor_unit_price', 'transport_unit_price' ) exclude=('delete', ) export_order = ( 'id', 'lot', 'code', 'name_tm', 'name_ru', 'name_en', 'name_original', 'quantity', 'unit', 'material_unit_price', 'labor_unit_price', 'transport_unit_price', 'delete' )
视图代码
class BaseBoqItemModelViewset(ModelViewSet): queryset = BaseBoqItem.objects.all() serializer_class = BaseBoqItemModelSerializer filterset_class = BaseBoqItemFilterSet pagination_class = CustomPagination @action(detail=False, methods=['get']) def exporttoexcel(self, request, *args, **kwargs): base_boq_item_resource = BaseBoqItemResource() dataset = base_boq_item_resource.export() response = HttpResponse(dataset.xlsx, content_type='application/vnd.ms-excel') response['Content-Disposition'] = 'attachment; filename="base_boq_items.xlsx"' return response @csrf_exempt @action(detail=False, methods=['post']) def importfromexcel(self, request, *args, **kwargs): excel = request.FILES['excel'] dataset = Dataset().load(excel) base_boq_item_resource = BaseBoqItemResource() result = base_boq_item_resource.import_data(dataset, dry_run=True, raise_exception=True) if not result.has_errors(): base_boq_item_resource.import_data(dataset, dry_run=False, raise_errors=True) return Response({'status': 'Imported successfully'}) return Response({'status': 'Error when importing'}, status=status.HTTP_400_BAD_REQUEST)
堆栈信息
System check identified no issues (0 silenced). January 19, 2024 - 15:09:34 Django version 4.2.8, using settings 'core.settings' Starting development server at http://127.0.0.1:8000/ Quit the server with CTRL-BREAK. Internal Server Error: /api/v1/baseboqitems/importfromexcel/ Traceback (most recent call last): File "C:\Users\a.soyunjaliyev\Documents\coding\smetcik\backend\venv\lib\site-packages\django\db\models\options.py", line 681, in get_field return self.fields_map[field_name] KeyError: 'delete' During handling of the above exception, another exception occurred: Traceback (most recent call last): File "C:\Users\a.soyunjaliyev\Documents\coding\smetcik\backend\venv\lib\site-packages\django\core\handlers\exception.py", line 55, in inner response = get_response(request) File "C:\Users\a.soyunjaliyev\Documents\coding\smetcik\backend\venv\lib\site-packages\django\core\handlers\base.py", line 197, in _get_response response = wrapped_callback(request, *callback_args, **callback_kwargs) File "C:\Users\a.soyunjaliyev\Documents\coding\smetcik\backend\venv\lib\site-packages\django\views\decorators\csrf.py", line 56, in wrapper_view return view_func(*args, **kwargs) File "C:\Users\a.soyunjaliyev\Documents\coding\smetcik\backend\venv\lib\site-packages\rest_framework\viewsets.py", line 125, in view return self.dispatch(request, *args, **kwargs) File "C:\Users\a.soyunjaliyev\Documents\coding\smetcik\backend\venv\lib\site-packages\rest_framework\views.py", line 509, in dispatch response = self.handle_exception(exc) File "C:\Users\a.soyunjaliyev\Documents\coding\smetcik\backend\venv\lib\site-packages\rest_framework\views.py", line 469, in handle_exception self.raise_uncaught_exception(exc) File "C:\Users\a.soyunjaliyev\Documents\coding\smetcik\backend\venv\lib\site-packages\rest_framework\views.py", line 480, in raise_uncaught_exception raise exc File "C:\Users\a.soyunjaliyev\Documents\coding\smetcik\backend\venv\lib\site-packages\rest_framework\views.py", line 506, in dispatch response = handler(request, *args, **kwargs) File "C:\Users\a.soyunjaliyev\Documents\coding\smetcik\backend\venv\lib\site-packages\django\views\decorators\csrf.py", line 56, in wrapper_view return view_func(*args, **kwargs) File "C:\Users\a.soyunjaliyev\Documents\coding\smetcik\backend\common\views.py", line 280, in importfromexcel result = base_boq_item_resource.import_data(dataset, dry_run=True, raise_exception=True) File "C:\Users\a.soyunjaliyev\Documents\coding\smetcik\backend\venv\lib\site-packages\import_export\resources.py", line 896, in import_data result = self.import_data_inner( File "C:\Users\a.soyunjaliyev\Documents\coding\smetcik\backend\venv\lib\site-packages\import_export\resources.py", line 1020, in import_data_inner self.bulk_update( File "C:\Users\a.soyunjaliyev\Documents\coding\smetcik\backend\venv\lib\site-packages\import_export\resources.py", line 452, in bulk_update self.handle_import_error(result, e, raise_errors) File "C:\Users\a.soyunjaliyev\Documents\coding\smetcik\backend\venv\lib\site-packages\import_export\resources.py", line 446, in bulk_update self._meta.model.objects.bulk_update( File "C:\Users\a.soyunjaliyev\Documents\coding\smetcik\backend\venv\lib\site-packages\django\db\models\manager.py", line 87, in manager_method return getattr(self.get_queryset(), name)(*args, **kwargs) File "C:\Users\a.soyunjaliyev\Documents\coding\smetcik\backend\venv\lib\site-packages\django\db\models\query.py", line 854, in bulk_update fields = [self.model._meta.get_field(name) for name in fields] File "C:\Users\a.soyunjaliyev\Documents\coding\smetcik\backend\venv\lib\site-packages\django\db\models\query.py", line 854, in <listcomp> fields = [self.model._meta.get_field(name) for name in fields] File "C:\Users\a.soyunjaliyev\Documents\coding\smetcik\backend\venv\lib\site-packages\django\db\models\options.py", line 683, in get_field raise FieldDoesNotExist( django.core.exceptions.FieldDoesNotExist: BaseBoqItem has no field named 'delete' [19/Jan/2024 15:09:38] "POST /api/v1/baseboqitems/importfromexcel/ HTTP/1.1" 500 149979
使用版本:django-import-export==3.3.4
请问遗漏了什么配置?如何解决该错误?
解决方案
问题原因
开启use_bulk=True时,django-import-export会尝试批量更新所有变更字段,但当前配置中手动定义的delete字段仍会被视为需要处理的字段(即使标记了exclude=('delete',)),批量更新时会试图将delete字段传递给Django的bulk_update方法,而模型中并没有这个字段,从而触发错误。
仅标记delete为1时,流程进入删除逻辑而非更新逻辑,所以不会触发批量更新错误;未修改的文件因为skip_unchanged=True跳过了更新,也不会报错。
修改方法
调整Resource配置,确保delete字段不参与导入的更新操作:
class BaseBoqItemResource(resources.ModelResource): lot = fields.Field( column_name='lot', attribute='lot', widget=ForeignKeyWidget(BaseLot, field='code')) unit = fields.Field( column_name='unit', attribute='unit', widget=ForeignKeyWidget(BaseUnit, field='code_tm')) # 添加import_id_fields=False,明确该字段不参与导入字段映射 delete = fields.Field(readonly=True, import_id_fields=False) def for_delete(self, row, instance): # 直接读取行数据中的delete标记,无需通过clean方法 return row.get('delete') in ('1', 1) class Meta: model = BaseBoqItem skip_unchanged = True use_bulk = True fields = ( 'id', 'lot', 'code', 'name_tm', 'name_ru', 'name_en', 'name_original', 'quantity', 'unit', 'material_unit_price', 'labor_unit_price', 'transport_unit_price' ) exclude=('delete', ) export_order = ( 'id', 'lot', 'code', 'name_tm', 'name_ru', 'name_en', 'name_original', 'quantity', 'unit', 'material_unit_price', 'labor_unit_price', 'transport_unit_price', 'delete' )
解释
import_id_fields=False告诉django-import-export,该字段不需要映射到模型字段,也不会参与导入时的更新操作- 直接读取row中的值判断删除标记,避免调用
clean方法带来的额外处理,同时确保逻辑准确
如果不介意性能损耗,也可以临时关闭use_bulk=True(改为use_bulk=False),但批量更新的效率优势会丢失,不推荐。
内容的提问来源于stack exchange,提问作者Aman
相关产品推荐
相关产品推荐

