我有一个使用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的工作方式是:
但是我应该能够在post_save中检索HTML。我对post_save的理解不正确吗?
添加了备注:
答案 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()后可以调用的自定义信号?