Django Heresies

  • 14,369 views
Uploaded on

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

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

More in: Technology , Business
  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
  • 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.
    Are you sure you want to
    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/
    Are you sure you want to
    Your message goes here
  • Good and balanced presentation. Easy to understand.

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

    Regards
    Anisa
    http://phonehut.info
    http://www.jpolls.net
    Are you sure you want to
    Your message goes here
  • Django <3
    Are you sure you want to
    Your message goes here
No Downloads

Views

Total Views
14,369
On Slideshare
0
From Embeds
0
Number of Embeds
2

Actions

Shares
Downloads
354
Comments
5
Likes
29

Embeds 0

No embeds

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
    No notes for slide

Transcript

  • 1. Django Heresies Simon Willison @simonw EuroDjangoCon http://simonwillison.net/ 4th May 2009
  • 2. http://www.flickr.com/photos/ianlloyd/264753656/
  • 3. DJANGO IS AWESOME
  • 4. Selling bacon on the internet
  • 5. Pulitzer prize winning journalism
  • 6. Saving children’s lives in Kenya
  • 7. Heresy “An opinion at variance with the orthodox or accepted doctrine”
  • 8. Templates
  • 9. {% if %} tags SUCK
  • 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.
  • 13. {% if photo.width > 390 %} ... {% endif %}
  • 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. 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. Silencing errors
  • 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. Project layout http://www.flickr.com/photos/macrorain/2789698166/
  • 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. 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. local_settings.py • svn:ignore local_settings.py ? • Can’t easily test your production settings • Configuration isn’t in source control!
  • 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. 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. Reusable code http://www.flickr.com/photos/ste3ve/521083510/
  • 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. 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. newforms-admin • De-coupled admin from the rest of Django • A new approach to customisation • Powerful subclassing pattern
  • 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. 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. 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. 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. 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. 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. 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. Example: restview.py Django Snippets: http://bit.ly/restview
  • 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. 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. 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. consumer.py Consumer LoginConsumer CookieConsumer SessionConsumer auth.py AuthConsumer registration.py RegistrationConsumer
  • 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. 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. 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. 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. 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. 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. Ticket #6735, scheduled for Django 1.2
  • 48. Storing state on self in a class-based generic view is not thread safe
  • 49. Storing state on self in a class-based generic view is not thread safe
  • 50. Testing
  • 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. Django community? • ... not so good • even though django.test.client is great • Many reusable apps lack tests • need more psychology!
  • 53. nose is more fun • nosetests --with-coverage • (coming to a SoC project near you) • nosetests --pdb • nosetests --pdb-failures
  • 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. 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. The HttpRequest constructor isn’t doing anything useful at the moment...
  • 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. settings.py is the root of all evil
  • 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. 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. 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. http://www.flickr.com/photos/raceytay/2977241805/ Turtles all the way down
  • 63. “an infinite regression belief about cosmology and the nature of the universe”
  • 64. The Django Contract • A view is a callable that takes a request object and returns a response object
  • 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. 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. 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. 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. 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. http://www.flickr.com/photos/enil/2440955556/ Let’s call them “turtles”
  • 71. Three species of turtle • Django request / response • WSGI • HTTP • What if they were interchangeable?
  • 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. http://www.flickr.com/photos/remiprev/336851630/ Micro frameworks
  • 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. djng http://github.com/simonw/djng (First commit at 5.47am this morning)
  • 76. A micro framework that depends on a macro framework
  • 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. Re-imagining the core Django APIs, minus urls.py and settings.py
  • 79. http://github.com/simonw/djangopeople.net
  • 80. http://www.flickr.com/photos/morgantj/2639793944/ Thank you