Pluggable patterns

67
Pluggable Patterns For Reusable Django Applications

Transcript of Pluggable patterns

Pluggable PatternsFor Reusable Django Applications

Project

Project

TemplatesURL routingConfiguration

Project

TemplatesURL routingConfiguration

Application Application Application

Application Application Application

Application Application Application

Project

TemplatesURL routingConfiguration

Application Application Application

Application Application Application

Application Application Application

Here there be power!

We need a better definition for “Django Application”

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

An application should be “pluggable”

FocusedWrite programs that do one thing and do it well.

— Doug McIlroy (inventor of UNIX pipes)

A “pluggable” app is

Self-ContainedBatteries are included

Dependencies are declared

A “pluggable” app is

Easily AdaptableA “pluggable” app is

Corey’s Law: The less adaptable you make your code, the

sooner you will be tasked to adapt it.

Easily InstalledA “pluggable” app is

pip install coolapp

You did declare your dependencies, right?

How do we make a “pluggable” application?

Stop thinking like this

http://upload.wikimedia.org/wikipedia/commons/archive/a/aa/20090315161532!Ferrari_Enzo_Ferrari.JPG

and think like this

http://www.flickr.com/photos/motagirl2/4301276868/

Applications can have very different purposes

http://www.photoshoproadmap.com/imagenes/blog/lego-brushes/lego-bricks-high-resolution.jpg

Application Types

• Data. Manages specific data and access to it

django.contrib.auth

• Utility. Provide a way of handling a specific

problem for any application

django-pagination, django.contrib.webdesign

• Decorator. Adds functionality to one or

aggregates functionality of many applications

django-mptt, django-tagging

Data Apps

http://www.geeky-gadgets.com/wp-content/uploads/2009/08/lego-usb-hub_2.jpg

Situation 1

Lots of variationsEach implementation is different

(e.g. blogs)

Abstract Models

EntryBase

FeaturableEntryMixin

StatusableEntryMixin

TaggableEntryMixin

HTMLFormattableEntryMixin

GLAMKit http://www.glamkit.org/

class PRBlog(EntryBase, StatusableEntryMixin):

subhead = models.CharField(… pdf = models.FileField(…

Situation 2

A few, well-known of variations(e.g. Use django.contrib.sites?)

Optional Field Settings

from django.conf import settingsfrom django.db import models

MULTIPLE_SITES = getattr(settings, 'BLOG_MULTIPLE_SITES', False)

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)

Situation 3

Optionally use another application

(e.g. Use django-tagging?)

Optional Integration

from django.conf import settingsfrom django.db import models

try: from tagging.fields import TagField HAS_TAGGING = Trueexcept ImportError: HAS_TAGGING = False

Optional Integration

from django.conf import settingsfrom django.db import modelsfrom myapp.settings import USE_TAGGING

if USE_TAGGING and 'tagging' in settings.INSTALLED_APPS: from tagging.fields import TagField HAS_TAGGING = Trueelse: HAS_TAGGING = False

class Entry(models.Model): title = models.CharField(max_length=100) … if HAS_TAGGING: tags = TagField()

Situation 3

You want to reference different models

(e.g. Customizable author field)

Configurable Foreign Keys

from django.db.models import get_modelfrom django.conf import settings

model_string = getattr(settings, 'AUTHOR', 'auth.User')AUTHOR_MODEL = get_model(*model_string.split('.'))

class Entry(models.Model): title = models.CharField(max_length=100) author = models.ForeignKey(AUTHOR_MODEL) …

Viewpoint http://github.com/washingtontimes

Situation 3

You want to provide hooks for other applications

(e.g. While processing in a view)

Emit Signals

In signals.pyimport django.dispatchsatchmo_cart_add_complete = django.dispatch.Signal()satchmo_cart_changed = django.dispatch.Signal()

In a view in views.pysatchmo_cart_add_complete.send( cart, cart=cart, cartitem=added_item, product=product, request=request, form=formdata)

satchmo_cart_changed.send(cart, cart=cart, request=request)

Satchmo http://www.satchmoproject.com/

Utility Apps

http://www.1000steine.com/brickset/images/852759-1.jpg

Utility apps are just code libraries

Projects withtemplate tags need one

models.py

Does it need to be in INSTALLED_APPS?

Yes, if you have:

• Templates

• Template Tags

• Management Commands

Decorator Apps

http://trendland.net/wp-content/uploads/2008/12/lego-ring.jpg

CoolApp

New FieldNew Method

New AdminCustom Manager

Registering for Decoration

Simple Model Registry

registry = []

def register(model): if model in registry: return registry.append(model)

from coolapp import registerfrom models import MyModel

register(MyModel)

Model-Data Registry

registry = {}

def register(model, handler): if model in registry: return registry[model] = handler

from coolapp import registerfrom models import MyModel, my_handler

register(MyModel, my_handler)

Lazy RegistrationTINYMCE_ADMIN_FIELDS = { 'app1.model1': ('body',), 'app1.model2': ('blog_text', 'blog_teaser')}

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

Situation 4

You want to add a field to a model

(e.g. Link to another model)

Simple Model Registryfrom django.db.models import FieldDoesNotExist, ForeignKeyfrom categories.models import Category

registry = []

def register(model, category_field='category'): if model in registry: return registry.append(model)

opts = model._meta try: opts.get_field(category_field) except FieldDoesNotExist: ForeignKey(Category).contribute_to_class( model, category_field)

Situation 5

You want to add a method to a model

(e.g. Link to another model)

Adding methodsfrom django.db.models import FieldDoesNotExist, IntegerFieldfrom coolcategories import functions

registry = []

def register(model, ...): if model in registry: return registry.append(model) TreeManager(parent_attr, left_attr, right_attr, tree_id_attr, level_attr).contribute_to_class(model, tree_manager_attr)

setattr(model, 'insert_at', functions.insert_at)

Situation 6

You want to customize a model’s ModelAdmin

(e.g. Change the widget of a field)

Lazy Registration of a Custom ModelAdmin

TINYMCE_ADMIN_FIELDS = { 'app1.model1': ('body',), 'app1.model2': ('blog_text', 'blog_teaser')}

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

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)

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],} ) )

Touch Points

Touch Points of an App

The parts of an application that usually need tweaking• URLs• Templates• Configurable options• View responses

Situation 7

You want the URLs of your app to live under any prefix

(e.g. /blogs/ vs. /weblogs/)

Name your URLs

from django.conf.urls.defaults import *

urlpatterns = patterns('', url(r'^$', 'coolapp_app.views.index', name='coolapp_index'),)

Reference your URLs by name

<p>Go to the <a href="{% url index %}">Index</a></p>

from django.core.urlresolvers import reverse

def myview(request): return HttpResponseRedirect(reverse('index', args=[]))

Situation 8

You want your templates to be easily overridable

“Namespace” Templates

coolapp

templates

coolapp

base.html

“Namespace” Templates

coolapp

templates

coolapp

base.html

All templates in

a template

“name space”

“Namespace” Templates

coolapp

templates

coolapp

base.html

All templates

extend a single

template

“Namespace” Templates

coolapp

templates

coolapp

base.html

This template simply

{% extends “base.html” %}

Import your blocks

{% extends "base.html" %}

{% block extra_head %} {{ block.super }} {% import "coolapp/extra_head.html" %}{% endblock %}

{% block content %} {% import "coolapp/content.html" %}{% endblock %}

extra_head.html

content.html

base.html

Allows you to override any of the templates

Situation 9

You want to configure your app without modifying its code

(e.g. API keys)

Configurable Options

from django.conf import settings

MODULES = getattr(settings, 'SUPERTAGGING_MODULES', {})

Django Supertagging http://github.com/josesoa

Internal Name Setting Name Default Value

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

Define a Storage Option# How to store the files. The ``DEFAULT_STORAGE`` is used for all media types# unless overridden by another setting.## Should be a string in the format: 'module.Class'# **Default:** 'django.core.files.storage.FileSystemStorage'DEFAULT_STORAGE = getattr(settings, "MMEDIA_DEFAULT_STORAGE", settings.DEFAULT_FILE_STORAGE)

from massmedia import settings as appsettingsfrom django.core.files.storage import get_storage_class

IMAGE_STORAGE = get_storage_class(appsettings.DEFAULT_STORAGE)

class Image(models.Model): file = models.FileField( upload_to = appsettings.IMAGE_UPLOAD_TO, blank = True, null = True, storage=IMAGE_STORAGE())

Situation 10

You want to alter the data your views use

(e.g. Extra context, different

template)

Add keyword arguments

def register(request, backend, success_url=None, form_class=None, disallowed_url='registration_disallowed', template_name='registration/registration_form.html', extra_context=None):

Django Registration http://bitbucket.org/ubernostrum

urlpatterns = patterns('', url(r'^register/$', 'registration.views.register', {'template_name': 'my_registration_form.html'}, name='coolapp_index'),)

Class-based Viewshttp://github.com/bfirsh/django-class-based-views

http://github.com/pegasus/django-baseviews

http://code.djangoproject.com/ticket/6735

http://codysoyland.com/2010/feb/3/thread-safe-object-

oriented-views-django/

http://www.toddreed.name/content/django-view-class/

To be continued…

My Info

[email protected]

• @coordt

• github.com/coordt

• github.com/washingtontimes

• opensource.washingtontimes.com