Django

From Code Self Study Wiki
Jump to: navigation, search

These are notes about Django.

Django Resources

To Research

Books

To research:

Users

To research:

Create a New Project

Don't forget Virtualenv.

django-admin.py startproject mysite

Sample layout from Two Scoops of Django book:

icecreamratings_project/
    .gitignore
    Makefile
    docs/
    README.rst
    requirements.txt
    icecreamratings/
        manage.py
        media/
        products/
        profiles/
        ratings/
        static/
        templates/
        icecreamratings/
            __init__.py
            settings/
            urls.py
            wsgi.py

The Server

Start the server with: $ python manage.py runserver

Settings

Edit mysite/settings.py.

Two Scoops of Django book recommends a settings directory instead of just a file:

settings/
    __init__.py
    base.py
    local.py
    staging.py
    test.py
    production.py

The file local.py could alternatively be named dev.py.

Database Choice

Edit:

  • engine
  • name
  • user
  • password
  • host

See this page.

PostgreSQL and MySQL require you to create the database first. SQLite doesn't require you to create the database first.

Timezone

Set this to the correct timeone. E.g., America/Los_Angeles.

Setup

Run python manage.py syncdb to setup the database with the default apps.

Create models in your new app: python manage.py startapp polls

Add the new app to the settings.py file:

INSTALLED_APPS = (
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.sites',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    # Uncomment the next line to enable the admin:
    # 'django.contrib.admin',
    # Uncomment the next line to enable admin documentation:
    # 'django.contrib.admindocs',
    'polls',
)

Check what tables will be created with python manage.py sql polls. Check for errors with python manage.py validate. Create the tables with python manage.py syncdb. Play with the modules in the shell with python manage.py shell and:

>>> from polls.models import Poll, Choice # or whatever models were created

Sites

In Django 1.7+, sites aren't enabled by default. The docs have instructions.

Static Files

During development, Django's server will handle the static files.

Create a static directory in your app directory. Files will be located at places like polls/static/polls/style.css and accessible in the browser at /polls/style.css.

Then load it in the polls/templates/index.html template:

{% load staticfiles %}
 
<link rel="stylesheet" type="text/css" href="{% static 'polls/style.css' %}" />

The file settings.py can be given information:

# The URL path where Django will serve the files from.
# This is only for the URL -- the directory doesn't have to exist.
STATIC_URL = '/static/'
 
# This lists places where Django will look outside of apps for static files to collect
STATICFILES_DIRS = (
    os.path.join(BASE_DIR, "static_src"),
)
 
# This is where the auto-generated static files will be kept
STATIC_ROOT = os.path.join(BASE_DIR, "static_output")

More info:

For minification, see Django-Pipeline.

Models

This is the sample model from the Django docs:[1][2]

from django.db import models
 
class Poll(models.Model):
    question = models.CharField(max_length=200)
    pub_date = models.DateTimeField('date published')
 
    # this custom function keeps track of whether a poll is recent or not
    def was_published_recently(self):
        return self.pub_date >= timezone.now() - datetime.timedelta(days=1)
    was_published_recently.admin_order_field = 'pub_date'
    was_published_recently.boolean = True
    was_published_recently.short_description = 'Published recently?'
 
    # add a function like this to each model
    def __unicode__(self):
        return self.question
 
class Choice(models.Model):
    poll = models.ForeignKey(Poll)
    choice_text = models.CharField(max_length=200)
    votes = models.IntegerField(default=0)
 
    # add a function like this to each model
    def __unicode__(self):
        return self.choice_text

Model Examples

@property

Migrations

Django Migrations

New versions of Django (1.7+) have built in migrations. Three useful commands are:

  • ./manage.py makemigrations <app_name> -- creates a migration file
  • ./manage.py migrate -- applies migrations
  • ./manage.py sqlmigrate <app_name> <migration_name> -- shows the SQL that will be executed (docs)

See the docs on how to upgrade from South.

South

For Django 1.6 and earlier use South:

Install South:

$ pip install south

For new projects, add south to installed apps and install the db table:

$ ./manage.py syncdb

Initial command to create first migration file:

$ ./manage.py schemamigration myapp --initial

Then migrate it:

$ ./manage.py migrate myapp

Now, when you change the model, you create the migration file and run the migration like this:

$ ./manage.py schemamigration myapp --auto $ ./manage.py migrate myapp

Convert to south:

$ ./manage.py convert_to_south myapp

Admin Dashboard

Resources:

Enable the Dashboard

To activate the admin dashboard:

  1. Edit settings.py and uncomment django.contrib.admin.
  2. Create database tables with python manage.py syncdb.
  3. Edit the urls.py file and uncomment the three lines for admin URLs.

Admin will now be found at localhost:8000/admin/.

Add an App to the Dashboard

  1. Create an admin.py file in the directory of your app.
  2. Add this to the file, assuming that the model is "Poll":
from django.contrib import admin
from polls.models import Poll
 
admin.site.register(Poll)

Reorder the fields on the polls, e.g., to put the date field above the question field, like this:

from django.contrib import admin
from polls.models import Poll
 
class PollAdmin(admin.ModelAdmin):
    fields = ['pub_date', 'question']
 
admin.site.register(Poll, PollAdmin)

Add fieldsets:

from django.contrib import admin
from polls.models import Poll
 
class PollAdmin(admin.ModelAdmin):
    fieldsets = [
        (None,               {'fields': ['question']}),
        ('Date information', {'fields': ['pub_date'], 'classes': ['collapse']}), # An automatic class for collapsing fieldsets by default
    ]
 
admin.site.register(Poll, PollAdmin)

There are two ways to add the choices. First, like the polls:

from polls.models import Choice
 
admin.site.register(Choice)

Better way to add the choices:

from django.contrib import admin
from polls.models import Choice, Poll
 
# You can either have them stacked or inline -- choose one or the other
# class ChoiceInline(admin.TabularInline):
class ChoiceInline(admin.StackedInline):
    model = Choice
    extra = 3 # by default, have input fields for three choices
 
class PollAdmin(admin.ModelAdmin):
    fieldsets = [
        (None,               {'fields': ['question']}),
        ('Date information', {'fields': ['pub_date'], 'classes': ['collapse']}),
    ]
    inlines = [ChoiceInline]
 
    # adds a way to filter the list by pub_date
    list_filter = ['pub_date']
 
    # adds search
    search_fields = ['question']
 
    # drill down functionality
    date_hierarchy = 'pub_date'
 
    # The following line adds more fields to the admin list display. There is one custom method: was_published_recently.
    list_display = ('question', 'pub_date', 'was_published_recently')
 
admin.site.register(Poll, PollAdmin)

Templates

Note: for alternatives, check out Mako Templates and Jinja (via Coffin?).

Template Naming

There is an interesting naming convention here. Basically, all templates are put into one of three categories:

  1. Extendable templates -- anything that gets extended with {% extends %}. Prefix these templates with double underscores. This can also include some layout templates like __l_single_col.html, __l_right_sidebar.html. You can put them in a layouts directory if there are many of them.
  2. Includable templates -- anything that gets included with {% include %}. Prefix each includable template name with a single underscore.
  3. Page templates -- these are the ones that get called by views.

His example for __base.html is:

{% load staticfiles i18n %}
<!DOCTYPE html>
 
<html>
    <head>
        {% block meta_tags %}{% endblock %}
        <title>
            {% block title %}{% trans "Django Gazette" %}{% endblock %}
        </title>
        {% block stylesheets %}
            <link rel="stylesheet" href="{% static "gazette/bootstrap/css/bootstrap.min.css" %}" type="text/css" />
            <link rel="stylesheet" href="{% static "gazette/css/base.css" %}" type="text/css" />
        {% endblock %}
        {% block javascript %}
            <script src="{% static "gazette/js/jquery-1.7.2.min.js" %}" type="text/javascript"></script>
            <script src="{% static "gazette/bootstrap/js/bootstrap.min.js" %}" type="text/javascript"></script>
        {% endblock %}
        {% block extra_head %}{% endblock %}
    </head>
    <body>
        <header class="navbar navbar-fixed-top">
            {% include "gazette/_navigation.html" %}
        </header>
        <div id="main" role="main">
            <div class="container">
                {% block content %}
                {% endblock %}
            </div>
        </div>
    </body>
</html>

His example for __l_right_sidebar.html is:

{% extends "gazette/__base.html" %}
 
{% block content %}
    <div class="row">
        <div class="span8">
            {% block main_col %}{% endblock %}
        </div>
        <div class="span4">
            {% block side_col %}{% endblock %}
        </div>
    </div>
{% endblock %}

Then an article_detail.html page template might look like this:

{% extends "gazette/__l_right_sidebar.html" %}
 
{% block title %}{{ article.title }} | {{ block.super }}{% endblock %}
 
{% block main_col %}
    {% include "gazette/_article_full.html" %}
{% endblock %}
 
{% block side_col %}
    {% include "gazette/_tag_list.html" with tags=article.tags.all %}
    {% include "gazette/_category_list.html" with categories=article.categories.all %}
{% endblock %}

Settings.py

Add the location of your templates directory to setting.py:

TEMPLATE_DIRS = (
    "{0}/templates".format(BASE_DIR),
)

Admin Templates

Find the location of the Django files:

python -c "
import sys
sys.path = sys.path[1:]
import django
print(django.__path__)"

Alternatively, start the Django shell and find the location with:

from django.contrib import admin
admin.__file__

Copy django/contrib/admin/templates/admin/base_site.html and .../index.html to your templates/admin directory. You can override any admin template this way.

General Templating

Put a template at polls/templates/polls/index.html. See also the docs.

The example below is from the Django tutorial. The polls:detail comes from the namespacing in mysite/urls.py. You can change the URLs sitewide from that URLs file, instead of hardcoding them in the templates.

{% if latest_poll_list %}
    <ul>
    {% for poll in latest_poll_list %}
        <li><a href="{% url 'polls:detail' poll.id %}">{{ poll.question }}</a></li>
    {% endfor %}
    </ul>
{% else %}
    <p>No polls are available.</p>
{% endif %}

Put this in the view:

from django.http import HttpResponse
from django.template import Context, loader
 
from polls.models import Poll
 
def index(request):
    latest_poll_list = Poll.objects.order_by('-pub_date')[:5]
    template = loader.get_template('polls/index.html')
    context = Context({
        'latest_poll_list': latest_poll_list,
    })
    return HttpResponse(template.render(context))

The tutorial also mentions using render as a shortcut:

from django.shortcuts import render
 
from polls.models import Poll
 
def index(request):
    latest_poll_list = Poll.objects.all().order_by('-pub_date')[:5]
    context = {'latest_poll_list': latest_poll_list}
    return render(request, 'polls/index.html', context)

Using either of the above methods, the index template should render now.

Including Templates

You can include templates, like a widget in a sidebar. This one passes a variable to the other template:

{% include "featured_items.html" with items=1 %}

Template Tags and Filters

For custom filters:

  1. Create a templatetags directory within the templates directory.
  2. Create a filter file (like redact.py)
from django import template
register = template.Library()
 
@register.filter
def redact_it(input_string, to_redact):
    to_redact_length = len(to_redact)
    num_of_xs = to_redact_length * "X"
    return input_string.replace(to_redact, num_of_xs)

Then load the custom filter and use it:

{% load redact %}
 
{{ greeting | redact_it:"some text" }}

Looping Over Dicts

If you have a dictionary like this:

deck = {
    'Hearts': ['A', 2, 3, 4, 5, 6, 7, 8, 9, 10, 'J', 'Q', 'K'],
    'Clubs': ['A', 2, 3, 4, 5, 6, 7, 8, 9, 10, 'J', 'Q', 'K'],
    'Spades': ['A', 2, 3, 4, 5, 6, 7, 8, 9, 10, 'J', 'Q', 'K'],
    'Diamonds': ['A', 2, 3, 4, 5, 6, 7, 8, 9, 10, 'J', 'Q', 'K']
}

You can loop over it like this:

<ul>
{% for suit, cards in deck.iteritems %}
<li>{{ suit }}
    <ul>
        {% for card in cards %}
        <li>{{ card }} of {{ suit }}</li>
        {% endfor %}
    </ul>
</li>
{% endfor %}
</ul>

Views

Three steps:

Add some code to polls/views.py:

from django.http import HttpResponse
 
def index(request):
    return HttpResponse("This is the poll index.")
 
def detail(request, poll_id):
    return HttpResponse("This is poll %s." % poll_id)
 
def results(request, poll_id):
    return HttpResponse("This is the results page for poll %s." % poll_id)
 
def vote(request, poll_id):
    return HttpResponse("This is the voting page for poll %s." % poll_id)

Create polls/urls.py and add some paths:

from django.conf.urls import patterns, url
 
from polls import views
 
urlpatterns = patterns('',
    # /polls/
    url(r'^$', views.index, name='index'),
    # /polls/1/
    url(r'^(?P<poll_id>\d+)/$', views.detail, name='detail'),
    # /polls/1/results/
    url(r'^(?P<poll_id>\d+)/results/$', views.results, name='results'),
    # /polls/1/vote/
    url(r'^(?P<poll_id>\d+)/vote/$', views.vote, name='vote'),
)

Then the mysite/urls.py needs to know about the new URL rules:

urlpatterns = patterns('',
    url(r'^polls/', include('polls.urls', namespace="polls")), # namespace the apps
    url(r'^admin/', include(admin.site.urls)),
)

Connecting Views to Models

Put this in the polls view:

from django.http import HttpResponse
 
from polls.models import Poll
 
def index(request):
    # get the last 5 polls
    latest_poll_list = Poll.objects.order_by('-pub_date')[:5]
    output = ', '.join([p.question for p in latest_poll_list])
    return HttpResponse(output)

Still needs a template at polls/templates. See the template section.

HTTP Errors

404 Errors

Put a 404.html file in the root of the templates directory.

See these shortcuts:

# be sure to import the shortcut
from django.shortcuts import render, get_object_or_404
# ...
def detail(request, poll_id):
    poll = get_object_or_404(Poll, pk=poll_id)
    return render(request, 'polls/detail.html', {'poll': poll})

500 Errors

Put a 500.html file in the root of the templates directory if you want a custom error page.

Class-Based Generic Views

There are also generic views available, like ListView and DetailView.

An alternate way to do it would be to put this in polls/urls.py

from django.conf.urls import patterns, url
from django.views.generic import DetailView, ListView
from polls.models import Poll
 
urlpatterns = patterns('',
    url(r'^$',
        ListView.as_view(
            queryset=Poll.objects.order_by('-pub_date')[:5],
            context_object_name='latest_poll_list',
            template_name='polls/index.html'),
        name='index'),
    url(r'^(?P<pk>\d+)/$',
        DetailView.as_view(
            model=Poll,
            template_name='polls/detail.html'),
        name='detail'),
    url(r'^(?P<pk>\d+)/results/$',
        DetailView.as_view(
            model=Poll,
            template_name='polls/results.html'),
        name='results'),
    url(r'^(?P<poll_id>\d+)/vote/$', 'polls.views.vote', name='vote'),
)

DetailView is going to look for a template at <app name>/<model name>_detail.html (e.g., polls/poll_detail.html) and ListView will look for <app name>/<model name>_list.html. The example above overrides the defaults.

Using the above example, polls/views.py can have the index, detail, and results functions deleted.

More on class-based views:

@login_required

Block and redirect unauthenticated users on a view level:

Forms

Sample form from the Django tutorial. This goes in polls/details.html.

See also Crispy Forms and Django Bootstrap.

<h1>{{ poll.question }}</h1>
 
{% if error_message %}<p><strong>{{ error_message }}</strong></p>{% endif %}
 
<form action="{% url 'polls:vote' poll.id %}" method="post">
{% csrf_token %}
{% for choice in poll.choice_set.all %}
    <input type="radio" name="choice" id="choice{{ forloop.counter }}" value="{{ choice.id }}" />
    <label for="choice{{ forloop.counter }}">{{ choice.choice_text }}</label><br />
{% endfor %}
<input type="submit" value="Vote" />
</form>

It would then be handled something like this in polls/views.py:

from django.shortcuts import get_object_or_404, render
from django.http import HttpResponseRedirect, HttpResponse
from django.core.urlresolvers import reverse
from polls.models import Choice, Poll
# ...
def vote(request, poll_id):
    p = get_object_or_404(Poll, pk=poll_id)
    try:
        selected_choice = p.choice_set.get(pk=request.POST['choice'])
    except (KeyError, Choice.DoesNotExist):
        # Redisplay the poll voting form.
        return render(request, 'polls/detail.html', {
            'poll': p,
            'error_message': "You didn't select a choice.",
        })
    else:
        selected_choice.votes += 1
        selected_choice.save()
        # Always return an HttpResponseRedirect after successfully dealing
        # with POST data. This prevents data from being posted twice if a
        # user hits the Back button.
        return HttpResponseRedirect(reverse('polls:results', args=(p.id,)))

Then in the view:

def results(request, poll_id):
    poll = get_object_or_404(Poll, pk=poll_id)
    return render(request, 'polls/results.html', {'poll': poll})

and the polls/results.html template:

<h1>{{ poll.question }}</h1>
 
<ul>
{% for choice in poll.choice_set.all %}
    <li>{{ choice.choice_text }} -- {{ choice.votes }} vote{{ choice.votes|pluralize }}</li>
{% endfor %}
</ul>
 
<a href="{% url 'polls:detail' poll.id %}">Vote again?</a>

Multiple Models in Django Form

Media

Testing

Tests go in the tests.py file in the app. Example from the beginner tutorial:

import datetime
from django.utils import timezone
from django.test import TestCase
 
from polls.models import Poll
 
class PollMethodTests(TestCase):
 
    def test_was_published_recently_with_future_poll(self):
        """
        was_published_recently() should return False for polls whose pub_date is in the future
        """
        future_poll = Poll(pub_date=timezone.now() + datetime.timedelta(days=30))
        self.assertEqual(future_poll.was_published_recently(), False)

See also Python Testing and these links:

Fixures

Logging

Helpers

You can make a directory called lib and put a helpers.py file there. Then call it in your view like this:

from myapp.helpers import say_hi

Scaffolding

Rails style scaffolding: https://github.com/modocache/django-generate-scaffold

Django Channels

See Django Channels.

Building APIs

Django Rest Hooks

Django REST Framework

  • TODO

Tasty Pie

User Registration

Security

Read about Website Security.

  • See the PASSWORD_HASHERS setting. It support bcrypt: pip install bcrypt.
  • CSRF tokens must be used for POST requests. It has to use the render method and not render_to_response.

Deployment

Heroku

See the Django deployment with Heroku page.

References

  1. Django Tutorial
  2. Django Models