10. {% if %} tags SUCK
⢠Every time you {% endifnotequal %},
God kicks the Django Pony
11. {% if %} tags SUCK
⢠Every time you {% endifnotequal %},
God kicks the Django Pony
12. http://docs.djangoproject.com/en/dev/misc/design-philosophies/
â Donât invent a programming language
The template system intentionally doesnât allow the
following:
â˘
Assignment to variables
â˘
Advanced logic
The goal is not to invent a programming language.
The goal is to offer just enough programming-esque
functionality, such as branching and looping, that is
â
essential for making presentation-related decisions.
16. http://www.djangosnippets.org/snippets/1350/
Chris Beaven (aka SmileyChris)
'''
A smarter {% if %} tag for django templates.
While retaining current Django functionality, it also
handles equality, greater than and less than operators.
Some common case examples::
{% if articles|length >= 5 %}...{% endif %}
{% if quot;ifnotequal tagquot; != quot;beautifulquot; %}...{% endif %}
'''
{% load smartif %} replaces the Django {% if %} tag
17. http://www.djangosnippets.org/snippets/1350/
Chris Beaven (aka SmileyChris)
'''
A smarter {% if %} tag for django templates.
While retaining current Django functionality, it also
handles equality, greater than and less than operators.
Some common case examples::
{% if articles|length >= 5 %}...{% endif %}
{% if quot;ifnotequal tagquot; != quot;beautifulquot; %}...{% endif %}
'''
{% load smartif %} replaces the Django {% if %} tag
19. ⢠2003: âtemplate authors shouldnât be able to
break the siteâ
⢠2008: âI can't think of a single time this
feature has helped me, and plenty of
examples of times that it has tripped me up.â
⢠Silent {{ foo.bar }} is OK, silent tags are evil
⢠django-developers: http://bit.ly/silentfail
21. Relocatable
TEMPLATE_DIRS = (
# Don't forget to use absolute paths,
# not relative paths.
)
import os
OUR_ROOT = os.path.realpath(
os.path.dirname(__file__)
)
...
TEMPLATE_DIRS = os.path.join(OUR_ROOT, 'templates')
22. Relocatable
TEMPLATE_DIRS = (
# Don't forget to use absolute paths,
# not relative paths.
)
import os
OUR_ROOT = os.path.realpath(
os.path.dirname(__file__)
)
...
TEMPLATE_DIRS = os.path.join(OUR_ROOT, 'templates')
23. local_settings.py
⢠svn:ignore local_settings.py ?
⢠Canât easily test your production settings
⢠ConďŹguration isnât in source control!
29. object_detail drawbacks
⢠You canât swap the ORM for something else
(without duck typing your own queryset)
⢠You have to use RequestContext
⢠You canât modify something added to the
context; you can only specify extra_context
⢠Thatâs despite a great deal of effort going in
to making the behaviour customisable
30. newforms-admin
⢠De-coupled admin from the rest of Django
⢠A new approach to customisation
⢠Powerful subclassing pattern
31. Fine grained permissions
class Entry(models.Model):
title = models.CharField(max_length=255)
author = models.ForeignKey('auth.User')
class EntryAdmin(admin.ModelAdmin):
exclude = ('author',)
def queryset(self, request):
queryset = super(EntryAdmin, self).queryset(request)
return queryset.filter(author = request.user)
def save_model(self, request, obj, form, change):
obj.author = request.user
obj.save()
def has_change_permission(self, request, axj=None):
if not obj:
return True # access to change list
return obj.author == request.user
has_delete_permission = has_change_permission
admin.site.register(Entry, EntryAdmin)
32. Fine grained permissions
class Entry(models.Model):
title = models.CharField(max_length=255)
author = models.ForeignKey('auth.User')
class EntryAdmin(admin.ModelAdmin):
exclude = ('author',)
def queryset(self, request):
queryset = super(EntryAdmin, self).queryset(request)
return queryset.filter(author = request.user)
def save_model(self, request, obj, form, change):
obj.author = request.user
obj.save()
def has_change_permission(self, request, obj=None):
if not obj:
return True # access to change list
return obj.author == request.user
has_delete_permission = has_change_permission
admin.site.register(Entry, EntryAdmin)
33. Fine grained permissions
class Entry(models.Model):
title = models.CharField(max_length=255)
author = models.ForeignKey('auth.User')
class EntryAdmin(admin.ModelAdmin):
exclude = ('author',)
def queryset(self, request):
queryset = super(EntryAdmin, self).queryset(request)
return queryset.filter(author = request.user)
def save_model(self, request, obj, form, change):
obj.author = request.user
obj.save()
def has_change_permission(self, request, obj=None):
if not obj:
return True # access to change list
return obj.author == request.user
has_delete_permission = has_change_permission
admin.site.register(Entry, EntryAdmin)
34. Fine grained permissions
class Entry(models.Model):
title = models.CharField(max_length=255)
author = models.ForeignKey('auth.User')
class EntryAdmin(admin.ModelAdmin):
exclude = ('author',)
def queryset(self, request):
queryset = super(EntryAdmin, self).queryset(request)
return queryset.filter(author = request.user)
def save_model(self, request, obj, form, change):
obj.author = request.user
obj.save()
def has_change_permission(self, request, obj=None):
if not obj:
return True # access to change list
return obj.author == request.user
has_delete_permission = has_change_permission
admin.site.register(Entry, EntryAdmin)
35. Fine grained permissions
class Entry(models.Model):
title = models.CharField(max_length=255)
author = models.ForeignKey('auth.User')
class EntryAdmin(admin.ModelAdmin):
exclude = ('author',)
def queryset(self, request):
queryset = super(EntryAdmin, self).queryset(request)
return queryset.filter(author = request.user)
def save_model(self, request, obj, form, change):
obj.author = request.user
obj.save()
def has_change_permission(self, request, obj=None):
if not obj:
return True # access to change list
return obj.author == request.user
has_delete_permission = has_change_permission
admin.site.register(Entry, EntryAdmin)
36. Fine grained permissions
class Entry(models.Model):
title = models.CharField(max_length=255)
author = models.ForeignKey('auth.User')
class EntryAdmin(admin.ModelAdmin):
exclude = ('author',)
def queryset(self, request):
queryset = super(EntryAdmin, self).queryset(request)
return queryset.filter(author = request.user)
def save_model(self, request, obj, form, change):
obj.author = request.user
obj.save()
def has_change_permission(self, request, obj=None):
if not obj:
return True # access to change list
return obj.author == request.user
has_delete_permission = has_change_permission
admin.site.register(Entry, EntryAdmin)
37. Objects can be views
⢠A Django view is a function that takes a
request object and returns a response
object
A Django view is a callable that takes a
request object and returns a response
object
38. Objects can be views
⢠A Django view is a function that takes a
request object and returns a response
object
⢠A Django view is a callable that takes a
request object and returns a response
object
⢠Just deďŹne __call__() on the class
41. from django.http import HttpResponse
class RestView(object):
def __call__(self, request, *args, **kwargs):
if not hasattr(self, method):
return self.method_not_allowed(method)
return getattr(self, method)(request, *args, **kwargs)
def method_not_allowed(self, method):
response = HttpResponse('Not allowed: %s' % method)
response.status_code = 405
return response
42. django_openid
⢠Next generation of my django-openid project
⢠Taken a lot longer than I expected
⢠Extensive use of class-based customisation
GitHub: http://github.com/simonw/django-openid
44. Suggestions from
django_openid
⢠Every decision should use a method
⢠Every form should come from a method
⢠Every model interaction should live in a method
⢠Everything should go through a render() method
48. TemplateResponse
class MyCustom(BaseView):
def index(self):
response = super(MyCustom, self).index()
# response is a TemplateResponse
response.context['counter'] += 1
response.template = 'some/other/template.html'
return response
# Two classes
SimpleTemplateResponse(template, context)
TemplateResponse(request, template, context)
49. TemplateResponse
⢠Subclasses can re-use your logic and
extend or modify your context
⢠So can middleware and unit tests
⢠GZip Middleware writes to
response.content, needs work arounds
⢠Should HttpResponse be immutable?
54. Django Core
⢠Excellent testing culture
⢠Dubious âďŹnd... | grep... | xargs wc -lâ:
⢠74k lines of code
⢠45k lines of tests
⢠âNo new code without testsâ
⢠Coverage = 54.4%, increasing over time
55. Django community?
⢠... not so good
⢠even though django.test.client is great
⢠Many reusable apps lack tests
⢠need more psychology!
56. nose is more fun
⢠nosetests --with-coverage
⢠(coming to a SoC project near you)
⢠nosetests --pdb
⢠nosetests --pdb-failures
57. Test views directly
⢠Hooking up views to a URLconf just so
you can test them is ďŹddly
⢠... and sucks for reusable apps
⢠A view function takes a request and
returns a response
60. A web-based interface?
⢠Testing would be more fun with pretty graphs
⢠... and animated progress meters
⢠... and a âtest nowâ button
⢠... maybe the Django pony could smile at
you when your tests pass
⢠Cheap continuous integration: run tests every
time a ďŹle changes on disk?
62. Why did PHP magic_quotes suck?
⢠They made it impossible to write reusable code
⢠What if your code expects them to be on, but a
library expects them to be off?
⢠Check get_magic_quotes_gpc() and
unescape... but what if some other library has
done that ďŹrst?
63. settings.py problems
⢠Middleware applies globally, even to
those applications that donât want it
⢠Anything ďŹxed in settings.py I inevitably
want to dynamically alter at runtime
⢠TEMPLATE_DIRS for mobile sites
⢠DB connections
⢠How about per-application settings?
64. Grr
>>> from django import db
Traceback (most recent call last):
File quot;<stdin>quot;, line 1, in <module>
...
ImportError: Settings cannot be imported,
because environment variable
DJANGO_SETTINGS_MODULE is undefined.
67. The Django Contract
⢠A view is a callable that takes a request object and
returns a response object
68. The Django Contract
⢠A view is a callable that takes a request object and
returns a response object
⢠Primary URLconf: selects a view based on regular
expressions
69. The Django Contract
⢠A view is a callable that takes a request object and
returns a response object
⢠Primary URLconf: selects a view based on regular
expressions
⢠Application: sometimes has its own URLconf include()d
in to the primary
70. The Django Contract
⢠A view is a callable that takes a request object and
returns a response object
⢠Primary URLconf: selects a view based on regular
expressions
⢠Application: sometimes has its own URLconf include()d
in to the primary
⢠Middleware: a sequence of globally applied classes
process_request/process_response/process_exception
71. The Django Contract
⢠A view is a callable that takes a request object and
returns a response object
⢠Primary URLconf: selects a view based on regular
expressions
⢠Application: sometimes has its own URLconf include()d
in to the primary
⢠Middleware: a sequence of globally applied classes
process_request/process_response/process_exception
⢠Site: a collection of applications + settings.py + urls.py
72. Extended Contract
⢠A view is a callable that
takes a request and returns a response
⢠URLconf: a callable that
takes a request and returns a response
⢠Application: a callable that
takes a request and returns a response
⢠Middleware: a callable that
takes a request and returns a response
⢠Site: a callable that takes a request and returns a response
80. class Router(object):
    quot;quot;quot;
Convenient wrapper around Django's urlresolvers, allowing them to be used
from normal application code.
Â
from django.conf.urls.defaults import url
router = Router(
url('^foo/$', lambda r: HttpResponse('foo'), name='foo'),
url('^bar/$', lambda r: HttpResponse('bar'), name='bar')
)
request = RequestFactory().get('/bar/')
print router(request)
quot;quot;quot;
    def __init__(self, *urlpairs):
        self.urlpatterns = patterns('', *urlpairs)
        self.resolver = urlresolvers.RegexURLResolver(r'^/', self)
   Â
    def handle(self, request):
        path = request.path_info
        callback, callback_args, callback_kwargs = self.resolver.resolve(path)
        return callback(request, *callback_args, **callback_kwargs)
   Â
    def __call__(self, request):
        return self.handle(request)