2014-02-14 2 views
5

У меня есть сайт, использующий Django. Каждый пост - это объект, называемый статьей. Я хочу, чтобы получить HTML Почты после сохранения его, так что я написал следующее post_save крюка:Разве Django 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-х лет (добавляемых в качестве теста), а затем пытается извлечь HTML поста, используя его URL, и записывает код состояния запроса в файл для отладки.

Проблема заключается в том, что я всегда получаю статус = 404 при первом сохранении, он работает на 2-й и последующие сейвы. Я думал, что работа Django будет в порядке:

  1. сохранить экземпляр базы данных, используя save(). На данный момент пост получит URL-адрес
  2. отправить post_save сигнал

Но тогда я должен быть в состоянии восстановить HTML в post_save. Я неправильно понимаю post_save?

Добавлены примечания:

  1. Ввод этого кода в Save() метод не работает. Не стоит. Сообщение добавляется в базу данных в конце метода save() и поэтому не должно содержать URL до завершения сохранения().
  2. Это на производственной площадке, а не на сервере разработки.
  3. Я хочу использовать ссылки в HTML, чтобы отправить 'pingbacks', или фактически webmention. Но все мои pingbacks отклоняются, потому что сообщение еще не имеет URL-адреса. Это минимальный код, который не работает.
+0

Вы рассмотрели другой способ сделать это: вместо того, чтобы извлекать статью с помощью HTTP, просто непосредственно визуализируйте шаблон статьи в строку. См. Https://docs.djangoproject.com/en/1.6/ref/templates/api/#the-render-to-string-shortcut – Scintillo

+0

Я хочу использовать ссылки в HTML для отправки «pingbacks» или фактически [webmention ] (http://indiewebcamp.com/webmention). Но все мои pingbacks отклоняются, потому что сообщение еще не имеет URL-адреса. Это минимальный минимальный код, который не работает. –

ответ

3

Хотя это совершенно неправильный подход (*), проблема, вероятно, связана с транзакциями базы данных. Текущий поток сохраняет статью, но в рамках этой незавершенной транзакции вы пытаетесь получить эти данные через другой поток (через веб-сервер). В этом случае это поведение является полностью правильным. Либо вам нужно зафиксировать до получения другого потока, либо получить HTML по-другому.

(*) должен выполняться асинхронно на фоне (сельдерей или другое более легкое приложение для асинхронной очереди), или вы можете напрямую вызвать представление, если хотите получить HTML (в зависимости от вашего вида вам может потребоваться подделать запрос, если он слишком сложный, вы можете создать вспомогательную функцию, которая вибрирует минимальный код для визуализации шаблона). Если вам нужно только вызвать сторонний API после того, как вы что-то сохранили, вы хотите сделать это асинхронно. Если вы этого не сделаете, успех вашего «save() кода» будет зависеть от доступности вашего подключения или стороннего сервиса, и вам нужно будет иметь дело с транзакциями на месте, где вы не будете иметь дело с транзакциями ;)

+0

Вы, кажется, прямо о нитях, хотя я вообще этого не понимаю. Я поставил 60-секундный сон в post_save и во время его запуска попытался получить доступ к сообщению через браузер, и он дает 404. –

+1

В транзакционных базах данных, если вы делаете запрос на вставку/обновление, измененные данные на самом деле не сохраняются, они временны , Это тот же принцип, как если бы вы открывали txt-файл, вы вносили изменения, но вы не сохраняете файл. Вы видите изменения на экране (текущий поток), но когда кто-то другой открывает тот же файл (другой поток веб-сервера), есть старый контент. Неважно, как долго файл открывается (ваше время сна), вы должны сделать Ctrl + S (фиксация), сначала они видны для других. – Bruce

+0

Да, это имеет смысл сейчас! Благодарю. –

0

Вы пытались переопределить метод сохранения объекта, вызвав супер, ожидая, а затем попытавшись извлечь HTML-код? Также вы используете сервер разработки? У него могут возникнуть проблемы с обработкой второго запроса, пока первый все еще идет. Может быть, попробовать это на правильном сервере?

+0

да. Сообщение не имеет URL-адреса, если сохранение полностью выполнено, что кажется правильным. И я делаю все это на реальном сервере. –

0

У меня была аналогичная проблема, вызванная, вероятно, тем же вопросом (задано по-другому, https://plus.google.com/u/0/106729891586898564412/posts/Aoq3X1g4MvX). Я не решил это правильно, но вы можете попробовать играть с кешем базы данных или (увидеть его в другой проблеме с базой данных django) закрыть все подключения к базе данных и запрос.

0

Edit 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 

Создание 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 (оболочка python.exe manage.py).

>>> 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 сделал. Оба, похоже, ведут себя так, как предполагалось.
Вы должны проверить, не отличается ли ваш код от приведенного выше примера или каким-то образом мешает выполнению программы.

Edit 1:
Сказав, что (ниже), в соответствии с What Happens When You Save, сигнал post_save должен работать после сохранения базы данных.
Могли бы быть другие части вашего приложения/сайта каким-то образом вмешиваться в это?

Оригинальное сообщение:
Согласно Django documentation, сигнал post_save посылается в конце Save(), а не после него.
Насколько я понимаю, Django Signals являются синхронными (in-process), поэтому они останавливают фактическое сохранение(). Он не будет полностью заполнен до тех пор, пока сигналы не будут выполнены.

Это не всегда применимо, но вы считали, что пользовательский сигнал вы можете вызвать после сохранения()?

+0

Я не вижу, как.Например, код выше работает, если я сохраняю сообщение второй раз и каждый раз после него. Это не работает при первом сохранении. –

+0

Я добавил подробный пример pre_save и post_save, работающих по назначению. Не могли бы вы сравнить это с вашим собственным кодом? –

+0

Ваш пример кода работает. Кажется, что почта имеет возвращаемый URL через обратную связь, но недоступна другими агентами, например браузером, до завершения post_save. Я тестировал это, имея только 60-секундный сон в post_save и доступ к почте через браузер, и это не сработало. Кажется, что @Bruce прав, даже думал, что я не понимаю, что он сказал. –

Смежные вопросы