【问题标题】:Django "ValueError: Can't bulk create a multi-table inherited model"Django“ValueError:无法批量创建多表继承模型”
【发布时间】:2018-09-24 08:57:46
【问题描述】:

问题

我正在使用 django-model-utils InheritanceManager。我有一个超级 Notification(models.Model) 类,我用它来创建许多通知子类,例如 PostNotification(Notification)CommentNotification(Notification) 等,当尝试运行 CommentNotification.objects.bulk_create(list_of_comment_notification_objects) 时,我得到以下回溯:

File "/home/me/.virtualenvs/project/local/lib/python2.7/site-packages/django/db/models/query.py", line 429, in bulk_create
    raise ValueError("Can't bulk create a multi-table inherited model")
ValueError: Can't bulk create a multi-table inherited model

在检查 query.py 文件后,我们发现这会导致错误:

for parent in self.model._meta.get_parent_list():
      if parent._meta.concrete_model is not self.model._meta.concrete_model:
           raise ValueError("Can't bulk create a multi-table inherited model")

环境 Django 模型工具版本:3.1.1 Django 版本:1.11.7 Python 版本:2.7.3

示例

PostNotification.objects.bulk_create(
   [PostNotification(related_user=user, post=instance) for user in users]
)

抛出上述异常

我已经尝试过,虽然最初是成功的:

我虽然只是运行: BaseClass.objects.bulk_create(list_of_SubClass_objects) 而不是 SubClass.objects.bulk_create(list_of_SubClass_objects) 将起作用并返回子类值列表,但随后运行 SubClass.objects.all() 将返回空结果。 bulk_create() 只会为列表中的每个项目创建一个 Notification 基类对象。

【问题讨论】:

  • 错误信息和documentation 都清楚地表明您不能在这种情况下使用bulk_create。所以就别用了吗?

标签: django django-models django-model-utils


【解决方案1】:

我已经完成了bulk_create 的自定义实现,这似乎适用于我的情况(只有一个父关系,而不是自动递增的 pk):

from django.db import models


class MultiTableChildQueryset(models.QuerySet):

    def bulk_create(self, objs, batch_size=None):
        assert batch_size is None or batch_size > 0
        if not objs:
            return objs

        self._for_write = True
        objs = list(objs)
        parent_model = self.model._meta.pk.related_model

        parent_objs = []
        for obj in objs:
            parent_values = {}
            for field in [f for f in parent_model._meta.fields if hasattr(obj, f.name)]:
                parent_values[field.name] = getattr(obj, field.name)
            parent_objs.append(parent_model(**parent_values))
            setattr(obj, self.model._meta.pk.attname, obj.id)
        parent_model.objects.bulk_create(parent_objs, batch_size=batch_size)

        with transaction.atomic(using=self.db, savepoint=False):
            self._batched_insert(objs, self.model._meta.local_fields, batch_size)

        return objs

【讨论】:

    【解决方案2】:

    找到了一个 hacky 解决方案。我希望它适用于你的情况。诀窍是动态创建一个模型(不是继承的),它有一些元(db_table)集。并使用此动态模型批量创建 Child 对象(即写入 Child 的 DB 表)。

        class Parent(models.Model):
            name = models.CharField(max_length=10)
    
    
        class Child(Parent):
            phone = models.CharField(max_length=12)
    
    # just an example. Should be expanded to work properly.
    field_type_mapping = {
        'OneToOneField': models.IntegerField,
        'CharField': models.CharField,
    }
    
    def create_model(Model, app_label='children', module='', options=None):
        """
        Create specified model
        """
        model_name = Model.__name__
        class Meta:
           managed = False
           db_table = Model._meta.db_table
    
        if app_label:
            # app_label must be set using the Meta inner class
            setattr(Meta, 'app_label', app_label)
    
        # Update Meta with any options that were provided
        if options is not None:
            for key, value in options.iteritems():
                setattr(Meta, key, value)
    
        # Set up a dictionary to simulate declarations within a class
        attrs = {'__module__': module, 'Meta': Meta}
    
        # Add in any fields that were provided
        fields = dict()
        for field in Model._meta.fields:
            if field.attname == 'id':
                continue
            if field.model.__name__ == model_name:
                field_class_name = type(field).__name__
                print(field.attname)
                fields[field.attname] = field_type_mapping[field_class_name]()
        # Create the class, which automatically triggers ModelBase processing
        attrs.update(fields)
    
        model = type(f'{model_name}Shadow', (models.Model,), attrs)
    
    
        return model
    
    mod = create_model(Child)
    parents = [Parent(name=i) for i in range(15)]
    parents = Parent.objects.bulk_create(parents)
    children = [mod(phone=parent.name, parent_ptr_id=parent.id) for parent in parents]
    mod.objects.bulk_create(children)
    

    【讨论】:

      猜你喜欢
      • 2015-02-26
      • 2011-03-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-02-19
      • 1970-01-01
      • 2012-09-20
      • 2012-04-20
      相关资源
      最近更新 更多