All notes
Djang

Walkthrough

Installation


import django
django.VERSION
// (1, 4, 2, 'final', 0)

python -m django --version

Dependencies:

Writing your first Django project

Create Project


mkdir ~/djcode
cd ~/djcode

# This will create a mysite directory in your current directory.
django-admin.py startproject mysite

The created mysite has the structure as:

# Outside dir. You can rename it to anything you like.
mysite/
	# Management app.
    manage.py
    # This dir name is written to manage.py.
    mysite/
		# A file required for Python to treat the mysite directory as a package.
        __init__.py
        settings.py
        urls.py
        wsgi.py

Create App

Make sure you’re in the same directory as manage.py and type this command:


python manage.py startapp polls

In mysite/urls.py, include polls' URLConf:


urlpatterns = [
    url(r'^polls/', include('polls.urls')),
    url(r'^admin/', admin.site.urls),
]

Databases

Open up mysite/settings.py, Set Database and TIME_ZONE.

If you're using a database besides SQLite, make sure you've created a database by this point. Do that with “CREATE DATABASE database_name;” within your database’s interactive prompt.

Also make sure that the database user provided in mysite/settings.py has “create database” privileges. This allows automatic creation of a test database.

Add models.py to polls.

Add polls config to mysite/settings.py:


INSTALLED_APPS = [
    'polls.apps.PollsConfig',
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
]

We need do this before "migration" because the migrate command will only run migrations for apps in INSTALLED_APPS.

Run:


python manage.py makemigrations polls

# Check the SQL:
python manage.py sqlmigrate polls 0001
# Or check:
python manage.py check

# Do the real migration
python manage.py migrate

The migrate command looks at the INSTALLED_APPS setting and creates any necessary database tables according to the database settings in your mysite/settings.py file and the database migrations shipped with the app.

The migrate command takes all the migrations that haven’t been applied (Django tracks which ones are applied using a special table in your database called django_migrations) and runs them against your database - essentially, synchronizing the changes you made to your models with the schema in the database.

Dry Principle

Don’t Repeat Yourself (DRY). Every distinct concept and/or piece of data should live in one, and only one, place. Redundancy is bad. Normalization is good. DjangoDoc: Dry.

The opposite is WET: We Edit Terribly. c2: DontRepeatYourself.

Playing with the API


python manage.py shell

manage.py sets the DJANGO_SETTINGS_MODULE environment variable, which gives Django the Python import path to your mysite/settings.py file.

You can play around the Models.

If you want helpful information from Model objects, add a __str__() method to both Question and Choice:


# polls/models.py

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

@python_2_unicode_compatible  # only if you need to support Python 2
class Question(models.Model):
    question_text = models.CharField(max_length=200)
    pub_date = models.DateTimeField('date published')
    def __str__(self):
        return self.question_text
    def was_published_recently(self):
        return self.pub_date >= timezone.now() - datetime.timedelta(days=1)

@python_2_unicode_compatible  # only if you need to support Python 2
class Choice(models.Model):
    question = models.ForeignKey(Question, on_delete=models.CASCADE)
    choice_text = models.CharField(max_length=200)
    votes = models.IntegerField(default=0)
    def __str__(self):
        return self.choice_text

The following are interactive examples:



from polls.models import Question, Choice

Question.objects.all()
# []

# Create a new Question.
from django.utils import timezone
q = Question(question_text="What's new?", pub_date=timezone.now())
# Save the object into the database. You have to call save() explicitly.
q.save()
q.id
# 1
# Access model field values via Python attributes.
q.question_text
# "What's new?"
q.pub_date
# datetime.datetime(2012, 2, 26, 13, 0, 0, 775217, tzinfo=UTC)

# Change values by changing the attributes, then calling save().
q.question_text = "What's up?"
q.save()

# objects.all() displays all the questions in the database.
Question.objects.all()

# Django provides a rich database lookup API that's entirely driven by keyword arguments.
Question.objects.filter(id=1)
# [<Question: What's up?>]
Question.objects.filter(question_text__startswith='What')
# [<Question: What's up?>]

# Get the question that was published this year.
from django.utils import timezone
current_year = timezone.now().year
Question.objects.get(pub_date__year=current_year)
# <Question: What's up?>

# Request an ID that doesn't exist, this will raise an exception.
Question.objects.get(id=2)
# Traceback (most recent call last):
#     ...
# DoesNotExist: Question matching query does not exist.

# Lookup by a primary key is the most common case, so Django provides a shortcut for primary-key exact lookups.
# The following is identical to Question.objects.get(id=1).
Question.objects.get(pk=1)
# <Question: What's up?>

# Make sure our custom method worked.
q = Question.objects.get(pk=1)
q.was_published_recently()
# True

# Give the Question a couple of Choices. The create call constructs a new Choice object, does the INSERT statement, adds the choice to the set of available choices and returns the new Choice object. Django creates a set to hold the "other side" of a ForeignKey relation (e.g. a question's choice) which can be accessed via the API.
q = Question.objects.get(pk=1)

# Display any choices from the related object set -- none so far.
q.choice_set.all()
# []

# Create three choices.
q.choice_set.create(choice_text='Not much', votes=0)
# <Choice: Not much>
q.choice_set.create(choice_text='The sky', votes=0)
# <Choice: The sky>

c = q.choice_set.create(choice_text='Just hacking again', votes=0)
# Choice objects have API access to their related Question objects.
c.question
# <Question: What's up?>

# And vice versa: Question objects get access to Choice objects.
q.choice_set.all()
# [<Choice: Not much>, <Choice: The sky>, <Choice: Just hacking again>]
q.choice_set.count()
# 3

# The API automatically follows relationships as far as you need.
# Use double underscores to separate relationships.
# This works as many levels deep as you want; there's no limit.
# Find all Choices for any question whose pub_date is in this year
# (reusing the 'current_year' variable we created above).
Choice.objects.filter(question__pub_date__year=current_year)
# [<Choice: Not much>, <Choice: The sky>, <Choice: Just hacking again>]

# Let's delete one of the choices. Use delete() for that.
c = q.choice_set.filter(choice_text__startswith='Just hacking')
c.delete()

Administration

Create superuser:


python manage.py createsuperuser

The admin URL is like: "http://127.0.0.1:8000/admin/".

We need to tell the admin that Question objects have an admin interface. To do this, open the polls/admin.py file, and edit it to look like this:


# polls/admin.py

from django.contrib import admin
from .models import Question

admin.site.register(Question)

Run server


python manage.py help

# Run a built-in, lightweight Web server
python manage.py runserver

python manage.py runserver 0.0.0.0:8000

python manage.py runserver --settings=settings_dev 0.0.0.0:8000
# The Python path to a settings module. If this isn't provided, the DJANGO_SETTINGS_MODULE environment variable will be used.

Create Json response

StackOverflow.


import json
from django.http import HttpResponse

response_data = {}
response_data['result'] = 'error'
response_data['message'] = 'Some error message'

# Pre-Django 1.7 you'd return it like this:
return HttpResponse(json.dumps(response_data), content_type="application/json")

# For Django 1.7+
from django.http import JsonResponse
return JsonResponse({'foo':'bar'})

# If it's run from Django, return HttpResponse.
if os.environ.get('DJANGO_SETTINGS_MODULE')!=None:
	return HttpResponse(json.dumps(response_data), content_type="application/json")
# Else just output json.
else:
	return json.dumps(response_data)

Manage command

StackOverflow: insert row into DB.


python manage.py shell

# Dump data to fixture
python manage.py dumpdata -o output.yml
# Import fixture data
python manage.py loaddata fixtureName

# Migration
manage.py squashmigrations myapp 0004

MVC

View


from django.http import HttpResponse
import datetime

def current_datetime(request):
	now = datetime.datetime.now()
	html = "<html><body>It is now %s.</body></html>" % now
	return HttpResponse(html)

def returnError(request):
	'''
	Return HTTP status 500: server error.
	'''
	return HttpResponse(status=500)

CSRF

DjangoDoc.

To disable it, you need declare on every view function:


from django.views.decorators.csrf import csrf_exempt
from django.http import HttpResponse

@csrf_exempt
def my_view(request):
	return HttpResponse('Hello world')

from django.conf.urls.defaults import patterns, include, url
# Import the module - mysite/views.py
from mysite.views import hello

urlpatterns = patterns('',
	# 'r' here denotes "raw string", so its contents should not interpret backslashes.
	#  There's a natural collision between Python's usage of backslashes and the backslashes that are found in regular expressions, so we always use raw string for regexp.
    url(r'^hello/$', hello),
)

urlconf

Dynamic URLs, Pretty URLs

Usually, you may query /time/plus?hours=3 for time in 3 hours, but the URL /time/plus/3/ is far cleaner.


# This will match to 0-99 hours.
url(r'^time/plus/\d{1,2}/$', hours_ahead)
# This will match to any hour, but is not good one with regards to boundary condition.
url(r'^time/plus/\d+/$', hours_ahead)

##########
# This is the magic one!!!
# With (), the matched text is captured and passed as arguments to the function.
url(r'^time/plus/(\d{1,2})/$', hours_ahead)

def hours_ahead(request, offset):
	try:
		# Converts the string value to an integer.
		offset = int(offset)
	except ValueError:
		raise Http404()
	dt = datetime.datetime.now() + datetime.timedelta(hours=offset)
	html = "<html><body>In %s hour(s), it will be %s.</body></html>" % (offset, dt)
	return HttpResponse(html)

Technically, captured values will always be Unicode objects, not plain Python bytestrings.


(r'^user/(?P<username>\w{0,50})/$', views.profile_page,),

For URL "domain/search/?q=haha", then you would use


request.GET.get('q', '')
# q is the parameter you want, And '' is the default value if q isn't found.

Logging

Wcf recommends to write INFO levels often, even in production enviroment, esp. for the server codes.

  1. DEBUG: Low level system information for debugging purposes
  2. INFO: General system information
  3. WARNING: Information describing a minor problem that has occurred.
  4. ERROR: Information describing a major problem that has occurred.
  5. CRITICAL: Information describing a critical problem that has occurred.

Writing to console and log file:


import os

LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'handlers': {
        'console': {
            'class': 'logging.StreamHandler',
        },
    },
    'loggers': {
        'django': {
            'handlers': ['console'],
            'level': os.getenv('DJANGO_LOG_LEVEL', 'INFO'),
        },
    },
}

Writes all request logging from the django.request logger to a local file:


LOGGING = {
	'version': 1,
	# If true, all loggers from the default configuration will be disabled. So usually set to False.
	'disable_existing_loggers': False,
	'handlers': {
		'mail_admins': {
			'level': 'ERROR',
			'class': 'django.utils.log.AdminEmailHandler'
		},
		'file': {
			'level': 'DEBUG',
			'class': 'logging.FileHandler',
			'filename': '/path/to/django/debug.log',
		},
	},
	'loggers': {
		'django.request': {
			'handlers': ['file'],
			'level': 'DEBUG',
			'propagate': True,
		},
	},
}

Deploy

Deploying static files

Before running nginx, you have to collect all Django static files in the static folder. First of all you have to edit mysite/settings.py adding:


# Edit in settings.py
STATIC_ROOT = os.path.join(BASE_DIR, "static/")

# and then run
# python manage.py collectstatic

Security

DjangoDoc.

Security Concerns

StackExchange.

Secure cookies

In settings.py, add


SSION_COOKIE_SECURE = True
# May be missing when Django less than 1.4:
CSRF_COOKIE_SECURE = True
# The following is optional:
SESSION_EXPIRE_AT_BROWSER_CLOSE=True

so cookies will only be sent via HTTPS connections.

In Django less than 1.4, a quick fix here. SESSION_COOKIE_SECURE=True. Editing django/middleware/csrf.py:


class CsrfViewMiddleware(object):
	...
	def process_response(self, request, response):
		...
		response.set_cookie(settings.CSRF_COOKIE_NAME,
			request.META["CSRF_COOKIE"], max_age = 60 * 60 * 24 * 7 * 52,
			domain=settings.CSRF_COOKIE_DOMAIN,
			secure=settings.SESSION_COOKIE_SECURE or None)

Direct HTTP requests to HTTPS in the webserver


server {
   listen 80;
   rewrite ^(.*) https://$host$1 permanent;
}

# # Turn on HSTS (HTTP Strict Transport Security) headers in your web server by adding a line to nginx:
# # This tells your web browser that your website for the next 10 years will be using HTTPS only. Enable this in caution!
# add_header Strict-Transport-Security max-age=31536000;

Make directs in Django always using HTTPS

# Either set the environment var:
os.environ['HTTPS'] = "on"
# Or
uwsgi --ini uwsgi.ini --evn HTTPS=on
# Or
# adding the line "env = HTTPS=on" to your uwsgi.ini file.
# See django/http/__init__.py for the need of setting HTTPS env var.

# If you are using wsgi:
os.environ['wsgi.url_scheme'] = 'https'

Submit to SSL Test.

Host header validation

Django validates Host headers against the ALLOWED_HOSTS setting in the django.http.HttpRequest.get_host() method.

This validation only applies via get_host(); if your code accesses the Host header directly from request.META you are bypassing this security protection.

Other concerns

DjangoSecurityDoc.

Debugging

Mike.tig.as.


python -m pdb manage.py runserver

Or insert in py (pythonDoc):


import pdb; pdb.set_trace()

from django.http import HttpResponse

def default(request):

	# completely innocuous variables
	foo = 1
	bar = 0

	# Set a break point here.
	import pdb; pdb.set_trace()

	ni = foo/bar

	return HttpResponse("Foo says %d" % ni, mimetype="text/plain")

Testing

DjangoProject.

If your tests rely on database access such as creating or querying models, be sure to create your test classes as subclasses of django.test.TestCase rather than unittest.TestCase. Using unittest.TestCase avoids the cost of running each test in a transaction and flushing the database.

Good examples

DjangoProject.


from django.test import TestCase
from myapp.models import Animal

class AnimalTestCase(TestCase):
	def setUp(self):
		Animal.objects.create(name="lion", sound="roar")
		Animal.objects.create(name="cat", sound="meow")

	def test_animals_can_speak(self):
		"""Animals that can speak are correctly identified"""
		lion = Animal.objects.get(name="lion")
		cat = Animal.objects.get(name="cat")
		self.assertEqual(lion.speak(), 'The lion says "roar"')
		self.assertEqual(cat.speak(), 'The cat says "meow"')

####################

# RequestFactory

from django.test import TestCase
from django.test import RequestFactory

import views as myViews

class syncTest(TestCase):

	def setUp(self):
		self.factory = RequestFactory()

	def test_syncTimely(self):
		request = self.factory.get('/syncTimely/')
		response = myViews.syncTimely(request)
		self.assertEqual(response.status_code, 200)

For RequestFactory, see DjangoAdvancedTestingDoc.


# The -Wall flag tells Python to display deprecation warnings.
python -Wall manage.py test

# discover tests below that directory:
python -Wall manage.py test animals/

# if your test files are named differently from the test*.py pattern:
./manage.py test --pattern="tests_*.py"

unittest module’s built-in test discovery. By default, this will discover tests in any file named “test*.py” under the current working directory.

The test database. Tests that require a database (namely, model tests) will not use your “real” (production) database. Separate, blank databases are created for the tests. Regardless of whether the tests pass or fail, the test databases are destroyed when all the tests have been executed.

By default the test databases get their names by prepending test_ to the value of the NAME settings for the databases defined in DATABASES. When using the SQLite database engine the tests will by default use an in-memory database (i.e., the database will be created in memory, bypassing the filesystem entirely!). If you want to use a different database name, specify NAME in the TEST dictionary for any given database in DATABASES.

Orders

In order to guarantee that all TestCase code starts with a clean database, the Django test runner reorders tests in the following way:

  1. All TestCase subclasses are run first.
  2. Then, all other Django-based tests (test cases based on SimpleTestCase, including TransactionTestCase) are run with no particular ordering guaranteed nor enforced among them.
  3. Then any other unittest.TestCase tests (including doctests) that may alter the database without restoring it to its original state are run.

Speeding up

In recent versions of Django, the default password hasher is rather slow by design. If during your tests you are authenticating many users, you may want to use a custom settings file and set the PASSWORD_HASHERS setting to a faster hashing algorithm:


PASSWORD_HASHERS = (
    'django.contrib.auth.hashers.MD5PasswordHasher',
)

Don’t forget to also include in PASSWORD_HASHERS any hashing algorithm used in fixtures, if any.

Builtin Loggers

DjangoLogging. Django provides several built-in loggers.

To silence a particular type of SuspiciousOperation, you can override that specific logger following this example:


'loggers': {
	'django.security.DisallowedHost': {
		'handlers': ['null'],
		'propagate': False,
	},
},
Test on unmanaged model

To make TEST use 'mytestdatabase' as databaseName instead of the default 'test_mydatabase', set here: DjangoProject.


DATABASES = {
	'default': {
		'ENGINE': 'django.db.backends.postgresql',
		'USER': 'mydatabaseuser',
		'NAME': 'mydatabase',
		'TEST': {
			'NAME': 'mytestdatabase',
		},
	},
}

StackOverflow.


from django.db import connection

class MyTest(unittest.TestCase):
	def setUp(self):
		connection.cursor().execute("CREATE TABLE ...")

	def tearDown(self):
		connection.cursor().execute("DROP TABLE ...")

i18n

Time zones

DjangoProject: timezones.


TIME_ZONE = 'Asia/Shanghai'

FAQ

How to name projects and apps

SO.

Try with Facebook:

facebook
  users
  messages
  notifications
  posts
  ...

Error: django.db.utils.OperationalError: no such table

SO.

If your views.py or similar executes code that tries to access the DB when imported, i.e. importing views.py has side effects, then starting from scratch won't work.

wcfNote. In my views.py, there are global serializer defined like this:


data = Doctor.objects.all()
serializer = DoctorSerializer(data, many=True)

It causes trouble when running "makemigrations", which has several checking steps. Putting the lines into function scope makes the error gone.

Error: ImportError: No module named 'MySQLdb'

cnblogs.


pip install PyMySQL

Add in the project's __init__.py:


import pymysql
pymysql.install_as_MySQLdb()

Execute code only once

SO: execute code when django starts once only.

Django 1.7 now has a hook for this:



########## file: myapp/apps.py
from django.apps import AppConfig
class MyAppConfig(AppConfig):
    name = 'myapp'
    verbose_name = "My Application"
    def ready(self):
        pass # startup code here

########## file: myapp/__init__.py
default_app_config = 'myapp.apps.MyAppConfig'

For Django < 1.7, put the startup code in any one of your INSTALLED_APPS init.py e.g. myapp/__init__.py:


def startup():
    pass # load a big thing

startup()

When using ./manage.py runserver this gets executed twice, but that is because runserver has some tricks to validate the models first etc. Normal deployments or even when runserver auto reloads, this is only executed once.