【问题标题】:Django many to many relation not savingDjango多对多关系不保存
【发布时间】:2018-07-05 02:57:08
【问题描述】:

更新

对于任何好奇的人,我想出了什么、为什么以及如何解决它。 在我看来,我有: fields = ['html', 'tags', 'title', 'text', 'taken_date', 'image'] 我在我的模板中使用 {{ form.as_p }}。显然,一旦从表单中发布,它真的,真的不希望有任何其他内容触及表单中尚未出现的表单字段。 所以我从我的视图中取出了“标签”字段,它起作用了。

感谢所有回复的人。

原始问题

使用 Django 2.0.1 和 PostgreSQL 9.2.18

我正在编写一个简单的相册应用程序。在其中我有一个照片对象和 PhotoTag 对象。照片可以有很多标签,标签可以关联很多照片,所以它需要是ManyToManyField。

保存提交的照片后,post_save 接收器调用函数来制作缩略图(工作正常)和更新标签的函数。

照片保存正常,update_tags 调用正常,标签从照片中读取正常,标签保存到 PhotoTag 中。但是将两者捆绑在一起的多对多表不会插入新行。除非代码在 update_tags 函数或 post_save 接收函数期间异常退出,否则将调用 update_tags 之后的 thumbs。

我什至尝试使用 connection.cursor 直接写入 m2m 表,它具有相同的行为。

如果我再次尝试在 Photo 对象上调用 save(),我会因为 post_save 信号而陷入无限循环。

我对发生的事情感到困惑。有什么线索吗?

# models.py

def update_tags(instance):
    tags = get_tags(instance.image)

    # Set initial values
    pt = []
    tagid = ''
    photoid = instance.id

    # Loop through tag list and insert into PhotoTag and m2m relation
    for x in range(0, len(tags)):
        # Make sure this tag doesn't already exist
        if PhotoTag.objects.filter(tag_text=tags[x]).count() == 0:
            pt = PhotoTag.objects.create(tag_text=tags[x])
            tagid = PhotoTag.objects.latest('id').id
            instance.tags.add(pt)
        else:
            # Only working with new tags right now
            pass

    return


class Photo(models.Model):
    author = models.ForeignKey(settings.AUTH_USER_MODEL,
                               on_delete=models.CASCADE)
    title = models.CharField(max_length=200, null=True, blank=True)
    text = models.TextField(null=True, blank=True)
    html = models.BooleanField(default=False)
    filename = models.CharField(default='', max_length=100, blank=True,
                                null=True)
    image = models.ImageField(upload_to=upload_path)
    location = models.CharField(max_length=100, blank=True, null=True)
    entry_date = models.DateTimeField(default=timezone.now)
    taken_date = models.DateTimeField(blank=True, null=True)

    tags = models.ManyToManyField(PhotoTag, blank=True)


@receiver(post_save, sender=Photo)
def thumbs(sender, instance, **kwargs):
    """
    Upon photo save, create thumbnails and then
    update PhotoTag and m2m with any Exif/XMP tags
    in the photo.
    """

    mk_thumb(instance.image, 'mid')
    mk_thumb(instance.image, 'th')
    mk_thumb(instance.image, 'sm')

    update_tags(instance)

    return

-------------
From views.py
-------------

class PhotoCreate(LoginRequiredMixin, CreateView):
    model = Photo
    template_name = 'photogallery/photo_edit.html'
    fields = ['html', 'tags', 'title', 'text', 'taken_date', 'image']

    def get_initial(self):
        self.initial = {'entry_date': timezone.now()}
        return self.initial

    def form_valid(self, form):
        form.instance.author = self.request.user

        return super(PhotoCreate, self).form_valid(form)

更新:

def save(self, mkthumb='', *args, **kwargs):
      super(Photo, self).save(*args, **kwargs)
      if mkthumb != "thumbs":
          self.mk_thumb(self.image, 'mid')
          self.mk_thumb(self.image, 'th')
          self.mk_thumb(self.image, 'sm')

          self.update_tags()

          mkthumb = "thumbs"

      return

【问题讨论】:

  • 可能跑题了,但有什么理由使用信号而不是覆盖save() 模型方法?这对我来说更容易和更清晰,并且可能有助于调试此类问题。
  • 我曾考虑过,但我不确定我能否正确排序,因为制作缩略图和阅读标签都需要在照片保存到数据库后进行磁盘。也许我会看看通过覆盖模型的 save() 方法可以做什么。
  • 当覆盖save 方法时,您可以选择您的操作是在save 之前还是之后发生,只需在super() 之前或之后编写代码即可。之后阅读和调试会容易得多。我敢肯定,在那之后你会自己发现问题。
  • 不,同样的行为。正在建立关系,在 update_tags() 的末尾,我可以根据实现输入print(instance.tags.all())print(self.tags.all()),它会输出正确的标签。
  • 你能用save 版本的代码编辑你的帖子吗?

标签: python django postgresql manytomanyfield


【解决方案1】:

像这样覆盖你的保存方法

def save(self, *args, **kwargs):
    tags = get_tags(self.image)

    # Set initial values
    pt = None

    # Loop through tag list and insert into PhotoTag and m2m relation
    for x in range(0, len(tags)):
        # Make sure this tag doesn't already exist
        if PhotoTag.objects.filter(tag_text=tags[x]).count() == 0:
            pt = PhotoTag.objects.create(tag_text=tags[x])
            self.tags.add(pt)
        else:
            # Only working with new tags right now
            pass
      super(Photo, self).save(*args, **kwargs)

【讨论】:

    【解决方案2】:

    我有一个similar issue,我在保存用户实例时尝试添加一个组。

    为什么会发生这种情况的答案在docs,更明确(使用代码)在this ticket

    保存ModelForm() 时(在管理员中点击保存),首先保存对象的一个​​实例,然后触发其所有信号等。第三步是使用ModelForm().cleaned_data 保存所有m2m 关系。如果ModelForm().cleaned_data['tags']None,则从您的信号创建的所有关系都将被删除。

    • 一个骇人听闻的解决方案是使用post_save 信号和transaction.on_commit(),它将在现有事务(包括保存所有m2m 关系的过程)提交到数据库后执行相关代码。

      def on_transaction_commit(func):
          ''' Create the decorator '''
          def inner(*args, **kwargs):
              transaction.on_commit(lambda: func(*args, **kwargs))
      
          return inner
      
      
      @receiver(post_save, sender=Photo)
      @on_transaction_commit
      def tags(instance, raw, **kwargs):
          """
          Create the relevant tags after the transaction 
          of instance is committed, unless the database is 
          populated with fixtures. 
          """
          if not raw:
              update_tags(instance)
      
    • 如果您的多对多关系没有blank=True,更合理的解决方案是使用m2m_changed() 信号,如in this post 或前面提到的ticket 所述。

    • 最好的方法是ditch the signals 并在使用ModelForm() 的情况下覆盖ModelForm().clean() 方法,并在直接保存模型的情况下覆盖Model().save() 方法。

      ModelForm().instance.my_flag 将很有用,因此您可以在 Model().save() 中检查现有的 Model().my_flag,以避免访问两次数据库。

    【讨论】:

      猜你喜欢
      • 2017-12-16
      • 2015-07-02
      • 1970-01-01
      • 2015-03-19
      • 2014-11-21
      • 1970-01-01
      • 1970-01-01
      • 2011-01-21
      • 2012-08-12
      相关资源
      最近更新 更多