Have you read the Django Signals Documentation and wondered, “Okay, how the heck do you actually use it? How about an example?” If so, then you’ve come to the right place.
Will demo by example adding signals for pre_save, pre_delete to raise an exception if a READ_ONLY_FILE exists to prevent the DB from changing. Then, add signals for post_save and post_delete to print the change to stdout. Using print as an example, a more useful thing would be to write it to syslog or something – say you want to log every DB change that occurs.
The code output is available at https://github.com/djangopractice/demo_signals
This starts with setting up a very basic Django app. It should feel very familiar, borrowing from the Django tutorial Polls as the starting point.
Create New Django Project with Polls App
pip install Django django-admin.py startproject demo_signals cd demo_signals django-admin.py startapp polls
Add polls to INSTALLED_APPS
Edit the django_signals/settings.py file, add ‘polls’ to INSTALLED_APPS
... INSTALLED_APPS = ( 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'polls', ) ...
Add READ_ONLY_FILE to settings
Edit the django_signals/settings.py file, append READ_ONLY_FILE to the end
... READ_ONLY_FILE = os.path.join(BASE_DIR, 'readonly')
We’re going to make our signal raise an exception and refuse to write to the DB if this file exists. This is just one use case of signals. Another could be if there is something critical downstream that needs to be available post save, you could check to be sure it’s available before you save the instance to the DB. Are there other use cases other than check something and prevent the write? Maybe. I haven’t used it for anything but that so far.
Create the Question and Choice models
Edit the polls/models.py file and make it look like this:
from django.db import models class Question(models.Model): question_text = models.CharField(max_length=200) pub_date = models.DateTimeField('date published') class Choice(models.Model): question = models.ForeignKey(Question) choice_text = models.CharField(max_length=200) votes = models.IntegerField(default=0)
Create the polls/signals.py file
I hope that it’s self evident what this will do once it’s hooked up. Note that it won’t do anything just by mere existence of this file. We’ll connect the dots in the next two instructions.
from django.db.models.signals import pre_save, pre_delete, post_save, post_delete from django.dispatch import receiver from django.conf import settings from polls.models import Question, Choice import os.path @receiver(pre_delete, sender=Question) @receiver(pre_delete, sender=Choice) @receiver(pre_save, sender=Question) @receiver(pre_save, sender=Choice) def model_pre_change(sender, **kwargs): if os.path.isfile(settings.READ_ONLY_FILE): raise ReadOnlyException('Model in read only mode, cannot save') @receiver(post_save, sender=Question) @receiver(post_save, sender=Choice) def model_post_save(sender, **kwargs): print('Saved: {}'.format(kwargs['instance'].__dict__)) @receiver(post_delete, sender=Question) @receiver(post_delete, sender=Choice) def model_post_delete(sender, **kwargs): print('Deleted: {}'.format(kwargs['instance'].__dict__)) class ReadOnlyException(Exception): pass
Create the polls/apps.py file
This is the first tricky part. There mere existence of this file also does nothing. We’ll point to this in the next step. This imports polls.signals in the ready method. By extending the AppConfig class from Django, we’re telling Django to import polls.signals when the Polls app is loaded.
from django.apps import AppConfig class PollsConfig(AppConfig): name = 'polls' verbose_name = 'Polls Application' def ready(self): import polls.signals
Edit the polls/__init__.py file
default_app_config = 'polls.apps.PollsConfig'
This default_app_config is some Django magic that tells Django to the use polls.apps.PollsConfig class when loading the Polls app.
Make migrations and migrate
$ python manage.py makemigrations polls Migrations for 'polls': 0001_initial.py: - Create model Choice - Create model Question - Add field question to choice $ python manage.py migrate Operations to perform: Apply all migrations: auth, admin, sessions, contenttypes, polls Running migrations: Applying contenttypes.0001_initial... OK Applying auth.0001_initial... OK Applying admin.0001_initial... OK Applying sessions.0001_initial... OK
Try it out in the shell
$ python manage.py shell Python 3.4.2 (default, Oct 16 2014, 05:21:12) [GCC 4.2.1 Compatible Apple LLVM 6.0 (clang-600.0.51)] on darwin Type "help", "copyright", "credits" or "license" for more information. (InteractiveConsole) >>> from polls.models import Question >>> from django.utils import timezone >>> question = Question(question_text='What is your favorite food?', pub_date=timezone.now()) >>> question.save() Saved: {'pub_date': datetime.datetime(2015, 1, 19, 22, 50, 54, 627393, tzinfo=<UTC>), 'question_text': 'What is your favorite food?', 'id': 1} >>> question.pub_date = timezone.now() >>> question.save() Saved: {'pub_date': datetime.datetime(2015, 1, 19, 22, 51, 21, 106598, tzinfo=<UTC>), 'question_text': 'What is your favorite food?', 'id': 1} >>> question.delete() Deleted: {'pub_date': datetime.datetime(2015, 1, 19, 22, 51, 21, 106598, tzinfo=<UTC>), 'question_text': 'What is your favorite food?', 'id': 1} >>> question = Question(question_text='What is your favorite color?', pub_date=timezone.now()) >>> question.save() Saved: {'pub_date': datetime.datetime(2015, 1, 19, 22, 51, 53, 689325, tzinfo=<UTC>), 'question_text': 'What is your favorite color?', 'id': 2} >>> exit()
This time with readonly file:
$ touch readonly $ python manage.py shell Python 3.4.2 (default, Oct 16 2014, 05:21:12) [GCC 4.2.1 Compatible Apple LLVM 6.0 (clang-600.0.51)] on darwin Type "help", "copyright", "credits" or "license" for more information. (InteractiveConsole) >>> from polls.models import Question >>> from django.utils import timezone >>> question = Question(question_text='What is your favorite food?', pub_date=timezone.now()) >>> question.save() Traceback (most recent call last): File "<console>", line 1, in <module> File "/Users/dkoopman/.virtualenvs/demo_signals/lib/python3.4/site-packages/django/db/models/base.py", line 589, in save force_update=force_update, update_fields=update_fields) File "/Users/dkoopman/.virtualenvs/demo_signals/lib/python3.4/site-packages/django/db/models/base.py", line 613, in save_base update_fields=update_fields) File "/Users/dkoopman/.virtualenvs/demo_signals/lib/python3.4/site-packages/django/dispatch/dispatcher.py", line 198, in send response = receiver(signal=self, sender=sender, **named) File "/Users/dkoopman/repos/demo_signals/polls/signals.py", line 14, in model_pre_change raise ReadOnlyException('Model in read only mode, cannot save') polls.signals.ReadOnlyException: Model in read only mode, cannot save >>> question = Question.objects.get(pk=2) >>> question.pub_date = timezone.now() >>> question.save() Traceback (most recent call last): File "<console>", line 1, in <module> File "/Users/dkoopman/.virtualenvs/demo_signals/lib/python3.4/site-packages/django/db/models/base.py", line 589, in save force_update=force_update, update_fields=update_fields) File "/Users/dkoopman/.virtualenvs/demo_signals/lib/python3.4/site-packages/django/db/models/base.py", line 613, in save_base update_fields=update_fields) File "/Users/dkoopman/.virtualenvs/demo_signals/lib/python3.4/site-packages/django/dispatch/dispatcher.py", line 198, in send response = receiver(signal=self, sender=sender, **named) File "/Users/dkoopman/repos/demo_signals/polls/signals.py", line 14, in model_pre_change raise ReadOnlyException('Model in read only mode, cannot save') polls.signals.ReadOnlyException: Model in read only mode, cannot save >>> question.delete() Traceback (most recent call last): File "<console>", line 1, in <module> File "/Users/dkoopman/.virtualenvs/demo_signals/lib/python3.4/site-packages/django/db/models/base.py", line 739, in delete collector.delete() File "/Users/dkoopman/.virtualenvs/demo_signals/lib/python3.4/site-packages/django/db/models/deletion.py", line 257, in delete sender=model, instance=obj, using=self.using File "/Users/dkoopman/.virtualenvs/demo_signals/lib/python3.4/site-packages/django/dispatch/dispatcher.py", line 198, in send response = receiver(signal=self, sender=sender, **named) File "/Users/dkoopman/repos/demo_signals/polls/signals.py", line 14, in model_pre_change raise ReadOnlyException('Model in read only mode, cannot save') polls.signals.ReadOnlyException: Model in read only mode, cannot save