Table of Contents

django學習筆記
隨筆亂記,陸續增加中

1 Coding Style

1.1 Use Explicit Relative Imports

here’s a table of the different Python import types and when to use them in Django projects:

Code Import Type Usage
from core.views import FoodMixin absolute import Use when importing from outside the current app
from .models import WaffleCone explicit relative Use when importing from another module in the current app
from models import WaffleCone implicit relative Often used when importing from another module in the current app, but not a good idea

2 Layout Django Project

For Example:
icecreamratings project/: git repository root
icecreamratings/: project root
products/, ratings/…etc: app root

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

可參考github上大大寫好的django project layout工具吧
https://github.com/pydanny/cookiecutter-django

3 Setting Files

3.1 如何處理多環境設定(production, staging, local)

將setting files分開成各個環境,所有setting file全部進入git控管
底下為例子,在<repository root>/config/settings

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

local設定繼承自base

# settings/local.py 
from .base import *
DEBUG = True

EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'

DATABASES = {
"default": {
"ENGINE": "django.db.backends.postgresql_psycopg2",
"NAME": "twoscoops",
"USER": "",
"PASSWORD": "",
"HOST":
"localhost",
"PORT": "",
}
}

INSTALLED_APPS += ("debug_toolbar", )

個人設定,繼承自local

# settings/dev_pydanny.py 
from .local import *
# Set short cache timeout
CACHE_TIMEOUT = 30

執行時用以下方式指定setting file執行

django-admin shell --settings=twoscoops.settings.local
django-admin runserver --settings=twoscoops.settings.local

在server上可以設定環境變數DJANGO SETTINGS MODULE and PYTHONPATH來指定setting file,或利用virtualenv來設定(add Export to the end of virtualenv's bin/activate)

3.2 重要的secret key問題(secret key不該進入version control)

secret key不該進入version control所以不要把它放在setting file中
最好是放在environment variable,setting file中用os.environ["SOME_SECRET_KEY"]去拿

linux下將以下放到.bashrc, .bash_profile, or .profile
或利用virtualenv來設定(add Export to the end of virtualenv's bin/activate)

export SOME_SECRET_KEY=1c3-cr3am-15-yummy

setting file用以下方式拿secret key

# Top of settings/production.py
import os
SOME_SECRET_KEY = os.environ["SOME_SECRET_KEY"]

上述方式在拿不到environ variable時錯誤訊息會是key_error,不甚好,可在base.py中加入以下改良

# settings/base.py 
import os
# Normally you should not import ANYTHING from Django directly
# into your settings, but ImproperlyConfigured is an exception.
from django.core.exceptions import ImproperlyConfigured .
def get_env_variable(var_name):
"""Get the environment variable or return exception."""
try:
return os.environ[var_name]
except KeyError:
error_msg = "Set the {} environment variable".format(var_name)
raise ImproperlyConfigured(erro.r_msg)
SOME_SECRET_KEY = get_env_variable("SOME_SECRET_KEY")

3.3 當環境限制無法使用environment variable時怎麼做呢

將secret_key放進json file(or xml, yml …etc),setting file中利用json util將secret_key讀出,注意此secret file不該進入version control

{
"FILENAME": "secrets.json",
"SECRET_KEY": "I've got a secret!",
"DATABASES_HOST": "127.0.0.1",
"PORT": "5432"
}
# settings/base.py
import json
# Normally you should not import ANYTHING from Django directly # into your settings, but ImproperlyConfigured is an exception. from django.core.exceptions import ImproperlyConfigured

# JSON-based secrets module

with open("secrets.json") as f:
secrets = json.loads(f.read())
def get_secret(setting, secrets=secrets):
"""Get the secret variable or return explicit exception."""
try:
return secrets[setting]
except KeyError:
error_msg = "Set the {0} environment variable".format(setting)
raise ImproperlyConfigured(error_msg)

SECRET_KEY = get_secret("SECRET_KEY")

3.4 Requirements Files也要照環境分開

不同環境可能需要裝不同package(ex: local才需要debug工具)
在<repository root>/requirements

requirements/
base.txt
local.txt
staging.txt
production.txt

in base.txt

Django==1.8.0
psycopg2==2.6
djangorestframework==3.1.1

in local.txt

-r base.txt # includes the base.txt requirements file

coverage==3.7.1
django-debug-toolbar==1.3.0

in production.txt

-r base.txt # includes the base.txt requirements file

裝package時用以下指令指定requirements檔案安裝

$ pip install -r requirements/local.txt
$ pip install -r requirements/production.txt

3.5 Setting Files中的Path不要使用Absolute Path

利用Unipath (http://pypi.python.org/pypi/Unipath/)

# At the top of settings/base.py 
from unipath import Path

BASE_DIR = Path(__file__).ancestor(3)
MEDIA_ROOT = BASE_DIR.child("media")
STATIC_ROOT = BASE_DIR.child("static")
STATICFILES_DIRS = (
BASE_DIR.child("assets"),
)
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
DIRS = (BASE_DIR.child("templates"),)
},
]

或用python內建的os.path

# At the top of settings/base.py
from os.path import join, abspath, dirname
here = lambda *dirs: join(abspath(dirname(__file__)), *dirs) BASE_DIR = here("..", "..")
root = lambda *dirs: join(abspath(BASE_DIR), *dirs)

# Configuring MEDIA_ROOT
MEDIA_ROOT = root("media")

# Configuring STATIC_ROOT
STATIC_ROOT = root("collected_static")

# Additional locations of static files
STATICFILES_DIRS = (
root("assets"),
)

# Configuring TEMPLATE_DIRS
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
DIRS = (root("templates"),)
},
]

4 Model

4.1 Model Inheritance

當重複field太多時,可考慮abstract base inheritance,例如幾乎每個model都要有created, modified

  • Abstract base classes: 實際上DB不會有parent table
  • multi-table inheritance: DB確實會長出parent table and child table然後用foreign key連結
  • proxy models

不要使用multi-table inheritance,由於其實是使用foreign key處理所以會有效能問題

以下為例子
core.models.TimeStampedModel裡有常用的created and modified field
flavors.Flovor繼承TimeStampedModel的field
注意
class Meta:
abstract = True

# core/models.py
from django.db import models
class TimeStampedModel(models.Model):
"""
An abstract base class model that provides self-
updating ``created`` and ``modified`` fields.
"""

created = models.DateTimeField(auto_now_add=True)
modified = models.DateTimeField(auto_now=True)

class Meta:
abstract = True
# flavors/models.py
from django.db import models
from core.models import TimeStampedModel

class Flavor(TimeStampedModel):
title = models.CharField(max_length=200)

4.2 Model Design Ordering

  1. Start Normalized
  2. Cache Before Denormalizing
  3. Denormalize Only if Absolutely Needed(try cache, row SQL, indexes)

4.3 When to Use Null and Blank

table6_2.png

4.4 When to Use BinaryField

Don't Serve Files From BinaryField. Use FileField!!!

  • MessagePack-formatted content.
  • Raw sensor data.
  • Compressed data e.g. the type of data Sentry stores as a BLOB, but is required to base64-encode due to legacy issues.

4.5 Try to Avoid Using Generic Relations

Cons:

  • Reduction in speed of queries due to lack of indexing between models.
  • Danger of data corruption as a table can refer to another against a non-existent record.

So:

  • Try to avoid generic relations and GenericForeignKey.
  • If you think you need generic relations, see if the problem can be solved through better model design or the new PostgreSQL elds.
  • If usage can’t be avoided, try to use an existing third-party app. e isolation a third-party app provides will help keep data cleaner.

4.6 The Model meta API

Main Usages:

  • Get a list of a model’s fields.
  • Get the class of a particular eld for a model (or its inheritance chain or other info derived from such).
  • Ensure that how you get this information remains constant across future Django versions.

Examples:

  • Building a Django model introspection tool.
  • Building your own custom specialized Django form library.
  • Creating admin-like tools to edit or interact with Django model data.
  • Writing visualization or analysis libraries, e.g. analyzing info only about elds that start with “foo”.

4.7 Fat Models

將跟DB有關的邏輯從view中抽出放到Model中包裝是好的設計,但project到最後會發生Model肥大的問題,一個Model數千行這就不好了,底下提供兩個解法

5 Queries and the Database Layer

5.1 Use get object or 404() for Single Objects instead of get()

  • Only use it in views.
  • Don’t use it in helper functions, forms, model methods or anything that is not a view or directly view related.

5.2 Be Careful With Queries That Might Throw Exceptions

5.2.1 ObjectDoesNotExist vs. DoesNotExist

ObjectDoesNotExist can be applied to any model object, whereas DoesNotExist is for a speci c model.

from django.core.exceptions import ObjectDoesNotExist 
from flavors.models import Flavor
from store.exceptions import OutOfStock

def list_flavor_line_item(sku):
try:
return Flavor.objects.get(sku=sku, quantity__gt=0)
except Flavor.DoesNotExist:
msg = "We are out of {0}".format(sku)
raise OutOfStock(msg)
def list_any_line_item(model, sku):
try:
return model.objects.get(sku=sku, quantity__gt=0)
except ObjectDoesNotExist:
msg = "We are out of {0}".format(sku)
raise OutOfStock(msg)

5.2.2 When You Just Want One Object but Get Three Back

check for a MultipleObjectsRe- turned exception

from flavors.models import Flavor
from store.exceptions import OutOfStock, CorruptedDatabase

def list_flavor_line_item(sku):
try:
return Flavor.objects.get(sku=sku, quantity__gt=0) .
except Flavor.DoesNotExist:
msg = "We are out of {}".format(sku)
raise OutOfStock(msg)
except Flavor.MultipleObjectsReturned:
msg = "Multiple items have SKU {}. Please fix!".format(sku)
raise CorruptedDatabase(msg)

5.3 Transactions

5.3.1 Wrapping Each HTTP Request in a Transaction

# settings/base.py
DATABASES = {
'default': {
# ...
'ATOMIC_REQUESTS': True,
},
}

non atomic function include atomic code:

# flavors/views.py
from django.db import transaction
from django.http import HttpResponse
from django.shortcuts import get_object_or_404
from django.utils import timezone
from .models import Flavor

@transaction.non_atomic_requests
def posting_flavor_status(request, pk, status):
flavor = get_object_or_404(Flavor, pk=pk)

# This will execute in autocommit mode (Django's default).
flavor.latest_status_change_attempt = timezone.now()
flavor.save()

with transaction.atomic():
# This code executes inside a transaction.
flavor.status = status
flavor.latest_status_
change_success = timezone.now()
flavor.save()
return HttpResponse("Hooray")

# If the transaction fails, return the appropriate status
return HttpResponse("Sadness", status_code=400)

5.3.2 Explicit Transaction Declaration

6 Function- and Class-Based Views

6.1 When to Use FBVs or CBVs

figure8_1.png

6.2 Keep View Logic Out of URLConfs

Bad Example:

from django.conf.urls import url
from django.views.generic import DetailView
from tastings.models import Tasting

urlpatterns = [
url(r"ˆ(?P&lt;pk&gt;\d+)/$",
DetailView.as_view(
model=Tasting,
template_name="tastings/detail.html"),
name="detail"),
url(r"ˆ(?P&lt;pk&gt;\d+)/results/$",
DetailView.as_view(
model=Tasting,
template_name="tastings/results.html"),
name="results"),
]

Good view example:

# tastings/views.py
from django.views.generic import ListView, DetailView, UpdateView
from django.core.urlresolvers import reverse
from .models import Tasting

class TasteListView(ListView):
model = Tasting
class TasteDetailView(DetailView):
model = Tasting
class TasteResultsView(TasteDetailView):
template_name = "tastings/results.html"
class TasteUpdateView(UpdateView):
model = Tasting
def get_success_url(self):
return reverse("tastings:detail",
kwargs={"pk": self.object.pk})

Good urls example:

# tastings/urls.py
from django.conf.urls import url
from . import views

urlpatterns = [
url(
regex=r"ˆ$",
view=views.TasteListView.as_view(),
name="list"
),
url(
regex=r"ˆ(?P&lt;pk&gt;\d+)/$",
view=views.TasteDetailView.as_view(),
name="detail"
),
url(
regex=r"ˆ(?P&lt;pk&gt;\d+)/results/$",
view=views.TasteResultsView.as_.view(),
name="results"
),
url(
regex=r"ˆ(?P&lt;pk&gt;\d+)/update/$",
view=views.TasteUpdateView.as_view(),
name="update"
)
]

6.3 Use URL Namespaces

In the root URLConf we would add:

# urls.py at root of project
urlpatterns += [
url(r'ˆtastings/', include('tastings.urls', namespace='tastings')),
]

view example:

# tastings/views.py snippet
class TasteUpdateView(UpdateView):
model = Tasting
def get_success_url(self):
return reverse("tastings:detail", .
kwargs={"pk": self.object.pk})

template example:

{% extends "base.html" %}
{% block title %}Tastings{% endblock title %}
{% block content %}
&lt;ul&gt;
{% for taste in tastings %}
&lt;li&gt;
&lt;a href="{% url "tastings:detail" taste.pk %}"&gt;{{ taste.title }}&lt;/a&gt;
&lt;small&gt;
(&lt;a href="{% url "tastings:update" taste.pk %}"&gt;update&lt;/a&gt;)
&lt;/small&gt;
&lt;/li&gt;
{% endfor %}
&lt;/ul&gt;
{% endblock content %}


6.4 Django Views Are Functions

Class-Based Views Are Actually Called as Functions

# simplest_views.py
from django.http import HttpResponse
from django.views.generic import View

# The simplest FBV
def simplest_view(request):
# Business logic goes here
return HttpResponse("FBV")

# The simplest CBV
class SimplestView(View):
def get(self, request, *args, **kwargs):
# Business logic goes here
return HttpResponse("CBV")

6.5 Don't Use locals() as Views Context

Bad example:

def ice_cream_store_display(request, store_id): 
store = get_object_or_404(Store, id=store_id)
now = timezone.now()
return render(request, 'melted_ice_cream_report.html', locals())

Good example:

def ice_cream_store_display(request, store_id):
return render(request, 'melted_ice_cream_report.html', dict{
'store': get_object_or_404(Store, id=store_id),
'now': timezone.now()
})

7 Function-Based Views

7.1 Use Decorator To Modify Request And Response

Here’s a sample decorator template for use in function-based views:
functools.wraps() is a convenience tool that copies over metadata including critical data like docstrings to the newly decorated function.

# simple decorator template import functools
def decorator(view_func):
@functools.wraps(view_func)
def new_view_func(request, *args, **kwargs):
# You can modify the request (HttpRequest) object here.
response = view_func(request, *args, **kwargs)
# You can modify the response (HttpResponse) object here.
return response
return new_view_func

check_sprinkles is a decorator to modify request:

# sprinkles/decorators.py 
from functools import wraps
from . import utils

# based off the decorator template from Example 8.5
def check_sprinkles(view_func):
"""Check if a user can add sprinkles"""
@wraps(view_func)
def new_view_func(request, *args, **kwargs):
# Act on the request object with utils.can_sprinkle()
request = utils.can_sprinkle(request)
# Call the view function
response = view_func(request, *args, **kwargs)
# Return the HttpResponse object
return response
return new_view_func

Then we attach it to the function thus:

# views.py
from django.shortcuts import get_object_or_404, render
from .decorators import check_sprinkles
from .models import Sprinkle

# Attach the decorator to the view
@check_sprinkles
def sprinkle_detail(request, pk):
"""Standard detail view"""
sprinkle = get_object_or_404(Sprinkle, pk=pk)
return render(request, "sprinkles/sprinkle_detail.html",
{"sprinkle": sprinkle})

8 Class-Based Views

8.1 Guidelines When Working With CBVs

  • Less view code is better.
  • Never repeat code in views.
  • Views should handle presentation logic. Try to keep business logic in models when possible, or in forms if you must.
  • Keep your views simple.
  • Don’t use CBVs to write custom 403, 404, and 500 error handlers. Use FBVs instead.
  • Keep your mixins simpler.

8.2 Using Mixins With CBVs

The rules follow Python’s method resolution order, which in the most simplistic de nition possible, proceeds from left to right:

  1. The base view classes provided by Django always go to the right.
  2. Mixins go to the left of the base view.
  3. Mixins should inherit from Python’s built-in object type.

Example of the rules in action:

from django.views.generic import TemplateView 

class FreshFruitMixin(object):
def get_context_data(self, **kwargs):
context = super(FreshFruitMixin,
. self).get_context_data(**kwargs)
context["has_fresh_fruit"] = True
return context

class FruityFlavorView(FreshFruitMixin, TemplateView):
template_name = "fruity_flavor.html"

8.3 Which Django GCBV Should Be Used for What Task?

table10_1.png

8.4 General Tips for Django CBVs

8.4.1 Constraining Django CBV/GCBV Access to Authenticated Users

Use django-braces LoginRequiredMixin

# flavors/views.py
from django.views.generic import DetailView
from braces.views import LoginRequiredMixin
from .models import Flavor

class FlavorDetailView(LoginRequiredMixin, DetailView):
model = Flavor

8.4.2 Performing Custom Actions on Views With Valid Forms

from django.views.generic import CreateView 
from braces.views import LoginRequiredMixin
from .models import Flavor
class FlavorCreateView(LoginRequiredMixin, CreateView):
model = Flavor
fields = ('title', 'slug', 'scoops_remaining')
def form_valid(self, form):
# Do custom logic here
return super(FlavorCreateView, self).form_valid(form)

To perform custom logic on form data that has already been validated, simply add the logic to formvalid(). e return value of formvalid() should be a django.http.HttpResponseRedirect.

8.4.3 Performing Custom Actions on Views With Invalid Forms

from django.views.generic import CreateView 
from braces.views import LoginRequiredMixin
from .models import Flavor
class FlavorCreateView(LoginRequiredMixin, CreateView):
model = Flavor
def form_invalid(self, form):
# Do custom logic here .
return super(FlavorCreateView, self).form_invalid(form)

8.4.4 Using the View Object

If you are using class-based views for rendering content, consider using the view object itself to provide access to properties and methods that can be called by other method and properties. ey can also be called from templates. For example:

from django.utils.functional import cached_property
from django.views.generic import UpdateView, TemplateView
from braces.views import LoginRequiredMixin
from .models import Flavor
from .tasks import update_users_who_favorited

class FavoriteMixin(object):
@cached_property
def likes_and_favorites(self):
"""Returns a dictionary of likes and favorites"""
likes = self.object.likes()
favorites = self.object.favorites()
return {
"likes": likes,
"favorites": favorites,
"favorites_count": favorites.count(),
}
class FlavorUpdateView(LoginRequiredMixin, FavoriteMixin, UpdateView):
model = Flavor
fields = ('title', 'slug', 'scoops_remaining')
def form_valid(self, form):
update_users_who_favorited(
instance=self.object,
favorites=self.likes_and_favorites['favorites']
)
return super(FlavorCreateView, self).form_valid(form)
class FlavorDetailView(LoginRequiredMixin, FavoriteMixin, TemplateView):
model = Flavor

The nice thing about this is the various avors/ app templates can now access this property:

{# flavors/base.html #}
{% extends "base.html" %}

{% block likes_and_favorites %}
&lt;ul&gt;
&lt;li&gt;Likes: {{ view.likes_and_favorites.likes }}&lt;/li&gt;
&lt;li&gt;Favorites: {{ view.likes_and_favorites.favorites_count }}&lt;/li&gt;
&lt;/ul&gt;
{% endblock likes_and_favorites %}


8.5 How GCBVs and Forms Fit Together

First, let’s define a flavor model to use in this section’s view examples:

# flavors/models.py
from django.core.urlresolvers import reverse
from django.db import models
STATUS = (
(0, "zero"),
(1, "one"),
)

class Flavor(models.Model):
title = models.CharField(max_length=255)
slug = models.SlugField(unique=True)
scoops_remaining = models.IntegerField(default=0, choices=STATUS)
def get_absolute_url(self):
return reverse("flavors:detail", kwargs={"slug": self.slug})

8.5.1 Views + ModelForm Example

Here we have the following views:

  1. FlavorCreateView corresponds to a form for adding new flavors.
  2. FlavorUpdateView corresponds to a form for editing existing flavors.
  3. FlavorDetailView corresponds to the con rmation page for both avor creation and flavor updates.

Views:

# flavors/views.py
from django.contrib import messages
from django.views.generic import CreateView, UpdateView, DetailView
from braces.views import LoginRequiredMixin
from .models import Flavor

class FlavorActionMixin(object):
fields = ('title', 'slug', 'scoops_remaining')
@property
def success_msg(self):
return NotImplemented

def form_valid(self, form):
messages.info(self.request, self.success_msg)
return super(FlavorActionMixin, self).form_valid(form)

class FlavorCreateView(LoginRequiredMixin, FlavorActionMixin, CreateView):
model = Flavor
success_msg = "Flavor created!"
class FlavorUpdateView(LoginRequiredMixin, FlavorActionMixin, UpdateView):
model = Flavor
success_msg = "Flavor updated!"
class FlavorDetailView(DetailView):
model = Flavor

Template:

{# templates/flavors/flavor_detail.html #}
{% if messages %}
&lt;ul class="messages"&gt;
{% for message in messages %}
&lt;li id="message_{{ forloop.counter }}"
{% if message.tags %} class="{{ message.tags }}" .
{% endif %}&gt;
{{ message }}
&lt;/li&gt;
{% endfor %}
&lt;/ul&gt;
{% endif %}


8.5.2 Views + Form Example

Implemente flavor search page
We add the following code to flavors/views.py:

from django.views.generic import ListView
from .models import Flavor

class FlavorListView(ListView):
model = Flavor
def get_queryset(self):
# Fetch the queryset from the parent get_queryset
queryset = super(FlavorListView, self).get_queryset()

# Get the q GET parameter
q = self.request.GET.get("q")
if q:
# Return a filtered queryset
return queryset.filter(title__icontains=q)
# Return the base queryset
return queryset

Template:

{# templates/flavors/_flavor_search.html #}
{% comment %}
Usage: {% include "flavors/_flavor_search.html" %}
{% endcomment %}
&lt;form action="{% url "flavor_list" %}" .method="GET"&gt;
&lt;input type="text" name="q" /&gt;
&lt;button type="submit"&gt;search&lt;/button&gt;
&lt;/form&gt;


8.6 Using Just django.views.generic.View

What we find really useful, even on projects which use a lot of generic class-based views, is using the django.views.generic.View class with a GET method for displaying JSON, PDF or other non-HTML content. All the tricks that we’ve used for rendering CSV, Excel, and PDF les in function-based views apply when using the GET method. For example:

from django.http import HttpResponse .
from django.shortcuts import get_object_or_404
from django.views.generic import View
from braces.views import LoginRequiredMixin
from .models import Flavor
from .reports import make_flavor_pdf

class PDFFlavorView(LoginRequiredMixin, View):
# Get the flavor
def get(self, request, *args, **kwargs):
flavor = get_object_or_404(Flavor, slug=kwargs['slug'])
# create the response
response = HttpResponse(content_type='application/pdf')
# generate the PDF stream and attach to the response
response = make_flavor_pdf(response, flavor)
return response

9 Form Fundamentals

9.1 Validate All Incoming Data With Django Forms

舉個input data為csv file的例子
Bad Example:

import csv import StringIO
from .models import Purchase

def add_csv_purchases(rows):
rows = StringIO.StringIO(rows)
records_added = 0
# Generate a dict per row, with the first CSV row being the keys
for row in csv.DictReader(rows, delimiter=","):
# DON'T DO THIS: Tossing unvalidated data into your model.
Purchase.objects.create(**row)
records_added += 1
return records_added

以上Bad example在Purchase create前需要自己寫input data驗證code

Good Example:

import csv import StringIO
from django import forms
from .models import Purchase, Seller

class PurchaseForm(forms.ModelForm):
class Meta:
model = Purchase
def clean_seller(self):
seller = self.cleaned_data["seller"]
try:
Seller.objects.get(name=seller)
except Seller.DoesNotExist:
msg = "{0} does not exist in purchase #{1}.".format(
seller,
self.cleaned_data["purchase_number"]
)
raise forms.ValidationError(msg) return seller

def add_csv_purchases(rows):
rows = StringIO.StringIO(rows)
records_added = 0
errors = []
# Generate a dict per row, with the first CSV row being the k
for row in csv.DictReader(rows, delimiter=","):
# Bind the row data to the PurchaseForm.
form = PurchaseForm(row)
# Check to see if the row data is valid.
if form.is_valid():
# Row data is valid so save the record.
form.save()
records_added += 1
else:
errors.append(form.errors)
return recordded, errors

利用django ModelForm的is_valid來做input驗證

9.2 Always Use CSRF Protection and POST With HTTP Forms That Modify Data

You should use Django’s CsrfViewMiddleware as blanket protection across your site rather than manually decorating views with csrf protect.
You should use Django’s CSRF protection even when posting data via AJAX.

9.3 Understand How to Add Django Form Instance Attributes

Inserting the request.user object into forms
form:

from django import forms
from .models import Taster
class TasterForm(forms.ModelForm):
class Meta:
model = Taster

def __init__(self, *args, **kwargs):
# set the user as an attribute of the form
self.user = kwargs.pop('user')
super(TasterForm, self).__init__(*args, **kwargs)

view:

from django.views.generic import UpdateView 
from braces.views import LoginRequiredMixin
from .forms import TasterForm
from .models import Taster

class TasterUpdateView(LoginRequiredMixin, UpdateView):
model = Taster
form_class = TasterForm
success_url = "/someplace/"
def get_form_kwargs(self):
"""This method is what injects forms with their keyword arguments."""
# grab the current set of form #kwargs
kwargs = super(TasterUpdateView, self).get_form_kwargs()
# Update the kwargs with the user_id
kwargs['user'] = self.request.user
return kwargs

9.4 Know How Form Validation Works

Form validation workflow:

  • If the form has bound data, form.is valid() calls the form.full clean() method.
  • form.fullclean() iterates through the form fields and each field validates itself:
    • Data coming into the eld is coerced into Python via the to python() method or raises a ValidationError.
    • Data is validated against eld-speci c rules, including custom validators. Failure raises a ValidationError.
    • If there are any custom clean <field>() methods in the form, they are called at this time.
  • form.fullclean() executes the form.clean() method.
  • If it’s a ModelForm instance, form. post clean() does the following:
    • Sets ModelForm data to the Model instance, regardless of whether form.is valid() is True or False.
    • Calls the model’s clean() method. For reference, saving a model instance through the ORM does not call the model’s clean() method.

9.4.1 ModelForm Data Is Saved to the Form, Then the Model In- stance

In a ModelForm, form data is saved in two distinct steps:

  1. First, form data is saved to the form instance.
  2. Later, form data is saved to the model instance.

For example, perhaps you need to catch the details of failed submission attempts for a form, saving both the user-supplied form data as well as the intended model instance changes.

# core/models.py
from django.db import models

class ModelFormFailureHistory(models.Model):
form_data = models.TextField()
model_data = models.TextField()
# flavors/views.py import json
from django.contrib import messages
from django.core import serializers
from core.models import ModelFormFailureHistory

class FlavorActionMixin(object):
@property
def success_msg(self):
return NotImplemented
def form_valid(self, form):
messages.info(self.request, self.success_msg)
return super(FlavorActionMixin, self).form_valid(form)
def form_invalid(self, form):
"""Save invalid form and model data for later reference."""
form_data = json.dumps(form.cleaned_data)
model_data = serializers.serialize("json",
[form.instance])[1:-1]
ModelFormFailureHistory.objects.create(
form_data=form_data,
model_data=model_data
)
return super(FlavorActionMixin, self).form_invalid(form)

9.5 Add Errors to Forms with Form.add error()

We can streamline Form.clean() with the Form.add error() method.

from django import forms
class IceCreamReviewForm(forms.Form):
# Rest of tester form goes here ...
def clean(self):
cleaned_data = super(TasterForm, self).clean()
flavor = cleaned_data.get("flavor")
age = cleaned_data.get("age")
if flavor == 'coffee' and age &lt; 3:
# Record errors that will be displayed later.
msg = u"Coffee Ice Cream is not for Babies."
self.add_error('flavor', msg)
self.add_error('age', msg)
# Always return the full collection of cleaned data.
return cleaned_data

10 Common Patterns for Forms

10.1 Pattern 1: Simple ModelForm With Default Validators

# flavors/views.py
from django.views.generic import CreateView, UpdateView
from braces.views import LoginRequiredMixin
from .models import Flavor
class FlavorCreateView(LoginRequiredMixin, CreateView):
model = Flavor
fields = ('title', 'slug', 'scoops_remaining')
class FlavorUpdateView(LoginRequiredMixin, UpdateView):
model = Flavor
fields = ('title', 'slug', 'scoops_remaining')
  • FlavorCreateView and FlavorUpdateView are assigned Flavor as their model.
  • Both views auto-generate a ModelForm based on the Flavor model.
  • Those ModelForms rely on the default field validation rules of the Flavor model.

10.2 Pattern 2: Custom Form Field Validators in ModelForms

write validator:

# core/validators.py
from django.core.exceptions import ValidationError
def validate_tasty(value):
"""Raise a ValidationError if the value doesn't start with the word 'Tasty'."""
if not value.startswith(u"Tasty"):
msg = u"Must start with Tasty"
raise ValidationError(msg)

In Django, a custom field validator is simply a function that raises an error if the submitted argument doesn’t pass its test.

validator可以加在兩個地方

10.2.1 put validator in Model

# core/models.py
from django.db import models
from .validators import validate_tasty

class TastyTitleAbstractModel(models.Model):
title = models.CharField(max_length=255, validators=[validate_tasty])
class Meta:
abstract = True
# flavors/models.py
from django.core.urlresolvers import reverse
from django.db import models
from core.models import TastyTitleAbstractModel .

class Flavor(TastyTitleAbstractModel):
slug = models.SlugField()
scoops_remaining = models.IntegerField(default=0)
def get_absolute_url(self):
return reverse("flavors:detail", kwargs={"slug": self.slug})

10.2.2 put validator in Form

# flavors/forms.py
from django import forms
from core.validators import validate_tasty from .models import Flavor

class FlavorForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(FlavorForm, self).__init__(*args, **kwargs)
self.fields["title"].validators.append(validate_tasty)
self.fields["slug"].validators.append(validate_tasty)
class Meta:
model = Flavor
# flavors/views.py
from django.contrib import messages
from django.views.generic import CreateView, UpdateView, DetailView
from braces.views import LoginRequiredMixin
from .models import Flavor
from .forms import FlavorForm

class FlavorActionMixin(object):
model = Flavor
fields = ('title', 'slug', 'scoops_remaining')
@property
def success_msg(self):
return NotImplemented
def form_valid(self, form):
messages.info(self.request, self.success_msg)
return super(FlavorActionMixin, self).form_valid(form)
class FlavorCreateView(LoginRequiredMixin, FlavorActionMixin, CreateView):
success_msg = "created"
# Explicitly attach the FlavorForm class
form_class = FlavorForm
class FlavorUpdateView(LoginRequiredMixin, FlavorActionMixin, UpdateView):
success_msg = "updated"
# Explicitly attach the FlavorForm class
form_class = FlavorForm
class FlavorDetailView(DetailView):
model = Flavor

10.3 Pattern 3: Overriding the Clean Stage of Validation

use cases:

  • Multi-field validation
  • Validation involving existing data from the database that has already been validate

Django provides a second stage and process for validating incoming data, this time via the clean() method and clean <field name>() methods.

  • clean() validate two or more fields against each other.
  • clean <field name>() validate against persistent data.

Example:
clean slug(): prevent users from ordering flavors that are out of stock
clean(): validate the flavor and toppings fields against each other

# flavors/forms.py
from django import forms
from flavors.models import Flavor

class IceCreamOrderForm(forms.Form):
"""Normally done with forms.ModelForm. But we use forms.Form here
to demonstrate that these sorts of techniques work on every
type of form.
"""

slug = forms.ChoiceField("Flavor")
toppings = forms.CharField()

def __init__(self, *args, **kwargs):
super(IceCreamOrderForm, self).__init__(*args,
**kwargs)
# We dynamically set the choices here rather than
# in the flavor field definition. Setting them in
# the field definition means status updates won't
# be reflected in the form without server restarts.
self.fields["slug"].choices = [
(x.slug, x.title) for x in Flavor.objects.all()
]
# NOTE: We could filter by whether or not a flavor
# has any scoops, but this is an example of
# how to use clean_slug, not filter().

def clean_slug(self):
slug = self.cleaned_data["slug"]
if Flavor.objects.get(slug=slug).scoops_remaining &lt;= 0:
msg = u"Sorry, we are out of that flavor."
raise forms.ValidationError(msg)
return slug

def clean(self):
cleaned_data = super(IceCreamOrderForm, self).clean()
slug = cleaned_data.get("slug", "")
toppings = cleaned_data.get("toppings", "")
# Silly "too much chocolate" validation example
if u"chocolate" in slug.lower() and \ u"chocolate" in toppings.lower():
msg = u"Your order has too much chocolate."
raise forms.ValidationError(msg) return cleaned_data

10.4 Pattern 4: Hacking Form Fields (2 CBVs, 2 Forms, 1 Model)

example:
IceCreamStore在create時只需要填入title, address,update時再強制其補上phone, description

IceCreamStore Model:

from django.core.urlresolvers import reverse 
from django.db import models

class IceCreamStore(models.Model):
title = models.CharField(max_length=100)
block_address = models.TextField() .
phone = models.CharField(max_length=20, blank=True)
description = models.TextField(blank=True)

def get_absolute_url(self):
return reverse("store_detail", kwargs={"pk": self.pk})

First we see the bad approach:

# stores/forms.py
from django import forms
from .models import IceCreamStore

class IceCreamStoreUpdateForm(forms.ModelForm):
# Don't do this! Duplication of the model field!
phone = forms.CharField(required=True)
# Don't do this! Duplication of the model field!
description = forms.TextField(required=True)

class Meta:
model = IceCreamStore

上面的方法幾乎是copy了model中的field,想像一下若我們需要在description中加入help text,就必須同時在model與form中同時加上不然沒有作用,這不是一個好的設計

Now we use form.fields[].required to do this.
Form:

# stores/forms.py
from django import forms
from .models import IceCreamStore

class IceCreamStoreCreateForm(forms.ModelForm):
class Meta:
model = IceCreamStore
fields = ("title", "block_address", )

class IceCreamStoreUpdateForm(IceCreamStoreCreateForm):
def __init__(self, *args, **kwargs):
super(IceCreamStoreUpdateForm,
self).__init__(*args, **kwargs)
self.fields["phone"].required = True
self.fields["description"].required = True
class Meta(IceCreamStoreCreateForm.Meta):
# show all the fields!
fields = ("title", "block_address", "phone", "description", )

Views:

# stores/views
from django.views.generic import CreateView, UpdateView
from .forms import IceCreamStoreCreateForm
from .forms import IceCreamStoreUpdateForm
from .models import IceCreamStore

class IceCreamCreateView(CreateView):
model = IceCreamStore
form_class = IceCreamStoreCreateForm

class IceCreamUpdateView(UpdateView):
model = IceCreamStore
form_class = IceCreamStoreUpdateForm

10.5 Pattern 5: Reusable Search Mixin View

In this example, we’re going to cover how to reuse a search form in two views that correspond to two different models.
simple search mixin for our view:

# core/views.py
class TitleSearchMixin(object):

def get_queryset(self):
# Fetch the queryset from the parent's get_queryset
queryset = super(TitleSearchMixin, self).get_queryset()
# Get the q GET parameter
q = self.request.GET.get("q")
if q:
# return a filtered queryset
return queryset.filter(title__icontains=q)
# No q is specified so we return queryset
return queryset

views:

# add to flavors/views.py
from django.views.generic import ListView
from core.views import TitleSearchMixin
from .models import Flavor

class FlavorListView(TitleSearchMixin, ListView):
model = Flavor
# add to store/views.py
from django.views.generic import ListView
from core.views import TitleSearchMixin
from .models import Store

class IceCreamStoreListView(TitleSearchMixin, ListView):
model = Store

template:

{# form to go into stores/store_list.html template #} 
&lt;form action="" method="GET"&gt;
&lt;input type="text" name="q" /&gt;
&lt;button type="submit"&gt;search&lt;/button&gt;
&lt;/form&gt;
{# form to go into flavors/flavor_list.html template #} 
&lt;form action="" method="GET"&gt;
&lt;button type="submit"&gt;search&lt;/button&gt;
&lt;input type="text" name="q" /&gt;
&lt;/form&gt;


11 Templates

11.1 Keep Templates Mostly in templates/

Template layout:

templates/
base.html
... (other sitewide templates in here)
freezers/
("freezers" app templates in here)

However, some tutorials advocate putting templates within a subdirectory of each app. We find that the extra nesting is a pain to deal with

freezers/
templates/
freezers/
... ("freezers" app templates in here)
templates/
base.html
... (other sitewide templates in here)

11.2 Template Architecture Patterns

11.2.1 2-Tier Template Architecture Example

all templates inherit from a single root base.html

templates/
base.html
dashboard.html # extends base.html
profiles/
profile_detail.html # extends base.html
profile_form.html # extends base.html

This is best for sites with a consistent overall layout from app to app.

11.2.2 3-Tier Template Architecture Example

  • Each app has a base_<app name>.html template. App-level base templates share a common parent base.html template.
  • Templates within apps share a common parent base_<app name>.html template.
  • Any template at the same level as base.html inherits base.html.
templates/
base.html
dashboard.html # extends base.html
profiles/
base_profiles.html # extends base.html
profile_detail.html # extends base_profiles.html
profile_form.html # extends base_profiles.html

The 3-tier architecture is best for websites where each section requires a distinctive layout. For example, a news site might have a local news section, a classified ads section, and an events section. Each of these sections requires its own custom layout.

11.2.3 Flat Is Better Than Nested

template層數越少越好維護

11.3 Limit Processing in Templates

Whenever you iterate over a queryset in a template, ask yourself the following questions:

  • How large is the queryset? Looping over gigantic querysets in your templates is almost always a bad idea.
  • How large are the objects being retrieved? Are all the fields needed in this template?
  • During each iteration of the loop, how much processing occurs?

Let’s now explore some examples of template code that can be rewritten more efficiently.
Model:

# vouchers/models.py
from django.core.urlresolvers import reverse
from django.db import models
from .managers import VoucherManager

class Voucher(models.Model):
"""Vouchers for free pints of ice c.ream."""
name = models.CharField(max_length=100)
email = models.EmailField()
address = models.TextField()
birth_date = models.DateField(blank=True)
sent = models.BooleanField(default=False)
redeemed = models.BooleanField(default=False)
objects = VoucherManager()

11.3.1 Gotcha 1: Aggregation in Templates

  • Don’t iterate over the entire voucher list in your template’s JavaScript section, using JavaScript variables to hold age range counts.
  • Don’t use the add template lter to sum up the voucher counts.

11.3.2 Gotcha 2: Filtering With Conditionals in Templates

A very bad way to implement this would be with giant loops and if statements at the template level.
Bad Example:

&lt;h2&gt;Greenfelds Who Want Ice Cream&lt;/h2&gt; 
&lt;ul&gt;
{% for voucher in voucher_list %}
{# Don't do this: conditional filtering in templates #}
{% if "greenfeld" in voucher.name.lower %}
&lt;li&gt;{{ voucher.name }}&lt;/li&gt;
{% endif %}
{% endfor %}
&lt;/ul&gt;

&lt;h2&gt;Roys Who Want Ice Cream&lt;/h2&gt;
&lt;ul&gt;
{% for voucher in voucher_list %}
{# Don't do this: conditional filtering in templates #}
{% if "roy" in voucher.name.lower %}
&lt;li&gt;{{ voucher.name }}&lt;/li&gt;
{% endif %}
{% endfor %}
&lt;/ul&gt;


Good Example
views:

# vouchers/views.py
from django.views.generic import TemplateView
from .models import Voucher

class GreenfeldRoyView(TemplateView):
template_name = "vouchers/views_conditional.html"
def get_context_data(self, **kwargs):
context = super(GreenfeldRoyView, self).get_context_data(**kwargs)
context["greenfelds"] = Voucher.objects.filter(name__icontains="greenfeld")
context["roys"] = Voucher.objects.filter(name__icontains="roy")
return context

template:

&lt;h2&gt;Greenfelds Who Want Ice Cream&lt;/h2&gt; 
&lt;ul&gt;
{% for voucher in greenfelds %}
&lt;li&gt;{{ voucher.name }}&lt;/li&gt;
{% endfor %}
&lt;/ul&gt;

&lt;h2&gt;Roys Who Want Ice Cream&lt;/h2&gt;
&lt;ul&gt;
{% for voucher in roys %}
&lt;li&gt;{{ voucher.name }}&lt;/li&gt;
{% endfor %}
&lt;/ul&gt;


11.3.3 Gotcha 3: Complex Implied Queries in Templates

Bad Example:

{# list generated via User.object.all() #}
&lt;h1&gt;Ice Cream Fans and their favorite flavors.&lt;/h1&gt;
&lt;ul&gt;
{% for user in user_list %}
&lt;li&gt;
{{ user.name }}:
{# DON'T DO THIS: Generated implicit query per user #}
{{ user.flavor.title }}
{# DON'T DO THIS: Second implicit query per user!!! #}
{{ user.flavor.scoops_remaining }}
&lt;/li&gt;
{% endfor %}
&lt;/ul&gt;


One quick correction is to use the Django ORM’s select related() method:

{% comment %}
List generated via User.object.all().select_related("flavors")
{% endcomment %}
&lt;h1&gt;Ice Cream Fans and their favorite flavors.&lt;/h1&gt;
&lt;ul&gt;
{% for user in user_list %}
&lt;li&gt;
{{ user.name }}:
{{ user.flavor.title }}
{{ user.flavor.scoops_remaining }}
&lt;/li&gt;
{% endfor %}
&lt;/ul&gt;


11.3.4 Gotcha 4: Hidden CPU Load in Templates

需要大量CPU LOAD的code不該在template中,例如save image to file system

11.3.5 Gotcha 5: Hidden REST API Calls in Templates

REST API call不該放在template中,由於REST API可能會跑很久
建議在javascript code or view 中處理REST API call

  • JavaScript code so after your project serves out its content, the client’s browser handles the work. is way you can entertain or distract the client while they wait for data to load.
  • The view’s Python code where slow processes might be handled in a variety of ways including message queues, additional threads, multiprocesses, or more.

11.4 Exploring Template Inheritance

base.html:

{# simple base.html #} 
{% load staticfiles %}
&lt;html&gt;
&lt;head&gt;
&lt;title&gt;
{% block title %}Two Scoops of Django{% endblock title %}
&lt;/title&gt;
{% block stylesheets %}
&lt;link rel="stylesheet" type="text/css" href="{% static "css/project.css" %}"&gt;
{% endblock stylesheets %} &lt;/head&gt;
&lt;body&gt;
&lt;div class="content"&gt;
{% block content %}
&lt;h1&gt;Two Scoops&lt;/h1&gt;
{% endblock content %}
&lt;/div&gt;
&lt;/body&gt;
&lt;/html&gt;


The base.html file contains the following features:

  • A title block containing “Two Scoops of Django”.
  • A stylesheets block containing a link to a project.css file used across our site.
  • A content block containing “<h1>Two Scoops</h1>”.


Template Tag Purpose
{% load %} Loads the staticfiles built-in template tag library
{% block %} Since base.html is a parent template, these define which child blocks can be filled in by child templates. We place links and scripts inside them so we can override if necessary.
{% static %} Resolves the named static media argument to the static media server.


we’ll have a simple about.html inherit the following from it:

  • A custom title.
  • The original stylesheet and an additional stylesheet.
  • The original header, a sub header, and paragraph content.
  • The use of child blocks.
  • The use of the template variable.


{% extends "base.html" %}
{% load staticfiles %}
{% block title %}About Audrey and Daniel{% endblock title %}
{% block stylesheets %}
{{ block.super }}
&lt;link rel="stylesheet" type="text/css" href="{% static "css/about.css" %}"&gt;
{% endblock stylesheets %}
{% block content %}
{{ block.super }}
&lt;h2&gt;About Audrey and Daniel&lt;/h2&gt;
&lt;p&gt;They enjoy eating ice cream&lt;/p&gt;
{% endblock content %}

it generates the following HTML:

&lt;html&gt; 
&lt;head&gt;
&lt;title&gt;
About Audrey and Daniel
&lt;/title&gt;
&lt;link rel="stylesheet" type="text/css" href="/static/css/project.css"&gt;
&lt;link rel="stylesheet" type="text/css" href="/static/css/about.css"&gt;
&lt;/head&gt;
&lt;body&gt;
&lt;div class="content"&gt;
&lt;h1&gt;Two Scoops&lt;/h1&gt;
&lt;h2&gt;About Audrey and Daniel&lt;/h2&gt;
&lt;p&gt;They enjoy eating ice cream&lt;/p&gt;
&lt;/div&gt;
&lt;/body&gt;
&lt;/html&gt;
Template Object Purpose
{% extends %} Informs Django that about.html is inheriting or extending from base.html
{% block %} Since about.html is a child template, block overrides the content provided by base.html. This means our title will render as <title>Audrey and Daniel</title>
{{ block.super }} When placed in a child template's block, it ensures that the parent's content is also included in the block. In the content block of the about.html template, this will render <h1>Two Scoops</h1>.


11.5 block.super Gives the Power of Control

Here are three examples:

Template using both project.css and a custom link:

{% extends "base.html" %}
{% block stylesheets %}
{{ block.super }} {# this brings in project.css #}
&lt;link rel="stylesheet" type="text/css" href="{% static "css/custom.css" %}" /&gt;
{% endblock %}

Dashboard template that excludes the project.css link:

{% extends "base.html" %}
{% block stylesheets %} .
&lt;link rel="stylesheet" type="text/css" href="{% static "css/dashboard.css" %}" /&gt;
{% comment %}
By not using {{ block.super }}, this block overrides the stylesheet block of base.html .
{% endcomment %}
{% endblock %}

Template just linking the project.css file:

{% extends "base.html" %}
{% comment %}
By not using {% block stylesheets %}, this template inherits the
stylesheets block from the base.html parent, in this case the
default project.css link.
{% endcomment %}


11.6 Useful Things to Consider

11.6.1 Avoid Coupling Styles Too Tightly to Python Code

Aim to control the styling of all rendered templates entirely via CSS and JS.

  • If you have magic constants in your Python code that are entirely related to visual design layout, you should probably move them to a CSS file.
  • The same applies to JavaScript.

11.6.2 Common Conventions


  • We prefer underscores over dashes in template names, block names, and other names in tem- plates. Most Django users seem to follow this convention. Why? Well, because underscores are allowed in names of Python objects but dashes are forbidden.
  • We rely on clear, intuitive names for blocks. {% block javascript %} is good.
  • We include the name of the block tag in the endblock. Never write just {% endblock %}, include the whole {% endblock javascript %}.
  • Templates called by other templates are prefixed with ‘_’. is applies to templates called via {% include %} or custom template tags. It does not apply to templates inheritance controls such as {% extends %} or {% block %}.


11.6.3 Location

Templates should usually go into the root of the Django project, at the same level as the apps.
The only exception is when you bundle up an app into a third-party package. That packages template directory should go into app directly.

11.6.4 Use Named Context Objects


use {{ topping list }} and {{ topping }} in your templates, instead of {{ object list }} and {{ object }}

11.6.5 Use URL Names Instead of Hardcoded Paths

A common developer mistake is to hardcode URLs in templates like this:

&lt;a href="/flavors/"&gt;

Instead, we use the {% url %} tag and references the names in our URLConf les:

&lt;a href="{% url 'flavors_list' %}"&gt;


11.6.6 Debugging Complex Templates

A trick recommended by Lennart Regebro is that when templates are complex and it becomes dif- cult to determine where a variable is failing, you can force more verbose errors through the use of the string if invalid option in OPTIONS of your TEMPLATES setting:

# settings/local.py
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'APP_DIRS': True,
'OPTIONS':
'string_if_invalid': 'INVALID EXPRESSION: %s'
},
]

11.7 Error Page Templates

It’s standard practice to create at least 404.html and 500.html templates.
We suggest serving your error pages from a static file server (e.g. Nginx or Apache) as entirely self- contained static HTML files. That way, if your entire Django site goes down but your static le server is still up, then your error pages can still be served.

GitHub’s 404 and 500 error pages are great examples of fancy but entirely static, self-contained error pages:

  • https://github.com/404
  • https://github.com/500
  • All CSS styles are inline in the head of the same HTML page, eliminating the need for a separate stylesheet.
  • All images are entirely contained as data within the HTML page. ere are no <img> links to external URLs.
  • All JavaScript needed for the page is contained within the HTML page. There are no external links to JavaScript assets.

For more information, see the Github HTML Styleguide:

12 Template Tags and Filters

12.1 Filters Are Functions

Filters are functions that accept just one or two arguments.
大多數的filters都只是作很簡單的事,通常都是將實作寫在utils.py,再由filters去import utils.py
可參考django的default filters實作:https://github.com/django/django/blob/stable/1.8.x/django/template/defaultfilters.py
django.template.defaultfilters.slugify是呼叫django.utils.text.slugify

12.1.1 When to Write Filters

Filters are good for modifying the presentation of data, and they can be readily reused in REST APIs and other output formats.

12.2 Custom Template Tags

  • Template Tags Are Harder to Debug
  • Template Tags Make Code Reuse Harder
  • The Performance Cost of Template Tags

12.2.1 When to Write Template Tags

Think these before writing custom tags:

  • Anything that causes a read/write of data might be better placed in a model or object method.
  • Since we implement a consistent naming standard across our projects, we can add an abstract base class model to our core.models module. Can a method or property in our project’s abstract base class model do the same work as a custom template tag?

When should you write new template tags? We recommend writing them in situations where they are only responsible for rendering of HTML. For example, projects with very complex HTML layouts with many different models or data types might use them to create a more exible, understandable template architecture.

12.3 Naming Your Template Tag Libraries

The convention we follow is <app name>_tags.py.

  • flavors_tags.py
  • blog_tags.py

12.4 Loading Your Template Tag Modules


{% extends "base.html" %}

{% load flavors_tags %}


12.4.1 Watch Out for This Crazy Anti-Pattern

Don't Use This

# Don't use this code!
# It's an evil anti-pattern!
from django import template
template.add_to_builtins(
"flavors.templatetags.flavors_tags"
)

13 Building REST APIs

13.1 Fundamentals of Basic REST API Design

HTTP method:

Purpose of Request HTTP Method Rough SQL equivalent
Create a new resource POST INSERT
Read an existing resource GET SELECT
Request the metadata of an existing resource HEAD  
Update an existing resource PUT UPDATE
Update part of an existing resource PATCH UPDATE
Delete an existing resource DELETE DELETE
Return the supported HTTP methods for the given URL OPTIONS  
Echo back the request TRACE  
Tunneling over TCP/IP (usually not implemented) CONNECT  

some common HTTP status codes:
table16_2.png

13.2 Implementing a Simple JSON API

use django-rest-framework
model:

# flavors/models.py
from django.core.urlresolvers import reverse
from django.db import models

class Flavor(models.Model):
title = models.CharField(max_length=255)
slug = models.SlugField(unique=True)
scoops_remaining = models.IntegerField(default=0)

def get_absolute_url(self):
return reverse("flavors:detail", kwargs={"slug": self.slug})

Define the serializer class:

from rest_framework import serializers
from .models import flavor

class FlavorSerializer(serializers.ModelSerializer):
class Meta:
model = flavor
fields = ('title', 'slug', 'scoops_remaining')

Views:

# flavors/views
from rest_framework.generics import ListCreateAPIView
from rest_framework.generics import RetrieveUpdateDestroyAPIView
from .models import Flavor
from .serializers import FlavorSerializer

class FlavorCreateReadView(ListCreateAPIView):
queryset = Flavor.objects.all()
serializer_class = FlavorSerializer
lookup_field = 'slug'

class FlavorReadUpdateDeleteView(RetrieveUpdateDestroyAPIView):
queryset = Flavor.objects.all()
serializer_class = FlavorSerializer
lookup_field = 'slug'

flavors/urls.py:

# flavors/urls.py
from django.conf.urls import url
from flavors import views

urlpatterns = [
url(
regex=r"ˆapi/$",
view=views.FlavorCreateReadView.as_view(),
name="flavor_rest_api"
),
url(
regex=r"ˆapi/(?P&lt;slug&gt;[-\w]+)/$",
view=views.FlavorReadUpdateDeleteView.as_view(),
name="flavor_rest_api"
)
]
Url View Url Name (same)
flavors/api FlavorCreateReadView flavor_rest api
flavors/api:slug/ FlavorReadUpdateDeleteView flavor_rest api

Don't forget to add authenticate and permissions:

13.3 REST API Architecture

13.3.1 Code for a Project Should Be Neatly Organized

For example, we might place all our views, serializers, and other API components in an app titled apiv4.
The downside is the possibility for the API app to become too large and disconnected from the apps that power it. Hence why we consider an alternative in the next subsection.

13.3.2 Code for an App Should Remain in the App

REST APIs are just views. For simpler, smaller projects, REST API views should go into views.py or viewsets.py modules
The downside is that if there are too many small, interconnecting apps, it can be hard to keep track of the myriad of places API components are placed. Hence why we considered another approach in the previous subsection.

13.3.3 Grouping API URLs

If you have REST API views in multiple Django apps, how do you build a project-wide API that looks like this?

api/flavors/ # GET, POST 
api/flavors/:slug/ # GET, PUT, DELETE
api/users/ # GET, POST
api/users/:slug/ # GET, PUT, DELETE


 # core/api.py
"""Called from the project root's urls.py URLConf thus:
url(r"ˆapi/", include("core.api", namespace="api")),
"""

from django.conf.urls import url
from flavors import views as flavor_views
from users import views as user_views

urlpatterns = [
# {% url "api:flavors" %}
url(
regex=r"ˆflavors/$",
view=flavor_views.FlavorCreateReadView.as_view(),
name="flavors" .
),

# {% url "api:flavors" flavor.slug %}
url(
regex=r"ˆflavors/(?P&lt;slug&gt;[-\w]+)/$",
view=flavor_views.FlavorReadUpdateDeleteView.as_view(),
name="flavors"
),

# {% url "api:users" %}
url(
regex=r"ˆusers/$",
view=user_views.UserCreateReadView.as_view(),
name="users"
),

# {% url "api:users" user.slug %}
url(
regex=r"ˆusers/(?P&lt;slug&gt;[-\w]+)/$",
view=user_views.UserReadUpdateDeleteView.as_view(),
name="users" .
),
]


13.3.4 Version Your API

It’s a good practice to abbreviate the urls of your API with the version number e.g. /api/v1/flavors or /api/v1/users and then as the API changes, /api/v2/flavors or /api/v2/users.

13.4 Shutting Down an External API

  1. Notify User
  2. Replace API With 410 Error View
# core/apiv1_shutdown.py
from django.http import HttpResponseGone

apiv1_gone_msg = """APIv1 was removed on April 2, 2015. Please switch to APIv3:
&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.example.com/api/v3/"&gt;APIv3 Endpoint&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://example.com/apiv3_docs/"&gt;APIv3 Documentation&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="http://example.com/apiv1_shutdown/"&gt;APIv1 shut down notice&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;
"""

def apiv1_gone(request):
return HttpResponseGone(apiv1_gone_msg)

13.5 Rate Limiting Your API

Rate limiting is when an API restricts how many requests can be made by a user of the API within a period of time.

14 Working With the Django Admin

14.1 It's Not for End Users

The Django admin interface is designed for site administrators, not end users.

14.2 Add str to Model

The default admin page for a Django app looks something like this:
figure19_2.png

Implementing __str__():

from django.db import models
from django.utils.encoding import python_2_unicode_compatible

@python_2_unicode_compatible # For Python 3.4 and 2.7
class IceCreamBar(models.Model):
name = models.CharField(max_length=100)
shell = models.CharField(max_length=100)
filling = models.CharField(max_length=100)
has_stick = models.BooleanField(default=True)

def __str__(self):
return self.name

The result:
figure19_3.png

If you still want to show data for additional elds on the app’s admin list page, you can then use list display:

from django.contrib import admin
from .models import IceCreamBar

class IceCreamBarAdmin(admin.ModelAdmin):
list_display = ("name", "shell", "filling",)

admin.site.register(IceCreamBar, IceCreamBarAdmin)

The result with the specified fields:
figure19_5.png

14.3 Adding Callables to ModelAdmin Classes

For example, it’s not uncommon to want to see the exact URL of a model instance in the Django admin.

from django.contrib import admin
from django.core.urlresolvers import reverse
from django.utils.html import format_html
from icecreambars.models import IceCreamBar

class IceCreamBarAdmin(admin.ModelAdmin):
list_display = ("name", "shell", "filling",)
readonly_fields = ("show_url",)

def show_url(self, instance):
url = reverse("ice_cream_bar_detail",
kwargs={"pk": instance.pk})
response = format_html("""&lt;a href="{0}"&gt;{1}&lt;/a&gt;""", url, url)
return response

show_url.short_description = "Ice Cream Bar URL"
# Displays HTML tags
# Never set allow_tags to True against user submitted data!!!
show_url.allow_tags = True

admin.site.register(IceCreamBar, IceCreamBarAdmin)

figure19_6.png

14.4 Django's Admin Documentation Generator

  1. pip install docutils into your project’s virtualenv.
  2. Add django.contrib.admindocs to your INSTALLED APPS.
  3. Add (r'ˆadmin/doc/', include('django.contrib.admindocs.urls')) to your root URLConf. Make sure it’s included before the r'ˆadmin/' entry, so that requests to admin/doc don’t get handled by the latter entry.
  4. Optional : Linking to templates requires the ADMIN FOR setting to be con gured.
  5. Optional : Using the admindocs bookmarklets requires the XViewMiddleware to be installed.

14.5 Securing the Django Admin and Django Admin Docs

15 Dealing With the User Model

15.1 Use Django's Tools for Finding the User Model

The advised way to get to the user class is as follows:

# Stock user model definition
&gt;&gt;&gt; from django.contrib.auth import get_user_model
&gt;&gt;&gt; get_user_model()
&lt;class 'django.contrib.auth.models.User'&gt; .

# When the project has a custom user model definition
&gt;&gt;&gt; from django.contrib.auth import get_user_model
&gt;&gt;&gt; get_user_model()
&lt;class 'profiles.models.UserProfile'&gt;

15.1.1 Use settings.AUTH_USER_MODEL for Foreign Keys to User

from django.conf import settings 
from django.db import models

class IceCreamStore(models.Model):
owner = models.OneToOneField(settings.AUTH_USER_MODEL)
title = models.CharField(max_length=255)

15.1.2 Don't Use get user model() for Foreign Keys to User

This is bad, as it tends to create import loops.

# DON'T DO THIS!
from django.db import models
from django.contrib.auth import get_user_model

class IceCreamStore(models.Model):
# This following line tends to create import loops.
owner = models.OneToOneField(get_user_model())
title = models.CharField(max_length=255)

15.2 Custom User Fields for Django 1.8 Projects

15.2.1 Option 1: Subclass AbstractUser

Choose this option if you like Django’s User model elds the way they are, but need extra fields.

# profiles/models.py
from django.contrib.auth.models import AbstractUser
from django.db import models
from django.utils.translation import ugettext_lazy as _

class KarmaUser(AbstractUser):
karma = models.PositiveIntegerField(verbose_name=_("karma"), default=0, blank=True)

The other thing you have to do is set this in your settings:

AUTH_USER_MODEL = "profiles.KarmaUser" .

15.2.2 Option 2: Subclass AbstractBaseUser

AbstractBaseUser is the bare-bones option with only 3 fields: password, last login, and is active.
Choose this option if:

  • You’re unhappy with the elds that the User model provides by default, such as first name and last name.
  • You prefer to subclass from an extremely bare-bones clean slate but want to take advantage of the AbstractBaseUser sane default approach to storing passwords.

If you want to go down this path, we recommend the following reading:

15.2.3 Option 3: Linking Back From a Related Model

Use Case: Creating a Third Party Package

  • We are creating a third-party package for publication on PyPI.
  • The package needs to store additional information per user, perhaps a Stripe ID or another payment gateway identifier.
  • We want to be as unobtrusive to the existing project code as possible. Loose coupling!

Use Case: Internal Project Needs

  • We are working on our own Django project.
  • We want different types of users to have different fields.
  • We might have some users with a combination of different user types.
  • We want to handle this at the model level, instead of at other levels.
  • We want this to be used in conjunction with a custom user model from options #1 or #2.
# profiles/models.py
from django.conf import settings
from django.db import models
from flavors.models import Flavor

class EaterProfile(models.Model):
# Default user profile
# If you do this you need to either have a post_save signal or
# redirect to a profile_edit view on initial login.
user = models.OneToOneField(settings.AUTH_USER_MODEL)
favorite_ice_cream = models.ForeignKey(Flavor, null=True, blank=True)

class ScooperProfile(models.Model):
user = models.OneToOneField(settings.AUTH_USER_MODEL)
scoops_scooped = models.IntegerField(default=0)

class InventorProfile(models.Model):
user = models.OneToOneField(setting.s.AUTH_USER_MODEL)
flavors_invented = models.ManyToManyField(Flavor, null=True, blank=True)

16 Third-Party Packages

16.1 Wiring Up Django Packages: The Basics

  • Step 1: Read the Documentation for the Package
  • Step 2: Add Package and Version Number to Your Requirements
  • Step 3: Install the Requirements Into Your Virtualenv
  • Step 4: Follow the Package's Installation Instructions Exactl

17 Testing

17.1 Useful Library for Testing Django Projects

17.2 How to Structure Tests

“Flat is better than nested,”

popsicles/
__init__.py
admin.py
forms.py
models.py
test_forms.py
test_models.py
test_views.py
views.py

17.3 How to Write Unit Tests

17.3.1 Each Test Method Tests One Thing

example:

# flavors/test_api.py 
import json
from django.core.urlresolvers import reverse
from django.test import TestCase
from flavors.models import Flavor

class DjangoRestFrameworkTests(TestCase):
def setUp(self):
Flavor.objects.get_or_create(title="title1", slug="slug1")
Flavor.objects.get_or_create(title="title2", slug="slug2")
self.create_read_url = reverse("flavor_rest_api")
self.read_update_delete_url = reverse("flavor_rest_api", kwargs={"slug": "slug1"})

def test_list(self):
response = self.client.get(self.create_read_url)
# Are both titles in the conten.t?
self.assertContains(response, "title1")
self.assertContains(response, "title2")

def test_detail(self):
response = self.client.get(self.read_update_delete_url)
data = json.loads(response.content)
content = {"id": 1, "title": "title1", "slug": "slug1", "scoops_remaining": 0}
self.assertEquals(data, content)

def test_create(self):
post = {"title": "title3", "slug": "slug3"}
response = self.client.post(self.create_read_url, post)
data = json.loads(response.content)
self.assertEquals(response.status_code, 201)
content = {"id": 3, "title": "title3", "slug": "slug3", "scoops_remaining": 0}
self.assertEquals(data, content)
self.assertEquals(Flavor.objects.count(), 3)

def test_delete(self):
response = self.client.delete(self.read_update_delete_url)
self.assertEquals(response.status_code, 204)
self.assertEquals(Flavor.objects.count(), 1)

17.3.2 For Views, When Possible Use the Request Factory

The django.test.client.RequestFactory provides a way to generate a request instance that can be used as the first argument to any view.
But the request factory doesn’t support middleware, including session and authentication.

from django.contrib.auth.models import AnonymousUser
from django.contrib.sessions.middleware import SessionMiddleware
from django.test import TestCase, RequestFactory
from .views import cheese_flavors

def add_middleware_to_request(request, .middleware_class):
middleware = middleware_class()
middleware.process_request(request)
return request

def add_middleware_to_response(request, middleware_class):
middleware = middleware_class()
middleware.process_request(request)
return request

class SavoryIceCreamTest(TestCase):
def setUp(self):
# Every test needs access to the request factory.
self.factory = RequestFactory()

def test_cheese_flavors(self):
request = self.factory.get('/cheesy/broccoli/')
request.user = AnonymousUser()

# Annotate the request object with a session
request = add_middleware_to_request(request, SessionMiddleware)
request.session.save()

# process and test the request
response = cheese_flavors(request)
self.assertContains(response, "bleah!")

17.3.3 Don't Repeat Yourself Doesn't Apply to Writing Tests

17.3.4 Don't Rely on Fixtures

Rather than wrestle with xtures, we’ve found it’s easier to write code that relies on the ORM. Other people like to use third-party packages.(factory boy, model mommy, mock)

17.3.5 Things That Should Be Tested

Everything! Seriously, you should test whatever you can, including:

Views: Viewing of data, changing of data, and custom class-based view methods.
Models: Creating/updating/deleting of models, model methods, model manager methods.
Forms: Form methods, clean() methods, and custom elds.
Validators: Really dig in and write multiple test methods against each custom validator you write. Pretend you are a malignant intruder attempting to damage the data in the site.
Signals: Since they act at a distance, signals can cause grief especially if you lack tests on them.
Filters: Since lters are essentially just functions accepting one or two arguments, writing tests for them should be easy.
Template Tags: Since template tags can do anything and can even accept template context, writing tests often becomes much more challenging. This means you really need to test them, since otherwise you may run into edge cases.
Miscellany: Context processors, middleware, email, and anything else not covered in this list.
Failure What happens when any of the above fail?

17.3.6 Test for Failure

17.3.7 Use Mock to Keep Unit Tests From Touching the World

during tests we should not access external APIs, receive emails or webhooks, or anything that is not part of of the tested action.
when you are trying to write a unit test for a function that interacts with an external API, you have two choices:

  • Choice #1: Change the unit test to be an Integration Test.
  • Choice #2: Use the Mock library to fake the response from the external API.

Test list_flavors_sorted():

import mock import unittest
import icecreamapi
from flavors.exceptions import CantListFlavors
from flavors.utils import list_flavors_sorted

class TestIceCreamSorting(unittest.TestCase):
# Set up monkeypatch of icecreamapi.get_flavors()
@mock.patch.object(icecreamapi, "get_flavors")
def test_flavor_sort(self, get_flavors):
# Instructs icecreamapi.get_flavors() to return an unordered list.
get_flavors.return_value = ['chocolate', 'vanilla', 'strawberry', ]

# list_flavors_sorted() calls t.he icecreamapi.get_flavors()
# function. Since we've monkeypatched the function, it will always
# return ['chocolate', 'strawberry', 'vanilla', ]. Which the.
# list_flavors_sorted() will sort alphabetically
flavors = list_flavors_sorted()
self.assertEqual(
flavors,
['chocolate', 'strawberry', 'vanilla', ]
)

Now let’s demonstrate how to test the behavior of the list flavors sorted() function when the Ice Cream API is innaccessible.

@mock.patch.object(icecreamapi, "get_flavors") 
def test_flavor_sort_failure(self, get_flavors):
# Instructs icecreamapi.get_flavors() to throw a FlavorError.
get_flavors.side_effect = icecreamapi.FlavorError()

# list_flavors_sorted() catches the icecreamapi.FlavorError()
# and passes on a CantListFlavors exception.
with self.assertRaises(CantListFlavors):
list_flavors_sorted()

As an added bonus for API authors, here’s how we test how code handles two different python- requests connection problems:

@mock.patch.object(requests, "get") 
def test_request_failure(self, get)
"""Test if the target site is innaccessible."""
get.side_effect = requests.exception.ConnectionError()

with self.assertRaises(CantListFlavors):
list_flavors_sorted()

@mock.patch.object(requests, "get")
def test_request_failure(self, get)
"""Test if we can handle SSL problems elegantly."""
get.side_effect = requests.exception.SSLError()

with self.assertRaises(CantListFlavors):
list_flavors_sorted()

17.3.8 Use Fancier Assertion Methods

We’ve found the following assert methods extremely useful:

  • assertRaises
  • Python 2.7: ListItemsEqual(), Python 3+ assertCountEqual()
  • assertDictEqual()
  • assertFormError()
  • assertContains() Check status 200, checks in response.content.
  • assertHTMLEqual() Amongst many things, ignores whitespace differences.
  • assertJSONEqual()

17.3.9 Document the Purpose of Each Test

17.4 What About Integration Tests?

  • Selenium tests to con rm that an application works in the browser.
  • Actual testing against a third-party API instead of mocking responses. For example, Django Packages conducts periodic tests against GitHub, BitBucket, and PyPI API to ensure that its interaction with those systems is valid.
  • Interacting with http://requestb.in or http://httpbin.org/ to confirm the validity of out-bound requests.
  • Using http://runscope.com to validate that our API is working as expected.

17.5 Continuous Integration

For projects of any size, we recommend setting up a continuous integration (CI) server to run the project’s test suite whenever code is committed and pushed to the project repo.

17.6 Setting Up the Test Coverage Game

17.6.1 Step 1: Start Writing Tests

17.6.2 Step 2: Run Tests and Generate Coverage Report

In the command-line, at the <project root>, type:

$ coverage run manage.py test --settings=twoscoops.settings.test

If we have nothing except for the default tests for two apps, we should get a response that looks like:

Creating test database for alias "default"...
..
-----------------------------------------------
Ran 2 tests in 0.008s
OK
Destroying test database for alias "default"...

17.6.3 Step 3: Generate the Report!

In the command-line, at the <project root>:

$ coverage html --omit="admin.py"

After this runs, in the <project root> directory there is a new directory called htmlcov/ . In the htmlcov/ directory, open the index.html file using any browser.

18 Documentation

18.1 Use reStructuredText for Python Docs

http://docutils.sourceforge.net/docs/ref/rst/restructuredtext.html
here is a quick primer of some very useful commands you should learn:

Section Header

**emphasis (bold/strong)**

*italics*

Simple link: http://django.2scoops.org
Fancier Link: `Two Scoops of Django`_

.. _Two Scoops of Django: https://django.2scoops.org

Subsection Header

#) An enumerated list item

#) Second item

* First bullet

* Second bullet

* Indented Bullet

* Note carriage return and indents

Literal code block::

def like():
print("I like Ice Cream")

for i in range(10):
like()

Python colored code block (requires pygments):

code-block:: python

# You need to "pip install pygments" to make this work.
for i in range(10):
like()

JavaScript colored code block:

code-block:: javascript

console.log("Don't use alert()");

18.2 Use Sphinx to Generate Documentation From reStructuredText

Sphinx is a tool for generating nice-looking docs from your .rst les. Output formats include HTML, LaTeX, manual pages, and plain text.
Follow the instructions to generate Sphinx docs: http://sphinx-doc.org/.

18.3 What Docs Should Django Projects Contain?

Here we provide a table that describes what we consider the absolute minimum documentation:
table23_1.png

18.4 Additional Documentation Resources

19 Finding and Reducing Bottlenecks

19.1 Should You Even Care?

Remember, premature optimization is bad. If your site is small- or medium-sized and the pages are loading ne, then it’s okay to skip this chapter.

19.2 Speed Up Query-Heavy Pages

19.2.1 Find Excessive Queries With Django Debug Toolbar

You’ll find bottlenecks such as:

  • Duplicate queries in a page.
  • ORM calls that resolve to many more queries than you expected.
  • Slow queries.

19.2.2 Reduce the Number of Queries

  • Try using select_related() in your ORM calls to combine queries. It follows ForeignKey relations and combines more data into a larger query. If using CBVs, django-braces makes doing this trivial with the SelectRelatedMixin.
  • For many-to-many and many-to-one relationships that can’t be optimized with select_related(), explore using prefetch_related() instead.
  • If the same query is being generated more than once per template, move the query into the Python view, add it to the context as a variable, and point the template ORM calls at this new context variable.
  • Implement caching using a key/value store such as Memcached. en write tests to assert the number of queries run in a view. See http://2scoops.co/1.8-test-num-queries for instructions.
  • Use the django.utils.functional.cached_property decorator to cache in memory the result of method call for the life of an object instance. This is incredibly useful.

19.2.3 Speed Up Common Queries

  • Make sure your indexes are helping speed up your most common slow queries. Look at the raw SQL generated by those queries, and index on the fields that you filter/sort on most frequently. Look at the generated WHERE and ORDER BY clauses.
  • Understand what your indexes are actually doing in production. Development machines will never perfectly replicate what happens in production, so learn how to analyze and understand what’s really happening with your database.
  • Look at the query plans generated by common queries.
  • Turn on your database’s slow query logging feature and see if any slow queries occur frequently.
  • Use django-debug-toolbar in development to identify potentially-slow queries defensively, before they hit production.

19.2.4 Switch ATOMIC REQUESTS to False

19.3 Know What Doesn't Belong in the Database

Three things that should never go into any large site’s relational database:
Logs. Don’t add logs to the database. Logs may seem OK on the surface, especially in development. Yet adding this many writes to a production database will slow their performance. When the ability to easily perform complex queries against your logs is necessary, we recommend third-party services such as Splunk or Loggly, or use of document-based NoSQL databases.

Ephemeral data. Don’t store ephemeral data in the database. What this means is data that re- quires constant rewrites is not ideal for use in relational databases. is includes examples such as django.contrib.sessions, django.contrib.messages, and metrics. Instead, move this data to things like Memcached, Redis, Riak, and other non-relational stores.

Binary Data

19.4 Cache Queries With Memcached or Redis

19.5 Identify Specific Places to Cache

Here are things to think about:

  • Which views/templates contain the most queries?
  • Which URLs are being requested the most?
  • When should a cache for a page be invalidated?

19.6 Compression and Minification of HTML, CSS, and JavaScript

  • Apache and Nginx compression modules
  • django-pipeline

19.7 Use Upstream Caching or a Content Delivery Network

Upstream caches such as Varnish are very useful. They run in front of your web server and speed up web page or content serving signi cantly. See http://varnish-cache.org/.

Content Delivery Networks (CDNs) like Fastly, Akamai, and Amazon Cloudfront serve static me- dia such as images, video, CSS, and JavaScript les. ey usually have servers all over the world, which serve out your static content from the nearest location. Using a CDN rather than serving static content from your application servers can speed up your projects.

20 Asynchronous Task Queues

An asynchronous task queue is one where tasks are executed at a different time from when they are created, and possibly not in the same order they were created.

20.1 Do We Need a Task Queue?

Here is a useful rule of thumb for determining if a task queue should be used:
Results take time to process: Task queue should probably be used.
Users can and should see results immediately: Task queue should not be used.

Use Task Queue? Issue
Yes Sending bulk email
Yes Modifying files (including images)
Yes Fetching large amounts of data from third-party Ice Cream APIs
Yes Inserting or updating a lot of records into a table
No Updating a user profile
No Adding a blog or CMS entry
Yes Performing time-intensive calculations
Yes Sending or receiving of webhooks
  • Sites with small-to-medium amounts of traffic may never need a task queue for any of these actions.
  • Sites with larger amounts of traffic may discover that nearly every user action requires use of a task queue.

20.2 Choosing Task Queue Software

Celery, Redis Queue, django-background-tasks, which to choose? Let’s go over their pros and cons:

Software Pros Cons
Celery Defacto Django standard, many different storage types, flexible, full-featured, great for high volume Challenging setup, overkill for slower traffic sites
Redis Queue Flexible, powered by Redis, lower memory footprint then Celery, great for high volume, can use django-rq or not Not as many features as Celery, medium difficulty setup, Redis only option for storage
django-background-tasks Very easy setup, easy to use, good for small volume or batch jobs, uses Django ORM for backend Uses Django ORM for backend, absolutely terrible for medium-to-high volume

Here is our general rule of thumb:

  • For most high-to-low volume projects, we recommend Redis Queue.
  • For high-volume projects with the need for complex task management, we recommend Celery.
  • For small volume projects, especially for running of periodic batch jobs, we recommend django-background-tasks

20.3 Best Practices for Task Queues

20.3.1 Treat Tasks Like Views

we recommend that views contain as little code as possible, calling methods and functions from other places in the code base. We believe the same thing applies to tasks.
you can put your task code into a function, put that function into a helper module, and then call that function from a task function.

20.3.2 Tasks Aren't Free

20.3.3 Only Pass JSON-Serializable Values to Task Functions

That limits us to integers, oats, strings, lists, tuples, and dictionaries. Don’t pass in complex objects.
Passing in an object representing persistent data (ORM instances for example can cause a race condition.)

20.3.4 Learn How to Monitor Tasks and Workers

Some useful tools:

20.3.5 Logging!

20.3.6 Monitor the Backlog

As traffic increases, tasks can pile up if there aren’t enough workers. When we see this happening, it’s time to increase the number of workers.

20.3.7 Periodically Clear Out Dead Tasks

20.3.8 Use the Queue's Error Handling

  • Max retries for a task
  • Retry delays

When a task fails, we like to wait at least 10 seconds before trying again. Even better, if the task queue software allows it, increase the delay each time an attempt is made.

21 Security

21.1 Know Django's Security Features

Django 1.8’s security features include:

  • Cross-site scripting (XSS) protection.
  • Cross-site request forgery (CSRF) protection.
  • SQL injection protection.
  • Clickjacking protection.
  • Support for TLS/HTTPS/HSTS, including secure cookies.
  • Secure password storage, using the PBKDF2 algorithm with a SHA256 hash by default.
  • Automatic HTML escaping.
  • An expat parser hardened against XML bomb attacks.
  • Hardened JSON, YAML, and XML serialization/deserialization tools.

21.2 Turn Off DEBUG Mode in Production

Keep in mind that when you turn off DEBUG mode, you will need to set ALLOWED HOSTS or risk raising a SuspiciousOperation error, which generates a 500 error that can be hard to debug.

21.3 Keep Your Secret Keys Secret

21.4 HTTPS Everywhere

21.4.1 Use django.middleware.security.SecurityMiddleware

The tool of choice for projects on Django 1.8+ for enforcing HTTPS/SSL across an entire site through middleware is built right in. To activate this middleware just follow these steps:

  1. Add django.middleware.security.SecurityMiddleware to the settings.MIDDLEWARE CLASSES definition.
  2. Set settings.SECURE SSL HOST to True.

django.middleware.security.SecurityMiddleware Does Not Include static/media, static assets are typically served directly by the web
server (nginx, Apache)

21.4.2 Use Secure Cookies

Your site should inform the target browser to never send cookies unless via HTTPS. You’ll need to set the following in your settings:

SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True

21.4.3 Use HTTP Strict Transport Security (HSTS)

HSTS can be con gured at the web server level. Follow the instructions for your web server, platform- as-a-service, and Django itself (via settings.SECURE HSTS SECONDS).

21.4.4 HTTPS Configuration Tools

Mozilla provides a SSL con guration generator at the https://mozilla.github.io/server-side-tls/ssl-config-generator/
Once you have a server set up (preferably a test server), use the Qualys SSL Labs server test at https://www.ssllabs.com/ssltest/ to see how well you did.

21.6 Always Use CSRF Protection With HTTP Forms That Modify Data

21.7 Obfuscate Primary Keys with UUIDs

import uuid as uuid_lib
from django.db import models
from django.utils.encoding import python_2_unicode_compatible

@python_2_unicode_compatible
class IceCreamPayment(models.Model):

uuid = models.UUIDField(
db_index=True,
default=uuid_lib.uuid4,
editable=False)

def __str__(self):
return str(self.pk)

22 Logging

22.1 When to Use Each Log Level

In your production environment, we recommend using every log level except for DEBUG.

22.1.1 Log Catastrophes With CRITICAL

For example, if your code relies on an internal web service being available, and if that web service is part of your site’s core functionality, then you might log at the CRITICAL level anytime that the web service is inaccessible.

22.1.2 Log Production Errors With ERROR

whenever code raises an exception that is not caught, the event gets logged by Django using the following code:

# Taken directly from core Django code.
# Used here to illustrate an example only, so don't
# copy this into your project.
logger.error("Internal Server Error: %s", request.path,
exc_info=exc_info,
extra={
"status_code": 500,
"request": request
}
)

we recommend that you use the ERROR log level whenever you need to log an error that is worthy of being emailed to you or your site admins.

22.1.3 Log Lower-Priority Problems With WARNING

This level is good for logging events that are unusual and potentially bad, but not as bad as ERROR- level events.
For example, when an incoming POST request is missing its csrf token, the event gets logged as follows:

# Taken directly from core Django code.
# Used here to illustrate an example only, so don't
# copy this into your project.
logger.warning("Forbidden (%s): %s",
REASON_NO_CSRF_COOKIE, request.path,
extra={
"status_code": 403,
"request": request,
} )

22.1.4 Log Useful State Information With INFO

We recommend using this level to log any details that may be particularly important when analysis is needed. These include:

  • Startup and shutdown of important components not logged elsewhere
  • State changes that occur in response to important events
  • Changes to permissions, e.g. when users are granted admin access

In addition to this, the INFO level is great for logging any general information that may help in performance analysis.

22.1.5 Log Debug-Related Messages to DEBUG

In development, we recommend using DEBUG and occasionally INFO level logging wherever you’d consider throwing a print statement into your code for debugging purposes.

import logging
from django.views.generic import TemplateView
from .helpers import pint_counter

logger = logging.getLogger(__name__)

class PintView(TemplateView):

def get_context_data(self, *args, **kwargs):
context = super(PintView, self).get_context_data(**kwargs)
pints_remaining = pint_counter()
logger.debug("Only %d pints of ice cream left." % pints_remaining)
return context

22.2 Log Tracebacks When Catching Exceptions

  1. Logger.exception() automatically includes the traceback and logs at ERROR level.
  2. For other log levels, use the optional exc_info keyword argument.
import logging import requests

logger = logging.getLogger(__name__)

def get_additional_data():
try:
r = requests.get("http://example.com/something-optional/")
except requests.HTTPError as e:
logger.exception(e)
logger.debug("Could not get additional data", exc_info=True)
return None
return r

22.3 One Logger Per Module That Uses Logging

# You can place this snippet at the top
# of models.py, views.py, or any other
# file where you need to log.
import logging

logger = logging.getLogger(__name__)

If you’re running into a strange issue in production that you can’t replicate locally, you can temporarily turn on DEBUG logging for just the module related to the issue

22.4 Log Locally to Rotating Files

A common way to set up log rotation is to use the UNIX logrotate utility with log- ging.handlers.WatchedFileHandler.
Note that if you are using a platform-as-a-service, you might not be able to set up rotating log files. In this case, you may need to use an external logging service such as Loggly: http://loggly.com/.

23 Signals: Use Cases and Avoidance Techniques

Signals can be useful, but they should be used as a last resort, only when there’s no good way to avoid using them.

23.1 When to Use and Avoid Signals

Do not use signals when:

  • The signal relates to one particular model and can be moved into one of that model’s methods, possibly called by save().
  • The signal can be replaced with a custom model manager method.
  • The signal relates to a particular view and can be moved into that view.

It might be okay to use signals when:

  • Your signal receiver needs to make changes to more than one model.
  • You want to dispatch the same signal from multiple apps and have them handled the same way by a common receiver.
  • You want to invalidate a cache after a model save.
  • You have an unusual scenario that needs a callback, and there’s no other way to handle it besides using a signal. For example, you want to trigger something based on the save() or init() of a third-party app’s model. You can’t modify the third-party code and extending it might be impossible, so a signal provides a trigger for a callback.

23.2 Signal Avoidance Techniques

23.2.1 Using Custom Model Manager Methods Instead of Signals

Let’s imagine that our site handles user-submitted ice cream-themed events, and each ice cream event goes through an approval process. These events are set with a status of “Unreviewed” upon creation. The problem is that we want our site administrators to get an email for each event submission so they know to review and post things quickly.
We could have done this with a signal, but unless we put in extra logic in the post save() code, even administrator created events would generate emails.

Use custom model manager instead of signals to solve above.
EventManager:

# events/managers.py
from django.db import models

class EventManager(models.Manager):

def create_event(self, title, start, end, creator):
event = self.model(title=title,
start=start,
end=end,
creator=creator)
event.save()
event.notify_admins()
return event

Let’s attach it to our model (which comes with a notify admins() method:

# events/models.py
from django.conf import settings
from django.core.mail import mail_admins
from django.db import models
from model_utils.models import TimeStampedModel
from .managers import EventManager

class Event(TimeStampedModel):
STATUS_UNREVIEWED, STATUS_REVIEWED = (0, 1)
STATUS_CHOICES = (
(STATUS_UNREVIEWED, "Unreviewed"),
(STATUS_REVIEWED, "Reviewed"),
)
title = models.CharField(max_length=100)
start = models.DateTimeField()
end = models.DateTimeField()
status = models.IntegerField(choices=STATUS_CHOICES,
default=STATUS_UNREVIEWED)
creator = models.ForeignKey(settings.AUTH_USER_MODEL)
objects = EventManager()

def notify_admins(self):
# create the subject and message
subject = "{user} submitted a new event!".format(
user=self.creator.get_full_name())
message = """TITLE: {title}
START: {start}
END: {end}""".format(title=self.title, start=self.start,

end=self.end)

# Send to the admins!
mail_admins(subject=subject,
message=message,
fail_silently=False)

To generate an event, instead of calling create(), we call a create event() method.

&gt;&gt;&gt; from django.contrib.auth import get_user_model
&gt;&gt;&gt; from django.utils import timezone
&gt;&gt;&gt; from events.models import Event
&gt;&gt;&gt; user = get_user_model().get(username="audreyr")
&gt;&gt;&gt; now = timezone.now()
&gt;&gt;&gt; event = Event.objects.create_event(
... title="International Ice Cream Tasting Competition",
... start=now,
... end=now,
... user=user
... )

23.2.2 Validate Your Model Elsewhere

If you’re using a pre save signal to trigger input cleanup for a speci c model, try writing a custom validator for your eld(s) instead.
If validating through a ModelForm, try overriding your model’s clean() method instead.

23.2.3 Override Your Model's Save or Delete Method Instead

If you’re using pre save and post save signals to trigger logic that only applies to one particular model, you might not need those signals. You can often simply move the signal logic into your model’s save() method.

23.2.4 Use a Helper Function Instead of Signals

We find this approach useful under two conditions:

  • Refactoring: Once we realize that certain bits of code no longer need to be obfuscated as signals and want to refactor, the question of ‘Where do we put the code that was in a signal?’ arises. If it doesn’t belong in a model manager, custom validator, or overloaded model method, where does it belong?
  • Architecture: Sometimes developers use signals because we feel the model has become too heavyweight and we need a place for code. While Fat Models are a nice approach, we admit it’s not much fun to have to parse through a 500 or 2000 line chunk of code.

24 Utilities

24.1 Create a Core App for Your Utilities

TIP: Always make the core app a real Django app
Our core app would therefore look like:

core/
__init__.py
managers.py # contains the custom model manager(s)
models.py
views.py # Contains the custom view mixin(s)

24.2 Optimize Apps with Utility Modules

Synonymous with helpers, these are commonly called utils.py and sometimes helpers.py.

24.2.1 Trimming Models

One day we notice that our fat model has reached brobdingnagian proprtions and is over a thousand lines of code.
We start looking for methods (or properties or classmethods) whose logic can be easily encapsulated in functions stored in flavors/utils.py.

24.3 Django's Own Swiss Army Knife

Most of the modules in django.utils are designed for internal use and only the following parts can be considered stable and thus backwards compatible
https://docs.djangoproject.com/en/1.8/ref/utils/

Some useful utils from django:

  • django.contrib.humanize
  • django.utils.decorators.method decorator(decorator)
  • django.utils.decorators.decorator from middleware( middleware)
  • django.utils.encoding.force text(value)
  • django.utils.functional.cached property
  • django.utils.html.format html(format str, args, **kwargs)
  • django.utils.html.strip tags(value)
  • django.utils.six
  • django.utils.text.slugify(value)
  • django.utils.timezone
  • django.utils.translation

24.4 Exceptions

24.4.1 django.core.exceptions.ImproperlyConfigured

The purpose of this module is to inform anyone attempting to run Django that there is a con guration issue.

24.4.2 django.core.exceptions.ObjectDoesNotExist

We’ve found this is a really nice tool for working with utility functions that fetch generic model instances and do something with them. Here is a simple example:

# core/utils.py
from django.core.exceptions import ObjectDoesNotExist

class BorkedObject(object):
loaded = False

def generic_load_tool(model, pk):
try:
instance = model.objects.get(pk=pk)
except ObjectDoesNotExist:
return BorkedObject()
instance.loaded = True
return instance

We can create our own variant of django's django.shortcuts.getobjector404 function, perhaps raising a HTTP 403 exception instead of a 404:

# core/utils.py
from django.core.exceptions import MultipleObjectsReturned
from django.core.exceptions import ObjectDoesNotExist
from django.core.exceptions import PermissionDenied

def get_object_or_403(model, **kwargs): .
try:
return model.objects.get(**kwargs)
except ObjectDoesNotExist:
raise PermissionDenied
except MultipleObjectsReturned:
raise PermissionDenied

24.4.3 django.core.exceptions.PermissionDenied

if something bad happens, instead of just returning a 500 exception, which may rightly alarm users, we simply provide a “Permission Denied” screen.

# stores/calc.py

def finance_data_adjudication(store, sales, issues):
if store.something_not_right:
msg = "Something is not right. Please contact the support team."
raise PermissionDenied(msg)
# Continue on to perform other logic.
# urls.py
# This demonstrates the use of a custom permission denied view. The default
# view is django.views.defaults.permission_denied
handler403 = 'core.views.permission_denied_view'

24.5 Serializers and Deserializers

Django has some great tools for working with serialization and deserialization of data of JSON, Python, YAML and XML data.
Here is how we serialize data:

# serializer_example.py
from django.core.serializers import get_serializer
from favorites.models import Favorite
# Get and instantiate the serializer class
# The 'json' can be replaced with 'python' or 'xml'.
# If you have pyyaml installed, you can replace it with # 'pyyaml'
JSONSerializer = get_serializer("json").
serializer = JSONSerializer()

favs = Favorite.objects.filter()[:5]

# Serialize model data
serialized_data = serializer.serialize(favs)

# save the serialized data for use in the next example
with open("data.json", "w") as f:
f.write(serialized_data)

Here is how we deserialize data:

# deserializer_example.py
from django.core.serializers import get_serializer
from favorites.models import Favorite

favs = Favorite.objects.filter()[:5]

# Get and instantiate the serializer class
# The 'json' can be replaced with 'python' or 'xml'.
# If you have pyyaml installed, you can replace it with
# 'pyyaml'
JSONSerializer = get_serializer("json")
serializer = JSONSerializer()

# open the serialized data file
with open("data.txt") as f:
serialized_data = f.read()

# deserialize model data into a generator object
# we'll call 'python data'
python_data = serializer.deserialize(serialized_data)

# iterate through the python_data
for element in python_data:
# Prints 'django.core.serializers.base.DeserializedObject'
print(type(element))

# Elements have an 'object' that are literally instantiated
# model instances (in this case, favorites.models.Favorite)
print(element.object.pk, element.object.created)

using Django’s built-in serializers and deserializers: they can cause problems. From painful experience, we know that they don’t handle complex data structures well.
Consider these guidelines that we follow in our projects:

  • Serialize data at the simplest level.
  • Any database schema change may invalidate the serialized data.
  • Don’t just import serialized data. Consider using Django’s form libraries to validate incoming data before saving to the database.

24.5.1 django.core.serializers.json.DjangoJSONEncoder

Python’s built-in JSON module can’t handle encoding of date/time or decimal types.
Fortunately for all of us, Django provides a very useful JSONEncoder class. See the code example below:

# json_encoding_example.py 
import json
from django.core.serializers.json import DjangoJSONEncoder
from django.utils import timezone

data = {"date": timezone.now()}

# If you don't add the DjangoJSONEncoder class then
# the json library will throw a TypeError.
json_data = json.dumps(data, cls=DjangoJSONEncoder)
print(json_data)

25 Deployment:Platforms as a Service

The most commonly-used Django-friendly PaaS companies as of this writing are:

25.1 Automate All the Things!

Use simple automation using one of the following tools:

  • Makefiles are useful for simple projects. Their limited capability means we won’t be tempted to make things too fancy. As soon as you need more power, it’s time to use something else. Something like Invoke as described in the next bullet.
  • Invoke is the direct descendant of the venerable Fabric library. It is similar to Fabric, but is designed for running tasks locally rather than on a remote server. Tasks are de ned in Python code, which allows for a bit more complexity in task de nitions (although it’s easy to take things too far). It has full support for Python 3.4.

26 Deploying Django Projects

26.1 Single-Server for Small Projects

26.1.1 Example: Quick Ubuntu + Gunicorn Setup

  • An old computer or cheap cloud server
  • Ubuntu Server OS
  • PostgreSQL
  • Virtualenv
  • Gunicorn

You can either use a computer that you have lying around your house, or you can use a cheap cloud server from a provider like DigitalOcean, Rackspace, or AWS.
installing at least these:
For pip/virtualenv python-pip, python-virtualenv
For PostgreSQL postgresql, postgresql-contrib, libpq-dev, python-dev

Then we do all the server setup basics like updating packages and creating a user account for the project.
At this point, it’s Django time. We clone the Django project repo into our user’s home directory and create a virtualenv with the project’s Python package dependencies, including Gunicorn. We create a PostgreSQL database for the Django project and run python manage.py migrate.
Then we run the Django project in Gunicorn. As of this writing, this requires a simple 1-line com- mand. See: https://docs.djangoproject.com/en/1.8/howto/deployment/wsgi/gunicorn/
adding nginx, Redis, and/or memcached, or setting up Gunicorn behind an nginx proxy.

26.2 Multi-Server for Medium to Large Projects

figure31_1.png
This is what you need at the most basic level:

  • Database server. Typically PostgreSQL in our projects when we have the choice, though Eventbrite uses MySQL.
  • WSGI application server. Typically uWSGI or Gunicorn with Nginx, or Apache with mod wsgi.

Additionally, we may also want one or more of the following:

  • Static file server. If we want to do it ourselves, Nginx or Apache are fast at serving static files. However, CDNs such as Amazon CloudFront are relatively inexpensive at the basic level.
  • Caching and/or asynchronous message queue server. is server might run Redis, Memcached or Varnish.
  • Miscellaneous server. If our site performs any CPU-intensive tasks, or if tasks involve waiting for an external service (e.g. the Twitter API) it can be convenient to offload them onto a server separate from your WSGI app server.

Finally, we also need to be able to manage processes on each server. We recommend in descending order of preference:

  1. Supervisord
  2. init scripts

26.2.1 Advanced Multi-Server Setup

figure31_3.png
Load balancers can be hardware- or software-based. Commonly-used examples include:

  • Software-based: HAProxy, Varnish, Nginx
  • Hardware-based: Foundry, Juniper, DNS load balancer
  • Cloud-based: Amazon Elastic Load Balancer, Rackspace Cloud Load Balancer

26.3 WSGI Application Servers

The most commonly-used WSGI deployment setups are:

  1. uWSGI with Nginx.
  2. Gunicorn behind a Nginx proxy.
  3. Apache with mod wsgi.

table31_1.png

26.4 Automated, Repeatable Deployments

27 Continuous Integration

Here’s a typical development work ow when using continuous integration:

  1. Developer writes code, runs local tests against it, then pushes the code to an instance of a code repository such as Git or Mercurial. This should happen at least once per day.
  2. The code repository informs an automation tool that code has not been submitted for integration.
  3. Automation integrates the code into the project, building out the project. Any failures during the build process and the commit is rejected.
  4. Automation runs developer-authored tests against the new build. Any failures of the tests and the commit is rejected.
  5. The developer is notified of success or the details of failure. Based on the report, the developer can mitigate the failures. If there are no failures, the developer celebrates and moves to the next task.

27.1 Principles of Continuous Integration

27.1.1 Write Lots of Tests!

27.1.2 Keeping the Build Fast

  • Avoid fixtures. This is yet another reason why we advise against their use.
  • Avoid TransactionTestCase except when absolutely necessary.
  • Avoid heavyweight setUp() methods.
  • Write small, focused tests that run at lightning speed, plus a few larger integration-style tests.
  • Learn how to optimize your database for testing. is is discussed in public forums like Stack Overflow: http://stackoverflow.com/a/9407940/93270

27.2 Tools for Continuously Integrating Your Project

27.2.1 Tox

http://tox.readthedocs.org/
This is a generic virtualenv management and testing command-line tool that allows us to test our projects against multiple Python and Django versions with a single command at the shell.

27.2.2 Jenkins

http://jenkins-ci.org/
Jenkins is a extensible continuous integration engine used in private and open source efforts around the world. It is the standard for automating the components of Continuous Integration, with a huge community and ecosystem around the tool.

27.3 Continuous Integration as a Service

There are various services that provide automation tools powered by Jenkins or analogues. Some of these plug right into popular repo hosting sites like GitHub and BitBucket, and most provide free repos for open source projects. Some of our favorites include:

Service Python Versions Supported Link
Travis-CI 3.3, 3.2, 2.7, 2.6, PyPy https://travis-ci.org
AppVeyor (Windows) 3.4, 3.3, 2.7 http://www.appveyor.com/
CircleCI 3.4, 3.3, 2.7, 2.6, PyPy, many more https://circleci.com
Drone.io 2.7, 3.3 https://drone.io/
Codeship 3.4, 2.7 https://codeship.com

27.3.1 Code Coverage as a Service

https://codecov.io can generate coverage reports

28 Debugging

28.1 Debugging in Development

28.1.1 Use django-debug-toolbar

If you don’t have it set up and con gured, stop everything else you are doing and add it to your project.

28.1.2 That Annoying CBV Error

twoscoopspress$ python discounts/manage.py runserver 8001 
Starting development server at http://127.0.0.1:8001/
Quit the server with CONTROL-C.

Internal Server Error: /
Traceback (most recent call last):
File "/Users/python/lib/python2.7/site-packages/django/core/handlers/base.py",
line 132, in get_response response = wrapped_callback(request,
*callback_args, **callback_kwargs)
File "/Users/python/lib/python2.7/site-packages/django/utils/decorators.py",
line 145, in inner
return func(*args, **kwargs)
TypeError: __init__() takes exactly 1 argument (2 given)

This error is caused because lack as_view() at urls.py
Example of TypeError generating code:

# Forgetting the 'as_view()' method 
url(r'ˆ$', HomePageView, name="home"),

Example of fixed code:

url(r'ˆ$', HomePageView.as_view(), name="home"),

28.1.3 Master the Python Debugger

ipdb(ipdb does is add the ipython interface to the PDB interface) is also very useful
References:

28.1.4 Remember the Essentials for Form File Uploads

There are two easily forgotten items that will cause file uploads to fail silently.
check the following:

  1. Does the <form> tag include an encoding type?


&lt;form action="{% url 'stores:file_upload' store.pk %}"
method="post"
enctype="multipart/form-data"&gt;


  1. Do the views handle request.FILES? In Function-Based Views?
# stores/views.py
from django.shortcuts import render, redirect, get_object_or_404
from django.views.generic import View
from stores.forms import UploadFileForm
from stores.models import Store

def upload_file(request, pk):
"""Simple FBV example"""
store = get_object_or_404(Store, pk=pk)
if request.method == 'POST':
# Don't forget to add request.FILES!
form = UploadFileForm(request.POST, request.FILES)
if form.is_valid():
store.handle_uploaded_file(request.FILES['file'])
return redirect(store)
else:
form = UploadFileForm()
return render(request, 'upload.html', {'form': form, 'store': store})

Or what about Class-Based Views?

# stores/views.py
from django.shortcuts import render, redirect, get_object_or_404
from django.views.generic import View
from stores.forms import UploadFileForm
from stores.models import Store

class UploadFile(View):
"""Simple CBV example"""
def get_object(self):
return get_object_or_404(Store, pk=self.kwargs['pk'])

def post(self, request, *args, **kwargs):
store = self.get_object() .
form = UploadFileForm(request.POST, request.FILES)
if form.is_valid():
store.handle_uploaded_file(request.FILES['file'])
return redirect(store)
return redirect('stores:file_upload', pk=pk)

def get(self, request, *args, **kwargs):
store = self.get_object()
form = UploadFileForm()
return render(request, 'upload.html', {'form': form, 'store': store})

If a view inherits from one of the following then we don’t need to worry about re- quest.FILES in your view code. Django handles most of the work involved.

  • django.views.generic.edit.FormMixin
  • django.views.generic.edit.FormView
  • django.views.generic.edit.CreateView
  • django.views.generic.edit.UpdateView

28.2 Debugging Production Systems

28.2.1 Read the Logs the Easy Way

use a log aggregator like Sentry get a better view into what is going on in your application.

28.2.2 Mirroring Production

28.2.3 UserBasedExceptionMiddleware

What if you could provide superusers with access to the settings.DEBUG=True 500 error page in production?
there is a way to use this powerful debugging tool in production.

# core/middleware.py
import sys
from django.views.debug import technical_500_response

class UserBasedExceptionMiddleware(object):
def process_exception(self, request, exception):
if request.user.is_superuser:
return technical_500_response(request, *sys.exc_info())

28.2.4 That Troublesome settings.ALLOWED HOSTS Error

Unfortunately, as soon as settings.DEBUG is False, a project with an incorrectly set ALLOWED_HOSTS will generate constant 500 errors. Checking the logs will show that SuspiciousOperation errors are being raised, but those errors don’t come with a meaningful message.

Therefore, whenever a project is deployed for the first time and always returns a 500 error, check settings.ALLOWED HOSTS. As for knowing what to set, here is a starting example:

# settings.py
ALLOWED_HOSTS = [
'.djangopackages.com',
'localhost', # Ensures we can run DEBUG = False locally
'127.0.0.1' # Ensures we can run DEBUG = False locally
]

28.3 Feature Flags

Feature Flags allow us to turn a project’s feature on or off via a web-based interface.
What if we could allow a subset of real users (i.e. ‘beta users’) defined through an admin-style interface to interact with a new feature before turning it on for everyone?
This is what feature ags are all about!

28.3.2 Unit Testing Code Affected by Feature Flags

One gotcha with feature ags is running tests against code that are turned off by them. How do we know that our new feature is tested when the flag to run them is turned off?
The answer to this question is that our tests should cover both code paths, with feature flags on or off. To do this, we need to familiarize ourselves with how to turn a feature ag on or off within the Django testing framework:

29 Internationalization and Localization

29.1 Define Python Source Code Encodings

at the top each module add:

# -*- coding: utf-8 -*-

29.2 Wrap Content Strings with Translation Functions

https://docs.djangoproject.com/en/1.8/topics/i18n/translation/

Function Purpose Link
ugettext() For content executed at runtime, e.g. form validation errors. https://docs.djangoproject.com/en/1.8/topics/i18n/translation/#standard-translation
ugettext lazy() For content executed at compile time, e.g. verbose name in models. https://docs.djangoproject.com/en/1.8/topics/i18n/translation/#lazy-translation
string concat() Replaces the standard str.join() method for joining strings. Rarely used. https://docs.djangoproject.com/en/1.8/topics/i18n/translation/#localized-names-of-languages

29.2.1 Convention: Use the Underscore Alias to Save Typing

# -*- coding: utf-8 -*-
from django.utils.translation import ugettext as _

print(_("We like gelato."))

29.3 Don't Interpolate Words in Sentences

Bad Example:

# DON'T DO THIS!

# Skipping the rest of imports for the sake of brevity
class FlavorActionMixin(object):

@property
def action(self):
msg = "{0} is missing action.".format(self.__class__)
raise NotImplementedError(msg)

def form_valid(self, form):
msg = "Flavor {0}!".format(self.action)
messages.info(self.request, msg)
return super(FlavorActionMixin, self).form_valid(form)
# Snipping the rest of this module for the sake of brevity

want to change above to international string but it does not works:

# DON'T DO THIS!
from django.utils.translations import ugettext as _

# Skipping the rest of this module for the sake of brevity
def form_valid(self, form):
# This generates a useless translation object.
msg = _("Flavor {0}!".format(self.action)) messages.info(self.request, msg)
return super(FlavorActionMixin, self).form_valid(form)
# Skipping the rest of this module for the sake of brevity

improve above:

# -*- coding: utf-8 -*-
# Skipping the rest of imports for the sake of brevity from django.utils.translation import ugettext as _

class FlavorActionMixin(object):
@property
def success_msg(self): .
return NotImplemented

class FlavorCreateView(LoginRequiredMixin, FlavorActionMixin, CreateView):
model = Flavor
# Slightly longer but more meaningful dialogue
success_msg = _("Flavor created!")

29.4 Unicode Tricks

  • Python 3 Can Make Unicode Easier
  • Use django.utils.encoding.force text() Instead of unicode() or str()

29.5 Browser Page Layout

Assuming you’ve got your content and Django templates internationalized and localized, you can discover that your layouts are broken.
Mozilla’s answer is to determine the width of a title container, then use JavaScript adjust the font size of the title text downwards until the text fits into the container with wrapping.

30 Security Settings Reference

Setting Development Production
ALLOWED_HOSTS any list see previous chapter
DEBUG True False
DEBUG_PROPAGATE_EXCEPTIONS False False
Email SSL see below see below
MIDDLEWARE_CLASSES Standard AddSecurityMiddleware
SECRET_KEY Use cryptographic key see previous chapter
SECURE_PROXY_SSL_HEADER None see below
SECURE_SSL_HOST False True
SESSION_COOKIE_SECURE False True
SESSION SERIALIZER see below see below

30.1 Email SSL

https://docs.djangoproject.com/en/1.8/ref/settings/#email-use-tls

  • EMAIL_USE_TLS
  • EMAIL_USE_SSL
  • EMAIL_SSL_CERTFILE
  • EMAIL_SSL_KEYFILE

30.2 SESSION SERIALIZER

SESSION SERIALIZER = django.contrib.sessions.serializers.JSONSerializer.

30.3 SECURE PROXY SSL HEADER

For some setups, most notably Heroku, this should be:
SECURE PROXY SSL HEADER = (`HTTP X FORWARDED PROTO', `https')

31 Some Useful Packages

31.1 Core

31.2 Asynchronous

31.3 Deployment

31.4 Forms

31.5 Logging

31.6 Project Templates

31.7 REST APIs

31.8 Testing

  • coverage http://coverage.readthedocs.org/ Checks how much of your code is covered with tests.
  • mock https://pypi.python.org/pypi/mock Not explicitly for Django, this allows you to replace parts of your system with mock objects. This project made its way into the standard library as of Python 3.4.
  • tox http://tox.readthedocs.org/ A generic virtualenv management and test command line tool that allows testing of projects against multiple Python version with a single command at the shell.

31.9 Views

32 Reference

此筆記大多來自這本書:
Two Scoops of Django: Best Practices for Django 1.8
by Daniel Roy Greenfeld (Author), Audrey Roy Greenfeld (Author)
https://www.twoscoopspress.com/

Last Updated 2017-03-10 五 10:26.
Render by hexo-renderer-org with Emacs 25.2.1 (Org mode 8.2.10)