You are on page 1of 31

Rethinking the Reusable

Application Paradigm
Alex Gaynor

Monday, September 6, 2010


I work here.
Monday, September 6, 2010
I work with these guys.
Monday, September 6, 2010
We do a lot of this.
Monday, September 6, 2010
You have been sold a
lie!

Monday, September 6, 2010


By this man!
Monday, September 6, 2010
The dangerous lie:

def my_view(request, pk, template_name="my_app/my_page.html", form_class=MyForm,


redirect_url="/home/place/"):
# magical view stuff here
return HttpResponse()

This does not, a reusable app, make.


Monday, September 6, 2010
That doesn’t mean it’s useless.
But it doesn’t solve all the problems.
What the hell are the problems we’re trying to solve
anyways?

Monday, September 6, 2010


Why Reusable Apps?

Similar patterns across many sites (tagging, voting,


commenting, etc.).
Write once, use everywhere.
No need to reinvent the wheel.

Monday, September 6, 2010


The problems.
Differing business logic.
Everyone wants comments, but can anonymous users
comment, is there a comment preview page?
Differing data storage
Does a forum belong to a group, does it need a slug? Do
categories have an icon?

Monday, September 6, 2010


The Solutions

Monday, September 6, 2010


Class based views

See Ben Firshman at 2:20: Alternate Views


Simple view functions don’t provide entry points for
changing logic, besides what you create a parameter for.
Subclass to change the logic, e.g. the admin.

Monday, September 6, 2010


django.contrib.admin
class ArticleAdmin(admin.ModelAdmin):
def save_model(self, request, obj, form, change):
obj.user = request.user
obj.save()

There’s like 30 of these.

Monday, September 6, 2010


Do Less

aka: reusable frameworks


Create a framework, so other people can write their buisness
logic around it.
Example: badges.

Monday, September 6, 2010


Badges

Can badges have multiple levels?


How do you know if a user has received a given badge, at a
given level?
Can a user receive a badge more than once?

Monday, September 6, 2010


The stuff everyone needs...

Templatetags for getting badge count or list of badges for a


user.
Queueing of computations, when they’re expensive.
Persistence.

Monday, September 6, 2010


brabeion
class PointsBadge(Badge):
slug = "points"
levels = [
"Bronze",
"Silver",
"Gold",
]
events = [
"points_awarded",
]
multiple = False

def award(self, user, **state):


points = user.profile.points
if points > 10000:
return BadgeAwarded(level=3)
elif points > 7500:
return BadgeAwarded(level=2)
elif points > 5000:
return BadgeAwarded(level=1)

Monday, September 6, 2010


Be flexible

You can’t predict all the ways people want to use your
software.
But you know what the common way is (probably whatever
you wanted to do in the first place).
So provide a default, but let people swap it out.

Monday, September 6, 2010


django-taggit
class Event(models.Model):
title = models.CharField(max_length=100)
date = models.DateField()

tags = TaggableManager()

Common case: I want some tags on my stuff.

Monday, September 6, 2010


Uncommon cases

Non-integer primary keys (IntegerField for the


GenericForeignKey, by default)
Per-user tags.
Official tags.
Custom caching managers, or anything else.

Monday, September 6, 2010


class EventTaggedItems(BaseTaggedItem):
event = models.ForeignKey("Event")

class Event(models.Model):
title = models.CharField(max_length=100, primary_key=True)
date = models.DateField()

tags = TaggableManager(through=EventTaggedItems)

Swap out the intermediary model (also the Tag model)

Monday, September 6, 2010


Custom backends

django-registration
django.core.cache
django.contrib.auth
django.core.files
django.contrib.sessions

Monday, September 6, 2010


django-registration
class SimpleBackend(object):
def register(self, request, **kwargs):
username, email, password = kwargs['username'], kwargs['email'], kwargs['password1']
User.objects.create_user(username, email, password)

# authenticate() always has to be called before login(), and


# will return the user we just created.
new_user = authenticate(username=username, password=password)
login(request, new_user)
signals.user_registered.send(sender=self.__class__,
user=new_user,
request=request)
return new_user

def activate(self, **kwargs):


raise NotImplementedError

def registration_allowed(self, request):


return getattr(settings, 'REGISTRATION_OPEN', True)

def get_form_class(self, request):


return RegistrationForm

def post_registration_redirect(self, request, user):


return (user.get_absolute_url(), (), {})

def post_activation_redirect(self, request, user):


raise NotImplementedError

Monday, September 6, 2010


Libraries

Not everything is business logic


Tools for other people
e.g.: django.forms

Monday, September 6, 2010


django-filters
class ProductFilterSet(django_filters.FilterSet):
class Meta:
model = Product
fields = ['name', 'price', 'manufacturer']

Create filters like the admin’s list_filter.


Built on top of django.forms

Monday, September 6, 2010


Other Tools
django-fixture-generator
Generate fixtures programatically.
django-templatetag-sugar
A declarative API for creating templatetags.
django_compressor
Compress and combine static media (CSS + JS)
Monday, September 6, 2010
When to ignore all of
this

Monday, September 6, 2010


Well defined problem spaces.
Extremely common use cases.
You just don’t care about the 20% (internal apps?)

Monday, September 6, 2010


user_messages

Internal Eldarion app


Allows user to send messages to other users.
Almost no customizability.
Only control is to enable multi-user messaging or not.
Dead simple to use.

Monday, September 6, 2010


0 flexibility, 0 work

Add to INSTALLED_APPS
Add to urls.py
Write templates.
Done.

Monday, September 6, 2010


Questions?
http://alexgaynor.net

Monday, September 6, 2010

You might also like