Effective Django. Part 1


I present to you the translation of articles about Django from effectivedjango.com . I came across this site while studying this framework. The information posted on this resource seemed useful to me, but since I could not find a translation into Russian anywhere, I decided to do this good deed myself. This article series, I think, will be useful to web developers who take only the first steps in learning Django.



Table of contents


Introduction


Django is a popular, powerful Python framework. It has many " batteries ", and allows you to immediately begin development . However, all this power means that you can write base code that will seem working. So what is meant by Effective Django? By Effective Django we mean the use of Django in such a way that the written code is connected , testable and scalable . What does each of these words mean? A

connected” code is code that focuses on doing one thing, only one thing. This means that when you write a function or method - the code you wrote should do one thing and do it well.

This directly relates to writing testable code : code that does a lot of things is often too complicated to test. When I catch myself thinking: “Well, this piece of code is too complex to write tests for it - it's just not worth the effort” - this is a signal to go back and focus on simplification. The tested code is such code that allows you to simply write tests for it; code where problems are easy to find.

Finally, we want to write scalable code. This means not just scaling it in terms of execution, but also increasing it in terms of team and team understanding. Well-tested applications are easier to understand by others (and easier to modify by them), which implies a great opportunity to improve your application by adding new engineers.

My goal is to convince you of the importance of these principles, and provide examples of how, following them, to build a more robust Django application. I’m going to go through the process of building a contact management application, telling about the solutions and testing strategy that I use.

These documents are a combination of notes and examples prepared for PyCon 2012, PyOhio 2012, and PyCon 2013, as well as for Eventbrite web development. I am still working on combining them into one document, but I hope you find them useful.

Sample code for this tutorial is available on github . You can send feedback, suggestions and questions to nathan@yergler.net .
This document is available on the website , as well as in PDF and EPub formats .

The video of this guide from PyCon can be viewed on YouTube .

Chapter 1. Getting Started


1.1. Your development environment


When talking about your development environment, there are three important facts to keep in mind: isolation , predestination, and similarity . Each of them is important and they all interact with each other in concert.

Isolation means that you cannot accidentally use tools or packages installed outside your environment. This is especially important when this happens with something similar to Python packages with extensions written in C: if you use something installed at the system level and don’t know about it, then when you deploy or distribute your code, you may find that it does not work as intended. Tools like virtualenvcan help create something like an isolated environment.

Your environment is predefined if you are sure which version of your dependencies you rely on and whether you can surely reproduce the system environment.

And finally, the similarity with the production or development server environments means that the same operating system is installed everywhere (even the same release is possible) and you use the same tools to configure your development environment as well as to configure your production environment. This is by no means a necessity, but if you are building large and complex software, the similarity will be useful for making sure that all the problems that you can see on the “battle” server are reproducible in the environment where you are developing. Also, similarity limits the scope of your code.

1.1.1. Insulation


  • We want to avoid using unknown dependencies or unknown versions.
  • virtualenv provides an easy way to work on a project without using system site-packages.

1.1.2. Predestination



You can pinpoint versions using either the PyPI package version or a specific revision (SHA in git, revision number in Subversion, etc.). This ensures that you can get exactly the same package versions that you use when testing.

1.1.3. Similarity


  • Work in an environment similar to the one where you will deploy your application and try to detect problems.
  • If you are developing something that requires additional services - the similarity becomes even more important.
  • Vagrant is a virtual machine management tool that allows you to easily create an environment separate from your everyday environment.


1.2. Customize Your Environment


1.2.1. Create a clean workspace


Translator's note:
First, create a directory ( tutorial) in which we will work:

~$ mkdir tutorial
~$ cd tutorial
~/tutorial$ mkdir venv project

The directory venvwill contain our virtual environment, and the directory will contain a projectDjango project

~/tutorial$ virtualenv --prompt="(venv:tutorial)" ./venv/
New python executable in ./venv/bin/python
Installing setuptools............done.
Installing pip...............done.
~/tutorial$ source ./venv/bin/activate
(venv:tutorial)~/tutorial$


1.2.2. Creating a dependency file


Create a file requirements.txtin a directory tutorialwith a single line (dependency) in it:

Django==1.6.7

Translator's note:
In case you want to use the latest version of Django (1.7 - at the time of writing the translation) - Django==1.6.7just leave a line instead Django- pip will install the latest available version.

1.2.3. Dependency Installation


And now we can use pip to install the dependencies:

(venv:tutorial)~/tutorial$ pip install -U -r requirements.txt
Downloadping/unpacking Django==1.6.7 (from -r requirements.txt (line 1))
  Downloading Django-1.6.7.tar.gz (6.6MB): 6.6MB downloaded
  Running setup.py egg_info for package Django
    warning: no previously-included files matching ’__pycache__’ found under directory ’*’
    warning: no previously-included files matching ’*.py[co]’ found under directory ’*’
Installing collected packages: Django
  Running setup.py install for Django
    changing mode of build/scripts-2.7/django-admin.py from 644 to 755
    warning: no previously-included files matching ’__pycache__’ found under directory ’*’
    warning: no previously-included files matching ’*.py[co]’ found under directory ’*’
    changing mode of /home/nathan/p/edt/bin/django-admin.py to 755
Successfully installed Django
Cleaning up...


1.3. Start of the Django Project


When a building is under construction, scaffolding is often used to maintain the structure before construction is completed. Scaffolding may be temporary or they may serve as part of the foundation of the building, but despite this, they provide some support when you are just starting out.

Django, like many web frameworks, provides scaffolding for your development. This is done by making decisions and providing a starting point for your code, which allows you to focus on the problem you are trying to solve, rather than how to parse the HTTP request. Django provides scaffolding both for working with HTTP and for working with the file system.

HTTP scaffolding controls, for example, converting an HTTP request into a Python language object, and also provides tools to more easily create server-side responses. File system scaffolding is different: it is a set of conventions for organizing your code. These conventions make it easier to add new engineers to the project, as engineers (hypothetically) already understand how the code is organized. In terms of Django, a project is the final product, and it combines one or more applications within itself . In Django 1.4, the way projects and applications are placed on disk has been changed, making it easier to disconnect and reuse applications in different projects.

1.3.1. Project creation


Django installs a script in the system django-admin.pyto handle scaffold tasks. A task is used to create project files startproject. We will determine the name of the project and the name of the directory in which we want to place the project. Since we are already in an isolated environment, you can simply write:

Translator’s note:
We ~/tutorial/project/’ll go over the directory and in the future we will work only from this directory ( $we will mean by further ~/tutorial/project/$):

(venv:tutorial)~/tutorial/$ cd project

(venv:tutorial)$ django-admin.py startproject addressbook .

The created project has the following structure

manage.py
./addressbook
    __init__.py
    settings.py
    urls.py
    wsgi.py


1.3.2. Project scaffolding


  • manage.py- is a link to a script django-admin, but with predefined environment variables pointing to your project, both for reading settings from there and for managing it if necessary;
  • settings.py- here are the settings for your project. The file already contains several reasonable settings, but the database is not specified;
  • urls.py- contains URLs for mapping (displaying) representations: we will soon ( in subsequent chapters ) talk about this in more detail;
  • wsgi.py- This is a WSGI wrapper for your application. This file is used by the Django development server and possibly other containers, such as mod_wsgi, uwsgietc. on the “battle” server.


1.3.3. Application creation


(venv:tutorial)$ python ./manage.py startapp contacts

The created application has the following structure:

./contacts
    __init__.py
    models.py
    tests.py
    views.py

  • Starting with Django 1.4, applications are hosted inside the project package. This is a wonderful improvement, especially when it comes time to deploy the project on a "battle" server;
  • models.py will contain Django ORM models for your application;
  • views.py will contain the submission code;
  • tests.py will contain unit and integration tests written by you.
  • Django 1.7: admin.pywill contain a model for the administrative interface.
  • Django 1.7: migrations/contains migration files

Translator’s note:
Currently, our directory ~/tutorial/contains a dependency file ( requirements.txt), a directory with a virtual environment ( venv/), one project ( project/addressbook), one application ( project/contacts) and has the following contents:

~/tutorial/
	requirements.txt
	venv/
		...
	project/
		manage.py
		addressbook/
			__init__.py
			settings.py
			urls.py
			wsgi.py
		contacts/
			__init__.py
			models.py
			tests.py
			views.py


Chapter 2. Using the Model


2.1. Database configuration


Django supports out of the box MySQL, PostgreSQL, SQLite3, and Oracle. SQLite3 has been part of Python since version 2.5, so we will use it in our project (for simplicity). If you want to use MySQL, for example, then you need to add mysql-python to yours requirements.txt.

To use SQLite as the database, edit the definition DATABASESin the file addressbook/settings.py. The settings.py file contains the Django settings for our project. It has several settings that you must specify - for example DATABASES- as well as other, optional, settings. Django sets some settings by itself when it generates a project. The documentation contains a complete list of settings.. In addition, you can add your own settings if necessary.

To use SQLite, we need to specify the engine ( ENGINE) and the base name ( NAME). SQLite interprets the database name as the file name for the database:

DATABASES = {
    'defaults': {
	    'ENGINE': 'django.db.backends.sqlite3,' # ’postgresql_psycopg2’, ’mysql’, ’sqlite3’ or ’oracle'.
		'NAME': os.path.join(BASE_DIR, 'address.db'),
		'USER': '',     # Not used with sqlite3.
		'PASSWORD': '', # Not used with sqlite3.
		'HOST': '',     # Set to empty string for localhost. Not used with sqlite3.
		'PORT': '',     # Set to empty string for default. Not used with sqlite3.
	}
}

Note that the database engine is indicated by a string, not a direct reference to a Python object. This is done for the reason that the settings file should be easily imported without causing any third-party effects . You should avoid adding import calls to this file.

You rarely have to directly import the settings file: Django imports it for you, and makes the settings available as django.conf.settings. You typically import settings from django.conf:

from django.conf import settings


2.2. Model creation


Django models display (roughly) database tables, and provide a place to encapsulate business logic. All models are descendants of the base class Model and contain definition fields. Let's create a simple model Contactsfor our application in a file contacts/models.py:

from django.db import models
class Contact(models.Model):
    first_name = models.CharField(
        max_length=255,
    )
    last_name = models.CharField(
        max_length=255,
    )
    email = models.EmailField()
	def __str__(self):
	    return ' '.join([
            self.first_name,
            self.last_name,
        ])

Django provides a set of fields for displaying data types and various validation rules. For example, the EmailFieldone we used is a mapping to a column with a type CharField, but adds data validation.

Once you have created the model, you need to supplement your database with new tables. The Django team syncdblooks at the installed models and creates (if necessary) tables for them:

Примечание переводчика:
Django предложит создать суперпользователя для андминки, которая включена в этой версии по умолчанию. Воспользуйтесь его предложением.
Примечание переводчика:
С версии Django 1.7 во фреймворк добавлена нативная поддержка миграций и команда syncdb объявлена устаревшей. Так что будьте так любезны, воспользуйтесь командой migrate вместо syncdb.

(venv:tutorial)$ python ./manage.py syncdb
Creating tables ...
Creating table django_admin_log
Creating table auth_permission
Creating table auth_group_permissions
Creating table auth_group
Creating table auth_user_groups
Creating table auth_user_user_permissions
Creating table auth_user
Creating table django_content_type
Creating table django_session
You just installed Django's auth system, which means you don't have any superusers defined.
Would you like to create one now? (yes/no): yes
Username (leave blank to use 'bjakushka'):
Email address: 
Password:
Password (again):
Superuser created successfully.
Installing custom SQL ...
installing indexes ...
Installed 0 object(s) from 0 fixture(s)
(venv:tutorial)$
Примечание переводчика:
Если вы используете Django версии 1.7 и выше — вывод будет следующий:


(venv:tutorial)$ python ./manage.py migrate
Opperation to perform:
    Apply all migrations: admin, contenttypes, auth, sessions
Running migrations:
    Applying contenttypes.0001_initial... OK
    Applying auth.0001_initial... OK
    Applying admin.0001_initial... OK
    Applying sessions.0001_initial... OK
(venv:tutorial)$ 

However, our contact table is nowhere to be seen. The reason for this is that we still need to tell the project to use the application.

The setting INSTALLED_APPScontains a list of applications used in the project. This list contains strings that display Python packages. Django will import each of the specified packages, and then watch the module models. Let's add our application contactsto the project settings ( addressbook/settings.py):

INSTALLED_APPS = (
  'django.contrib.admin',
  'django.contrib.auth',
  'django.contrib.contenttypes',
  'django.contrib.sessions',
  'django.contrib.messages',
  'django.contrib.staticfiles',
  'contacts',
)

After that, run syncdbagain:
Translator's note:
For Django version 1.7 and higher, you will need to run the command first makemigrations- to create migrations based on changes in models, and then run the command migrate- in order to apply the created migrations.

(venv:tutorial)$ python ./manage.py syncdb
Creating tables ...
Creating table contacts_contact
Installing custom SQL ...
Installing indexes ...
Installed 0 object(s) from 0 fixture(s)
(venv:tutorial)$
Translator Note:
Output for Django 1.7 and later:


(venv:tutorial)$ python ./manage.py makemigrations
Migrations for 'contacts':
    0001_initial.py:
        - Create model Contact
(venv:tutorial)$ python ./manage.py migrate
Opperation to perform:
    Apply all migrations: admin, contenttypes, sessions, auth, contacts
Running migrations:
    Applying contacts.0001_initial... OK
(venv:tutorial)$ 

Note that Django creates a table with the name contacts_contact: By default, Dj ango gives the tables names using a combination of the application name and model name. You can change this with the Meta model options .

2.3. Model Interaction


Now that the model is synchronized with the database, we can interact with it using the interactive shell:

(venv:tutorial)$ python ./manage.py shell
Python 2.7.3 (default, Mar 14 2014, 11:57:14)
[GCC 4.7.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>> from contacts.models import Contact
>>> Contact.objects.all()
[]
>>> Contact.objects.create(first_name='Nathan', last_name='Yergler')

>>> Contact.objects.all()
[]
>>> nathan = Contact.objects.get(first_name='Nathan')
>>> nathan

>>> print nathan
Nathan Yergler
>>> nathan.id
1

Several new pieces were used here. First, the team manage.py shelllaunches the interactive Python shell for us with the correct paths for Django. If you try to start the Python interpreter and just import your application, an exception will be thrown because Django does not know what settings to use, and cannot map model instances to the database.

Secondly, the property of objectsour model was used here . This is a model manager . So, if one instance of the model is an analogy for a row in the database, then the model manager is an analogy for a table. By default, the model manager provides query functionality and can be customized. When we call all(), filter()or the manager himself, the object returns QuerySet.QuerySetIt is an iterable object and loads data from the database as needed.

And the last - above we used a field with a name idthat we did not define in our model. Django adds this field as the primary key for the model, but only if you yourself have not decided which field will be the primary key .

2.4. Writing tests


Our model defines one method __str__, so it's time to write tests. The method __str__will be used in only a few places, and, quite possibly, will be fully shown to the end user . For this method, it is worth writing a test, while we understand how it works . Django created the file tests.pywhen he created the application, so we will add the first test to this file, the application contacts.

from django.test import TestCase
from contacts.models import Contact
class ContactTests(TestCase):
    """Contact model tests."""
    def test_str(self):
        contact = Contact(first_name='John', last_name='Smith')
        self.assertEquals(
            str(contact),
            'John Smith',
        )

You can run tests for your application using the command manage.py test:

(venv:tutorial)$ python ./manage.py test

If you run this, you will see that about 420 tests have been completed. This is surprising since we wrote only one. This happened because, by default, Django runs tests for all installed applications. When you added the application contactsto our project, you could see that several Django built-in applications were added there by default. An additional 419 tests were taken from there.
Translator's note:
In our case (when using Django version 1.6.7), the previous paragraph is a little outdated: only one test will run - the one we created. The output of the command will be as follows.

If you want to run tests for a specific application - specify the name of the application in the command:

(venv:tutorial)$ python manage.py test contacts
Creating test database for alias ’default’...
.
----------------------------------------------------------------------
Ran 1 tests in 0.001s
OK
Destroying test database for alias ’default’...
(venv:tutorial)$

Another interesting thing to note before moving on is the first and last line of output: Creating test databaseand Destroying test database. Some tests require access to the database, and since we don’t want to interfere with the test data with the “real” ones (for various reasons, not the least of which is predetermination), Django helpfully creates a test database for us before running the tests. Essentially, a new database is created, and then launched syncdbfor it. If the test class is a descendant of the class TestCase(like ours), Django will also reset the data to default values ​​after running each test, so that changes in one of the tests will not affect the others.

2.5. Summary


  • The model defines the fields in the table, and contains business logic.
  • Команда syncdb создает таблицы в вашей базе данных из моделей. В Django версии 1.7 и выше вместо команды syncdb необходимо использовать сначала команду makemigrations — для создания миграций, а после этого команду migrate — для внесение изменений в базу.
  • Менеджер модели позволяет вам оперировать коллекциями экземпляров: запросы, создание и т. д..
  • Пишите модульные тесты для методов, которые вы добавили в модель.
  • Команда управления test запускает модульные тесты на выполнение.

Примечание переводчика:
Для того чтобы протестировать наше, пока еще пустое, приложение нужно выполнить следующую команду:

(venv:tutorial)$ python ./manage.py runserver 0.0.0.0:8080

Это запустит встроенный сервер, функционал которого любезно предоставляет нам Django. В параметрах после runserver указывается ip-адрес и порт, который будет слушаться работающим сервер. В нашем случае сервер будет принимать запросы от всех ip-адресов при обращении на 8080 порт.

Я использую для разработки домашний сервер с внутренним IP 192.168.1.51. Так что для того что-бы увидеть результат работы сервера разработки в браузере я захожу по адресу http://192.168.1.51:8080/. Вы же должны подставить адрес своего сервера.




Do you think it necessary to continue the translation of the remaining chapters? Did you find the translation useful?
I will be glad to constructive criticism in the comments.
Please inform about errors and inaccuracies in the translation in a personal message.

The image used at the beginning of the post was created as a variation of the image of the user MaGIc2laNTern

Also popular now: