Django Heresies
Upcoming SlideShare
Loading in...5
×
 

Django Heresies

on

  • 19,507 views

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.

Statistics

Views

Total Views
19,507
Views on SlideShare
18,808
Embed Views
699

Actions

Likes
29
Downloads
352
Comments
5

16 Embeds 699

http://simonwillison.net 578
http://www.slideshare.net 48
http://archimedes.local 19
http://lanyrd.com 18
http://www.dailydjango.com 8
http://127.0.0.1 5
http://www.mefeedia.com 5
http://www.soup.io 5
http://japhy.soup.io 4
http://www.python.rk.edu.pl 2
http://www.rkblog.rk.edu.pl 2
http://webcache.googleusercontent.com 1
http://www.indypendance.com 1
http://python.rk.edu.pl 1
http://tlog.4zal.net 1
http://115.112.206.131 1
More...

Accessibility

Categories

Upload Details

Uploaded via as Adobe PDF

Usage Rights

© All Rights Reserved

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Processing…
  • 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
    Processing…
  • 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
    Processing…
  • Good and balanced presentation. Easy to understand.

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

    Regards
    Anisa
    http://phonehut.info
    http://www.jpolls.net
    Are you sure you want to
    Your message goes here
    Processing…
  • Django <3
    Are you sure you want to
    Your message goes here
    Processing…
Post Comment
Edit your comment

Django Heresies Django Heresies Presentation Transcript

  • 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 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.
  • {% if photo.width > 390 %} ... {% endif %}
  • 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
  • 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
  • Silencing errors
  • • 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
  • 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 = os.path.realpath( os.path.dirname(__file__) ) ... TEMPLATE_DIRS = os.path.join(OUR_ROOT, 'templates')
  • 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')
  • local_settings.py • svn:ignore local_settings.py ? • Can’t easily test your production settings • Configuration isn’t in source control!
  • 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
  • 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'
  • 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_name=None, template_name_field=None, template_loader=loader, extra_context=None, context_processors=None, template_object_name='object', mimetype=None ):
  • 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
  • newforms-admin • De-coupled admin from the rest of Django • A new approach to customisation • Powerful subclassing pattern
  • 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)
  • 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)
  • 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)
  • 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)
  • 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)
  • 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)
  • 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
  • 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
  • Example: restview.py Django Snippets: http://bit.ly/restview
  • 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)
  • 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
  • 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
  • consumer.py Consumer LoginConsumer CookieConsumer SessionConsumer auth.py AuthConsumer registration.py RegistrationConsumer
  • 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
  • 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 ) ...
  • 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 ) ...
  • 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 ) ...
  • 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)
  • 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?
  • 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 • 45k lines of tests • “No new code without tests” • Coverage = 54.4%, increasing over time
  • Django community? • ... not so good • even though django.test.client is great • Many reusable apps lack tests • need more psychology!
  • nose is more fun • nosetests --with-coverage • (coming to a SoC project near you) • nosetests --pdb • nosetests --pdb-failures
  • 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
  • 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
  • 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 • ... 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?
  • 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 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?
  • 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?
  • 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.
  • 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 URLconf: selects a view based on regular expressions
  • 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
  • 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
  • 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
  • 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
  • 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_wsgi_app() http://bit.ly/djwsgi • django_view_dec_from_wsgi_middleware() • HTTP • paste.proxy
  • http://www.flickr.com/photos/remiprev/336851630/ Micro frameworks
  • 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
  • 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 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)
  • 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