Django Heresies

Simon Willison                  @simonw
EuroDjangoCon     http://simonwillison.net/
4th May 2009
http://www.flickr.com/photos/ianlloyd/264753656/
DJANGO IS AWESOME
Selling bacon on the internet
Pulitzer prize winning journalism
Saving children’s lives in Kenya
Heresy
“An opinion at variance with the
orthodox or accepted doctrine”
Templates
{% if %} tags SUCK
{% if %} tags SUCK
•   Every time you {% endifnotequal %},
    God kicks the Django Pony
{% if %} tags SUCK
•   Every time you {% endifnotequal %},
    God kicks the Django Pony
http://docs.djangoproject.com/en/dev/misc/design-philosophies/




“   Don’t invent a programming language
    The templat...
{% if photo.width > 390 %}
...
{% endif %}
http://www.djangosnippets.org/snippets/1350/
               Chris Beaven (aka SmileyChris)


'''
A smarter {% if %} tag fo...
http://www.djangosnippets.org/snippets/1350/
               Chris Beaven (aka SmileyChris)


'''
A smarter {% if %} tag fo...
Silencing errors
•   2003: “template authors shouldn’t be able to
    break the site”

•   2008: “I can't think of a single time this
    f...
Project layout
      http://www.flickr.com/photos/macrorain/2789698166/
Relocatable
TEMPLATE_DIRS = (
    # Don't forget to use absolute paths,
    # not relative paths.
)

import os
OUR_ROOT = ...
Relocatable
TEMPLATE_DIRS = (
    # Don't forget to use absolute paths,
    # not relative paths.
)

import os
OUR_ROOT = ...
local_settings.py

•   svn:ignore local_settings.py ?
    •   Can’t easily test your production settings
    •   Configurat...
Environments

zoo/configs/common_settings.py

zoo/configs/alpha/app.wsgi
zoo/configs/alpha/manage.py
zoo/configs/alpha/set...
zoo/configs/alpha/settings.py

from zoo.configs.common_settings import *

DEBUG = True
TEMPLATE_DEBUG = DEBUG

# Database s...
Reusable code
        http://www.flickr.com/photos/ste3ve/521083510/
Generic views
def object_detail(request, queryset, object_id=None,
        slug=None, slug_field='slug',
        template_...
object_detail drawbacks
 •   You can’t swap the ORM for something else
     (without duck typing your own queryset)
 •   Y...
newforms-admin

•   De-coupled admin from the rest of Django
•   A new approach to customisation
•   Powerful subclassing ...
Fine grained permissions
class Entry(models.Model):
    title = models.CharField(max_length=255)
    author = models.Forei...
Fine grained permissions
class Entry(models.Model):
    title = models.CharField(max_length=255)
    author = models.Forei...
Fine grained permissions
class Entry(models.Model):
    title = models.CharField(max_length=255)
    author = models.Forei...
Fine grained permissions
class Entry(models.Model):
    title = models.CharField(max_length=255)
    author = models.Forei...
Fine grained permissions
class Entry(models.Model):
    title = models.CharField(max_length=255)
    author = models.Forei...
Fine grained permissions
class Entry(models.Model):
    title = models.CharField(max_length=255)
    author = models.Forei...
Objects can be views
•   A Django view is a function that takes a
    request object and returns a response
    object
   ...
Objects can be views
•   A Django view is a function that takes a
    request object and returns a response
    object
•  ...
Example: restview.py
 Django Snippets: http://bit.ly/restview
class ArticleView(RestView):

  def GET(request, article_id):
    return render(quot;article.htmlquot;, {
       'article'...
from django.http import HttpResponse

class RestView(object):

    def __call__(self, request, *args, **kwargs):
        i...
django_openid

 •   Next generation of my django-openid project
 •   Taken a lot longer than I expected
 •   Extensive use...
consumer.py           Consumer




            LoginConsumer




   CookieConsumer     SessionConsumer




auth.py
       ...
Suggestions from
          django_openid

•   Every decision should use a method
•   Every form should come from a method
...
render()
class Consumer(object):
    ...
    base_template = 'django_openid/base.html'
    ...
    def render(self, reques...
render()
class Consumer(object):
    ...
    base_template = 'django_openid/base.html'
    ...
    def render(self, reques...
render()
class Consumer(object):
    ...
    base_template = 'django_openid/base.html'
    ...
    def render(self, reques...
TemplateResponse
class MyCustom(BaseView):

   def index(self):
       response = super(MyCustom, self).index()
       # r...
TemplateResponse
•   Subclasses can re-use your logic and
    extend or modify your context
    •   So can middleware and ...
Ticket #6735, scheduled for Django 1.2
Storing state on self in
a class-based generic
view is not thread safe
Storing state on self in
a class-based generic
view is not thread safe
Testing
Django Core
•   Excellent testing culture
•   Dubious “find... | grep... | xargs wc -l”:
    •   74k lines of code
    •   ...
Django community?

•   ... not so good
    •   even though django.test.client is great
•   Many reusable apps lack tests
 ...
nose is more fun

•   nosetests --with-coverage
    •   (coming to a SoC project near you)
•   nosetests --pdb
•   nosetes...
Test views directly

•   Hooking up views to a URLconf just so
    you can test them is fiddly
    •   ... and sucks for re...
RequestFactory
rf = RequestFactory()

get_request = rf.get('/hello/')
post_request = rf.post('/submit/', {
   'foo': 'bar'...
The HttpRequest constructor
isn’t doing anything useful at
        the moment...
A web-based interface?
•   Testing would be more fun with pretty graphs
    •   ... and animated progress meters
    •   ....
settings.py is the root of all evil
Why did PHP magic_quotes suck?


•   They made it impossible to write reusable code
•   What if your code expects them to ...
settings.py problems
•   Middleware applies globally, even to
    those applications that don’t want it
•   Anything fixed ...
Grr
>>> from django import db
Traceback (most recent call last):
  File quot;<stdin>quot;, line 1, in <module>
  ...
Impor...
http://www.flickr.com/photos/raceytay/2977241805/




Turtles all the way down
“an infinite regression belief
 about cosmology and the
  nature of the universe”
The Django Contract
•   A view is a callable that takes a request object and
    returns a response object
The Django Contract
•   A view is a callable that takes a request object and
    returns a response object

•   Primary UR...
The Django Contract
•   A view is a callable that takes a request object and
    returns a response object

•   Primary UR...
The Django Contract
•   A view is a callable that takes a request object and
    returns a response object

•   Primary UR...
The Django Contract
•   A view is a callable that takes a request object and
    returns a response object

•   Primary UR...
Extended Contract
•   A view is a callable that
    takes a request and returns a response

•   URLconf: a callable that
 ...
http://www.flickr.com/photos/enil/2440955556/




Let’s call them “turtles”
Three species of turtle

•   Django request / response
•   WSGI
•   HTTP


•   What if they were interchangeable?
Three species of turtle
•   Django request / response
•   WSGI
    •   Christopher Cahoon, SoC
    •   django_view_from_ws...
http://www.flickr.com/photos/remiprev/336851630/




Micro frameworks
http://github.com/
juno   782 lines
                       breily/juno
                   http://github.com/
newf   144 li...
djng
   http://github.com/simonw/djng
(First commit at 5.47am this morning)
A micro framework
that depends on a
macro framework
class Router(object):
    quot;quot;quot;
    Convenient wrapper around Django's urlresolvers, allowing them to be used
  ...
Re-imagining the core
 Django APIs, minus
urls.py and settings.py
http://github.com/simonw/djangopeople.net
http://www.flickr.com/photos/morgantj/2639793944/




                                                   Thank you
Django Heresies
Django Heresies
Django Heresies
Upcoming SlideShare
Loading in...5
×

Django Heresies

14,702

Published on

A talk given at EuroDjangoCon on the 4th May 2009: things that bother me about Django.

Published in: Technology, Business
5 Comments
30 Likes
Statistics
Notes
  • Good slide on Django. I’m Ana Mui Stanley, working on my latest site on lyrics, www.lyrics-search.org/ . I enjoy reading the slide.
       Reply 
    Are you sure you want to  Yes  No
    Your message goes here
  • I have learned a couple of things from your presentation. Nicely done!

    http://www.riding-mower.org/

    http://www.riding-mower.org/la105-john-deere-lawn-tractor/
       Reply 
    Are you sure you want to  Yes  No
    Your message goes here
  • Good and balanced presentation. Easy to understand.

    John.
    www.freeringtones.ws/
       Reply 
    Are you sure you want to  Yes  No
    Your message goes here
  • Hello.. sound good.. great sharing

    Regards
    Anisa
    http://phonehut.info
    http://www.jpolls.net
       Reply 
    Are you sure you want to  Yes  No
    Your message goes here
  • Django <3
       Reply 
    Are you sure you want to  Yes  No
    Your message goes here
No Downloads
Views
Total Views
14,702
On Slideshare
0
From Embeds
0
Number of Embeds
2
Actions
Shares
0
Downloads
355
Comments
5
Likes
30
Embeds 0
No embeds

No notes for slide

Django Heresies

  1. 1. Django Heresies Simon Willison @simonw EuroDjangoCon http://simonwillison.net/ 4th May 2009
  2. 2. http://www.flickr.com/photos/ianlloyd/264753656/
  3. 3. DJANGO IS AWESOME
  4. 4. Selling bacon on the internet
  5. 5. Pulitzer prize winning journalism
  6. 6. Saving children’s lives in Kenya
  7. 7. Heresy “An opinion at variance with the orthodox or accepted doctrine”
  8. 8. Templates
  9. 9. {% if %} tags SUCK
  10. 10. {% if %} tags SUCK • Every time you {% endifnotequal %}, God kicks the Django Pony
  11. 11. {% if %} tags SUCK • Every time you {% endifnotequal %}, God kicks the Django Pony
  12. 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.
  13. 13. {% if photo.width > 390 %} ... {% endif %}
  14. 14. 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
  15. 15. 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
  16. 16. Silencing errors
  17. 17. • 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
  18. 18. Project layout http://www.flickr.com/photos/macrorain/2789698166/
  19. 19. 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')
  20. 20. 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')
  21. 21. local_settings.py • svn:ignore local_settings.py ? • Can’t easily test your production settings • Configuration isn’t in source control!
  22. 22. Environments zoo/configs/common_settings.py zoo/configs/alpha/app.wsgi zoo/configs/alpha/manage.py zoo/configs/alpha/settings.py zoo/configs/testing/app.wsgi zoo/configs/testing/manage.py zoo/configs/testing/settings.py
  23. 23. zoo/configs/alpha/settings.py from zoo.configs.common_settings import * DEBUG = True TEMPLATE_DEBUG = DEBUG # Database settings DATABASE_NAME = 'zoo_alpha' DATABASE_USER = 'zoo_alpha'
  24. 24. Reusable code http://www.flickr.com/photos/ste3ve/521083510/
  25. 25. Generic views def object_detail(request, queryset, object_id=None, slug=None, slug_field='slug', template_name=None, template_name_field=None, template_loader=loader, extra_context=None, context_processors=None, template_object_name='object', mimetype=None ):
  26. 26. 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
  27. 27. newforms-admin • De-coupled admin from the rest of Django • A new approach to customisation • Powerful subclassing pattern
  28. 28. 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)
  29. 29. 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)
  30. 30. 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)
  31. 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, 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)
  32. 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. 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. 34. 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
  35. 35. 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 define __call__() on the class
  36. 36. Example: restview.py Django Snippets: http://bit.ly/restview
  37. 37. class ArticleView(RestView): def GET(request, article_id): return render(quot;article.htmlquot;, { 'article': get_object_or_404( Article, pk = article_id), }) def POST(request, article_id): form = ... return HttpResponseRedirect(request.path)
  38. 38. 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
  39. 39. 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
  40. 40. consumer.py Consumer LoginConsumer CookieConsumer SessionConsumer auth.py AuthConsumer registration.py RegistrationConsumer
  41. 41. 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
  42. 42. render() class Consumer(object): ... base_template = 'django_openid/base.html' ... def render(self, request, template, context=None): context = context or {} context['base_template'] = self.base_template return TemplateResponse( request, template, context ) ...
  43. 43. render() class Consumer(object): ... base_template = 'django_openid/base.html' ... def render(self, request, template, context=None): context = context or {} context['base_template'] = self.base_template return TemplateResponse( request, template, context ) ...
  44. 44. render() class Consumer(object): ... base_template = 'django_openid/base.html' ... def render(self, request, template, context=None): context = context or {} context['base_template'] = self.base_template return TemplateResponse( request, template, context ) ...
  45. 45. 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)
  46. 46. 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?
  47. 47. Ticket #6735, scheduled for Django 1.2
  48. 48. Storing state on self in a class-based generic view is not thread safe
  49. 49. Storing state on self in a class-based generic view is not thread safe
  50. 50. Testing
  51. 51. Django Core • Excellent testing culture • Dubious “find... | grep... | xargs wc -l”: • 74k lines of code • 45k lines of tests • “No new code without tests” • Coverage = 54.4%, increasing over time
  52. 52. Django community? • ... not so good • even though django.test.client is great • Many reusable apps lack tests • need more psychology!
  53. 53. nose is more fun • nosetests --with-coverage • (coming to a SoC project near you) • nosetests --pdb • nosetests --pdb-failures
  54. 54. Test views directly • Hooking up views to a URLconf just so you can test them is fiddly • ... and sucks for reusable apps • A view function takes a request and returns a response
  55. 55. RequestFactory rf = RequestFactory() get_request = rf.get('/hello/') post_request = rf.post('/submit/', { 'foo': 'bar' }) delete_request = rf.delete('/item/1/') http://bit.ly/requestfactory
  56. 56. The HttpRequest constructor isn’t doing anything useful at the moment...
  57. 57. 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 file changes on disk?
  58. 58. settings.py is the root of all evil
  59. 59. 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 first?
  60. 60. settings.py problems • Middleware applies globally, even to those applications that don’t want it • Anything fixed in settings.py I inevitably want to dynamically alter at runtime • TEMPLATE_DIRS for mobile sites • DB connections • How about per-application settings?
  61. 61. 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.
  62. 62. http://www.flickr.com/photos/raceytay/2977241805/ Turtles all the way down
  63. 63. “an infinite regression belief about cosmology and the nature of the universe”
  64. 64. The Django Contract • A view is a callable that takes a request object and returns a response object
  65. 65. 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
  66. 66. 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
  67. 67. 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
  68. 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 • 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
  69. 69. 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
  70. 70. http://www.flickr.com/photos/enil/2440955556/ Let’s call them “turtles”
  71. 71. Three species of turtle • Django request / response • WSGI • HTTP • What if they were interchangeable?
  72. 72. Three species of turtle • Django request / response • WSGI • Christopher Cahoon, SoC • django_view_from_wsgi_app() http://bit.ly/djwsgi • django_view_dec_from_wsgi_middleware() • HTTP • paste.proxy
  73. 73. http://www.flickr.com/photos/remiprev/336851630/ Micro frameworks
  74. 74. http://github.com/ juno 782 lines breily/juno http://github.com/ newf 144 lines JaredKuolt/newf http://github.com/ mnml 205 lines bradleywright/mnml http://github.com/ itty 406 lines toastdriven/itty
  75. 75. djng http://github.com/simonw/djng (First commit at 5.47am this morning)
  76. 76. A micro framework that depends on a macro framework
  77. 77. 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)
  78. 78. Re-imagining the core Django APIs, minus urls.py and settings.py
  79. 79. http://github.com/simonw/djangopeople.net
  80. 80. http://www.flickr.com/photos/morgantj/2639793944/ Thank you
  1. A particular slide catching your eye?

    Clipping is a handy way to collect important slides you want to go back to later.

×