在将实例保存到数据库之前/之后是否触发了Django post_save?

时间:2014-02-14 00:30:08

标签: python django post save

我有一个使用Django的网站。每个帖子都是一个名为Article的对象。我想在保存后检索帖子的HTML,所以我编写了以下post_save钩子:

@receiver(models.signals.post_save, sender=Article)
def _send_article_mentions(sender, instance, **kwargs):
    import requests
    from django.contrib.sites.models import Site
    from urlparse import urljoin
    from ParallelTransport.settings import ARTICLES_URL
    SITE_URL = 'http://'+Site.objects.get_current().domain
    article_url = urljoin( SITE_URL, instance.get_absolute_url() )
    import time
    time.sleep(20)
    r = requests.get(article_url)
    error_file = open(ARTICLES_URL+'/'+'error.txt','w')
    error_file.write('file started1\n')

    m = r.status_code
    error_file.write(str(m))
    error_file.close()

它基本上等待20秒(作为测试添加)然后尝试使用其URL检索帖子的HTML,并将请求状态代码写入文件以进行调试。

问题是我在第一次保存时总是得到status = 404,它在第二次和后续保存时都有效。我认为Django的工作方式是:

  1. 使用save()将实例保存到数据库。此时帖子将获得一个URL
  2. 发送post_save信号
  3. 但是我应该能够在post_save中检索HTML。我对post_save的理解不正确吗?

    添加了备注:

    1. 将此代码放在save()方法中不起作用。也不应该。该帖子在save()方法结束时添加到数据库中,因此在save()结束之前不应该有任何URL。
    2. 这是在生产站点上,而不是在开发服务器上。
    3. 我想使用HTML中的链接发送'pingbacks'或实际webmention。但我的所有pingback都被拒绝,因为该帖子还没有URL。这是最不起作用的最低代码。

4 个答案:

答案 0 :(得分:3)

虽然这是一种完全错误的方法(*),但问题可能出在数据库事务中。当前线程保存文章,但在这个未通信的事务中,您试图通过另一个线程(通过Web服务器)获取这些数据。在这种情况下,此行为是完全正确的。您需要在通过另一个线程检索之前提交,或者通过其他方式获取HTML。

(*)应该在后台异步完成(Celery或其他更轻量级的异步队列应用程序),或者如果你想获取HTML,你可以直接调用视图(根据你的视图,你可能需要伪造请求;如果太复杂,你可以创建一个帮助函数,樱桃最小的代码来渲染模板)。如果您只需要在保存某些内容后调用第三方API,则需要异步执行。如果您不这样做,“save()代码”的成功将取决于您的连接或第三方服务的可用性,您将需要处理您不会处理交易的地方的交易;)

答案 1 :(得分:0)

您是否尝试覆盖对象的save方法,调用super,等待然后尝试检索HTML? 您还在使用开发服务器吗?处理第二个请求时可能会遇到问题,而第一个请求仍然存在。也许在适当的服务器上试试?

答案 2 :(得分:0)

我有类似的问题可能是由同一个问题造成的(问题不同,https://plus.google.com/u/0/106729891586898564412/posts/Aoq3X1g4MvX)。 我没有以正确的方式解决它,但您可以尝试使用数据库缓存,或者(在另一个django数据库问题中看到它)关闭所有数据库连接并重新查询。

答案 3 :(得分:0)

编辑2:
我创建了一个简单的例子(使用Django 1.5.5)来测试它是否按预期工作。据我所知,确实如此。 pre_save在数据库提交之前触发,post_save在之后触发 示例详述:

两个示例模型。
物品用于触发信号 ArticleUrl用于记录Article.get_absolute_url()的响应。

# models.py
from django.db import models
from django.core.urlresolvers import reverse

class Article(models.Model):
  name = models.CharField(max_length=150)

  def get_absolute_url(self):
  """
  Either return the actual url or a string containing '404' if reverse
   fails (Article is not saved).
  """
    try:
      return reverse('article:article-detail', kwargs={'pk': self.pk})
    except:
      return '404'

class ArticleUrl(models.Model):
  article_name = models.CharField(max_length=150)
  url = models.CharField(max_length=300)

  def __unicode__(self):
    return self.article_name + ': ' + self.url

示例views.py和urls.py被省略,因为它们很简单。如果需要,我可以添加它们。

# views.py, url.py

为Article:

创建pre_save和post_save信号
# signals.py
from django.db.models import signals
from django.dispatch import receiver
from article.models import Article, ArticleUrl


@receiver(signals.pre_save, sender=Article)
def _log_url_pre(sender, instance, **kwargs):
  article = instance
  article_url = ArticleUrl(
    article_name = 'pre ' + article.name,
    url = article.get_absolute_url()
  )
  article_url.save()


@receiver(signals.post_save, sender=Article)
def _log_url_post(sender, instance, **kwargs):
  article = instance
  article_url = ArticleUrl(
    article_name = 'post ' + article.name,
    url = article.get_absolute_url()
  )
  article_url.save()

导入我的signals.py,以便Django可以使用它:

# __init__.py
import signals

在定义了上面的内容后,我继续在Django shell中创建了一个新文章(python.exe manage.py shell)。

>>> from article.models import *
>>> a = Article(name='abcdd')
>>> a.save()
>>> ArticleUrl.objects.all()
[<ArticleUrl: pre abcdd: 404>, <ArticleUrl: post abcdd: /article/article/8>]

上面的例子确实表明pre_save确实没有返回一个url,但是post_save确实没有。两者似乎都符合预期。
您应该检查您的代码是否偏离上述示例或以某种方式干扰程序执行。

编辑1:
说过(下面),根据What Happens When You Save,post_save信号应该在数据库保存后运行 您的应用/网站的其他部分是否会以某种方式干扰这一点?

原帖:
根据{{​​3}},post_save信号在save()结束时发送,而不是在它之后发送 据我所知,Django信号是同步的(进程中),因此它们会停止实际的save()。在信号完成之前,它不会完全完成。

这并不总是适用,但您是否考虑过在save()后可以调用的自定义信号?