Django Workshop

by a Django Noob

https://slides.com/marksteve/django-workshop

https://github.com/marksteve/django-workshop

I'm Steve

I go by marksteve in the internet

I create sites and apps for a living and for fun

I'm a Django Noob

Why Django?

Django

The web framework for perfectionists with deadlines.

Ridiculously fast.
Reassuringly secure.
Exceedingly scalable.

Of course I just copy-pasted that from their homepage

I've been using it for a handful of projects for more than a couple of months now

It took some time before I got used to it but I can now confirm that it delivers the features it touts

Sold?

Let's get started then!

What are we building?

Tindero

Tinder for Sales People

# Setup our virtualenv
python3 -m venv venv

# Activate it
source venv/bin/activate

# Install django
echo "django<1.8" > requirements.txt
pip install -r requirements.txt

# Check if installed
django-admin --version
# Start a django project
django-admin startproject tindero .

# Try it out
python manage.py runserver

# Now go to http://localhost:8000
# It should look like this:
# Perform initial db migrations
python manage.py migrate

# Check the db out
sqlite db.sqlite3
sqlite> .tables

# Create admin user
python manage.py createsuperuser

# Run the server and go to
# http://localhost:8000/admin/
# You should be able to log in

Review

  • Start a django project:
    • django-admin startproject PROJECT_NAME PROJECT_DIR
  • Run the development server:
    • python manage.py runserver
  • Perform migrations:
    • python manage.py migrate
  • Create superusers:
    • python manage.py createsuperuser

Registration

Most apps require some sort of user authentication and management. The user system bundled with Django is feature-filled and very flexible.

# Create an app for registration
python manage.py startapp registration

What's an app?

You can think of projects as Django installations: a collection of Django apps. A Django app is basically a package that does a certain functionality of your site.

# Create an app for registartion
python manage.py startapp registration

# Add the app to your INSTALLED_APPS
# tindero/settings.py
INSTALLED_APPS = (
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',

    'registration',
)

settings.py

Settings management is a fairly complex topic depending on the environment and setup you have for deployment. Django has good conventions for dealing with configuration. It provides a base settings.py file to get you started.

# Add auth urls
# tindero/urls.py
from django.conf.urls import patterns, include, url
from django.contrib import admin

urlpatterns = patterns(
    '',
    url(r'^', include('django.contrib.auth.urls')),
    url(r'^admin/', include(admin.site.urls)),
)

urls.py

Django uses Python modules called URLconfs for URL dispatching. It uses regular expressions and can be nested.

# Create registration templates folder
mkdir -p registration/templates/registration

# Create login template
# registration/templates/registration/login.html
<!doctype html>
<html>
<body>
<form action="{% url "login" %}" method="post">
  {% csrf_token %}
  <h2>Login</h2>
  {{ form.as_p }}
  <p>
    <button type="submit">
      Login
    </button>
  </p>
</form>
</body>
</html>

# Run the server and try accessing
# http://localhost:8000/login/
# Add register view
# registration/views.py
from django.contrib.auth.forms import UserCreationForm
from django.shortcuts import render, redirect


def register(request):
    if request.method == 'POST':
        form = UserCreationForm(request.POST)
        if form.is_valid():
            form.save()
            return redirect('login')
    else:
        form = UserCreationForm()
    context = dict(form=form)
    return render(request, 'registration/register.html',
                  context)
# Create register template
# registration/templates/registration/register.html
<!doctype html>
<html>
<body>
<form action="{% url "register" %}" method="post">
  {% csrf_token %}
  <h2>Register</h2>
  {{ form.as_p }}
  <p>
    <button type="submit">
      Register
    </button>
  </p>
</form>
</body>
</html>
# Add URLconf for register view
# registration/urls.py
from django.conf.urls import patterns, url

from . import views

urlpatterns = patterns(
    '',
    url(r'^register/', views.register, name='register'),
)

# Include registration URLconf to project URLconf
# tindero/urls.py
urlpatterns = patterns(
    '',
    url(r'^', include('django.contrib.auth.urls')),
    url(r'^', include('registration.urls')),
    url(r'^admin/', include(admin.site.urls)),
)


Review

  • Start a django app:
    • python manage.py startapp APP_NAME
  • Project configuration lives in

    • settings.py
  • URL configuration lives in
    • urls.py
  • Django (by default) looks for templates files inside the "templates/" folder of apps
  • Views live in
    • views.py

Main App

# Create main app
python manage.py startapp app

# Add app to INSTALLED_APPS
# tindero/settings.py
INSTALLED_APPS = (
    ...
    'app',
)

# Create user profile model
# app/models.py
from django.contrib.auth.models import User
from django.db import models

class UserProfile(models.Model):
    user = models.OneToOneField(User)

    photo = models.ImageField(upload_to='photos')
    bio = models.TextField()

    def __unicode__(self):
        return self.user.get_full_name()
# Add upload settings
# tindero/settings.py
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
MEDIA_URL = '/media/'

# Install Pillow
pip install pillow

# Make migrations
python manage.py makemigrations app

# Apply migrations
python manage.py migrate
# Hook models to admin
# app/admin.py
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin
from django.contrib.auth.models import User

from . import models


class UserProfileInline(admin.StackedInline):
    model = models.UserProfile
    can_delete = False
    verbose_name_plural = 'profile'


class UserAdmin(UserAdmin):
    inlines = (UserProfileInline,)


admin.site.unregister(User)
admin.site.register(User, UserAdmin)

Django Admin

The Admin app is one of Django's most powerful features. It generates admin forms automatically based from your models. But it remains customizable to fit your use case.

# Hook models to admin
# app/admin.py
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin
from django.contrib.auth.models import User

from . import models


class UserProfileInline(admin.StackedInline):
    model = models.UserProfile
    can_delete = False
    verbose_name_plural = 'profile'


class UserAdmin(UserAdmin):
    inlines = (UserProfileInline,)


admin.site.unregister(User)
admin.site.register(User, UserAdmin)

Show me a random user

# Create index view
# app/views.py
from django.contrib.auth.models import User
from django.shortcuts import render


def index(request):
    context = dict(
        tindero=User.objects.order_by('?')[0],
    )
    return render(request, 'index.html', context)

# Create app URLconf
# app/urls.py
from django.conf.urls import patterns, url

from . import views

urlpatterns = patterns(
    '',
    url(r'^$', views.index, name='index'),
)
# Add app and media root to project URLconf
# tindero/urls.py
from django.conf import settings
from django.conf.urls import include, patterns, url
from django.conf.urls.static import static
from django.contrib import admin

urlpatterns = patterns(
    '',
    url(r'^', include('django.contrib.auth.urls')),
    url(r'^', include('registration.urls')),
    url(r'^', include('app.urls')),
    url(r'^admin/', include(admin.site.urls)),
) + static(settings.MEDIA_URL,
           document_root=settings.MEDIA_ROOT)
# Create index template
# app/templates/index.html
<!doctype html>
<html>
<body>
<div class="tindero">
  <img src="{{ tindero.userprofile.photo.url }}">
  <div class="name">
    {{ tindero.username }}
  </div>
  <div class="bio">{{ tindero.userprofile.bio }}</div>
</div>
</body>
</html>

Require login

# Require login
# app/views.py
from django.contrib.auth.decorators import login_required
...

@login_required
def index(request):
    ...

# Set login urls
# tindero/settings.py
LOGIN_URL = 'login'
LOGIN_REDIRECT_URL = 'index'

I don't want to check out myself

# Don't include logged in user
# app/views.py
from django.contrib.auth.decorators import login_required
from django.contrib.auth.models import User
from django.shortcuts import render


@login_required
def index(request):
    try:
        tindero = (User.objects
                   .exclude(id=request.user.id)
                   .order_by('?')[0])
    except IndexError:
        tindero = None
    context = dict(tindero=tindero)
    return render(request, 'index.html', context)
# Update template
# app/templates/index.html
<!doctype html>
<html>
<body>
<div class="tindero">
  {% if tindero %}
  <img src="{{ tindero.userprofile.photo.url }}">
  <div class="name">
    {{ tindero.username }}
  </div>
  <div class="bio">{{ tindero.userprofile.bio }}</div>
  {% else %}
  Wait for more people to join!
  {% endif %}
</div>
</body>
</html>

User voting

# Add UserVote model
# app/models.py
class UserVote(models.Model):
    user = models.ForeignKey(User)
    voter = models.ForeignKey(User, related_name='given_vote')
    vote = models.BooleanField(default=False)

    class Meta:
        unique_together = (('user', 'voter'))

# Migrate
python manage.py makemigrations app
python manage.py migrate
# Create voting views
# app/views.py
def create_vote(request, user_id, vote):
    user = User.objects.get(pk=user_id)
    models.UserVote.objects.create(
        user=user,
        voter=request.user,
        vote=vote,
    )
    return redirect('index')

@login_required
def nice(request, user_id):
    return create_vote(request, user_id, True)

@login_required
def nope(request, user_id):
    return create_vote(request, user_id, False)

# NOTE: Don't forget to import redirect and models!
from django.shortcuts import redirect
from . import models
# Add urls for new views
# app/urls.py
from django.conf.urls import patterns, url

from . import views

urlpatterns = patterns(
    '',
    url(r'^$', views.index, name='index'),
    url(r'^nice/(?P<user_id>\d+)$', views.nice, name='nice'),
    url(r'^nope/(?P<user_id>\d+)$', views.nope, name='nope'),
)
# Add links to template
# app/templates/index.html
<div class="tindero">
  {% if tindero %}
  <img src="{{ tindero.userprofile.photo.url }}">
  <div class="name">
    {{ tindero.username }}
  </div>
  <div class="bio">{{ tindero.userprofile.bio }}</div>
  <a href="{% url "nice" user_id=tindero.id %}">
    Nice
  </a>
  <a href="{% url "nope" user_id=tindero.id %}">
    Nope
  </a>
  {% else %}
  Wait for more people to join!
  {% endif %}
</div>
# Exclude users you already voted on
# app/views.py
tindero = (User.objects
           .exclude(id=request.user.id)
           .exclude(uservote__voter=request.user)
           .order_by('?')[0])

User profile

# Create profile form
# apps/forms.py
from django.forms import ModelForm

from . import models


class UserProfileForm(ModelForm):
    class Meta:
        model = models.UserProfile
        exclude = ('user',)
# Add profile view
# app/views.py
from . import forms

@login_required
def profile(request):
    try:
        profile = request.user.userprofile
    except models.UserProfile.DoesNotExist:
        profile = None
    if request.method == 'POST':
        form = forms.UserProfileForm(request.POST, request.FILES,
                                     instance=profile)
        if form.is_valid():
            if profile:
                form.save()
            else:
                profile = form.save(commit=False)
                profile.user = request.user
                profile.save()
    form = forms.UserProfileForm(instance=profile)
    context = dict(form=form)
    return render(request, 'profile.html', context)
# Add profile template
# app/templates/profile.html
<!doctype html>
<html>
<body>
<form action="{% url "profile" %}" method="post"
  enctype="multipart/form-data">
  {% csrf_token %}
  <h2>Profile</h2>
  {{ form.as_p }}
  <p>
    <button type="submit">
      Save
    </button>
  </p>
</form>
</body>
</html>
# Add profile url
# app/urls.py
from django.conf.urls import patterns, url

from . import views

urlpatterns = patterns(
    '',
    url(r'^$', views.index, name='index'),
    url(r'^nice/(?P<user_id>\d+)/$', views.nice, name='nice'),
    url(r'^nope/(?P<user_id>\d+)/$', views.nope, name='nope'),
    url(r'^profile/$', views.profile, name='profile'),
)

It's a match!

# Show matches
# app/views.py
def create_vote(request, user_id, vote):
    user = User.objects.get(pk=user_id)
    models.UserVote.objects.create(
        user=user,
        voter=request.user,
        vote=vote,
    )
    if vote:
        if models.UserVote.objects.filter(
            user=request.user,
            voter=user,
            vote=True,
        ).count():
            return render(request, 'match.html', dict(
                match=user,
            ))
    return redirect('index')
# Create match template
# app/templates/match.html
<!doctype html>
<html>
<body>
<h2>It's a match!</h2>
<p>
  <img src="{{ request.user.userprofile.photo.url }}">
  <img src="{{ match.userprofile.photo.url }}">
</p>
<p>You and {{ match.username }} like each other!</p>
<p><a href="{% url "index" %}">Play more!</a></p>
</body>
</html>

Make it pretty

# Create a base template
# app/templates/base.html

{% load staticfiles %}
<!doctype html>
<html>
<head>
  <title>Tindero</title>
  <link
    rel="stylesheet"
    type="text/css"
    href="{% static "css/main.css" %}">
</head>
<body>
<div class="page">
  <div class="app">
    <h1><a href="{% url "index" %}">Tindero</a></h1>
    {% block content %}{% endblock %}
  </div>
</div>
</body>
</html>
# Update templates to extend base template
# registration/templates/registration/login.html
{% extends "base.html" %}
{% block content %}
<form action="{% url "login" %}" method="post">
  {% csrf_token %}
  <h2>Login</h2>
  {{ form.as_p }}
  <p>
    <button type="submit">
      Login
    </button>
  </p>
</form>
{% endblock %}

Staticfiles

Django is bundled with a staticfiles app that handles, unsurprisingly, static files. It's not recommend to be used in production though. You should use a proper static file server or a CDN.  

We've only scratched the surface.

There's still a bunch to learn about Django and the third party packages its brilliant community provides. But you won't regret spending time learning Django if you're serious about web development in Python.

https://github.com/marksteve/django-workshop

Django Workshop

By Mark Steve Samson