custom signals for uncoupled design
DESCRIPTION
Presentation given at DjangoCon 2009 on the use of custom signals in the Django framework to enhance reusability of applications.TRANSCRIPT
![Page 2: Custom Signals for Uncoupled Design](https://reader037.vdocuments.site/reader037/viewer/2022110115/548c9f35b4795961318b4737/html5/thumbnails/2.jpg)
I’m going to show youhow to get from this
![Page 3: Custom Signals for Uncoupled Design](https://reader037.vdocuments.site/reader037/viewer/2022110115/548c9f35b4795961318b4737/html5/thumbnails/3.jpg)
![Page 4: Custom Signals for Uncoupled Design](https://reader037.vdocuments.site/reader037/viewer/2022110115/548c9f35b4795961318b4737/html5/thumbnails/4.jpg)
To this
![Page 5: Custom Signals for Uncoupled Design](https://reader037.vdocuments.site/reader037/viewer/2022110115/548c9f35b4795961318b4737/html5/thumbnails/5.jpg)
![Page 6: Custom Signals for Uncoupled Design](https://reader037.vdocuments.site/reader037/viewer/2022110115/548c9f35b4795961318b4737/html5/thumbnails/6.jpg)
Without surgery
![Page 7: Custom Signals for Uncoupled Design](https://reader037.vdocuments.site/reader037/viewer/2022110115/548c9f35b4795961318b4737/html5/thumbnails/7.jpg)
Or magic
![Page 8: Custom Signals for Uncoupled Design](https://reader037.vdocuments.site/reader037/viewer/2022110115/548c9f35b4795961318b4737/html5/thumbnails/8.jpg)
![Page 9: Custom Signals for Uncoupled Design](https://reader037.vdocuments.site/reader037/viewer/2022110115/548c9f35b4795961318b4737/html5/thumbnails/9.jpg)
A real world example
![Page 10: Custom Signals for Uncoupled Design](https://reader037.vdocuments.site/reader037/viewer/2022110115/548c9f35b4795961318b4737/html5/thumbnails/10.jpg)
(too boring)
![Page 11: Custom Signals for Uncoupled Design](https://reader037.vdocuments.site/reader037/viewer/2022110115/548c9f35b4795961318b4737/html5/thumbnails/11.jpg)
A contrived example
class PonyForm(forms.Form): color=forms.CharField( label='Color', max_length=20, required=True, choices=PONY_COLORS)
![Page 12: Custom Signals for Uncoupled Design](https://reader037.vdocuments.site/reader037/viewer/2022110115/548c9f35b4795961318b4737/html5/thumbnails/12.jpg)
Might look like
Color: White
Submit
![Page 13: Custom Signals for Uncoupled Design](https://reader037.vdocuments.site/reader037/viewer/2022110115/548c9f35b4795961318b4737/html5/thumbnails/13.jpg)
![Page 14: Custom Signals for Uncoupled Design](https://reader037.vdocuments.site/reader037/viewer/2022110115/548c9f35b4795961318b4737/html5/thumbnails/14.jpg)
Adding form flexibility
def __init__(self, *args, **kwargs):super(PonyForm, self).__init__( *args, **kwargs)form_init.send( PonyForm, form=self)
![Page 15: Custom Signals for Uncoupled Design](https://reader037.vdocuments.site/reader037/viewer/2022110115/548c9f35b4795961318b4737/html5/thumbnails/15.jpg)
The Unicorn App
def form_init_listener(sender, form=None, **kwargs):form.fields[’horn'] = \ forms.CharField(’Horn', required=True, max_length=20, choices=HORN_CHOICES)
![Page 16: Custom Signals for Uncoupled Design](https://reader037.vdocuments.site/reader037/viewer/2022110115/548c9f35b4795961318b4737/html5/thumbnails/16.jpg)
Linking them
form_init.connect(form_init_listener, sender=PonyForm)
![Page 17: Custom Signals for Uncoupled Design](https://reader037.vdocuments.site/reader037/viewer/2022110115/548c9f35b4795961318b4737/html5/thumbnails/17.jpg)
Might look like
Color:
Horn:
White
Submit
Silver
![Page 18: Custom Signals for Uncoupled Design](https://reader037.vdocuments.site/reader037/viewer/2022110115/548c9f35b4795961318b4737/html5/thumbnails/18.jpg)
![Page 19: Custom Signals for Uncoupled Design](https://reader037.vdocuments.site/reader037/viewer/2022110115/548c9f35b4795961318b4737/html5/thumbnails/19.jpg)
Promise kept!
![Page 20: Custom Signals for Uncoupled Design](https://reader037.vdocuments.site/reader037/viewer/2022110115/548c9f35b4795961318b4737/html5/thumbnails/20.jpg)
The Challenge
![Page 21: Custom Signals for Uncoupled Design](https://reader037.vdocuments.site/reader037/viewer/2022110115/548c9f35b4795961318b4737/html5/thumbnails/21.jpg)
Ideal Situation
![Page 22: Custom Signals for Uncoupled Design](https://reader037.vdocuments.site/reader037/viewer/2022110115/548c9f35b4795961318b4737/html5/thumbnails/22.jpg)
![Page 23: Custom Signals for Uncoupled Design](https://reader037.vdocuments.site/reader037/viewer/2022110115/548c9f35b4795961318b4737/html5/thumbnails/23.jpg)
![Page 24: Custom Signals for Uncoupled Design](https://reader037.vdocuments.site/reader037/viewer/2022110115/548c9f35b4795961318b4737/html5/thumbnails/24.jpg)
![Page 25: Custom Signals for Uncoupled Design](https://reader037.vdocuments.site/reader037/viewer/2022110115/548c9f35b4795961318b4737/html5/thumbnails/25.jpg)
![Page 26: Custom Signals for Uncoupled Design](https://reader037.vdocuments.site/reader037/viewer/2022110115/548c9f35b4795961318b4737/html5/thumbnails/26.jpg)
Custom Signals are a big part of the answer
![Page 27: Custom Signals for Uncoupled Design](https://reader037.vdocuments.site/reader037/viewer/2022110115/548c9f35b4795961318b4737/html5/thumbnails/27.jpg)
Best Practices
![Page 28: Custom Signals for Uncoupled Design](https://reader037.vdocuments.site/reader037/viewer/2022110115/548c9f35b4795961318b4737/html5/thumbnails/28.jpg)
File Names
Signals go in:
signals.py
Listeners go in:
listeners.py
![Page 29: Custom Signals for Uncoupled Design](https://reader037.vdocuments.site/reader037/viewer/2022110115/548c9f35b4795961318b4737/html5/thumbnails/29.jpg)
Setup
Call “start_listening”
in listeners.py
from models.py
(helps localize imports)
![Page 30: Custom Signals for Uncoupled Design](https://reader037.vdocuments.site/reader037/viewer/2022110115/548c9f35b4795961318b4737/html5/thumbnails/30.jpg)
Rules of Thumb
Most signals should go in:
Models
Forms
(not so much Views)
![Page 31: Custom Signals for Uncoupled Design](https://reader037.vdocuments.site/reader037/viewer/2022110115/548c9f35b4795961318b4737/html5/thumbnails/31.jpg)
What about?
That pesky “caller” attribute?
If in doubt, use a string.
“mysignal.send(‘somelabel’)”
Strings are immutable
![Page 32: Custom Signals for Uncoupled Design](https://reader037.vdocuments.site/reader037/viewer/2022110115/548c9f35b4795961318b4737/html5/thumbnails/32.jpg)
![Page 33: Custom Signals for Uncoupled Design](https://reader037.vdocuments.site/reader037/viewer/2022110115/548c9f35b4795961318b4737/html5/thumbnails/33.jpg)
Examples
![Page 34: Custom Signals for Uncoupled Design](https://reader037.vdocuments.site/reader037/viewer/2022110115/548c9f35b4795961318b4737/html5/thumbnails/34.jpg)
(there are goingto be five)
![Page 35: Custom Signals for Uncoupled Design](https://reader037.vdocuments.site/reader037/viewer/2022110115/548c9f35b4795961318b4737/html5/thumbnails/35.jpg)
Most of these use“Signals Ahoy”
![Page 36: Custom Signals for Uncoupled Design](https://reader037.vdocuments.site/reader037/viewer/2022110115/548c9f35b4795961318b4737/html5/thumbnails/36.jpg)
![Page 37: Custom Signals for Uncoupled Design](https://reader037.vdocuments.site/reader037/viewer/2022110115/548c9f35b4795961318b4737/html5/thumbnails/37.jpg)
Example 1:Searching
![Page 38: Custom Signals for Uncoupled Design](https://reader037.vdocuments.site/reader037/viewer/2022110115/548c9f35b4795961318b4737/html5/thumbnails/38.jpg)
![Page 39: Custom Signals for Uncoupled Design](https://reader037.vdocuments.site/reader037/viewer/2022110115/548c9f35b4795961318b4737/html5/thumbnails/39.jpg)
def search(request): data = request.GET keywords = data.get('keywords', '').split(' ') results = {}
application_search.send(“search”, request=request, keywords=keywords, results=results) context = RequestContext(request, { 'results': results, 'keywords' : keywords}) return render_to_response('search.html', context)
The View
![Page 40: Custom Signals for Uncoupled Design](https://reader037.vdocuments.site/reader037/viewer/2022110115/548c9f35b4795961318b4737/html5/thumbnails/40.jpg)
def base_search_listener(sender, results={}, **kwargs): results['base'] = 'Base search results'
The Listener
![Page 41: Custom Signals for Uncoupled Design](https://reader037.vdocuments.site/reader037/viewer/2022110115/548c9f35b4795961318b4737/html5/thumbnails/41.jpg)
Example 2:Url Manipulation
![Page 42: Custom Signals for Uncoupled Design](https://reader037.vdocuments.site/reader037/viewer/2022110115/548c9f35b4795961318b4737/html5/thumbnails/42.jpg)
![Page 43: Custom Signals for Uncoupled Design](https://reader037.vdocuments.site/reader037/viewer/2022110115/548c9f35b4795961318b4737/html5/thumbnails/43.jpg)
urlpatterns = patterns('tests.localsite.views', (r’signalled_view/', ’signalled_view', {}),)
collect_urls.send(sender=localsite, patterns=urlpatterns)
The Base Urls File
![Page 44: Custom Signals for Uncoupled Design](https://reader037.vdocuments.site/reader037/viewer/2022110115/548c9f35b4795961318b4737/html5/thumbnails/44.jpg)
from django.conf.urls.defaults import *
custompatterns = patterns('tests.customapp.views', (r'^collect_urls/$', 'collect_urls', {}), (r'^async_note/$', 'async_note_create'))
The Custom App Urls File
![Page 45: Custom Signals for Uncoupled Design](https://reader037.vdocuments.site/reader037/viewer/2022110115/548c9f35b4795961318b4737/html5/thumbnails/45.jpg)
from urls import custompatterns
def add_custom_urls(sender, patterns=(), **kwargs): patterns += custompatterns
The Listener
![Page 46: Custom Signals for Uncoupled Design](https://reader037.vdocuments.site/reader037/viewer/2022110115/548c9f35b4795961318b4737/html5/thumbnails/46.jpg)
Example 3:Views
![Page 47: Custom Signals for Uncoupled Design](https://reader037.vdocuments.site/reader037/viewer/2022110115/548c9f35b4795961318b4737/html5/thumbnails/47.jpg)
![Page 48: Custom Signals for Uncoupled Design](https://reader037.vdocuments.site/reader037/viewer/2022110115/548c9f35b4795961318b4737/html5/thumbnails/48.jpg)
def signalled_view(request): ctx = { 'data' : ‘Not modified' } view_prerender.send('signalled_view', context=ctx) context = RequestContext(request, ctx) return render_to_response( ‘signalled_view.html', context)
The View
![Page 49: Custom Signals for Uncoupled Design](https://reader037.vdocuments.site/reader037/viewer/2022110115/548c9f35b4795961318b4737/html5/thumbnails/49.jpg)
<div style=“text-align:center”>{{ data }}</div>
The Template
![Page 50: Custom Signals for Uncoupled Design](https://reader037.vdocuments.site/reader037/viewer/2022110115/548c9f35b4795961318b4737/html5/thumbnails/50.jpg)
Unmodified View
Not modified
![Page 51: Custom Signals for Uncoupled Design](https://reader037.vdocuments.site/reader037/viewer/2022110115/548c9f35b4795961318b4737/html5/thumbnails/51.jpg)
def signalled_view_listener( sender, context={}, **kwargs): context['data'] = “Modified”
def start_listening(): view_prerender.connect( signalled_view_listener, sender=‘signalled_view’)
The Listener
![Page 52: Custom Signals for Uncoupled Design](https://reader037.vdocuments.site/reader037/viewer/2022110115/548c9f35b4795961318b4737/html5/thumbnails/52.jpg)
Modified View
Modified
![Page 53: Custom Signals for Uncoupled Design](https://reader037.vdocuments.site/reader037/viewer/2022110115/548c9f35b4795961318b4737/html5/thumbnails/53.jpg)
Example 4:Asynchronous
![Page 54: Custom Signals for Uncoupled Design](https://reader037.vdocuments.site/reader037/viewer/2022110115/548c9f35b4795961318b4737/html5/thumbnails/54.jpg)
![Page 55: Custom Signals for Uncoupled Design](https://reader037.vdocuments.site/reader037/viewer/2022110115/548c9f35b4795961318b4737/html5/thumbnails/55.jpg)
Importing a (big) XLS
![Page 56: Custom Signals for Uncoupled Design](https://reader037.vdocuments.site/reader037/viewer/2022110115/548c9f35b4795961318b4737/html5/thumbnails/56.jpg)
def locations_upload_xls(request, uuid = None): if request.method == "POST": data = request.POST.copy() form = UploadForm(data, request.FILES) if form.is_valid(): form.save(request.FILES['xls’], request.user) return HttpResponseRedirect( '/admin/location_upload/%s' % form.uuid) else: form = UploadForm() ctx = RequestContext(request, { 'form' : form}) return render_to_response( 'locations/admin_upload.html', ctx)
The View
![Page 57: Custom Signals for Uncoupled Design](https://reader037.vdocuments.site/reader037/viewer/2022110115/548c9f35b4795961318b4737/html5/thumbnails/57.jpg)
class UploadForm(forms.Form): xls = forms.FileField(label="Excel File", required=True) def save(self, infile, user): outfile = tempfile.NamedTemporaryFile(suffix='.xls') for chunk in infile.chunks(): outfile.write(chunk) outfile.flush() self.excelfile=outfile form_postsave.send(self, form=self) return True
The Form
![Page 58: Custom Signals for Uncoupled Design](https://reader037.vdocuments.site/reader037/viewer/2022110115/548c9f35b4795961318b4737/html5/thumbnails/58.jpg)
def process_excel_listener(sender, form=None, **kwargs): parsed = pyExcelerator.parse_xls(form.excelfile.name) # do something with the parsed data – it won’t block processExcelListener = AsynchronousListener( process_excel_listener)
def start_listening(): form_postsave.connect( processExcelListener.listen, sender=UploadForm)
The Listener
![Page 59: Custom Signals for Uncoupled Design](https://reader037.vdocuments.site/reader037/viewer/2022110115/548c9f35b4795961318b4737/html5/thumbnails/59.jpg)
Example 5:Forms
(the long one)
![Page 60: Custom Signals for Uncoupled Design](https://reader037.vdocuments.site/reader037/viewer/2022110115/548c9f35b4795961318b4737/html5/thumbnails/60.jpg)
![Page 61: Custom Signals for Uncoupled Design](https://reader037.vdocuments.site/reader037/viewer/2022110115/548c9f35b4795961318b4737/html5/thumbnails/61.jpg)
def form_example(request): data = {} if request.method == "POST": form = forms.ExampleForm(request.POST) if form.is_valid(): data = form.save() else: form = forms.ExampleForm() ctx = RequestContext(request, { 'form' : form, 'formdata' : data }) return render_to_response(‘form_example.html', ctx)
The View
![Page 62: Custom Signals for Uncoupled Design](https://reader037.vdocuments.site/reader037/viewer/2022110115/548c9f35b4795961318b4737/html5/thumbnails/62.jpg)
class ExampleForm(forms.Form): name = forms.CharField( max_length=30, label='Name', required=True)
def __init__(self, *args, **kwargs): initial = kwargs.get('initial', {}) form_initialdata.send( ExampleForm, form=self, initial=initial) kwargs['initial'] = initial super(ExampleForm, self).__init__( *args, **kwargs) signals.form_init.send(ExampleForm, form=self)
The Form
![Page 63: Custom Signals for Uncoupled Design](https://reader037.vdocuments.site/reader037/viewer/2022110115/548c9f35b4795961318b4737/html5/thumbnails/63.jpg)
def clean(self, *args, **kwargs): super(ExampleForm, self).clean(*args, **kwargs) form_validate.send(ExampleForm, form=self) return self.cleaned_data
def save(self): data = self.cleaned_data form_presave.send(ExampleForm, form=self) form_postsave.send(ExampleForm, form=self) return self.cleaned_data
The Form (pt 2)
![Page 64: Custom Signals for Uncoupled Design](https://reader037.vdocuments.site/reader037/viewer/2022110115/548c9f35b4795961318b4737/html5/thumbnails/64.jpg)
Unmodified page
![Page 65: Custom Signals for Uncoupled Design](https://reader037.vdocuments.site/reader037/viewer/2022110115/548c9f35b4795961318b4737/html5/thumbnails/65.jpg)
def form_initialdata_listener( sender, form=None, initial={}, **kwargs): initial['email'] = "[email protected]" initial['name'] = 'test'
def form_init_listener( sender, form=None, **kwargs): form.fields['email'] = forms.EmailField( 'Email', required=True)
The Listeners
![Page 66: Custom Signals for Uncoupled Design](https://reader037.vdocuments.site/reader037/viewer/2022110115/548c9f35b4795961318b4737/html5/thumbnails/66.jpg)
def form_validate_listener( sender, form=None, **kwargs): """Do custom validation on form""" data = form.cleaned_data email = data.get('email', None) if email != '[email protected]': errors = form.errors if 'email' not in errors: errors['email'] = [] errors['email'].append( 'Email must be "[email protected]"')
The Listeners (pt2)
![Page 67: Custom Signals for Uncoupled Design](https://reader037.vdocuments.site/reader037/viewer/2022110115/548c9f35b4795961318b4737/html5/thumbnails/67.jpg)
Modified page
![Page 68: Custom Signals for Uncoupled Design](https://reader037.vdocuments.site/reader037/viewer/2022110115/548c9f35b4795961318b4737/html5/thumbnails/68.jpg)
Validation page
![Page 69: Custom Signals for Uncoupled Design](https://reader037.vdocuments.site/reader037/viewer/2022110115/548c9f35b4795961318b4737/html5/thumbnails/69.jpg)
Photo Credits
Pony/Unicorn: Bruce Kroeze (pony property of Mia Kroeze)
Gnome: Bruce Kroeze
Fork: Foxuman (sxc.hu)
Monkey: Lies Meirlaen
Air horns: Adrezej Pobiedzinski
![Page 70: Custom Signals for Uncoupled Design](https://reader037.vdocuments.site/reader037/viewer/2022110115/548c9f35b4795961318b4737/html5/thumbnails/70.jpg)
Photo Credits 2
Pirate Ship: Crystal Woroniuk
Telescope: Orlando Pinto
Dominoes: Elvis Santana
San Miguel Panorama: Bruce Kroeze
Birds on wire: Jake P (sxc.hu)
Feedback Form, “Excellent”: Dominik Gwarek
![Page 71: Custom Signals for Uncoupled Design](https://reader037.vdocuments.site/reader037/viewer/2022110115/548c9f35b4795961318b4737/html5/thumbnails/71.jpg)
Resources
Signals Ahoy: http://gosatchmo.com/apps/django-signals-ahoy
This presentation:http://ecomsmith.com/2009/speaking-at-djangocon-2009/