从内存中的数据初始化属于ManyToManyField的对象

时间:2014-12-18 15:46:41

标签: python django

我正在使用数据库模型的实例,我需要从内存中的数据构建对象(使用Python样式o = Object()而不是ModelClass.objects.create()。无论数据是否存在

将使用o.save()时,将在稍后决定将保存在数据库中。

这些模型有ManyToManyField并且拥有许多子对象。问题是,在实际保存子对象之前,我无法add()ManyToManyField。如何构造这些对象的方式可以在以后调用save()?我发现这个问题的每个可能的解决方案实际上并没有按照我的意愿行事。

以下是一些示例代码,展示了我尝试做的事情:

class Author:
  # ...
  @classmethod
  def create(cls, data):
    # ...
    pass

class Book(models.Model):
  title = models.CharField(max_length=128)
  pages = models.PositiveIntegerField()
  authors = models.ManyToManyField(Author)

  @classmethod
  @transaction.atomic
  def create(cls, data):
    try:
      with transaction.atomic():
        b = cls(title=data["title"],
                pages=data["pages"])

        # This works, but has an unwanted side effect: authors are saved to the database
        # as they're created here while the Book is not saved.
        b.authors = Author.objects.bulk_create([Author.create(a) for a in data["authors"]])
        return b

    except Exception:
      # ...
      raise

################### Later on...
# This data is NOT static - it's formed from JSON which comes from an API. Just is here as an example.
data = {
  "title": 1,
  "pages": 934,
  "authors": [
    {
      "name": "John Smith",
      # ...
    }
  ]
}

# We're going to use this now, but we're unsure if we want to actually save
# the object to the database.
b = Book.create(data)

# Save the data to the database if we want to.
b.save()

2 个答案:

答案 0 :(得分:2)

我能想到的唯一解决方案是在调用save()时对操作进行排队并执行它们。

class PostponedOpMixin(models.Model):
    def __init__(self, *args, **kwargs):
        self._postponed_ops = []
        super(PostponedOpMixin, self).__init__(*args, **kwargs)

    def _getattr(self, attr):
        result = self
        for part in attr.split('.'):
            result = getattr(result, part)
        return result

    def postpone(self, op, *args, **kwargs):
        if self.pk:  # execute now if self already has a pk
            return self._getattr(op)(*args, **kwargs)             
        self._postponed_ops.append((op, *args, **kwargs))

    def save(self, *args, *kwargs):
        super(PostponedOpMixin, self).save(*args, **kwargs)
        while self._postponed_ops:
            op, args, kwargs = self._postponed_ops.pop(0):
                self._getattr(op)(*args, **kwargs)

    def Meta:
        abstract = True

这样你就可以:

class Book(PostponedOpMixin):
    ...
    authors = models.ManyToManyField(Author)
    ...

instance = Book()
instance.title = "Romeo and Juliet"
instance.postpone('authors.add', shakespeare)
...

# and some time later:
instance.save()

此代码未经测试,仅作为起点。任何错误都留给读者练习。

答案 1 :(得分:2)

也许您可以推迟实际图书的作者添加到您确定要将该图书保存到数据库中的那一刻。
在此期间,您可以为每个图书对象存储已处理(但尚未保存)的作者对象列表。

class Book(models.Model):
    title = models.CharField(max_length=128)
    pages = models.PositiveIntegerField()
    authors = models.ManyToManyField(Author)

    @classmethod
    @transaction.atomic
    def create(cls, data):
        try:
            b = cls(title=data["title"],
                    pages=data["pages"])
            b.author_list = list()
            for a in data["authors"]:
                b.authors_list.append(Author.create(a))
            return b
        except Exception:
            # ...
            raise

然后,当您确定要保存对象时,必须将所有作者保存在该列表中,然后将它们添加到相应书籍对象中的“作者”字段。