Pluggable PatternsFor Reusable Django Applications
Friday, March 11, 2011
An app should not be a monolithic pile of code
For example, most blog “apps” available provide too much functionality
MyBlog App• Categories• Custom Tagging• Custom Comments • Comment
Moderation• Assumption of text
markup type• Single blogs• Multiple Sites
ACME MONOLITHS
Friday, March 11, 2011
An application should be “pluggable”
Friday, March 11, 2011
FocusedWrite programs that do one thing and do it well.
— Doug McIlroy (inventor of UNIX pipes)
A “pluggable” app is
Friday, March 11, 2011
Self-ContainedBatteries are included
Dependencies are declared
A “pluggable” app is
Friday, March 11, 2011
Easily AdaptableA “pluggable” app is
Corey’s Law: The less adaptable you make your code, the sooner you will be tasked to adapt it.
Friday, March 11, 2011
Easily InstalledA “pluggable” app is
pip install coolappYou did declare your dependencies, right?
Friday, March 11, 2011
How do we make a “pluggable” application?
Friday, March 11, 2011
Stop thinking like this
http://upload.wikimedia.org/wikipedia/commons/archive/a/aa/20090315161532!Ferrari_Enzo_Ferrari.JPGFriday, March 11, 2011
and think like this
Friday, March 11, 2011
Applications can have very different purposes
http://www.flickr.com/photos/tiemposdelruido/4051083769/Friday, March 11, 2011
Application Types
• Data. Manages specific data and access to itdjango.contrib.auth
• Utility. Provide a way of handling a specific problem for any applicationdjango-pagination, django.contrib.webdesign
• Decorator. Adds functionality to one or aggregates functionality of many applicationsdjango-mptt, django-tagging
Friday, March 11, 2011
Situation 1
You want to configure your app without modifying its code
(e.g. API keys)
Friday, March 11, 2011
Configurable Options
from django.conf import settings
MODULES = getattr(settings, 'SUPERTAGGING_MODULES', {})
Django Supertagging http://github.com/josesoa
Internal Name Setting Name Default Value
Friday, March 11, 2011
Configurable Options
from django.conf import settings
TOOLBAR_CONFIG = { 'INTERCEPT_REDIRECTS': True, 'SHOW_TOOLBAR_CALLBACK': default_show_toolbar, 'EXTRA_SIGNALS': [], 'HIDE_DJANGO_SQL': True, 'SHOW_TEMPLATE_CONTEXT': True, 'TAG': 'body',}
TOOLBAR_CONFIG.update(getattr(settings, 'DEBUG_TOOLBAR_CONFIG', {}))
Django Debug Toolbar http://github.com/robhudson
Friday, March 11, 2011
Data Apps
http://www.flickr.com/photos/29276244@N03/3200630853/Friday, March 11, 2011
Situation 2
Lots of variationsEach implementation is different
(e.g. blogs)
Friday, March 11, 2011
Abstract Models
EntryBase
FeaturableEntryMixin
StatusableEntryMixin
TaggableEntryMixin
HTMLFormattableEntryMixin
GLAMKit http://www.glamkit.org/
class PRBlog(EntryBase, StatusableEntryMixin):
subhead = models.CharField(… pdf = models.FileField(…
Friday, March 11, 2011
Situation 3
A few, well-known of variations(e.g. Use django.contrib.sites?)
Friday, March 11, 2011
Optional Field Settings
from django.db import modelsfrom myapp.settings import MULTIPLE_SITES
if MULTIPLE_SITES: from django.contrib.sites.models import Site
class Entry(models.Model): title = models.CharField(max_length=100) … if MULTIPLE_SITES: sites = models.ManyToManyField(Site)
Friday, March 11, 2011
Situation 4
Optionally use another application
(e.g. Use django-tagging?)
Friday, March 11, 2011
Optional Integration
from django.db import modelsfrom myapp.settings import USE_TAGGING
if USE_TAGGING: from tagging.fields import TagField
class Entry(models.Model): title = models.CharField(max_length=100) … if USE_TAGGING: tags = TagField()
Friday, March 11, 2011
Situation 5
You want to reference different models
(e.g. Customizable author field)
Friday, March 11, 2011
Runtime Configurable Foreign Keys
from django.conf import settingsfrom django.db.models import get_model
model_string = getattr(settings, 'VIEWPOINT_AUTHOR_MODEL', 'auth.User')AUTHOR_MODEL = get_model(*model_string.split('.'))
Viewpoint http://github.com/washingtontimes
Friday, March 11, 2011
Runtime Configurable Foreign Keys
from viewpoint.settings import AUTHOR_MODEL
class Entry(models.Model): title = models.CharField(max_length=100) author = models.ForeignKey(AUTHOR_MODEL) …
Viewpoint http://github.com/washingtontimes
Friday, March 11, 2011
Utility Apps
http://www.flickr.com/photos/s8/3638531205/Friday, March 11, 2011
Required for template tags or
management commands
models.py
Friday, March 11, 2011
Decorator Apps
http://www.flickr.com/photos/yum9me/2109549869/Friday, March 11, 2011
New FieldNew Method
New AdminCustom Manager
Friday, March 11, 2011
Situation 6
You want to add a field to a model
Friday, March 11, 2011
Lazy Field InsertionDjango Categories http://github.com/washingtontimes
Friday, March 11, 2011
Lazy Field Insertion
CATEGORY_FOREIGNKEYS = { 'app1.Model': ('category',) 'app2.Model': ('primary_category', 'secondary_category')}
Django Categories http://github.com/washingtontimes
Friday, March 11, 2011
Lazy Field Insertionfrom django.db.models import get_modelimport django.conf import settings
FOREIGNKEYS = getattr(settings, 'CATEGORY_FOREIGNKEYS', {})
for model_name, cat_fields in FOREIGNKEYS.items(): if not isinstance(model_name, basestring): continue model = get_model(*model_name.split('.')) for category_field in list(cat_fields): try: model._meta.get_field(category_field) except FieldDoesNotExist: ForeignKey(Category).contribute_to_class( model, category_field)
Django Categories http://github.com/washingtontimes
Friday, March 11, 2011
Situation 7
You want to add a custom manager to a model
Friday, March 11, 2011
Lazy Manager Insertion
COOLAPP_MODELS = { 'app1.Model': 'cool_manager', 'app2.Model': 'cool_manager',}
Django MPTT http://github.com/django-mptt
Friday, March 11, 2011
Adding a manager
from django.db.models import get_modelimport django.conf import settingsfrom coolapp.managers import CustomManager
MODELS = getattr(settings, 'COOLAPP_MODELS', {})
for model_name, mgr_name in MODELS.items(): if not isinstance(model_name, basestring): continue model = get_model(*model_name.split('.')) if not getattr(model, mgr_name, False): manager = CustomManager() manager.contribute_to_class(model, mgr_name)
Django MPTT http://github.com/django-mptt
Friday, March 11, 2011
Situation 8
You want to customize a model’s ModelAdmin
(e.g. Change the widget of a field)
Friday, March 11, 2011
Lazy Registration of a Custom ModelAdmin
TINYMCE_ADMIN_FIELDS = { 'app1.model1': ('body',), 'app1.model2': ('blog_text', 'blog_teaser')}
Django TinyMCE http://github.com/justquick
project’s settings.pyFriday, March 11, 2011
Lazy Registration of a Custom ModelAdmin
from django.db.models import get_modelimport django.conf import settings
REGISTRY = {}ADMIN_FIELDS = getattr(settings, 'TINYMCE_ADMIN_FIELDS', {})
for model_name, field in ADMIN_FIELDS.items(): if isinstance(model_name, basestring): model = get_model(*model_name.split('.')) if model in registry: return REGISTRY[model] = field
Django TinyMCE http://github.com/justquick
Django-TinyMCE’s models.pyFriday, March 11, 2011
Lazy Registration of a Custom ModelAdmin
# Define a new ModelAdmin subclass
class TinyMCEAdmin(admin.ModelAdmin): editor_fields = ()
def formfield_for_dbfield(self, db_field, **kwargs): if db_field.name in self.editor_fields: return db_field.formfield(widget=TinyMCE()) return super(TinyMCEAdmin, self).formfield_for_dbfield( db_field, **kwargs)
Django TinyMCE http://github.com/justquick
Django-TinyMCE’s admin.pyFriday, March 11, 2011
Lazy Registration of a Custom ModelAdmin
for model, modeladmin in admin.site._registry.items(): if model in REGISTRY: admin.site.unregister(model) admin.site.register( model, type('newadmin', (TinyMCEAdmin, modeladmin.__class__), {'editor_fields': REGISTRY[model],} ) )
Django TinyMCE http://github.com/justquick
bottom of Django-TinyMCE’s admin.pyFriday, March 11, 2011
Touch Points
Friday, March 11, 2011
Touch Points of an App
The parts of an application that usually need tweaking• URLs• Templates• View responses
Friday, March 11, 2011
Situation 9
You want the URLs of your app to live under any prefix
(e.g. /blogs/ vs. /weblogs/)
Friday, March 11, 2011
Name your URLs
from django.conf.urls.defaults import *
urlpatterns = patterns('', (r'^$', 'coolapp_app.views.index'),)
Friday, March 11, 2011
Name your URLs
from django.conf.urls.defaults import *
urlpatterns = patterns('', url(r'^$', 'coolapp_app.views.index', name='coolapp_index'),)
url Function name
Friday, March 11, 2011
Reference your URLs by name
<p>Go to the <a href="{% url coolapp_index %}">Index</a></p>
from django.core.urlresolvers import reverse
def myview(request): return HttpResponseRedirect(reverse('coolapp_index', args=[]))
Friday, March 11, 2011
Situation 10
You want your templates to be easily overridable
Friday, March 11, 2011
“Namespace” Templates
coolapp
templates
coolapp
All templates in a template
“name space”
base.html
Friday, March 11, 2011
“Namespace” Templates
coolapp
templates
coolapp
Referenced as “coolapp/base.html”
base.html
Friday, March 11, 2011
Extend one template
index.html
base.html
detail.htmlsummary.html
{% extends “base.html” %}
site_base.html
Friday, March 11, 2011
Extend one template
index.html
base.html
detail.htmlsummary.html
{% extends “coolapp/base.html” %}base.html
site_base.html
Friday, March 11, 2011
Extend one template
index.html
base.html
detail.htmlsummary.html
{% extends “coolapp/base.html” %}base.html
site_base.html
Friday, March 11, 2011
Extend one template
coolapp/base.html
{% extends "base.html" %}
{% block head %}{% endblock %}
{% block body %}{% endblock %}
Friday, March 11, 2011
Extend one template
coolapp/base.html
{% extends "base.html" %}
{% block head %}{% endblock %}
{% block body %}{% endblock %}
{% extends "site_base.html" %}
{% block extra_head %} {% block head %} {% endblock %}{% endblock %}
{% block content %} {% block body %} {% endblock %}{% endblock %}
Friday, March 11, 2011
Import your blocks
{% extends "coolapp/base.html" %}
{% block extra_head %} {{ block.super }} {% import "coolapp/extra_head.html" %}{% endblock %}
{% block content %} {# Important content stuff here #}
{% endblock %}
extra_head.html
coolapp/detail.html
Allows you to override any of the templates
Friday, March 11, 2011
Situation 11
You want flexibility storing uploaded files
Friday, March 11, 2011
Define a Storage Optionfrom django.conf import settingsfrom django.core.files.storage import get_storage_class
DEFAULT_STORAGE = get_storage_class(getattr(settings, "MMEDIA_DEFAULT_STORAGE", settings.DEFAULT_FILE_STORAGE)
)
from massmedia.settings import IMAGE_UPLOAD_TO, DEFAULT_STORAGE
class Image(models.Model): file = models.FileField( upload_to = IMAGE_UPLOAD_TO, blank = True, null = True, storage=DEFAULT_STORAGE())
Friday, March 11, 2011
Situation 12
You want to alter the data your views use
(e.g. Extra context, different template)
Friday, March 11, 2011
100% more class-based views!
django-cbv for backwards
compatibility!
Friday, March 11, 2011
My Info
• @coordt
• github.com/coordt
• github.com/washingtontimes
• opensource.washingtontimes.com
Friday, March 11, 2011