Django signals по-новому 1
На пути к 1.0 релизу Django претерпевал немало радикальных изменений. Одно из них рефакторинг системы сигналов.

Если вы первый раз читаете и не в курсе «что это такое и с чем его едят», то скажу в двух словах. Это система реагирования на события приложения. Любой JavaScript или прикладной UI программист хорошо знаком с системой событий (event) — клик мышкой, нажатие горячей клавиши и т.п. Для программистов серверной части веба все выглядит немного по другому. Есть HTTP-запрос и есть его обработчик, анализируется как правило URL на предмет «кому отправлять запрос». Но на самом деле это та же система сигналов-событий, только узкопрофилированная под обработку HTTP-запросов.
Оказывается серверная часть веб-приложения тоже может, и я уверен, просто должна генерировать намного больший спектр сигналов, чем просто обработку URL и данных запроса. С чем успешно и справляется Django. Теперь немного прозы. Какие же события может ловить Django «из-коробки».
pre_init— перед запуском метода-конструктора__init__()модели;post_init— после выполнения метода__init__()модели;pre_save— перед сохранением экземпляра модели в базу;post_save— после успешного сохранения экземпляра модели в базу;pre_delete,post_delete— перед и после удаления экземпляра модели из базы;post_syncdb— генерируется админкой Django после установки нового приложения (INSTALLED_APPS);request_started,request_finished,got_request_exception— генерируются при обработке HTTP-запросов;template_rendered— генерируется только в режиме тестирования приложения.
Итак сигналы *_init, _*save, *_delete и post_syncdb генерируются системой моделей Django и их можно импортировать из django.db.models.signals.
Сигналы обработки HTTP-запросов из django.core.signals. И тестовый template_rendered из django.test.signals.
А теперь реальный пример, как повесить обработчик на событие-сигнал сохранения модели. В нашем случае будет требоваться определить тип mime закачанного файла перед его сохранением в базу.
Код модели:
class MimeType(models.Model):
"""
Mime Types table
"""
name = models.CharField(max_length=200)
slug = models.SlugField()
def __unicode__(self):
return self.name
class Item(models.Model):
"""
Main file
"""
name = models.CharField(max_length=200)
slug = models.SlugField()
file = models.FileField(upload_to='files', blank=True)
mime = models.ForeignKey(MimeType, blank=True, null=True)
upload_date = models.DateTimeField(auto_now_add=True)
def __unicode__(self):
return self.name
def set_mime(self, mime):
obj, created = MimeType.objects.get_or_create(name=mime, slug=slugify(mime))
self.mime = obj
Посмотрите на метод set_mime модели Item. Он создает новый объект MimeType или использует если он уже создан.
При этом не сохраняя модель Item. Теперь я пишу функцию-callback, которая будет вызываться по событию сохранения модели Item.
import mimetypes
import os.path
# init mime types dict
mimetypes.init()
def add_mime_type(instance, **kwargs):
"""
Adding mime-type to uploaded file (for future use).
Would be called on post-save.
"""
if not hasattr(instance, 'mime') or not hasattr(instance, 'file'):
raise Exception("Object %s does not have 'mime' attribute! Can't set mime-type!" % instance)
if instance.file:
extension = os.path.splitext(instance.file.path)[1].lower()
instance.set_mime(mimetypes.types_map[extension])
Итак, функция-callback add_mime_type принимает параметр instance, который является экземпляром модели, сгенерировавшим сигнал (в нашем случае модели Item). Вторым параметром принимает словарь (dictionary). Кстати, это и есть один из моментов обратной несовместимости. После рефакторинга каждая функция-callback должна принимать параметр **kwargs.
Теперь третий самый простой шаг — связывание модели с сигналом. В самом конце файла моделей добавилась строчка:
models.signals.pre_save.connect(add_mime_type, sender=Item)
Теперь перед каждым сохранением объектов Item, будет выполняться проверка и установка Mime-типа.
Кстати, я советую если у вас больше одного сигнала, выносите их в отдельный файл signals.py или можно по-старинке писать в файле моделей (что ИМХО иногда мешает чтению кода).
Почитать про сигналы можно еще в официальной доке: Django Signals и Django Built-in signal reference. Почитать про рефаторинг можно на странице Backwards Incompatible Changes и в соответствующем коммите 8223.
Александр Кошелев тоже немного раньше написал по теме, статья «А вы поймали новые сигналы?» с хорошим примером создания абстрактного сигнала.
«На сегодня все. Вопросы в студию» :-)







Поначалу чото в рсс до* какого-то левого текста в пост свалилось.