Slide 1

Slide 1 text

No content

Slide 2

Slide 2 text

Django is awesome!

Slide 3

Slide 3 text

But it can’t come with all the awesome.

Slide 4

Slide 4 text

So let’s go shopping! "

Slide 5

Slide 5 text

A Day in the Life

Slide 6

Slide 6 text

A Day in the Life •Django Site

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

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?

Slide 15

Slide 15 text

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…

Slide 16

Slide 16 text

Batteries included?

Slide 17

Slide 17 text

TEMPLATES & VIEWS & AUTH & MODELS

Slide 18

Slide 18 text

Templates

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

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 %}

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

A Kill To A View

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

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?

Slide 28

Slide 28 text

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) } )

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

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)

Slide 31

Slide 31 text

“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)

Slide 32

Slide 32 text

“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)

Slide 33

Slide 33 text

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)

Slide 34

Slide 34 text

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)

Slide 35

Slide 35 text

… 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')

Slide 36

Slide 36 text

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')

Slide 37

Slide 37 text

… 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')

Slide 38

Slide 38 text

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')

Slide 39

Slide 39 text

… 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)

Slide 40

Slide 40 text

No content

Slide 41

Slide 41 text

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

Slide 42

Slide 42 text

Auth

Slide 43

Slide 43 text

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

Slide 44

Slide 44 text

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.

Slide 45

Slide 45 text

Secure is Hard

Slide 46

Slide 46 text

Insecure Is Easy!

Slide 47

Slide 47 text

No content

Slide 48

Slide 48 text

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

Slide 49

Slide 49 text

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

Slide 50

Slide 50 text

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

Slide 51

Slide 51 text

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

Slide 52

Slide 52 text

Typical use-case: SSO

Slide 53

Slide 53 text

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

Slide 54

Slide 54 text

Done. DATABASES = {}

Slide 55

Slide 55 text

Done. DATABASES = {}

Slide 56

Slide 56 text

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

Slide 57

Slide 57 text

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')

Slide 58

Slide 58 text

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

Slide 59

Slide 59 text

Strategies for safe model replacement •Continue to enforce strong separation 
 of concerns. •Use an existing encapsulation abstraction (SQLAlchemy ORM, MongoEngine, etc.), or “POPOs”.

Slide 60

Slide 60 text

“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')

Slide 61

Slide 61 text

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

Slide 62

Slide 62 text

PersonForm = ModelForm(Person)

Slide 63

Slide 63 text

PersonForm = ModelForm(Person)

Slide 64

Slide 64 text

PersonForm = ModelForm(Person)

Slide 65

Slide 65 text

“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.

Slide 66

Slide 66 text

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

Slide 67

Slide 67 text

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

Slide 68

Slide 68 text

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

Slide 69

Slide 69 text

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.