$30 off During Our Annual Pro Sale. View Details »

Django minus Django (DJangoCon EU 2014)

Django minus Django (DJangoCon EU 2014)

Jacob Kaplan-Moss

May 14, 2014
Tweet

More Decks by Jacob Kaplan-Moss

Other Decks in Technology

Transcript

  1. View Slide

  2. Django is awesome!

    View Slide

  3. But it can’t come
    with all the
    awesome.

    View Slide

  4. So let’s go shopping!
    "

    View Slide

  5. A Day in the Life

    View Slide

  6. A Day in the Life
    •Django Site

    View Slide

  7. A Day in the Life
    •Django Site
    •- Django’s template engine

    View Slide

  8. A Day in the Life
    •Django Site
    •- Django’s template engine
    •+ Plus Jinja2

    View Slide

  9. A Day in the Life
    •Django Site
    •- Django’s template engine
    •+ Plus Jinja2
    • No views, only REST

    View Slide

  10. A Day in the Life
    •Django Site
    •- Django’s template engine
    •+ Plus Jinja2
    • No views, only REST
    • Plus a whole lot of JavaScript nonsense

    View Slide

  11. A Day in the Life
    •Django Site
    •- Django’s template engine
    •+ Plus Jinja2
    • No views, only REST
    • Plus a whole lot of JavaScript nonsense
    • Start using lots of Redis

    View Slide

  12. A Day in the Life
    •Django Site
    •- Django’s template engine
    •+ Plus Jinja2
    • No views, only REST
    • Plus a whole lot of JavaScript nonsense
    • Start using lots of Redis
    • Start accessing RDBMS less

    View Slide

  13. A Day in the Life
    •Django Site
    •- Django’s template engine
    •+ Plus Jinja2
    • No views, only REST
    • Plus a whole lot of JavaScript nonsense
    • Start using lots of Redis
    • Start accessing RDBMS less
    • Some WSGI Wizardry

    View Slide

  14. A Day in the Life
    •Django Site
    •- Django’s template engine
    •+ Plus Jinja2
    • No views, only REST
    • Plus a whole lot of JavaScript nonsense
    • Start using lots of Redis
    • Start accessing RDBMS less
    • Some WSGI Wizardry
    • Maybe Flask for this bit?

    View Slide

  15. A Day in the Life
    •Django Site
    •- Django’s template engine
    •+ Plus Jinja2
    • No views, only REST
    • Plus a whole lot of JavaScript nonsense
    • Start using lots of Redis
    • Start accessing RDBMS less
    • Some WSGI Wizardry
    • Maybe Flask for this bit?
    • I hear node is cool…

    View Slide

  16. Batteries included?

    View Slide

  17. TEMPLATES &
    VIEWS &
    AUTH &
    MODELS

    View Slide

  18. Templates

    View Slide

  19. Templates with Jinja2
    https://github.com/mitsuhiko/templatetk/blob/master/POST_MORTEM
    •Jinja2 is basically Django Templates++
    •More expressive language
    •Byte-code rendering speedups
    •Sites using Django+Jinja:
    •Mozilla
    •Pitchfork

    View Slide

  20. Could not parse the remainder: '['username']' from 'user['username']'
    {% extends "base.html" %}
    {% block title %}home{% endblock %}
    !
    {% block content %}
    {% if user %}
    Hello {{ user['username'] }}
    {% endif %}
    {% endblock content %}

    View Slide

  21. from django.http import HttpResponse
    from django.template import RequestContext
    !
    from jinja2 import Environment, PackageLoader
    env = Environment(loader=PackageLoader('ourapp', 'templates'))
    !
    def jinja_render(request, template_name, dictionary=None):
    if not dictionary:
    dictionary = {}
    template = env.get_template("index.html")
    new_context = RequestContext(request, dictionary)
    context_dict = {}
    for d in new_context.dicts:
    context_dict.update(d)
    !
    rendered_template = template.render(**context_dict)
    return HttpResponse(rendered_template)
    !
    def home(request):
    # return render(request, "index.html")
    return jinja_render(request, “index.html")
    ourapp/views.py

    View Slide

  22. Jingo
    https://github.com/jbalogh/jingo/
    •Install Jingo.
    •Use all the Django functions.

    View Slide

  23. A Kill To A View

    View Slide

  24. A Brief Over-View
    • “A view is a callable which takes a request and
    returns a response.”

    View Slide

  25. A Brief Over-View
    • “A view is a callable which takes a request and
    returns a response.”
    haha, get it?

    View Slide

  26. django.http.HttpRequest
    A Brief Over-View
    • “A view is a callable which takes a request and
    returns a response.”
    haha, get it?

    View Slide

  27. django.http.HttpRequest
    django.http.HttpResponse
    A Brief Over-View
    • “A view is a callable which takes a request and
    returns a response.”
    haha, get it?

    View Slide

  28. A typical view
    from django.shortcuts import render
    from people.models import Person
    !
    def living_people(request):
    return render(request,
    template = 'people/living.html',
    context = {
    'people': Person.objects.filter(alive=True)
    }
    )

    View Slide

  29. def living_people(request):
    tmpl = template.loader.get_template('people/living.html')
    context = template.Context({
    'people': Person.objects.filter(alive=True)
    })
    body = tmpl.render(context)
    return http.HttpResponse(body)
    def living_people(request):
    return render(request,
    template = 'people/living.html',
    context = {
    'people': Person.objects.filter(alive=True)
    }
    )
    django.shortcuts.render

    View Slide

  30. Coupling by convention
    from django import http, template
    from people.models import Person
    !
    def living_people(request):
    tmpl = template.loader.get_template('people/living.html')
    context = template.Context({
    'people': Person.objects.filter(alive=True)
    })
    body = tmpl.render(context)
    return http.HttpResponse(body)

    View Slide

  31. “coupled” to django.template
    Coupling by convention
    from django import http, template
    from people.models import Person
    !
    def living_people(request):
    tmpl = template.loader.get_template('people/living.html')
    context = template.Context({
    'people': Person.objects.filter(alive=True)
    })
    body = tmpl.render(context)
    return http.HttpResponse(body)

    View Slide

  32. “coupled” to django models
    “coupled” to django.template
    Coupling by convention
    from django import http, template
    from people.models import Person
    !
    def living_people(request):
    tmpl = template.loader.get_template('people/living.html')
    context = template.Context({
    'people': Person.objects.filter(alive=True)
    })
    body = tmpl.render(context)
    return http.HttpResponse(body)

    View Slide

  33. This is also a view…
    from django.conf import settings
    from django import http, template
    !
    import redis
    db = redis.from_url(settings.REDIS_URL)
    !
    def living_people(request):
    tmpl = template.loader.get_template('people/living.html')
    context = template.Context({
    'people': db.get(‘people:living’)}
    })
    body = tmpl.render(context)
    return http.HttpResponse(body)

    View Slide

  34. Look, I “removed” models!
    This is also a view…
    from django.conf import settings
    from django import http, template
    !
    import redis
    db = redis.from_url(settings.REDIS_URL)
    !
    def living_people(request):
    tmpl = template.loader.get_template('people/living.html')
    context = template.Context({
    'people': db.get(‘people:living’)}
    })
    body = tmpl.render(context)
    return http.HttpResponse(body)

    View Slide

  35. … and this …
    import json
    import redis
    from django import http
    from django.conf import settings
    !
    db = redis.from_url(settings.REDIS_URL)
    !
    def living_people(request):
    body = json.dumps({
    'people': db.get('people:living')
    })
    return http.HttpResponse(body, content_type='application/json')

    View Slide

  36. Template engine? We don’t need
    no steeeking template engine.
    … and this …
    import json
    import redis
    from django import http
    from django.conf import settings
    !
    db = redis.from_url(settings.REDIS_URL)
    !
    def living_people(request):
    body = json.dumps({
    'people': db.get('people:living')
    })
    return http.HttpResponse(body, content_type='application/json')

    View Slide

  37. … and this …
    import json
    import redis
    from django import http
    from django.conf import settings
    !
    db = redis.from_url(settings.REDIS_URL)
    !
    class LivingPeople(object):
    def __call__(self, request):
    body = json.dumps({
    'people': db.get('people:living')
    })
    return http.HttpResponse(body, content_type='application/json')

    View Slide

  38. Hey, I’m a class view
    And this is crazy
    But I have a __call__ method
    So call me maybe
    … and this …
    import json
    import redis
    from django import http
    from django.conf import settings
    !
    db = redis.from_url(settings.REDIS_URL)
    !
    class LivingPeople(object):
    def __call__(self, request):
    body = json.dumps({
    'people': db.get('people:living')
    })
    return http.HttpResponse(body, content_type='application/json')

    View Slide

  39. … and even this.
    import redis
    from django.conf import settings
    from rest_framework import viewsets
    from rest_framework.response import Response
    !
    db = redis.from_url(settings.REDIS_URL)
    !
    class LivingPersonViewSet(viewsets.ViewSet):
    def list(self, request):
    people = db.get('people:living')
    return Response(people)
    !
    def retrieve(self, request, pk):
    person = db.get('people:living:%s' % int(pk))
    return Response(person)

    View Slide

  40. View Slide

  41. Django Rest Framework
    +
    AngularJS
    =
    (well, you know)
    http://blog.kevinastone.com/
    getting-started-with-django-rest-framework-and-angularjs.html

    View Slide

  42. Auth

    View Slide

  43. django.“contrib”.auth
    • django.contrib.auth.middleware.AuthenticationMiddleware
    • django.contrib.sessions.middleware.SessionMiddleware
    • django.contrib.auth.backends.ModelBackend
    • django.contrib.auth.models.User
    • django.contrib.auth.context_processors.auth

    View Slide

  44. What auth needs to do
    •Securely (hopefully!) verify 

    someone’s identify.
    •Set request.user to an object that
    quacks like a user.
    •That object should probably be a 

    model instance.

    View Slide

  45. Secure is Hard

    View Slide

  46. Insecure Is Easy!

    View Slide

  47. View Slide

  48. from gist_auth.models import GistUser
    !
    class SuperInsecureGistAuthBackend(object):
    !
    def authenticate(self, **credentials):
    username = credentials['username']
    !
    gists = self.get_gist_users()
    !
    if username not in user_gists:
    return None
    !
    user_dict = self.get_user_data(user_gists[username])
    real_password = user_dict['files']['password.txt']['content']
    !
    if credentials['password'] == real_password:
    user, created = GistUser.objects.get_or_create(username=username)
    if created:
    user.gist_id = user_dict['id']
    user.save()
    !
    return user
    gist_auth/auth.py

    View Slide

  49. from django.contrib.auth.models import AbstractUser
    from django.db import models
    !
    class GistUser(AbstractUser):
    !
    gist_id = models.CharField(max_length=255, unique=True)
    gist_auth/models.py
    AUTH_USER_MODEL = ‘gist_orm.GistUser' # app_name.label_of_model
    AUTHENTICATION_BACKENDS = (
    'gist_orm.auth.SuperInsecureGistAuthBackend',
    )
    gist_auth/settings.py
    https://github.com/jacobb/dj_minus_dj

    View Slide

  50. django.contrib.auth
    • django.contrib.auth.backends.ModelBackend
    • django.contrib.auth.models.User
    • django.contrib.auth.context_processors.auth
    • django.contrib.auth.middleware.AuthenticationMiddleware

    View Slide

  51. Do I Feel Lucky?
    from gist_auth.models import GistUser
    !
    class AuthRoulette(object):
    !
    def process_request(self, request):
    random_user = GistUser.objects.order_by('?')[0]
    request.user = random_user

    View Slide

  52. Typical
    use-case:
    SSO

    View Slide

  53. Now, the model
    layer. Seems easy,
    how hard could it be?

    View Slide

  54. Done.
    DATABASES = {}

    View Slide

  55. Done.
    DATABASES = {}

    View Slide

  56. “Just Say No”
    •“Removing” Django’s model layer is easy:
    just don’t use it.
    •However, there are consequences for
    your insolence…

    View Slide

  57. A whole new data store
    import json
    import redis
    from django import http
    from django.conf import settings
    !
    db = redis.from_url(settings.REDIS_URL)
    !
    def living_people(request):
    body = json.dumps({
    'people': db.get('people:living')
    })
    return http.HttpResponse(body, content_type='application/json')

    View Slide

  58. Frameworks are like ogres
    •Frameworks are layered for
    a reason.
    •Coupling of models—of any
    flavor—into your view code
    is an anti-pattern.

    View Slide

  59. Strategies for safe
    model replacement
    •Continue to enforce strong separation 

    of concerns.
    •Use an existing encapsulation abstraction
    (SQLAlchemy ORM, MongoEngine, etc.),
    or “POPOs”.

    View Slide

  60. “POPO”
    Plain Old Python Object
    class Person(object):
    db = redis.from_url(settings.REDIS_URL)
    !
    @classmethod
    def get_living_people(cls):
    return cls.db.get('people:living')

    View Slide

  61. Separation of concerns
    import json
    from django import http
    !
    from .models import Person
    !
    def living_people(request):
    body = json.dumps(Person.get_living_people())
    return http.HttpResponse(body, content_type='application/json')
    import redis
    from django.conf import settings
    !
    class Person(object):
    db = redis.from_url(settings.REDIS_URL)
    !
    @classmethod
    def get_living_people(cls):
    return cls.db.get('people:living')
    myapp/models.py
    myapp/views.py

    View Slide

  62. PersonForm = ModelForm(Person)

    View Slide

  63. PersonForm = ModelForm(Person)

    View Slide

  64. PersonForm = ModelForm(Person)

    View Slide

  65. “Doctor, it hurts when I do this.”
    •Many, many things depend on Django’s model layer, or
    things that themselves depend on models.
    •In particular, ModelForms and Auth. And what uses
    ModelForms and Auth?
    •… that’s right, the Admin.
    •Similarly, many larger 3rd-party apps won’t work with
    custom model layers, either.
    •It can be done (see, for example, github.com/vpulim/
    mango), but the tradeoffs can be difficult.

    View Slide

  66. Does Django play well with others?
    Templates A
    Auth (extending) A
    Auth (replacing) C
    View B
    Models A*

    View Slide

  67. What’s Left
    • Settings
    • URLConf/Routing
    • request → middleware → response cycle
    • Forms.
    • But forms are cool!

    View Slide

  68. “This part is left as an
    exercise to the reader”
    •Remove these last bits with pure WSGI.
    •Let us know how hard it was.

    View Slide

  69. THANK
    YOU Jacob Burch
    Revolution Systems

    @jacobburch

    Jacob Kaplan-Moss
    Heroku

    @jacobian
    Need a job? Heroku is hiring!
    Need someone to do a job? Revsys is hirable.

    View Slide