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