In this chapter, we will cover the following topics:
Working with a virtual environment
Creating a project file structure
Handling project dependencies with pip
Making your code compatible with both Python 2.7 and Python 3
Including external dependencies in your project
Configuring settings for development, testing, staging, and production environments
Defining relative paths in the settings
Creating and including local settings
Setting up STATIC_URL dynamically for Subversion users
Setting up STATIC_URL dynamically for Git users
Setting UTF-8 as the default encoding for MySQL configuration
Setting the Subversion ignore property
Creating a Git ignore file
Deleting Python-compiled files
Respecting the import order in Python files
Creating app configuration
Defining overwritable app settings
In this chapter, we will see a few good practices when starting a new project with Django 1.8 on Python 2.7 or Python 3. Some of the tricks introduced here are the best ways to deal with the project layout, settings, and configurations. However, for some tricks, you might have to find some alternatives online or in other books about Django. Feel free to evaluate and choose the best bits and pieces for yourself while digging deep into the Django world.
I am assuming that you are already familiar with the basics of Django, Subversion and Git version control, MySQL and PostgreSQL databases, and command-line usage. Also, I am assuming that you are probably using a Unix-based operating system, such as Mac OS X or Linux. It makes more sense to develop with Django on Unix-based platforms as the websites will most likely be published on a Linux server, therefore, you can establish routines that work the same while developing as well as deploying. If you are locally working with Django on Windows, the routines are similar; however, they are not always the same.
It is very likely that you will develop multiple Django projects on your computer. Some modules such as Python Imaging Library (or Pillow) and MySQLdb, can be installed once and then shared for all projects. Other modules such as Django, third-party Python libraries, and Django apps, will need to be kept isolated from each other. The virtualenv tool is a utility that separates all the Python projects in their own realms. In this recipe, we will see how to use it.
To manage Python packages, you will need pip
. It is included in your Python installation if you are using Python 2.7.9 or Python 3.4+. If you are using another version of Python, install pip
by executing the installation instructions at http://pip.readthedocs.org/en/stable/installing/. Let's install the shared Python modules Pillow and MySQLdb, and the virtualenv utility, using the following commands:
$ sudo pip install Pillow $ sudo pip install MySQL-python $ sudo pip install virtualenv
Once you have your prerequisites installed, create a directory where all your Django projects will be stored, for example, virtualenvs
under your home directory. Perform the following steps after creating the directory:
Go to the newly created directory and create a virtual environment that uses the shared system site packages:
$ cd ~/virtualenvs $ mkdir myproject_env $ cd myproject_env $ virtualenv --system-site-packages . New python executable in ./bin/python Installing setuptools………….done. Installing pip……………done.
To use your newly created virtual environment, you need to execute the activation script in your current shell. This can be done with the following command:
$ source bin/activate
You can also use the following command one for the same (note the space between the dot and bin):
$ . bin/activate
You will see that the prompt of the command-line tool gets a prefix of the project name, as follows:
(myproject_env)$
To get out of the virtual environment, type the following command:
$ deactivate
When you create a virtual environment, a few specific directories (bin
, build
, include
, and lib
) are created in order to store a copy of the Python installation and some shared Python paths are defined. When the virtual environment is activated, whatever you have installed with pip
or easy_install
will be put in and used by the site packages of the virtual environment, and not the global site packages of your Python installation.
To install Django 1.8 in your virtual environment, type the following command:
(myproject_env)$ pip install Django==1.8
The Creating a project file structure recipe
The Deploying on Apache with mod_wsgi recipe in Chapter 11, Testing and Deployment
A consistent file structure for your projects makes you well-organized and more productive. When you have the basic workflow defined, you can get in the business logic quicker and create awesome projects.
If you haven't done this yet, create a virtualenvs
directory, where you will keep all your virtual environments (read about this in the Working with a virtual environment recipe). This can be created under your home directory.
Then, create a directory for your project's environment, for example, myproject_env
. Start the virtual environment in it. I would suggest adding the commands
directory for local bash scripts that are related to the project, the db_backups
directory for database dumps, and the project
directory for your Django project. Also, install Django in your virtual environment.
Follow these steps in order to create a file structure for your project:
With the virtual environment activated, go to the project directory and start a new Django project as follows:
(myproject_env)$ django-admin.py startproject myproject
For clarity, we will rename the newly created directory as
django-myproject
. This is the directory that you will put under version control, therefore, it will have the.git
,.svn
, or similar directories.In the
django-myproject
directory, create aREADME.md
file to describe your project to the new developers. You can also put the pip requirements with the Django version and include other external dependencies (read about this in the Handling project dependencies with pip recipe). Also, this directory will contain your project's Python package namedmyproject
; Django apps (I recommend having an app calledutils
for different functionalities that are shared throughout the project); alocale
directory for your project translations if it is multilingual; a Fabric deployment script namedfabfile.py
, as suggested in the Creating and using the Fabric deployment script recipe in Chapter 11, Testing and Deployment; and theexternals
directory for external dependencies that are included in this project if you decide not to use pip requirements.In your project's Python package,
myproject
, create themedia
directory for project uploads, thesite_static
directory for project-specific static files, thestatic
directory for collected static files, thetmp
directory for the upload procedure, and thetemplates
directory for project templates. Also, themyproject
directory should contain your project settings, thesettings.py
andconf
directories (read about this in the Configuring settings for development, testing, staging, and production environments recipe), as well as theurls.py
URL configuration.In your
site_static
directory, create thesite
directory as a namespace for site-specific static files. Then, separate the separated static files in directories in it. For instance,scss
for Sass files (optional),css
for the generated minified Cascading Style Sheets,img
for styling images and logos,js
for JavaScript, and any third-party module combining all types of files such as the tinymce rich-text editor. Besides thesite
directory, thesite_static
directory might also contain overwritten static directories of third-party apps, for example,cms
overwriting static files from Django CMS. To generate the CSS files from Sass and minify the JavaScript files, you can use the CodeKit or Prepros applications with a graphical user interface.Put your templates that are separated by the apps in your templates directory. If a template file represents a page (for example,
change_item.html
oritem_list.html
), then directly put it in the app's template directory. If the template is included in another template (for example,similar_items.html
), put it in the includes subdirectory. Also, your templates directory can contain a directory calledutils
for globally reusable snippets, such as pagination, language chooser, and others.
The whole file structure for a complete project in a virtual environment will look similar to the following:
The Handling project dependencies with pip recipe
The Including external dependencies in your project recipe
The Configuring settings for development, testing, staging, and production environments recipe
The Deploying on Apache with mod_wsgi recipe in Chapter 11, Testing and Deployment
The Creating and using the Fabric deployment script recipe in Chapter 11, Testing and Deployment
The pip is the most convenient tool to install and manage Python packages. Besides installing the packages one by one, it is possible to define a list of packages that you want to install and pass it to the tool so that it deals with the list automatically.
You will need to have at least two different instances of your project: the development environment, where you create new features, and the public website environment that is usually called the production environment in a hosted server. Additionally, there might be development environments for other developers. Also, you may have a testing and staging environment in order to test the project locally and in a public website-like situation.
For good maintainability, you should be able to install the required Python modules for development, testing, staging, and production environments. Some of the modules will be shared and some of them will be specific. In this recipe, we will see how to organize the project dependencies and manage them with pip.
Before using this recipe, you need to have pip installed and a virtual environment activated. For more information on how to do this, read the Working with a virtual environment recipe.
Execute the following steps one by one to prepare pip requirements for your Django project:
Let's go to your Django project that you have under version control and create the
requirements
directory with these text files:base.txt
for shared modules,dev.txt
for development environment,test.txt
for testing environment,staging.txt
for staging environment, andprod.txt
for production.Edit
base.txt
and add the Python modules that are shared in all environments, line by line, for example:# base.txt Django==1.8 djangorestframework -e git://github.com/omab/python-social-auth.git@6b1e301c79#egg=python-social-auth
If the requirements of a specific environment are the same as in the
base.txt
, add the line including thebase.txt
in the requirements file of that environment, for example:# prod.txt -r base.txt
If there are specific requirements for an environment, add them as shown in the following:
# dev.txt -r base.txt django-debug-toolbar selenium
Now, you can run the following command in order to install all the required dependencies for development environment (or analogous command for other environments), as follows:
(myproject_env)$ pip install -r requirements/dev.txt
The preceding command downloads and installs all your project dependencies from requirements/base.txt
and requirements/dev.txt
in your virtual environment. As you can see, you can specify a version of the module that you need for the Django framework and even directly install from a specific commit at the Git repository for the python-social-auth
in our example. In practice, installing from a specific commit would rarely be useful, for instance, only when having third-party dependencies in your project with specific functionality that are not supported in the recent versions anymore.
When you have many dependencies in your project, it is good practice to stick to specific versions of the Python modules as you can then be sure that when you deploy your project or give it to a new developer, the integrity doesn't get broken and all the modules function without conflicts.
If you have already manually installed the project requirements
with pip one by one, you can generate the requirements/base.txt
file using the following command:
(myproject_env)$ pip freeze > requirements/base.txt
If you want to keep things simple and are sure that, for all environments, you will be using the same dependencies, you can use just one file for your requirements named requirements.txt
, by definition:
(myproject_env)$ pip freeze > requirements.txt
To install the modules in a new environment simply call the following command:
(myproject_env)$ pip install -r requirements.txt
Note
If you need to install a Python library from other version control system or local path, you can learn more about pip from the official documentation at http://pip-python3.readthedocs.org/en/latest/reference/pip_install.html.
Since version 1.7, Django can be used with Python 2.7 and Python 3. In this recipe, we will take a look at the operations to make your code compatible with both the Python versions.
When creating a new Django project or upgrading an old existing project, consider following the rules given in this recipe.
Making your code compatible with both Python versions consists of the following steps:
At the top of each module, add
from
__future__ import unicode_literals
and then use usual quotes without au
prefix for Unicode strings and ab
prefix for bytestrings.To ensure that a value is bytestring, use the
django.utils.encoding.smart_bytes
function. To ensure that a value is Unicode, use thedjango.utils.encoding.smart_text
ordjango.utils.encoding.force_text
function.For your models, instead of the
__unicode__
method, use the__str__
method and add thepython_2_unicode_compatible
decorator, as follows:# models.py # -*- coding: UTF-8 -*- from __future__ import unicode_literals from django.db import models from django.utils.translation import ugettext_lazy as _ from django.utils.encoding import \ python_2_unicode_compatible @python_2_unicode_compatible class NewsArticle(models.Model): title = models.CharField(_("Title"), max_length=200) content = models.TextField(_("Content")) def __str__(self): return self.title class Meta: verbose_name = _("News Article") verbose_name_plural = _("News Articles")
To iterate through dictionaries, use
iteritems()
,iterkeys()
, anditervalues()
fromdjango.utils.six
. Take a look at the following:from django.utils.six import iteritems d = {"imported": 25, "skipped": 12, "deleted": 3} for k, v in iteritems(d): print("{0}: {1}".format(k, v))
When you capture exceptions, use the
as
keyword, as follows:try: article = NewsArticle.objects.get(slug="hello-world") except NewsArticle.DoesNotExist as exc: pass except NewsArticle.MultipleObjectsReturned as exc: pass
To check the type of a value, use
django.utils.six
, as shown in the following:from django.utils import six isinstance(val, six.string_types) # previously basestring isinstance(val, six.text_type) # previously unicode isinstance(val, bytes) # previously str isinstance(val, six.integer_types) # previously (int, long)
Instead of
xrange
, userange
fromdjango.utils.six.moves
, as follows:from django.utils.six.moves import range for i in range(1, 11): print(i)
To check whether the current version is Python 2 or Python 3, you can use the following conditions:
from django.utils import six if six.PY2: print("This is Python 2") if six.PY3: print("This is Python 3")
All strings in Django projects should be considered as Unicode strings. Only the input of HttpRequest
and output of HttpResponse
is usually in the UTF-8 encoded bytestring.
Many functions and methods in Python 3 now return the iterators instead of lists, which make the language more efficient. To make the code compatible with both the Python versions, you can use the six library that is bundled in Django.
Read more about writing compatible code in the official Django documentation at https://docs.djangoproject.com/en/1.8/topics/python3/.
Tip
Downloading the example code
You can download the example code files for all Packt books that you have purchased from your account at http://www.packtpub.com. If you purchased this book elsewhere, you can visit http://www.packtpub.com/support and register in order to have the files e-mailed directly to you.
Sometimes, it is better to include external dependencies in your project. This ensures that whenever a developer upgrades third-party modules, all the other developers will receive the upgraded version in the next update from the version control system (Git, Subversion, or others).
Also, it is better to have external dependencies included in your project when the libraries are taken from unofficial sources, that is, somewhere other than Python Package Index (PyPI), or different version control systems.
Execute the following steps one by one:
If you haven't done this already, create an externals directory under your Django project
django-myproject
directory. Then, create thelibs
andapps
directories under it.The
libs
directory is for the Python modules that are required by your project, for example, boto, Requests, Twython, Whoosh, and so on. Theapps
directory is for third-party Django apps, for example, django-cms, django-haystack, django-storages, and so on.The directory structure should look something similar to the following:
The next step is to put the external libraries and apps under the Python path so that they are recognized as if they were installed. This can be done by adding the following code in the settings:
# settings.py # -*- coding: UTF-8 -*- from __future__ import unicode_literals import os import sys BASE_DIR = os.path.abspath(os.path.join( os.path.dirname(__file__), ".." )) EXTERNAL_LIBS_PATH = os.path.join( BASE_DIR, "externals", "libs" ) EXTERNAL_APPS_PATH = os.path.join( BASE_DIR, "externals", "apps" ) sys.path = ["", EXTERNAL_LIBS_PATH, EXTERNAL_APPS_PATH] + \ sys.path
A module is meant to be under the Python path if you can run Python and import that module. One of the ways to put a module under the Python path is to modify the sys.path
variable before importing a module that is in an unusual location. The value of sys.path
is a list of directories starting with an empty string for the current directory, followed by the directories in the virtual environment, and finally the globally shared directories of the Python installation. You can see the value of sys.path
in the Python shell, as follows:
(myproject_env)$ python >>> import sys >>> sys.path
When trying to import a module, Python searches for the module in this list and returns the first result that is found.
Therefore, we first define the BASE_DIR
variable, which is the absolute path to one level higher than the settings.py
file. Then, we define the EXTERNAL_LIBS_PATH
and EXTERNAL_APPS_PATH
variables, which are relative to BASE_DIR
. Lastly, we modify the sys.path
property, adding new paths to the beginning of the list. Note that we also add an empty string as the first path to search, which means that the current directory of any module should always be checked first before checking other Python paths.
The Creating a project file structure recipe
The Handling project dependencies with pip recipe
The Defining relative paths in the settings recipe
The Using the Django shell recipe in Chapter 10, Bells and Whistles
As noted earlier, you will be creating new features in the development environment, test them in the testing environment, then put the website to a staging server to let other people to try the new features, and lastly, the website will be deployed to the production server for public access. Each of these environments can have specific settings and you will see how to organize them in this recipe.
In a Django project, we'll create settings for each environment: development, testing, staging, and production.
Follow these steps to configure project settings:
In
myproject
directory, create aconf
Python module with the following files:__init__.py
,base.py
for shared settings,dev.py
for development settings,test.py
for testing settings,staging.py
for staging settings, andprod.py
for production settings.Put all your shared settings in
conf/base.py
.If the settings of an environment are the same as the shared settings, then just import everything from
base.py
there, as follows:# myproject/conf/prod.py # -*- coding: UTF-8 -*- from __future__ import unicode_literals from .base import *
Apply the settings that you want to attach or overwrite for your specific environment in the other files, for example, the development environment settings should go to
dev.py
as shown in the following:# myproject/conf/dev.py # -*- coding: UTF-8 -*- from __future__ import unicode_literals from .base import * EMAIL_BACKEND = \ "django.core.mail.backends.console.EmailBackend"
At the beginning of the
myproject/settings.py
, import the configurations from one of the environment settings and then additionally attach specific or sensitive configurations such asDATABASES
orAPI
keys that shouldn't be under version control, as follows:# myproject/settings.py # -*- coding: UTF-8 -*- from __future__ import unicode_literals from .conf.dev import * DATABASES = { "default": { "ENGINE": "django.db.backends.mysql", "NAME": "myproject", "USER": "root", "PASSWORD": "root", } }
Create a
settings.py.sample
file that should contain all the sensitive settings that are necessary for a project to run; however, with empty values set.
By default, the Django management commands use the settings from myproject/settings.py
. Using the method that is defined in this recipe, we can keep all the required non-sensitive settings for all environments under version control in the conf directory. Whereas, the settings.py
file itself would be ignored by version control and will only contain the settings that are necessary for the current development, testing, staging, or production environments.
Django requires you to define different file paths in the settings, such as the root of your media, the root of your static files, the path to templates, the path to translation files, and so on. For each developer of your project, the paths may differ as the virtual environment can be set up anywhere and the user might be working on Mac OS X, Linux, or Windows. Anyway, there is a way to define these paths that are relative to your Django project directory.
Modify your path-related settings accordingly instead of hardcoding the paths to your local directories, as follows:
# settings.py # -*- coding: UTF-8 -*- from __future__ import unicode_literals import os BASE_DIR = os.path.abspath( os.path.join(os.path.dirname(__file__), "..") ) MEDIA_ROOT = os.path.join(BASE_DIR, "myproject", "media") STATIC_ROOT = os.path.join(BASE_DIR, "myproject", "static") STATICFILES_DIRS = ( os.path.join(BASE_DIR, "myproject", "site_static"), ) TEMPLATE_DIRS = ( os.path.join(BASE_DIR, "myproject", "templates"), ) LOCALE_PATHS = ( os.path.join(BASE_DIR, "locale"), ) FILE_UPLOAD_TEMP_DIR = os.path.join( BASE_DIR, "myproject", "tmp" )
Configuration doesn't necessarily need to be complex. If you want to keep things simple, you can work with two settings files: settings.py
for common configuration and local_settings.py
for sensitive settings that shouldn't be under version control.
Most of the settings for different environments will be shared and saved in version control. However, there will be some settings that are specific to the environment of the project instance, for example, database or e-mail settings. We will put them in the local_settings.py
file.
To use local settings in your project, perform the following steps:
At the end of
settings.py
, add a version oflocal_settings.py
that claims to be in the same directory, as follows:# settings.py # … put this at the end of the file … try: execfile(os.path.join( os.path.dirname(__file__), "local_settings.py" )) except IOError: pass
Create
local_settings.py
and put your environment-specific settings there, as shown in the following:# local_settings.py DATABASES = { "default": { "ENGINE": "django.db.backends.mysql", "NAME": "myproject", "USER": "root", "PASSWORD": "root", } } EMAIL_BACKEND = \ "django.core.mail.backends.console.EmailBackend" INSTALLED_APPS += ( "debug_toolbar", )
As you can see, the local settings are not normally imported, they are rather included and executed in the settings.py
file itself. This allows you to not only create or overwrite the existing settings, but also adjust the tuples or lists from the settings.py
file. For example, we add debug_toolbar
to INSTALLED_APPS
here in order to be able to debug the SQL queries, template context variables, and so on.
The Creating a project file structure recipe
The Toggling the Debug Toolbar recipe in Chapter 10, Bells and Whistles
If you set STATIC_URL
to a static value, then each time you update a CSS file, JavaScript file, or image, you will need to clear the browser cache in order to see the changes. There is a trick to work around clearing the browser's cache. It is to have the revision number of the version control system shown in STATIC_URL
. Whenever the code is updated, the visitor's browser will force the loading of all-new static files.
This recipe shows how to put a revision number in STATIC_URL
for subversion users.
Make sure that your project is under the subversion version control and you have BASE_DIR
defined in your settings, as shown in the Defining relative paths in the settings recipe.
Then, create the utils
module in your Django project, and also create a file called misc.py
there.
The procedure to put the revision number in the STATIC_URL
setting consists of the following two steps:
Insert the following content:
# utils/misc.py # -*- coding: UTF-8 -*- from __future__ import unicode_literals import subprocess def get_media_svn_revision(absolute_path): repo_dir = absolute_path svn_revision = subprocess.Popen( 'svn info | grep "Revision" | awk \'{print $2}\'', stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True, cwd=repo_dir, universal_newlines=True) rev = svn_revision.communicate()[0].partition('\n')[0] return rev
Then, modify the
settings.py
file and add the following lines:# settings.py # … somewhere after BASE_DIR definition … from utils.misc import get_media_svn_revision STATIC_URL = "/static/%s/" % get_media_svn_revision(BASE_DIR)
The get_media_svn_revision()
function takes the absolute_path
directory as a parameter and calls the svn
info shell command in that directory to find out the current revision. We pass BASE_DIR
to the function as we are sure that it is under version control. Then, the revision is parsed, returned, and included in the STATIC_URL
definition.
If you don't want to refresh the browser cache each time you change your CSS and JavaScript files, or while styling images, you need to set STATIC_URL
dynamically with a varying path component. With the dynamically changing URL, whenever the code is updated, the visitor's browser will force loading of all-new uncached static files. In this recipe, we will set a dynamic path for STATIC_URL
when you use the Git version control system.
Make sure that your project is under the Git version control and you have BASE_DIR
defined in your settings, as shown in the Defining relative paths in the settings recipe.
If you haven't done it yet, create the utils
module in your Django project. Also, create a misc.py
file there.
The procedure to put the Git timestamp in the STATIC_URL
setting consists of the following two steps:
Add the following content to the
misc.py
file placed inutils/
:# utils/misc.py # -*- coding: UTF-8 -*- from __future__ import unicode_literals import subprocess from datetime import datetime def get_git_changeset(absolute_path): repo_dir = absolute_path git_show = subprocess.Popen( 'git show --pretty=format:%ct --quiet HEAD', stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True, cwd=repo_dir, universal_newlines=True, ) timestamp = git_show.communicate()[0].partition('\n')[0] try: timestamp = \ datetime.utcfromtimestamp(int(timestamp)) except ValueError: return "" changeset = timestamp.strftime('%Y%m%d%H%M%S') return changeset
Then, import the newly created
get_git_changeset()
function in the settings and use it for theSTATIC_URL
path, as follows:# settings.py # … somewhere after BASE_DIR definition … from utils.misc import get_git_changeset STATIC_URL = "/static/%s/" % get_git_changeset(BASE_DIR)
The get_git_changeset()
function takes the absolute_path
directory as a parameter and calls the git
show shell command with the parameters to show the Unix timestamp of the HEAD
revision in the directory. As stated in the previous recipe, we pass BASE_DIR
to the function as we are sure that it is under version control. The timestamp is parsed; converted to a string consisting of year, month, day, hour, minutes, and seconds; returned; and included in the definition of STATIC_URL
.
MySQL is the most popular open source database. In this recipe, I will tell you how to set UTF-8 as the default encoding for it. Note that if you don't set this encoding in the database configuration, you might get into a situation where LATIN1 is used by default with your UTF-8 encoded data. This will lead to database errors whenever symbols such as € are used. Also, this recipe will save you from the difficulties of converting the database data from LATIN1 to UTF-8, especially when you have some tables encoded in LATIN1 and others in UTF-8.
Make sure that the MySQL database management system and the MySQLdb Python module are installed and you are using the MySQL engine in your project's settings.
Open the /etc/mysql/my.cnf
MySQL configuration file in your favorite editor and ensure that the following settings are set in the sections: [client]
, [mysql]
, and [mysqld]
, as follows:
# /etc/mysql/my.cnf
[client]
default-character-set = utf8
[mysql]
default-character-set = utf8
[mysqld]
collation-server = utf8_unicode_ci
init-connect = 'SET NAMES utf8'
character-set-server = utf8
If any of the sections don't exist, create them in the file. Then, restart MySQL in your command-line tool, as follows:
$ /etc/init.d/mysql restart
If you are using Subversion for version control, you will need to keep most of the projects in the repository; however, some files and directories should only stay locally and not be tracked.
Open your command-line tool and set your default editor as nano
, vi
, vim
or any other that you prefer, as follows:
$ export EDITOR=nano
Tip
If you don't have a preference, I would recommend using nano
, which is very intuitive and a simple text editor for the terminal.
Then, go to your project directory and type the following command:
$ svn propedit svn:ignore myproject
This will open a temporary file in the editor, where you need to put the following file and directory patterns for Subversion to ignore:
# Project files and directories local_settings.py static media tmp # Byte-compiled / optimized / DLL files __pycache__ *.py[cod] *$py.class
# C extensions *.so # PyInstaller *.manifest *.spec # Installer logs pip-log.txt pip-delete-this-directory.txt # Unit test / coverage reports htmlcov .tox .coverage .coverage.* .cache nosetests.xml coverage.xml *.cover # Translations *.pot # Django stuff: *.log # PyBuilder target
Save the file and exit the editor. For every other Python package in your project, you will need to ignore several files and directories too. Just go to a directory and type the following command:
$ svn propedit svn:ignore .
Then, put this in the temporary file, save it, and close the editor, as follows:
# Byte-compiled / optimized / DLL files __pycache__ *.py[cod] *$py.class
# C extensions *.so # PyInstaller *.manifest *.spec # Installer logs pip-log.txt pip-delete-this-directory.txt # Unit test / coverage reports htmlcov .tox .coverage .coverage.* .cache nosetests.xml coverage.xml *.cover # Translations *.pot # Django stuff: *.log # PyBuilder target
In Subversion, you need to define the ignore properties for each directory of your project. Mainly, we don't want to track the Python-compiled files, for instance, *.pyc
. We also want to ignore local_settings.py
that is specific for each environment, static
that replicates collected static files from different apps, media
that contains uploaded files and changes together with the database, and tmp
that is temporarily used for file uploads.
If you are using Git—the most popular distributed version control system—ignoring some files and folders from version control is much easier than with Subversion.
Using your favorite text editor, create a .gitignore
file at the root of your Django project and put these files and directories there, as follows:
# .gitignore
# Project files and directories
/myproject/local_settings.py
/myproject/static/
/myproject/tmp/
/myproject/media/
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# PyInstaller
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
# Translations
*.pot
# Django stuff:
*.log
# Sphinx documentation
docs/_build/
# PyBuilder
target/
The .gitignore
file specifies the paths that should intentionally be untracked by the Git version control system. The .gitignore
file that we created in this recipe will ignore the Python-compiled files, local settings, collected static files, temporary directory for uploads, and media directory with the uploaded files.
When you run your project for the first time, Python compiles all your *.py
code in bytecode-compiled files, *.pyc
, which are used later for execution.
Normally, when you change the *.py
files, *.pyc
is recompiled; however, sometimes when switching branches or moving the directories, you need to clean up the compiled files manually.
Use your favorite editor and edit or create a .bash_profile
file in your home directory.
Add this alias at the end of .bash_profile
, as follows:
# ~/.bash_profile
alias delpyc="find . -name \"*.pyc\" -delete"
Now, to clean the Python-compiled files, go to your project directory and type the following command in the command line:
$ delpyc
At first, we create a Unix alias that searches for the *.pyc
files and deletes them in the current directory and its children. The .bash_profile
file is executed when you start a new session in the command-line tool.
When you create the Python modules, it is good practice to stay consistent with the structure in the files. This makes it easier for other developers and yourself to read the code. This recipe will show you how to structure your imports.
Use the following structure in a Python file that you create. Just after the first line that defines UTF-8 as the default Python file encoding, put the imports categorized in sections, as follows:
# -*- coding: UTF-8 -*- # System libraries from __future__ import unicode_literals import os import re from datetime import datetime # Third-party libraries import boto from PIL import Image # Django modules from django.db import models from django.conf import settings # Django apps from cms.models import Page # Current-app modules from . import app_settings
We have five main categories for the imports, as follows:
System libraries for packages in the default installation of Python
Third-party libraries for the additionally installed Python packages
Django modules for different modules from the Django framework
Current-app modules for relative imports from the current app
When coding in Python and Django, use the official style guide for Python code, PEP 8. You can find it at https://www.python.org/dev/peps/pep-0008/.
When developing a website with Django, you create one module for the project itself and then, multiple Python modules called applications or apps that combine the different modular functionalities and usually consist of models, views, forms, URL configurations, management commands, migrations, signals, tests, and so on. The Django framework has application registry, where all apps and models are collected and later used for configuration and introspection. Since Django 1.7, meta information about apps can be saved in the AppConfig
instance for each used app. Let's create a sample magazine
app to take a look at how to use the app configuration there.
Either create your Django app manually or using this command in your virtual environment (learn how to use virtual environments in the Working with a virtual environment recipe), as follows:
(myproject_env)$ django-admin.py startapp magazine
Add some NewsArticle
model to models.py
, create administration for the model in admin.py
, and put "magazine"
in INSTALLED_APPS
in the settings. If you are not yet familiar with these tasks, study the official Django tutorial at https://docs.djangoproject.com/en/1.8/intro/tutorial01/.
Follow these steps to create and use the app configuration:
First of all, create the
apps.py
file and put this content in it, as follows:# magazine/apps.py # -*- coding: UTF-8 -*- from __future__ import unicode_literals from django.apps import AppConfig from django.utils.translation import ugettext_lazy as _ class MagazineAppConfig(AppConfig): name = "magazine" verbose_name = _("Magazine") def ready(self): from . import signals
Then, edit the
__init__.py
file of the app and put the following content:# magazine/__init__.py # -*- coding: UTF-8 -*- from __future__ import unicode_literals default_app_config = "magazine.apps.MagazineAppConfig"
Lastly, let's create a
signals.py
file and add some signal handlers there:# magazine/signals.py # -*- coding: UTF-8 -*- from __future__ import unicode_literals from django.db.models.signals import post_save, post_delete from django.dispatch import receiver from django.conf import settings from .models import NewsArticle @receiver(post_save, sender=NewsArticle) def news_save_handler(sender, **kwargs): if settings.DEBUG: print("%s saved." % kwargs['instance']) @receiver(post_delete, sender=NewsArticle) def news_delete_handler(sender, **kwargs): if settings.DEBUG: print("%s deleted." % kwargs['instance'])
When you run an HTTP server or invoke a management command, django.setup()
is called. It loads the settings, sets up logging, and initializes app registry. The app registry is initialized in three steps, as shown in the following:
Django imports the configurations for each item from
INSTALLED_APPS
in the settings. These items can point to app names or configuration directly, for example,"magazine"
or"magazine.apps.NewsAppConfig"
.Django tries to import
models.py
from each app inINSTALLED_APPS
and collect all the models.Finally, Django runs the
ready()
method for each app configuration. This method is a correct place to register signal handlers, if you have any. Theready()
method is optional.In our example, the
MagazineAppConfig
class sets the configuration for themagazine
app. Thename
parameter defines the name of the current app. Theverbose_name
parameter is used in the Django model administration, where models are presented and grouped by apps. Theready()
method imports and activates the signal handlers that, when in DEBUG mode, print in the terminal that aNewsArticle
was saved or deleted.
After calling django.setup()
, you can load the app configurations and models from the registry as follows:
>>> from django.apps import apps as django_apps >>> magazine_app_config = django_apps.get_app_config("magazine") >>> magazine_app_config <MagazineAppConfig: magazine> >>> magazine_app_config.models_module <module 'magazine.models' from 'magazine/models.pyc'> NewsArticle = django_apps.get_model("magazine", "NewsArticle")
You can read more about app configuration in the official Django documentation at https://docs.djangoproject.com/en/1.8/ref/applications/
The Working with a virtual environment recipe
The Defining overwritable app settings recipe
Chapter 6, Model Administration
This recipe will show you how to define settings for your app that can be then overwritten in your project's settings.py
or local_settings.py
file. This is useful especially for reusable apps.
Either create your Django app manually or using the following command:
(myproject_env)$ django-admin.py startapp myapp1
If you just have one or two settings, you can use the following pattern in your models.py
file. If the settings are extensive and you want to have them organized better, create an app_settings.py
file in the app and put the settings in the following way:
# models.py or app_settings.py
# -*- coding: UTF-8 -*-
from __future__ import unicode_literals
from django.conf import settings
from django.utils.translation import ugettext_lazy as _
SETTING1 = getattr(settings, "MYAPP1_SETTING1", u"default value")
MEANING_OF_LIFE = getattr(settings, "MYAPP1_MEANING_OF_LIFE", 42)
STATUS_CHOICES = getattr(settings, "MYAPP1_STATUS_CHOICES", (
("draft", _("Draft")),
("published", _("Published")),
("not_listed", _("Not Listed")),
))
Then, you can use the app settings in models.py
, as follows:
# models.py
# -*- coding: UTF-8 -*-
from __future__ import unicode_literals
from django.db import models
from django.utils.translation import ugettext_lazy as _
from .app_settings import STATUS_CHOICES
class NewsArticle(models.Model):
# …
status = models.CharField(_("Status"),
max_length=20, choices=STATUS_CHOICES
)
If you want to overwrite the STATUS_CHOICES
setting for just one project, you simply open settings.py
and add the following:
# settings.py
# …
from django.utils.translation import ugettext_lazy as _
MYAPP1_STATUS_CHOICES = (
("imported", _("Imported")),
("draft", _("Draft")),
("published", _("Published")),
("not_listed", _("Not Listed")),
("expired", _("Expired")),
)
The getattr(object, attribute_name[, default_value])
Python function tries to get the attribute_name
attribute from object and returns default_value
if it is not found. In this case, different settings are tried in order to be taken from the Django project settings module, and if they are not found, the default values are assigned.