├── .nojekyll
├── settings.py
├── CNAME
├── _static
├── diving.jpg
├── building.jpg
├── eb_logo.gif
├── eb_logo_bw.png
├── tutorial
│ ├── boostrapped.png
│ ├── confirm_email.png
│ ├── TemplateDoesNotExist.png
│ └── authz-login-pagenotfound.png
└── custom.css
├── requirements.txt
├── handouts
├── Effective-Django-OSCON-2013.pdf
└── Effective-Django-PyCon-2013.pdf
├── scratchpad
├── form-resources.rst
└── gettingstarted.rst
├── .gitignore
├── LICENSE
├── .gitmodules
├── further-reading.rst
├── _templates
└── layout.html
├── acknowledgments.rst
├── README.rst
├── testing.rst
├── middleware.rst
├── tutorial
├── before.rst
├── index.rst
├── related.rst
├── static.rst
├── additional-views.rst
├── authzn.rst
├── getting-started.rst
├── models.rst
├── forms.rst
└── views.rst
├── index.rst
├── classbasedviews.rst
├── intro.rst
├── Makefile
├── orm.rst
├── conf.py
└── forms.rst
/.nojekyll:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/settings.py:
--------------------------------------------------------------------------------
1 | #
2 |
--------------------------------------------------------------------------------
/CNAME:
--------------------------------------------------------------------------------
1 | www.effectivedjango.com
--------------------------------------------------------------------------------
/_static/diving.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nyergler/effective-django/HEAD/_static/diving.jpg
--------------------------------------------------------------------------------
/_static/building.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nyergler/effective-django/HEAD/_static/building.jpg
--------------------------------------------------------------------------------
/_static/eb_logo.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nyergler/effective-django/HEAD/_static/eb_logo.gif
--------------------------------------------------------------------------------
/_static/eb_logo_bw.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nyergler/effective-django/HEAD/_static/eb_logo_bw.png
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | Django==1.5.4
2 | Sphinx~=1.3
3 | rebar==0.1
4 | sphinxcontrib-blockdiag~=1.5
5 | hieroglyph
6 | tut
7 |
--------------------------------------------------------------------------------
/_static/tutorial/boostrapped.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nyergler/effective-django/HEAD/_static/tutorial/boostrapped.png
--------------------------------------------------------------------------------
/_static/tutorial/confirm_email.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nyergler/effective-django/HEAD/_static/tutorial/confirm_email.png
--------------------------------------------------------------------------------
/_static/tutorial/TemplateDoesNotExist.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nyergler/effective-django/HEAD/_static/tutorial/TemplateDoesNotExist.png
--------------------------------------------------------------------------------
/handouts/Effective-Django-OSCON-2013.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nyergler/effective-django/HEAD/handouts/Effective-Django-OSCON-2013.pdf
--------------------------------------------------------------------------------
/handouts/Effective-Django-PyCon-2013.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nyergler/effective-django/HEAD/handouts/Effective-Django-PyCon-2013.pdf
--------------------------------------------------------------------------------
/scratchpad/form-resources.rst:
--------------------------------------------------------------------------------
1 |
2 | * django-form-utils
3 | http://pypi.python.org/pypi/django-form-utils/0.2.0
4 | * crispy forms
5 | * rebar
6 |
--------------------------------------------------------------------------------
/_static/tutorial/authz-login-pagenotfound.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nyergler/effective-django/HEAD/_static/tutorial/authz-login-pagenotfound.png
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /bin
2 | /eggs
3 | /develop-eggs
4 | /parts
5 | /.installed.cfg
6 | /_serekh
7 | /lib/
8 | /lib64/
9 | /include/
10 | .Python
11 |
12 | *.pyc
13 | /pip-selfcheck.json
14 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | "Effectivve Django" is licensed under the Creative Commons
2 | Attribution-ShareAlike 4.0 International License. To view a copy of
3 | this license, visit
4 | http://creativecommons.org/licenses/by-sa/4.0/deed.en_US.
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "src"]
2 | path = src
3 | url = git@github.com:nyergler/effective-django-tutorial.git
4 | [submodule "_build"]
5 | path = _build
6 | url = git@github.com:nyergler/effective-django.git
7 | branch = gh-pages
8 |
--------------------------------------------------------------------------------
/further-reading.rst:
--------------------------------------------------------------------------------
1 | .. slideconf::
2 | :theme: single-level
3 |
4 | Further Reading
5 | ===============
6 |
7 | * `Django Doesn't Scale`_, by Jacob Kaplan Moss
8 |
9 | .. _`Django Doesn't Scale`: http://www.oscon.com/oscon2012/public/schedule/detail/24030
10 |
--------------------------------------------------------------------------------
/_templates/layout.html:
--------------------------------------------------------------------------------
1 | {% extends "!layout.html" %}
2 |
3 | {%- block extrahead %}
4 | {{ super() }}
5 |
10 | {% endblock %}
11 |
12 | {% block footer %}
13 | {{ super() }}
14 |
23 | {% endblock %}
24 |
--------------------------------------------------------------------------------
/acknowledgments.rst:
--------------------------------------------------------------------------------
1 | .. slideconf::
2 | :theme: single-level
3 | :autoslides: False
4 |
5 | Acknowledgments
6 | ===============
7 |
8 | .. slide:: Thanks!
9 | :level: 2
10 |
11 | nathan@yergler.net
12 |
13 | http://effectivedjango.com/
14 | http://github.com/nyergler/effectivedjango
15 |
16 | It would have been impossible to put *Effective Django* together with
17 | lots and lots of help. Thanks to PyCon_ and PyOhio_ for allowing me to
18 | speak about these topics and develop the material. Thanks to
19 | Eventbrite_ for supporting my work in this area, and generally just
20 | being an awesome place to work.
21 |
22 | *Effective Django* would have been opaque, disjointed, and confusing
23 | without feedback and patience from early reviewers: Tamara Chu,
24 | Brandon L. Golm, Jason Herbst, Philip John James, Galen Krumel,
25 | Allison Lacker, Sanby Lee, Karl Mendes, Nam-Chi Van, Nicole Zuckerman,
26 | and Madeline_.
27 |
28 | Thanks for reading this far. If you have feedback, comments, or
29 | suggestions, please feel free to email them to me: nathan@yergler.net.
30 | Or find me on `identi.ca`_ or Twitter_ as @nyergler.
31 |
32 | .. _PyCon: http://us.pycon.org/2012
33 | .. _PyOhio: http://pyohio.org/
34 | .. _Eventbrite: http://www.eventbrite.com
35 | .. _`identi.ca`: http://identi.ca/nyergler
36 | .. _Twitter: http://twitter.com/nyergler
37 | .. _Madeline: http://yergler.net/madeline
38 |
--------------------------------------------------------------------------------
/README.rst:
--------------------------------------------------------------------------------
1 | ==================
2 | Effective Django
3 | ==================
4 |
5 | This is the repository for the text `Effective Django`_, an ongoing
6 | work in progress by Nathan Yergler. The sample code is maintained in
7 | the `effective-django-tutorial`_ repository.
8 |
9 | *Effective Django* is authored using `ReStructured Text`_ and Sphinx_.
10 | If you're interested in building HTML, PDF, ePub, or other generated
11 | formats, you can do so by:
12 |
13 | #. If you want to build PDF or ePub output, make sure LaTeX is
14 | installed on your machine. If you only care about HTML output, you
15 | can skip this step.
16 |
17 | For Macs, it is recommended you use `MacTeX`_
18 |
19 | ::
20 |
21 | $ brew install Caskroom/cask/mactex
22 |
23 | If you're building on Ubuntu, you should install the `texlive` and
24 | `texlive-latex-extra` packages.
25 |
26 | ::
27 |
28 | $ sudo apt-get install texlive texlive-latex-extra
29 |
30 | #. Check out this repository::
31 |
32 | $ git clone --recursive https://github.com/nyergler/effective-django.git
33 |
34 | Note that in order to build *Effective Django*, the sample code
35 | must be cloned into the ``src`` submodule. Using ``--recursive``
36 | will accomplish that.
37 |
38 | #. Create a virtualenv_ and install the dependencies::
39 |
40 | $ virtualenv .
41 | $ . bin/activate
42 | $ pip install -r requirements.txt
43 |
44 | #. Run ``make``::
45 |
46 | $ make all
47 |
48 | The output will be in the ``_build`` sub-directory.
49 |
50 | To only build HTML, specify the target explicitly::
51 |
52 | $ make html
53 |
54 | Run ``make`` without any parameters for a list of possible targets.
55 |
56 | .. _`Effective Django`: http://effectivedjango.com/
57 | .. _`effective-django-tutorial`: https://github.com/nyergler/effective-django-tutorial
58 | .. _`ReStructured Text`: http://docutils.sf.net/
59 | .. _Sphinx: http://sphinx-doc.org/
60 | .. _`MacTeX`: http://tug.org/mactex/
61 | .. _virtualenv: http://www.virtualenv.org/
62 |
--------------------------------------------------------------------------------
/testing.rst:
--------------------------------------------------------------------------------
1 | .. slideconf::
2 | :theme: single-level
3 |
4 | ===================
5 | Testing in Django
6 | ===================
7 |
8 | Testing Django
9 | ==============
10 |
11 | * There are Unit Tests and there are Integration tests
12 | * Unit Tests should not rely on external services
13 | * Unit Tests should be *fast*
14 |
15 | Writing a Unit Test
16 | ===================
17 |
18 | * Django bundles ``unittest2`` as ``django.utils.unittest``
19 |
20 | ::
21 |
22 | import django.http
23 | import django.utils.unittest as unittest2
24 |
25 | class LocaleMiddlewareTests(unittest2.TestCase):
26 |
27 | def test_request_not_processed(self):
28 |
29 | middleware = LocaleMiddle()
30 | response = django.http.HttpResponse()
31 | middleware.process_response(none, response)
32 |
33 | self.assertFalse(response.cookies)
34 |
35 | Test Client
36 | ===========
37 |
38 | * Django TestClient acts like a browser. Sort of.
39 | * Allows you to make a request against your application and inspect
40 | the response
41 | * The TestClient is *slow* (compared to plain unit tests)
42 |
43 | .. testcode::
44 |
45 | from django.test.client import Client
46 |
47 | c = Client()
48 |
49 | response = c.get('/login')
50 | self.assertEqual(response.status_code, 200)
51 |
52 | response = c.post('/login/', {'username': 'john', 'password': 'smith'})
53 |
54 | Request Factory
55 | ===============
56 |
57 | * Django 1.3 introduced ``RequestFactory``, with an API similar to
58 | Test Client
59 | * Easy way to generate ``Request`` objects, which can be passed to
60 | views
61 | * Note that middleware is **not** run on these Requests
62 |
63 | Running Tests
64 | =============
65 |
66 | * Django only looks in apps with ``models.py`` for tests
67 |
68 | ::
69 |
70 | $ ./manage.py test
71 |
72 | * Easy to replace the test runner with something like ``nose`` if you
73 | so desire
74 |
75 |
76 | Further Reading
77 | ===============
78 |
79 | * `Django Testing Documentation`_
80 | * `Django 1.1 Testing & Debugging`_
81 |
82 |
83 | .. _`Django Testing Documentation`: https://docs.djangoproject.com/en/1.4/topics/testing/
84 | .. _`Django 1.1 Testing & Debugging`: http://www.packtpub.com/django-1-1-testing-and-debugging/book
85 |
--------------------------------------------------------------------------------
/_static/custom.css:
--------------------------------------------------------------------------------
1 | @import url(http://fonts.googleapis.com/css?family=Inconsolata|Rosario:400,700);
2 |
3 | /* General styling */
4 |
5 | body {
6 | /* background: #123 !important;*/
7 | font-family: 'Rosario', sans-serif;
8 | }
9 |
10 | code, .code, pre {
11 | font-family: 'Inconsolata', 'Droid Sans Mono', 'Courier New', monospace;
12 | font-size: 26px;
13 | line-height: 1.125em;
14 | white-space: pre-wrap;
15 | margin-right: -35px;
16 | padding-right: 0;
17 | }
18 |
19 | /* Title Slide */
20 |
21 | .slides > article.level-1 {
22 | padding-top: 200px;
23 | padding-left: 0px;
24 | padding-right: 0px;
25 | }
26 |
27 | .slides > article > h1 {
28 | color: #222;
29 | font-size:72px;
30 | margin-top: 250px;
31 | background: rgba(255, 255, 255, 0.65);
32 | line-height: 1.5em;
33 | padding-left: 30px;
34 | }
35 |
36 | .slides > article.level-1 > p {
37 | color: #333;
38 | background: rgba(255, 255, 255, 0.65);
39 | text-align: right;
40 | margin-top: 0px;
41 | padding-right: 20px;
42 | padding-left: 60px;
43 | padding-bottom: 0.5em;
44 | }
45 |
46 | /* Section Slide Styling */
47 |
48 | /* .slides > article.level-2 { */
49 | /* padding-top: 450px; */
50 | /* } */
51 |
52 | /* .slides > article > h2 { */
53 | /* position: relative; */
54 | /* bottom: auto; */
55 | /* } */
56 |
57 | /* Interior Slide Styling */
58 |
59 | .slides > article > h3,
60 | .slides > article > h4 {
61 | font-size: 45px;
62 | line-height: 1.5em;
63 |
64 | padding: 0;
65 | margin: 0;
66 | padding-right: 40px;
67 |
68 | font-weight: 600;
69 |
70 | letter-spacing: -1px;
71 |
72 | color: rgb(90,90,90); /* rgb(70, 214, 189); */
73 | }
74 |
75 | .slides > article > h2 {
76 | border-bottom: 1px #656565 solid;
77 | width: 100%;
78 | color: rgb(15, 15, 15); /* rgb(0, 162, 165); */
79 | }
80 |
81 | table.context-table {
82 | border: none;
83 | }
84 |
85 | table.context-table td {
86 | border: none;
87 | text-align: center;
88 | padding: 30px 0px;
89 | }
90 |
91 | table.context-table tr.row-odd {
92 | background: #cdcdcd;
93 | }
94 |
95 | article#cc-chooser > h2,
96 | article#event-creation > h2,
97 | article#danger h2 {
98 | display: none;
99 | }
--------------------------------------------------------------------------------
/middleware.rst:
--------------------------------------------------------------------------------
1 | .. slideconf::
2 | :theme: single-level
3 |
4 | ==========================
5 | Understanding Middleware
6 | ==========================
7 |
8 |
9 | Overview of Middleware
10 | ======================
11 |
12 | * Lightweight "plug-ins" for Django
13 | * Allows modifying the Request or Response, or mutating the View
14 | parameters
15 | * Defined as a sequence (tuple) of classes in ``settings``
16 |
17 | .. testcode::
18 |
19 | MIDDLEWARE_CLASSES = (
20 | 'django.middleware.common.CommonMiddleware',
21 | 'django.contrib.sessions.middleware.SessionMiddleware',
22 | 'django.middleware.csrf.CsrfViewMiddleware',
23 | 'django.contrib.auth.middleware.AuthenticationMiddleware',
24 | 'django.contrib.messages.middleware.MessageMiddleware',
25 | )
26 |
27 | Middleware Hooks
28 | ================
29 |
30 | * Middleware classes have hooks for processing:
31 |
32 | - ``request``
33 | - ``response``
34 | - ``view``
35 | - ``template_response``
36 | - ``exception``
37 |
38 | * Individual middleware may implement some or all
39 |
40 | Typical Uses
41 | ============
42 |
43 | * Sessions
44 | * Authentication
45 | * CSRF Protection
46 | * GZipping Content
47 |
48 | Middleware Example
49 | ==================
50 |
51 | .. testcode::
52 |
53 | class LocaleMiddleware(object):
54 |
55 | def process_request(self, request):
56 |
57 | if 'locale' in request.cookies:
58 | request.locale = request.cookies.locale
59 | else:
60 | request.locale = None
61 |
62 | def process_response(self, request, response):
63 |
64 | if getattr(request, 'locale', False):
65 | response.cookies['locale'] = request.locale
66 |
67 |
68 | Request Middleware
69 | ==================
70 |
71 | * On ingress, middleware is executed in order
72 | * Request middleware returns ``None`` to continue processing
73 | * Returning an ``HttpResponse`` short circuits additional middleware
74 |
75 | Response Middleware
76 | ===================
77 |
78 | * On egress, middleware is executed in reverse order
79 | * Response middleware is executed *even if corresponding request
80 | middleware not executed*
81 |
82 | Writing Your Own
83 | ================
84 |
85 | * Simple Python Classes
86 | * Can implement all or part of the interface
87 | * Middleware is long-lived
88 | * The place for storing request-specific information is cunningly
89 | named ``request``
90 |
91 | WSGI Middleware
92 | ===============
93 |
94 | * WSGI_ also defines a middleware interface
95 | * The two have similar functions, but are **not** the same
96 |
97 | .. _WSGI: http://wsgi.org
98 |
--------------------------------------------------------------------------------
/tutorial/before.rst:
--------------------------------------------------------------------------------
1 | ===================
2 | Before You Arrive
3 | ===================
4 |
5 | Thanks for signing up to attend my tutorial, *Effective Django*. To
6 | help things get started smoothly, and avoid possible network issues,
7 | please complete the following beforehand.
8 |
9 | If you have questions, you can email me at nathan@yergler.net. If you
10 | have platform specific issues, I'll try and help you figure them out,
11 | and will update this document with any notes.
12 |
13 | #. Install Python
14 |
15 | I'll be using Python 2.7; you can download it from the official
16 | Python website: http://python.org/download/releases/2.7.5/
17 |
18 | The latest release of 2.7 is 2.7.5. If you already have 2.7.x
19 | installed, that will work fine.
20 |
21 | #. Install virtualenv and pip
22 |
23 | ``virtualenv`` is a tool for managing your Python environments. It
24 | includes ``pip``, a Python package installer, which we'll use to
25 | retrieve dependencies.
26 |
27 | You can download `virtualenv from PyPI`_ at
28 | https://pypi.python.org/packages/source/v/virtualenv/virtualenv-1.9.1.tar.gz
29 |
30 | After downloading, unpack the archive and run::
31 |
32 | $ python setup.py install
33 |
34 | You may need to run with sudo depending on your system::
35 |
36 | $ sudo python setup.py install
37 |
38 | #. Create a working directory for the tutorial, and make it a virtualenv
39 |
40 | You can create this directory anywhere, but this is where we'll be
41 | working on code during the tutorial. For example::
42 |
43 | $ cd Documents
44 | $ mkdir django-tutorial
45 |
46 | Once you've created the directory, create a virtualenv there::
47 |
48 | $ virtualenv ./django-tutorial
49 |
50 | #. Active the virtualenv
51 |
52 | On Linux and Mac OS X you can active the virtualenv by running the
53 | following from the command-line::
54 |
55 | $ cd django-tutorial
56 | $ source bin/activate
57 |
58 | On Windows you do::
59 |
60 | > cd django-tutorial
61 | > \Scripts\activate
62 |
63 | The `virtualenv documentation`_ may be useful if you have trouble activating.
64 |
65 | #. Install Django
66 |
67 | We'll talk about versions of Django during the tutorial, but in
68 | order to avoid network bottlenecks from dozens of people in one
69 | room downloading the source, it's helpful to install it into your
70 | virtualenv beforehand::
71 |
72 | $ pip install Django
73 |
74 | This will download Django and install it into the virtualenv. Once
75 | it's installed, you should see a ``django-admin.py`` script in the
76 | ``bin`` (Linux, Mac OS X) or ``Scripts`` (Windows) directory.
77 |
78 | If you have questions, you can email me at nathan@yergler.net. If you
79 | have platform specific issues, I'll try and help you figure them out,
80 | and will update this document with any notes.
81 |
82 | See you at the tutorial!
83 |
84 | Nathan
85 |
86 |
87 |
88 | .. _`virtualenv documentation`: http://www.virtualenv.org/en/latest/#activate-script
89 | .. _`virtualenv from PyPI`: https://pypi.python.org/pypi/virtualenv
90 |
--------------------------------------------------------------------------------
/index.rst:
--------------------------------------------------------------------------------
1 | .. slideconf::
2 | :autoslides: True
3 | :theme: single-level
4 |
5 | ==================
6 | Effective Django
7 | ==================
8 |
9 | .. note::
10 |
11 | There's new content and tutorials coming!
12 |
13 | `Sign up `_ to be notified with *Effective
14 | Django* is updated.
15 |
16 | .. note::
17 |
18 | `Video of the tutorial from PyCon`_ is available on YouTube.
19 |
20 | .. _`Video of the tutorial from PyCon`: https://www.youtube.com/watch?v=NfsJDPm0X54
21 |
22 | Django is a popular, powerful web framework for Python. It has lots of
23 | "batteries" included, and makes it easy to get up and going. But all
24 | of the power means you can write low quality code that still seems to
25 | work. *Effective Django* development means building applications that
26 | are testable, maintainable, and scalable -- not only in terms of
27 | traffic or load, but in terms of being able to add developers to
28 | projects. After reading *Effective Django* you should have an
29 | understanding of how Django's pieces fit together, how to use them to
30 | engineer web applications, and where to look to dig deeper.
31 |
32 | These documents are a combination of the notes and examples developed
33 | for talks prepared for PyCon 2012, PyOhio 2012, and PyCon 2013, and
34 | for Eventbrite web engineering. I'm still working on fleshing them out
35 | into a single document, but I hope you find them useful.
36 |
37 | Feedback, suggestions, and questions may be sent to
38 | nathan@yergler.net. You can find (and fork) the source on `github
39 | `_.
40 |
41 | These documents are available in PDF_ and EPub_ format, as well.
42 |
43 | .. _PDF: /latex/EffectiveDjango.pdf
44 | .. _EPub: /epub/EffectiveDjango.epub
45 |
46 | .. slides::
47 |
48 | .. figure:: /_static/building.jpg
49 | :class: fill
50 |
51 | CC BY-NC-SA http://www.flickr.com/photos/t_lawrie/278932896/
52 |
53 | http://effectivedjango.com
54 |
55 | Nathan Yergler // nathan@yergler.net // @nyergler
56 |
57 |
58 | What do you mean, "Effective"?
59 | ==============================
60 |
61 | * Testable
62 | * Maintainable
63 | * Scalable
64 |
65 | Contents
66 | ========
67 |
68 | .. toctree::
69 | :maxdepth: 2
70 |
71 | intro.rst
72 | tutorial/index.rst
73 | testing.rst
74 | middleware.rst
75 | Databases & Models
76 | classbasedviews.rst
77 | Forms
78 | acknowledgments.rst
79 | further-reading.rst
80 |
81 | Everything In Its Right Place
82 | =============================
83 |
84 | .. Table::
85 | :class: context-table
86 |
87 | +-------------------------+---------------------------------+
88 | | **Views** | Convert Request to Response |
89 | +-------------------------+---------------------------------+
90 | | **Forms** | Convert input to Python objects |
91 | +-------------------------+---------------------------------+
92 | | **Models** | Data and business logic |
93 | +-------------------------+---------------------------------+
94 |
--------------------------------------------------------------------------------
/scratchpad/gettingstarted.rst:
--------------------------------------------------------------------------------
1 | ===============
2 | Getting Started
3 | ===============
4 |
5 | Your Application Environment
6 | ============================
7 |
8 | Starting a Project
9 | ------------------
10 |
11 | Starting a project is easy::
12 |
13 | $ python django-admin.py startproject
14 |
15 | What comes next? Or right before?
16 |
17 |
18 | Deployment From Day 1
19 | ---------------------
20 |
21 | * Pretend you need to deploy from Day 1
22 | * And assume that you want that automated
23 |
24 | Dependency Management
25 | ---------------------
26 |
27 | * Choose a tool to track dependencies, use it in development
28 |
29 | * pip w/requirements.txt
30 | * virtualenv
31 | * buildout
32 |
33 | * Identify specific versions of dependencies (by number or SHA)
34 |
35 | Your Environment
36 | ----------------
37 |
38 | * If you're on your own, just use a virtualenv
39 | * If you're working with an ops person/team, consider Vagrant from the
40 | start
41 | * Even if you don't use Puppet to configure the dev VM, at least
42 | you're running code on another "machine".
43 |
44 |
45 | Beginning a Django Project
46 | ==========================
47 |
48 | Scaffolding
49 | -----------
50 |
51 | * Django provides file system scaffolding just like HTTP scaffolding
52 | * Helps engineers understand where to find things when they go looking
53 | * Django 1.4 made a change that decouples apps from projects
54 | * In Django parlance, your **project** is a collections of **applications**.
55 |
56 | Creating the Scaffolding
57 | ------------------------
58 |
59 | ::
60 |
61 | $ python django-admin.py startproject contactmgr
62 |
63 | ::
64 |
65 | ./contactmgr
66 | manage.py
67 | ./contactmgr
68 | __init__.py
69 | settings.py
70 | urls.py
71 | wsgi.py
72 |
73 | .. notslides::
74 |
75 | * ``manage.py`` is a pointer back to ``django-admin.py`` with an
76 | environment variable set, pointing to your project as the one to
77 | read settings from and operate on when needed.
78 | * ``settings.py`` is where you'll configure your app. It has a few
79 | sensible defaults, but no database chosen when you start.
80 | * ``urls.py`` contains the URL to view mappings: we'll talk more about
81 | that shortly.
82 | * ``wsgi.py`` is WSGI_ wrapper for your application. This is used by
83 | Django's development servers, and possibly (hopefully) other
84 | containers like mod_wsgi, uwsgi, etc.
85 |
86 | ...and the "App"
87 | ----------------
88 |
89 | ::
90 |
91 | $ python ./contactmgr/manage.py startapp contacts
92 |
93 | ::
94 |
95 | ./contactmgr
96 | ./contacts
97 | __init__.py
98 | models.py
99 | tests.py
100 | views.py
101 |
102 | .. notslides::
103 |
104 | * Beginning in Django 1.4, *apps* are placed alongside *project*
105 | packages. This is a great improvement when it comes to
106 | deployment.
107 | * ``models.py`` will contain the Django ORM models for your app.
108 | * ``views.py`` will contain the View code
109 | * ``tests.py`` will contain the unit and integration tests you
110 | write.
111 |
112 | .. _WSGI: http://www.python.org/dev/peps/pep-0333/
113 |
--------------------------------------------------------------------------------
/tutorial/index.rst:
--------------------------------------------------------------------------------
1 | .. tut::
2 | :path: ./src
3 |
4 | .. slideconf::
5 | :autoslides: False
6 | :theme: single-level
7 |
8 | ===========================
9 | Effective Django Tutorial
10 | ===========================
11 |
12 | .. note::
13 |
14 | `Video of this tutorial`_ from PyCon is available on YouTube.
15 |
16 | .. _`Video of this tutorial`: https://www.youtube.com/watch?v=NfsJDPm0X54
17 |
18 | Django is a popular, powerful web framework for Python. It has lots of
19 | "batteries" included, and makes it easy to get up and going. But all
20 | of the power means you can write low quality code that still seems to
21 | work. So what does *Effective Django* mean? It means using Django in a
22 | way that emphasizes writing code that's cohesive, testable, and
23 | scalable. What do each of those words mean?
24 |
25 | Well, "cohesive" code is code that is focused on doing one thing, and
26 | one thing alone. It means that when you write a function or a method,
27 | that it does one thing and does it well.
28 |
29 | This is directly related to writing testable code: code that's doing
30 | too much is often difficult to write tests for. When I find myself
31 | thinking, "Well, this piece of code is just too complex to write a
32 | test for, it's not really worth all the effort," that's a signal that
33 | I need to step back and focus on simplifying it. Testable code is code
34 | that makes it straight-forward to write tests for, and that's easy to
35 | diagnose problems with.
36 |
37 | Finally, we want to write scalable code. That doesn't just mean it
38 | scales in terms of performance, but that it also scales in terms of
39 | your team and your team's understanding. Applications that are well
40 | tested are easier for others to understand (and easier for them to
41 | modify), which means you're more able to improve your application by
42 | adding engineers.
43 |
44 | My goal is to convince you of the importance of these principles, and
45 | provide examples of how to follow them to build more robust Django
46 | applications. I'm going to walk through building a contact management
47 | application iteratively, talking about the choices and testing
48 | strategy as I go.
49 |
50 | The sample code for this tutorial is available in the
51 | `effective-django-tutorial`_ git repository.
52 |
53 | Slides for the tutorial are available at http://effectivedjango.com/slides/tutorial
54 |
55 | .. _`effective-django-tutorial`: https://github.com/nyergler/effective-django-tutorial/
56 |
57 | .. slide:: Effective Django
58 | :level: 1
59 |
60 | .. figure:: /_static/building.jpg
61 | :class: fill
62 |
63 | CC BY-NC-SA http://www.flickr.com/photos/t_lawrie/278932896/
64 |
65 | http://effectivedjango.com
66 |
67 | Nathan Yergler // nathan@yergler.net // @nyergler
68 |
69 | .. slide:: Goals
70 | :level: 2
71 |
72 | * Build a small Django application
73 | * Build it effectively
74 | * Explore some of the new features in Django
75 |
76 | .. slide:: Effective?
77 | :level: 2
78 |
79 | * Cohesive
80 | * Tested
81 | * Scalable
82 |
83 | .. ifnotslides::
84 |
85 | .. toctree::
86 | :maxdepth: 1
87 |
88 | getting-started.rst
89 | models.rst
90 | views.rst
91 | static.rst
92 | additional-views.rst
93 | forms.rst
94 | related.rst
95 | authzn.rst
96 |
97 | "Effective Django" is licensed under the Creative Commons
98 | `Attribution-ShareAlike 4.0 International License`_.
99 |
100 | .. _`Attribution-ShareAlike 4.0 International License`: http://creativecommons.org/licenses/by-sa/4.0/
101 |
--------------------------------------------------------------------------------
/classbasedviews.rst:
--------------------------------------------------------------------------------
1 | .. slideconf::
2 | :theme: single-level
3 |
4 | ===================
5 | Class Based Views
6 | ===================
7 |
8 | Class Based Views
9 | =================
10 |
11 | * New in Django 1.3 (generic views)
12 | * Allow "composing" a View from pieces
13 | * Intended to allow more flexible reuse
14 | * Base View plus a set of "mixins" provide composable functionality
15 | * Lots of power, lots of [potential] complexity
16 |
17 | Using Class Based Views
18 | =======================
19 |
20 | * Subclass ``View``
21 | * Define a method name that matches the HTTP method you're
22 | implementing
23 |
24 | .. testcode::
25 |
26 | from django.views.generic import View
27 |
28 | class ContactList(View):
29 |
30 | def get(self):
31 |
32 | return HttpResponse("You have no contacts")
33 |
34 | Using a Template
35 | ----------------
36 |
37 | .. testcode::
38 |
39 | from django.views.generic import TemplateView
40 |
41 | class ContactList(TemplateView):
42 |
43 | template_name = 'index.html' # or define get_template_names()
44 |
45 | def get_context_data(self, **kwargs):
46 |
47 | context = super(ContactList, self).\
48 | get_context_data(**kwargs)
49 | context['first_names'] = ['Nathan', 'Richard']
50 |
51 | return context
52 |
53 |
54 | Configuring URLs
55 | ----------------
56 |
57 | * Django URLConf needs a callable, not a class
58 | * ``View`` provides as ``as_view`` method
59 |
60 | ::
61 |
62 | urlpatterns = patterns('',
63 | (r'^index/$', ContactList.as_view()),
64 | )
65 |
66 | * ``kwargs`` passed to ``as_view`` can override properties on the View
67 | class
68 | * Arguments captured in the URL pattern are available as ``.args`` and
69 | ``.kwargs`` inside your class
70 |
71 |
72 | Idiomatic Class Based Views
73 | ===========================
74 |
75 | * Number of mixins can be confusing
76 | * However there are a few common idioms
77 | * Many times you don't wind up defining the HTTP methods directly,
78 | just the things you need
79 |
80 | Template Views
81 | --------------
82 |
83 | TemplateView
84 |
85 | * ``get_context_data()``
86 | * ``template_name``, ``get_template_names()``
87 | * ``response_class``
88 | * ``render_to_response()``
89 |
90 | Forms in Views
91 | --------------
92 |
93 | ProcessFormView
94 |
95 | * ``form_class``
96 | * ``get_success_url()``
97 | * ``form_valid(form)``
98 | * ``form_invalid(form)``
99 |
100 | Editing Views
101 | -------------
102 |
103 | CreateView, UpdateView
104 |
105 | * Includes Form processing
106 |
107 | * ``model``
108 | * ``get_object()``
109 |
110 | HTTP Methods
111 | ============
112 |
113 | * The ``http_method_names`` property defines a list of supported
114 | methods
115 | * In Django 1.5 this is::
116 |
117 | http_method_names = ['get', 'post', 'put', 'delete', 'head',
118 | 'options', 'trace']
119 |
120 | * If you want to support something like HTTP ``PATCH``, you need to
121 | add it to that list in your View subclass
122 | * Views will look for a class method named for the HTTP method:
123 | ``get()`` is called for ``GET``, etc.
124 |
125 | Writing Composable Views
126 | ========================
127 |
128 | * Think about the extension points you need
129 | * Call ``super()`` in your methods: this allows others to mix your
130 | View with others
131 |
132 | Example
133 | -------
134 |
135 | .. testcode::
136 |
137 | class EventsPageMixin(object):
138 | """View mixin to include the Event in template context."""
139 |
140 | def get_event(self):
141 |
142 | if not hasattr(self, 'event'):
143 | self.event = get_event()
144 |
145 | return self.event
146 |
147 | def get_context_data(self, **kwargs):
148 |
149 | context = super(EventsPageMixin, self).\
150 | get_context_data(**kwargs)
151 |
152 | context['event'] = self.get_event()
153 |
154 | return context
155 |
156 | .. notslides::
157 |
158 | * No actual view logic
159 | * Subclasses ``object``, not ``View``
160 | * Calls ``super`` on overridden methods
161 |
--------------------------------------------------------------------------------
/intro.rst:
--------------------------------------------------------------------------------
1 | .. slideconf::
2 | :autoslides: False
3 | :theme: single-level
4 |
5 | ============
6 | Introduction
7 | ============
8 |
9 | In the past decade the Python community has seen a wealth of riches
10 | spring up in the area of web development. Frameworks and tools have
11 | made it easier than ever to use Python for web applications, with some
12 | focused on particular domains, others on particular footprints, and
13 | still others on particular deployment strategies. Most of these
14 | frameworks have built upon WSGI_, the Web Server Gateway Interface,
15 | which became part of the Python standard library in version TK. WSGI
16 | provides some conventions for applications and servers to communicate
17 | with one another, much as it's spiritual predecessor, CGI_, provided
18 | conventions for executing scripts via a web server.
19 |
20 | With the inclusion of WSGI, it's possible to begin developing a web
21 | application by simply picking and choosing pieces that seem best for
22 | the task at hand. Indeed, some projects do just that. So why use a
23 | larger framework like Django_, Pylons_, or `Blue Bream`_? Frameworks
24 | build upon WSGI to provide a reasonable set of defaults, a set of
25 | conventions, for getting started with development and focusing on the
26 | specific problem at hand. It's *possible* to spend time evaluating
27 | libraries that map a URL to a view, but a framework's developers have
28 | already (presumably) done such an evaluation, and chosen one that they
29 | feel will work well with the other parts of the framework. TK:Community
30 |
31 | A framework is general purpose by definition, but that doesn't mean
32 | your use of it must be. Put another way, most frameworks support a
33 | variety of databases, platforms, and deployment infrastructures. But
34 | just because you use that framework doesn't mean you need to, as well.
35 | A good framework will help you get up to speed more quickly, and will
36 | let you target things for your environment when needed.
37 |
38 | Django is a popular, powerful web framework for Python. It has lots of
39 | "batteries" included, and makes it easy to get up and going. But all
40 | of the power means you can write low quality code that still seems to
41 | work. *Effective Django* development means building applications that
42 | are testable, maintainable, and scalable -- not only in terms of
43 | traffic or load, but in terms of being able to add developers to
44 | projects. When we're talking about Effective Django, we're really
45 | talking about software engineering for web applications. The examples
46 | and the details we're going to talk about are Django specific, but the
47 | ideas and principles are not.
48 |
49 | So what does *Effective Django* mean? It means using Django in a way
50 | that emphasizes writing code that's cohesive, testable, and scalable.
51 | What do each of those words mean? Well "cohesive" code is code that is
52 | focused on doing one thing, and one thing alone. It means that when
53 | you write a function or a method, that it does one thing and does it
54 | well. This is directly related to writing testable code: code that's
55 | doing too much is often difficult to write tests for. When I find
56 | myself thinking, "Well, this piece of code is just too complex to
57 | write a test for, it's not really worth all the effort," that's a
58 | signal that I need to step back and focus on simplifying it. Testable
59 | code is code that makes it straight-forward to write tests for, and
60 | that's easy to diagnose problems with. Finally, we want to write
61 | scalable code. That doesn't just mean it scales in terms of
62 | performance, but that it also scales in terms of your team and your
63 | team's understanding. Applications that are well tested are easier for
64 | others to understand (and easier for them to modify), which means
65 | you're more able to improve your application by adding engineers.
66 |
67 | Part of being able to effectively use Django is understanding
68 | what's available to you, and what the restrictions are. Frameworks
69 | are necessarily general purpose tools, which is great: the
70 | abstractions and tools they provide allow us to begin working
71 | immediately, without delving into the details. At some point,
72 | however, it's useful to understand what the framework is doing for
73 | you. Whether you're trying to stretch in a way the framework didn't
74 | imagine, or you're just trying to diagnose a mysterious bug, you
75 | have to look inside the black box and gain a deeper
76 | understanding. After reading *Effective Django* you should have an
77 | understanding of how Django's pieces fit together, how to use them to
78 | engineer web applications, and where to look to dig deeper.
79 |
80 | .. slide:: Format
81 | :level: 2
82 |
83 | * Talk / Demonstrate / Practice
84 | * Please ask questions
85 | * Two breaks planned
86 | * Notes, examples, etc available: http://effectivedjango.com
87 |
88 | My goal is to convince you of the importance of these principles, and
89 | provide examples of how to follow them to build more robust Django
90 | applications. I'm going to walk through building a contact management
91 | application iteratively, building tests as I go.
92 |
93 | .. _WSGI: http://www.python.org/dev/peps/pep-0333/
94 | .. _CGI: http://en.wikipedia.org/wiki/Common_Gateway_Interface
95 | .. _Django: http://djangoproject.com/
96 | .. _Pylons: http://www.pylonsproject.org/
97 | .. _`Blue Bream`: http://bluebream.zope.org/
98 |
--------------------------------------------------------------------------------
/tutorial/related.rst:
--------------------------------------------------------------------------------
1 | .. tut::
2 | :path: /src
3 |
4 | ==============
5 | Related Models
6 | ==============
7 |
8 | Adding Relationships
9 | ====================
10 |
11 | .. checkpoint:: address_model
12 |
13 | We have a basic email address book at this point, but there's other
14 | information we might want to track for our contacts. Mailing
15 | addresses, for example. A single Contact may have multiple addresses
16 | associated with them, so we'll store this in a separate table,
17 | allowing us to have multiple addresses for each Contact.
18 |
19 | .. literalinclude:: /src/contacts/models.py
20 | :pyobject: Address
21 |
22 | Django provides three types of fields for relating objects to each
23 | other: ``ForeignKey`` for creating one to many relationships,
24 | ``ManyToManyField`` for relating many to many, and ``OneToOneField``
25 | for creating a one to one relationship. You define the relationship in
26 | one model, but it's accessible from the other side, as well.
27 |
28 | Sync up the database to create the table, and then start the shell so
29 | we can explore this.
30 |
31 | ::
32 |
33 | (tutorial)$ python manage.py syncdb
34 | Creating tables ...
35 | Creating table contacts_address
36 | Installing custom SQL ...
37 | Installing indexes ...
38 | Installed 0 object(s) from 0 fixture(s)
39 |
40 | Now that we have the model created, we can again play with it using
41 | the interactive shell.
42 |
43 | ::
44 |
45 | (tutorial)$ python manage.py shell
46 | Python 2.7.3 (default, Aug 9 2012, 17:23:57)
47 | [GCC 4.7.1 20120720 (Red Hat 4.7.1-5)] on linux2
48 | Type "help", "copyright", "credits" or "license" for more information.
49 | (InteractiveConsole)
50 | >>> from contacts.models import Contact, Address
51 | >>> nathan = Contact.objects.create(first_name='Nathan', email='nathan@yergler.net')
52 | >>> nathan.address_set.all()
53 | []
54 | >>> nathan.address_set.create(address_type='home',
55 | ... city='San Francisco', state='CA', postal_code='94107')
56 |
57 | >>> nathan.address_set.create(address_type='college',
58 | ... address='354 S. Grant St.', city='West Lafayette', state='IN',
59 | ... postal_code='47906')
60 |
61 | >>> nathan.address_set.all()
62 | [, ]
63 | >>> nathan.address_set.filter(address_type='college')
64 |
65 | >>> Address.objects.filter(contact__first_name='Nathan')
66 | [, ]
67 |
68 | As you can see, even though we defined the relationship between
69 | Contacts and Addresses on the Address model, Django gives us a way to
70 | access things in the reverse direction. We can also use the double
71 | underscore notation to filter Addresses or Contacts based on the
72 | related objects.
73 |
74 | Let's go ahead and add address display to our contacts. We'll add the
75 | list of all Addresses to the Contact detail view in ``contact.html``.
76 |
77 | .. literalinclude:: /src/contacts/templates/contact.html
78 |
79 | Editing Related Objects
80 | =======================
81 |
82 | So how do we go about editing addresses for our contacts? You can
83 | imagine creating another CreateView like we did for Contacts, but the
84 | question remains: how do we wire the new Address to our Contact? We
85 | could conceivably just pass the Contact's ID through the the HTML, but
86 | we'd still need to validate that it hadn't been tampered with when we
87 | go to create the Address.
88 |
89 | To deal with this, we'll create a form that understands the
90 | relationship between Contacts and Addresses.
91 |
92 | .. checkpoint:: edit_addresses
93 |
94 | The editing interface we're going to build for Addresses is one that
95 | allows you to edit all the addresses for a Contact at once. To do
96 | this, we'll need to create a FormSet_ that handles all the Addresses
97 | for a single Contact. A FormSet is an object that manages multiple
98 | copies of the same Form (or ModelForm) in a single page. The `Inline
99 | FormSet`_ does this for a set of objects (in this case Addresses) that
100 | share a common related object (in this case the Contact).
101 |
102 | Because formsets are somewhat complex objects, Django provides factory
103 | functions that create the class for you. We'll add a call to the
104 | factory to our ``forms.py`` file.
105 |
106 | .. literalinclude:: /src/contacts/forms.py
107 | :lines: 3-8,40-
108 |
109 | When we create the view, we'll need to specify that this is the form
110 | we want to use, instead of having Django create one for us.
111 |
112 | .. literalinclude:: /src/contacts/views.py
113 | :pyobject: EditContactAddressView
114 |
115 | Note that even though we're editing Addresses with this view, we still
116 | have ``model`` set to ``Contact``. This is because an inline formset
117 | takes the parent object as its starting point.
118 |
119 | Once again, this needs to be wired up into the URL configuration.
120 |
121 | .. literalinclude:: /src/addressbook/urls.py
122 | :lines: 16-17
123 |
124 | And we have a simple template.
125 |
126 | .. literalinclude:: /src/contacts/templates/edit_addresses.html
127 | :language: html
128 |
129 | There are two new things in this template, both related to the fact
130 | we're using a formset instead of a form. First, there's a reference to
131 | ``form.management_form``. This is a set of hidden fields that provide
132 | some accounting information to Django: how many forms did we start
133 | with, how many empty ones are there, etc. If Django can't find this
134 | information when you POST the form, it will raise an exception.
135 |
136 | Second, we're iterating over form instead of just outputting it (``for
137 | address_form in form``). Again, this is because ``form`` here is a
138 | formset instead of a single form. When you iterate over a formset,
139 | you're iterating over the individual forms in it. These individual
140 | forms are just "normal" ``ModelForm`` instances for each Address, so
141 | you can apply the same output techniques you would normally use.
142 |
143 | .. _FormSet: https://docs.djangoproject.com/en/1.5/topics/forms/formsets/
144 | .. _`Inline FormSet`: https://docs.djangoproject.com/en/1.5/topics/forms/modelforms/#inline-formsets
145 |
--------------------------------------------------------------------------------
/tutorial/static.rst:
--------------------------------------------------------------------------------
1 | .. tut::
2 | :path: /src
3 |
4 | .. slideconf::
5 | :autoslides: True
6 | :theme: single-level
7 |
8 | ===================
9 | Using Static Assets
10 | ===================
11 |
12 | .. checkpoint:: static_files
13 |
14 | Now that we have a basic application where we can add contacts and
15 | list them, it's reasonable to think about how we'd make this look more
16 | appealing. Most modern web applications are a combination of server
17 | side code/views, and client side, static assets, such as JavaScript
18 | and CSS. Regardless of whether you choose JavaScript or CoffeeScript,
19 | CSS or SASS, Django provides support for integrating static assets
20 | into your project.
21 |
22 |
23 | Adding Static Files
24 | ===================
25 |
26 | Django distinguishes between "static" and "media" files. The former
27 | are static assets included with your app or project. The latter are
28 | files uploaded by users using one of the file storage backends. Django
29 | includes a contrib app, ``django.contrib.staticfiles`` for managing
30 | static files and, importantly, generating the URLs to them. You could,
31 | of course, simply hard code the URLs to your static assets, and that'd
32 | probably work for a while. But if you want to move your static assets
33 | to their own server, or to a CDN, using generated URLs let's you make
34 | that change without needing to update your templates.
35 | ``django.contrib.staticfiles`` is enabled by default when you create a
36 | new project, so you can just start using it.
37 |
38 | We're going to add Bootstrap_ to our project for some basic styling.
39 | You can download the Bootstrap files from its website,
40 | http://getbootstrap.com.
41 |
42 | .. _Bootstrap: http://getbootstrap.com
43 |
44 | Django supports adding static files at both the application and
45 | project level. Where you add them sort of depends on how tied to your
46 | specific assembly of apps they are. That is, are they reusable for
47 | anyone using your app, or are they specific to your particular
48 | deployment?
49 |
50 | App specific static files are stored in the ``static`` subdirectory
51 | within the app. Django will also look in any directories listed in the
52 | ``STATICFILES_DIRS`` setting. Let's update our project settings to
53 | specify a static files directory.
54 |
55 | .. literalinclude:: /src/addressbook/settings.py
56 | :language: python
57 | :prepend: import os.path
58 | ...
59 | :lines: 67-77
60 |
61 | Note that we use ``os.path`` to construct the absolute path. This
62 | ensures Django can locate the files unambiguously.
63 |
64 | Let's go ahead and create the static directory in our project and
65 | unpack Bootstrap into it.
66 |
67 | ::
68 |
69 | (tutorial)$ mkdir addressbook/static
70 | (tutorial)$ cd addressbook/static
71 | (tutorial)$ unzip ~/Downloads/bootstrap.zip
72 | Archive: /Users/nathan/Downloads/bootstrap.zip
73 | creating: bootstrap/
74 | creating: bootstrap/css/
75 | inflating: bootstrap/css/bootstrap-responsive.css
76 | inflating: bootstrap/css/bootstrap-responsive.min.css
77 | inflating: bootstrap/css/bootstrap.css
78 | inflating: bootstrap/css/bootstrap.min.css
79 | creating: bootstrap/img/
80 | inflating: bootstrap/img/glyphicons-halflings-white.png
81 | inflating: bootstrap/img/glyphicons-halflings.png
82 | creating: bootstrap/js/
83 | inflating: bootstrap/js/bootstrap.js
84 | inflating: bootstrap/js/bootstrap.min.js
85 |
86 |
87 | Referring to Static Files in Templates
88 | ======================================
89 |
90 | The Django staticfiles app includes a `template tag`_ that make it
91 | easy to refer to static files within your templates. You load template
92 | tag libraries using the ``load`` tag.
93 |
94 | .. _`template tag`: https://docs.djangoproject.com/en/1.5/ref/templates/builtins/
95 |
96 | ::
97 |
98 | {% load staticfiles %}
99 |
100 | After loading the static files library, you can refer to the file
101 | using the ``static`` tag.
102 |
103 | ::
104 |
105 |
107 |
108 | Note that the path we specify is *relative* to the static files
109 | directory. Django is going to join this path with the ``STATIC_URL``
110 | setting to generate the actual URL to use.
111 |
112 | The `STATIC_URL setting`_ tells Django what the root URL for your
113 | static files is. By default it's set to ``/static/``.
114 |
115 | .. _`STATIC_URL setting`: https://docs.djangoproject.com/en/1.5/ref/settings/#std:setting-STATIC_URL
116 |
117 | Simple Template Inclusion
118 | =========================
119 |
120 | We want to add the Boostrap CSS to all of our templates, but we'd like
121 | to avoid repeating ourself: if we add it to each template
122 | individually, when we want to make changes (for example, to add
123 | another stylesheet) we have to make them to all the files. To solve
124 | this, we'll create a base template that the others will inherit from.
125 |
126 | Let's create ``base.html`` in the ``templates`` directory of our
127 | ``contacts`` app.
128 |
129 | .. literalinclude:: /src/contacts/templates/base.html
130 |
131 | ``base.html`` defines the common structure for our pages, and includes
132 | a ``block`` tag, which other templates can fill in.
133 |
134 | We'll update ``contact_list.html`` to extend from ``base.html`` and
135 | fill in the ``content`` block.
136 |
137 | .. literalinclude:: /src/contacts/templates/contact_list.html
138 |
139 | Serving Static Files
140 | ====================
141 |
142 | We've told Django where we store our static files, and we've told it
143 | what URL structure to use, but we haven't actually connected the two
144 | together. Django doesn't serve static files by default, and for good
145 | reason: using an application server to serve static resources is going
146 | to be ineffecient, at best. The Django documentation on `deploying
147 | static files`_ does a good job of walking through the options for
148 | getting your static files onto your CDN or static file server.
149 |
150 | For development, however, it's convenient to do it all with one
151 | process, so there's a helper. We'll update our ``addressbook/urls.py``
152 | file to include the ``staticfiles_urlpatterns`` helper.
153 |
154 | .. literalinclude:: /src/addressbook/urls.py
155 |
156 | Now we can run the server and see our newly Boostrapped templates in
157 | action.
158 |
159 | .. image::
160 | /_static/tutorial/boostrapped.png
161 |
162 | Review
163 | ======
164 |
165 | * Django distinguishes between static site files, and user uploaded
166 | media
167 | * The ``staticfiles`` app is included to help manage static files and
168 | serve them during development
169 | * Static files can be included with apps, or with the project. Choose
170 | where you put them based on whether you expect all users of your app
171 | to need them.
172 | * Templates can extend one another, using ``block`` tags.
173 |
174 | .. _`deploying static files`: https://docs.djangoproject.com/en/1.5/howto/static-files/deployment/
175 |
176 | .. ifslides::
177 |
178 | * Next: :doc:`additional-views`
179 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | # Makefile for Effective Django
2 | #
3 |
4 | # You can set these variables from the command line.
5 | SPHINXOPTS =
6 | SPHINXBUILD = ./bin/sphinx-build
7 | PAPER = letter
8 | BUILDDIR = _build
9 | BUILDBRANCH = gh-pages
10 |
11 | # Internal variables.
12 | PAPEROPT_a4 = -D latex_paper_size=a4
13 | PAPEROPT_letter = -D latex_paper_size=letter
14 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
15 | # the i18n builder cannot share the environment and doctrees with the others
16 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
17 |
18 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext check-syntax push all
19 |
20 | help:
21 | @echo "Please use \`make ' where is one of"
22 | @echo " html to make standalone HTML files"
23 | @echo " dirhtml to make HTML files named index.html in directories"
24 | @echo " singlehtml to make a single large HTML file"
25 | @echo " pickle to make pickle files"
26 | @echo " json to make JSON files"
27 | @echo " htmlhelp to make HTML files and a HTML help project"
28 | @echo " qthelp to make HTML files and a qthelp project"
29 | @echo " devhelp to make HTML files and a Devhelp project"
30 | @echo " epub to make an epub"
31 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
32 | @echo " latexpdf to make LaTeX files and run them through pdflatex"
33 | @echo " text to make text files"
34 | @echo " man to make manual pages"
35 | @echo " texinfo to make Texinfo files"
36 | @echo " info to make Texinfo files and run them through makeinfo"
37 | @echo " gettext to make PO message catalogs"
38 | @echo " changes to make an overview of all changed/added/deprecated items"
39 | @echo " linkcheck to check all external links for integrity"
40 | @echo " doctest to run all doctests embedded in the documentation (if enabled)"
41 |
42 | clean:
43 | git --git-dir=$(BUILDDIR)/.git co $(BUILDBRANCH)
44 | rm -rf $(BUILDDIR)/*
45 | git --git-dir=$(BUILDDIR)/.git reset --hard $(BUILDBRANCH)
46 | git --git-dir=$(BUILDDIR)/.git checkout $(BUILDBRANCH)
47 |
48 | html:
49 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/
50 | @echo
51 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/."
52 |
53 | dirhtml:
54 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
55 | @echo
56 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
57 |
58 | singlehtml:
59 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
60 | @echo
61 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
62 |
63 | pickle:
64 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
65 | @echo
66 | @echo "Build finished; now you can process the pickle files."
67 |
68 | json:
69 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
70 | @echo
71 | @echo "Build finished; now you can process the JSON files."
72 |
73 | htmlhelp:
74 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
75 | @echo
76 | @echo "Build finished; now you can run HTML Help Workshop with the" \
77 | ".hhp project file in $(BUILDDIR)/htmlhelp."
78 |
79 | qthelp:
80 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
81 | @echo
82 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \
83 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:"
84 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/EffectiveDjango.qhcp"
85 | @echo "To view the help file:"
86 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/EffectiveDjango.qhc"
87 |
88 | devhelp:
89 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
90 | @echo
91 | @echo "Build finished."
92 | @echo "To view the help file:"
93 | @echo "# mkdir -p $$HOME/.local/share/devhelp/EffectiveDjango"
94 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/EffectiveDjango"
95 | @echo "# devhelp"
96 |
97 | epub:
98 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
99 | @echo
100 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub."
101 |
102 | latex:
103 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
104 | @echo
105 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
106 | @echo "Run \`make' in that directory to run these through (pdf)latex" \
107 | "(use \`make latexpdf' here to do that automatically)."
108 |
109 | latexpdf:
110 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
111 | @echo "Running LaTeX files through pdflatex..."
112 | $(MAKE) -C $(BUILDDIR)/latex all-pdf
113 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
114 |
115 | text:
116 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
117 | @echo
118 | @echo "Build finished. The text files are in $(BUILDDIR)/text."
119 |
120 | man:
121 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
122 | @echo
123 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man."
124 |
125 | texinfo:
126 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
127 | @echo
128 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
129 | @echo "Run \`make' in that directory to run these through makeinfo" \
130 | "(use \`make info' here to do that automatically)."
131 |
132 | info:
133 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
134 | @echo "Running Texinfo files through makeinfo..."
135 | make -C $(BUILDDIR)/texinfo info
136 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
137 |
138 | gettext:
139 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
140 | @echo
141 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
142 |
143 | changes:
144 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
145 | @echo
146 | @echo "The overview file is in $(BUILDDIR)/changes."
147 |
148 | linkcheck:
149 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
150 | @echo
151 | @echo "Link check complete; look for any errors in the above output " \
152 | "or in $(BUILDDIR)/linkcheck/output.txt."
153 |
154 | doctest:
155 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
156 | @echo "Testing of doctests in the sources finished, look at the " \
157 | "results in $(BUILDDIR)/doctest/output.txt."
158 |
159 | slides:
160 | $(SPHINXBUILD) -b slides $(ALLSPHINXOPTS) $(BUILDDIR)/slides
161 | @echo "Build finished. The HTML slides are in $(BUILDDIR)/slides."
162 |
163 | html+slides: html slides
164 |
165 | # Support for flymake-mode's flymake-simple-make-init in Emacs
166 | check-syntax:
167 | $(SPHINXBUILD) -n -N -q -b html $(ALLSPHINXOPTS) $(BUILDDIR)/
168 | $(SPHINXBUILD) -n -N -q -b slides $(ALLSPHINXOPTS) $(BUILDDIR)/slides
169 |
170 | MESSAGE = $(shell git log -1 --pretty=format:"%s (%h)")
171 |
172 | push:
173 | git --git-dir=$(BUILDDIR)/.git checkout $(BUILDBRANCH)
174 | git --git-dir=$(BUILDDIR)/.git add .
175 | git --git-dir=$(BUILDDIR)/.git commit -m '$(MESSAGE)'
176 | git --git-dir=$(BUILDDIR)/.git push origin gh-pages
177 |
178 | all: clean html slides latexpdf epub
179 |
180 | publish: all push
181 |
182 | # Support for flymake-mode's flymake-simple-make-init in Emacs
183 | check-syntax:
184 | $(SPHINXBUILD) -n -N -q -b html $(ALLSPHINXOPTS) $(BUILDDIR)/
185 |
--------------------------------------------------------------------------------
/tutorial/additional-views.rst:
--------------------------------------------------------------------------------
1 | .. tut::
2 | :path: /src
3 |
4 |
5 | .. slideconf::
6 | :autoslides: True
7 | :theme: single-level
8 |
9 | ========================
10 | Additional Generic Views
11 | ========================
12 |
13 | Edit Views
14 | ==========
15 |
16 | .. checkpoint:: edit_contact_view
17 |
18 | In addition to creating Contacts, we'll of course want to edit them.
19 | As with the List and Create views, Django has a generic view we can
20 | use as a starting point.
21 |
22 | .. literalinclude:: /src/contacts/views.py
23 | :prepend: from django.views.generic import UpdateView
24 | ...
25 | :pyobject: UpdateContactView
26 | :end-before: def get_context_data
27 |
28 | * we can re-use the same template
29 | * but how does it know which contact to load?
30 | * we need to either: provide a pk/slug, or override get_object().
31 | * we'll provide pk in the URL
32 |
33 | .. literalinclude:: /src/addressbook/urls.py
34 | :lines: 12-13
35 |
36 | We'll update the contact list to include an edit link next to each
37 | contact.
38 |
39 | .. literalinclude:: /src/contacts/templates/contact_list.html
40 | :language: html
41 |
42 | Note the use of ``pk=contact.id`` in the ``{% url %}`` tag to specify
43 | the arguments to fill into the URL pattern.
44 |
45 | If you run the server now, you'll see an edit link. Go ahead and click
46 | it, and try to make a change. You'll notice that instead of editing
47 | the existing record, it creates a new one. Sad face.
48 |
49 | If we look at the source of the edit HTML, we can easily see the
50 | reason: the form targets ``/new``, not our edit URL. To fix this --
51 | and still allow re-using the template -- we're going to add some
52 | information to the template context.
53 |
54 | The template context is the information available to a template when
55 | it's rendered. This is a combination of information you provide in
56 | your view -- either directly or indirectly -- and information added by
57 | `context processors`_, such as the location for static media and
58 | current locale. In order to use the same template for add and edit,
59 | we'll add information about where the form should redirect to the
60 | context.
61 |
62 | .. literalinclude:: /src/contacts/views.py
63 | :pyobject: CreateContactView
64 |
65 | .. literalinclude:: /src/contacts/views.py
66 | :pyobject: UpdateContactView
67 |
68 | We also update the template to use that value for the action and
69 | change the title based on whether or not we've previously saved.
70 |
71 | .. literalinclude:: /src/contacts/templates/edit_contact.html
72 | :lines: 5-11
73 | :language: html
74 |
75 | You may wonder where the ``contact`` value in the contact comes from:
76 | the class based views that wrap a single object (those that take
77 | a primary key or slug) expose that to the context in two different
78 | ways: as a variable named ``object``, and as a variable named after
79 | the model class. The latter often makes your templates easier to read
80 | and understand later. You can customize this name by overriding
81 | ``get_context_object_name`` on your view.
82 |
83 | .. sidebar:: Made a Change? Run the Tests.
84 |
85 | We've just made a change to our ``CreateContactView``, which means
86 | this is a perfect time to run the tests we wrote. Do they still pass?
87 | If not, did we introduce a bug, or did the behavior change in a way
88 | that we expected?
89 |
90 | (Hint: We changed how the contact list is rendered, so our tests
91 | that just expect the name there are going to fail. This is a case
92 | where you'd need to update the test case, but it also demonstrates
93 | how integration tests can be fragile.)
94 |
95 | Deleting Contacts
96 | =================
97 |
98 | .. checkpoint:: delete_contact_view
99 |
100 | The final view for our basic set of views is delete. The generic
101 | deletion view is very similar to the edit view: it wraps a single
102 | object and requires that you provide a URL to redirect to on success.
103 | When it processes a HTTP GET request, it displays a confirmation page,
104 | and when it receives an HTTP DELETE or POST, it deletes the object and
105 | redirects to the success URL.
106 |
107 | We add the view definition to ``views.py``:
108 |
109 | .. literalinclude:: /src/contacts/views.py
110 | :prepend: from django.views.generic import DeleteView
111 | ...
112 | :pyobject: DeleteContactView
113 |
114 | And create the template, ``delete_contact.html``, in our ``templates``
115 | directory.
116 |
117 | .. literalinclude:: /src/contacts/templates/delete_contact.html
118 | :language: html
119 |
120 | Of course we need to add this to the URL definitions:
121 |
122 | .. literalinclude:: /src/addressbook/urls.py
123 | :lines: 14-15
124 |
125 | And we'll add the link to delete to the edit page.
126 |
127 | .. literalinclude:: /src/contacts/templates/edit_contact.html
128 | :lines: 19-21
129 |
130 | Detail View
131 | ===========
132 |
133 | .. checkpoint:: contact_detail_view
134 |
135 | Finally, let's go ahead and add a detail view for our Contacts. This
136 | will show the details of the Contact: not much right now, but we'll
137 | build on this shortly. Django includes a generic ``DetailView``: think
138 | of it as the single serving ``ListView``.
139 |
140 | .. literalinclude:: /src/contacts/views.py
141 | :prepend: from django.views.generic import DetailView
142 | ...
143 | :pyobject: ContactView
144 |
145 | Again, the template is pretty straight forward; we create
146 | ``contact.html`` in the ``templates`` directory.
147 |
148 | .. literalinclude:: /src/contacts/templates/contact.html
149 | :language: html
150 |
151 | And add the URL mapping:
152 |
153 | .. literalinclude:: /src/addressbook/urls.py
154 | :lines: 10-11
155 |
156 | We're also going to add a method to our Contact model,
157 | ``get_absolute_url``. ``get_absolute_url`` is a Django convention for
158 | obtaining the URL of a single model instance. In this case it's just
159 | going to be a call to ``reverse``, but by providing this method, our
160 | model will play nicely with other parts of Django.
161 |
162 | .. literalinclude:: /src/contacts/models.py
163 | :prepend: class Contact(models.Model):
164 | ...
165 | :pyobject: Contact.get_absolute_url
166 |
167 | And we'll add the link to the contact from the contact list.
168 |
169 | .. literalinclude:: /src/contacts/templates/contact_list.html
170 | :lines: 7-12
171 | :language: html
172 |
173 | .. ifslides::
174 |
175 | * Next: :doc:`forms`
176 |
177 |
178 | .. _`Generic Views`: https://docs.djangoproject.com/en/1.5/topics/class-based-views/generic-display/
179 | .. _`Class Based Views`: https://docs.djangoproject.com/en/1.5/topics/class-based-views/
180 | .. _View: https://docs.djangoproject.com/en/1.5/ref/class-based-views/base/#view
181 | .. _ListView: https://docs.djangoproject.com/en/1.5/ref/class-based-views/generic-display/#listview
182 | .. _UpdateView: https://docs.djangoproject.com/en/1.5/ref/class-based-views/generic-editing/#updateview
183 | .. _CreateView: https://docs.djangoproject.com/en/1.5/ref/class-based-views/generic-editing/#createview
184 | .. _DeleteView: https://docs.djangoproject.com/en/1.5/ref/class-based-views/generic-editing/#deleteview
185 | .. _DetailView: https://docs.djangoproject.com/en/1.5/ref/class-based-views/generic-display/#detailview
186 | .. _`context processors`: https://docs.djangoproject.com/en/1.5/ref/templates/api/#subclassing-context-requestcontext
187 | .. _`Django Form`: https://docs.djangoproject.com/en/1.5/topics/forms/
188 | .. _HttpRequest: https://docs.djangoproject.com/en/1.5/ref/request-response/#httprequest-objects
189 | .. _HttpResponse: https://docs.djangoproject.com/en/1.5/ref/request-response/#httpresponse-objects
190 | .. _Client: https://docs.djangoproject.com/en/1.5/topics/testing/overview/#module-django.test.client
191 | .. _RequestFactory: https://docs.djangoproject.com/en/1.5/topics/testing/advanced/#django.test.client.RequestFactory
192 | .. _LiveServerTestCase: https://docs.djangoproject.com/en/1.5/topics/testing/overview/#liveservertestcase
193 | .. _Selenium: http://seleniumhq.org/
194 |
--------------------------------------------------------------------------------
/orm.rst:
--------------------------------------------------------------------------------
1 | ======================
2 | Effective Django ORM
3 | ======================
4 |
5 | Configuring the Database
6 | ========================
7 |
8 | Writing Models
9 | ==============
10 |
11 | .. testcode::
12 |
13 | from django.db import models
14 |
15 | class Address(models.Model):
16 |
17 | address = models.CharField(max_length=255, blank=True)
18 | city = models.CharField(max_length=150, blank=True)
19 | state = models.CharField(max_length=2, blank=True)
20 | zip = models.CharField(max_length=15, blank=True)
21 |
22 | class Contact(models.Model):
23 |
24 | first_name = models.CharField(max_length=255, blank=True)
25 | last_name = models.CharField(max_length=255, blank=True)
26 |
27 | birthdate = models.DateField(auto_now_add=True)
28 | phone = models.CharField(max_length=25, blank=True)
29 | email = models.EMailField(blank=True)
30 |
31 | address = models.ForeignKey(Address, null=True)
32 |
33 | Working with Models
34 | ===================
35 |
36 | .. testcode::
37 |
38 | nathan = Contact()
39 | nathan.first_name = 'Nathan'
40 | nathan.last_name = 'Yergler'
41 | nathan.save()
42 |
43 | What Goes in Models
44 | ===================
45 |
46 | * Models should encapsulate business logic
47 | * Encourages testable, composable code
48 | * If logic operates on a "set" of Models, put it in the Manager
49 |
50 | Saving Data
51 | ===========
52 |
53 | * ``.save()`` update the entire model, even if just some fields have changed.
54 | * `django-dirtyfields`_ helps you track which fields have changed, and save
55 | only these fields with its custom ``.save_dirty_fields()`` method.
56 |
57 | Managers
58 | ========
59 |
60 | * Models get a manager injected as ``.objects``
61 | * Managers allow you to operate over collections of your model
62 | * Default manager emulates part of the ``QuerySet`` API for
63 | convenience
64 |
65 | .. testcode::
66 |
67 | Contact.objects.filter(last_name__iexact='yergler')
68 | Contact.objects.filter(address__state='OH')
69 |
70 | Custom Managers
71 | ---------------
72 |
73 | * You can override the default Manager, or add additional ones
74 | * Operations on sets of Model instances belongs here
75 | * Subclass from ``models.Manager`` to get queryset emulation
76 |
77 | .. testcode::
78 |
79 | class ContactManager(models.Manager):
80 |
81 | def with_email(self):
82 | return self.filter(email__ne = '')
83 |
84 | class Contact(models.Model):
85 | ...
86 |
87 | objects = ContactManager()
88 |
89 | .. testcode::
90 |
91 | contacts.objects.with_email().filter(email__endswith='osu.edu')
92 |
93 | Low-level Managers
94 | ------------------
95 |
96 | * Sometimes you want to heavily customize the manager without
97 | re-implementing everything
98 | * ``Manager.get_query_set()``
99 | allows you to customize the basic QuerySet used by Manager methods
100 |
101 | Testing
102 | =======
103 |
104 | What to Test
105 | ------------
106 |
107 | * Business logic methods
108 | * Customized Manager methods
109 |
110 | Writing a Test
111 | --------------
112 |
113 | .. testcode::
114 |
115 | def test_with_email():
116 |
117 | # make a couple Contacts
118 | Contact.objects.create(first_name='Nathan')
119 | Contact.objects.create(email='nathan@eventbrite.com')
120 |
121 | self.assertEqual(
122 | len(Contact.objects.with_email()), 1
123 | )
124 |
125 |
126 | Test Objects
127 | ------------
128 |
129 | * Creating objects for tests is time consuming
130 | * Unnecessarily involves the database
131 | * `factory boy`_ provides an easy way to make model factories
132 |
133 | FactoryBoy Example
134 | ------------------
135 |
136 | .. testcode::
137 |
138 | import factory
139 | from models import Contact
140 |
141 | class ContactFactory(factory.Factory):
142 | FACTORY_FOR = Contact
143 |
144 | first_name = 'John'
145 | last_name = 'Doe'
146 |
147 | # Returns a Contact instance that's not saved
148 | contact = ContactFactory.build()
149 | contact = ContactFactory.build(last_name='Yergler')
150 |
151 | # Returns a saved Contact instance
152 | contact = ContactFactory.create()
153 |
154 | SubFactories for Related Objects
155 | --------------------------------
156 |
157 | .. testcode::
158 |
159 | class AddressFactory(factory.Factory):
160 | FACTORY_FOR = Address
161 |
162 | contact = factory.SubFactory(ContactFactory)
163 |
164 | .. testcode::
165 |
166 | address = AddressFactory(city='Columbus', state='OH')
167 | address.contact.first_name
168 |
169 | .. testoutput::
170 |
171 | 'John'
172 |
173 | Querying Your Data
174 | ==================
175 |
176 | * Query Sets are chainable
177 |
178 | .. testcode::
179 |
180 | Contact.objects.filter(state='OH').filter(email__ne='')
181 |
182 | * Multiple filters are collapsed into SQL "and" conditions
183 |
184 | OR conditions in Queries
185 | ------------------------
186 |
187 | If you need to do "or" conditions, you can use ``Q`` objects
188 |
189 | .. testcode::
190 |
191 | from django.db.models import Q
192 |
193 | Contact.objects.filter(
194 | Q(state='OH') | Q(email__endswith='osu.edu')
195 | )
196 |
197 | .. F objects let you refer to fields in the same object
198 | .. ----------------------------------------------------
199 |
200 | .. XXX
201 |
202 |
203 | ORM Performance
204 | ===============
205 |
206 |
207 | Instantiation is Expensive
208 | --------------------------
209 |
210 | ::
211 |
212 | for user in Users.objects.filter(is_active=True):
213 | send_email(user.email)
214 |
215 | * QuerySets are lazy, but have non-trivial overhead when evaluated
216 | * If a query returns 1000s of rows, users will notice this
217 | * ``.values()`` and ``.values_list()`` avoid instantiation
218 |
219 | Avoiding Instantiation
220 | ----------------------
221 |
222 | ::
223 |
224 | user_emails = Users.objects.\
225 | filter(is_active=True).\
226 | values_list('email', flat=True)
227 |
228 | for email in user_emails:
229 | send_email(email)
230 |
231 |
232 | Traversing Relationships
233 | ------------------------
234 |
235 | * Traversing foreign keys can incur additional queries
236 | * ``select_related`` queries for foreign keys in the initial query
237 |
238 | .. testcode::
239 |
240 | Contact.objects.\
241 | select_related('address').\
242 | filter(last_name = 'Yergler')
243 |
244 |
245 | Query Performance
246 | -----------------
247 |
248 | * QuerySets maintain state in memory
249 | * Chaining triggers cloning, duplicating that state
250 | * Unfortunately, QuerySets maintain a *lot* of state
251 | * If possible, don't chain more than one filter
252 |
253 | Falling Back to Raw SQL
254 | -----------------------
255 |
256 | * Django has to be database agnostic, you don't
257 | * Sometimes the clearest thing to do is write a SQL statement
258 | * The ``.raw()`` method lets you do this
259 |
260 | .. testcode::
261 |
262 | Contact.objects.raw('SELECT * FROM contacts WHERE last_name = %s', [lname])
263 |
264 | * Must retrieve the primary key
265 | * Omitted fields will be "deferred"
266 | * **DO NOT** use string formatting in ``raw()`` calls
267 |
268 | Other Manager Operations
269 | ------------------------
270 |
271 | Managers have some additional helpers for operating on the table or
272 | collection:
273 |
274 | * ``get_or_create``
275 | * ``update``
276 | * ``delete``
277 | * ``bulk_insert``
278 |
279 |
280 | Read Repeatable
281 | ---------------
282 |
283 | MySQL's default transaction isolation for InnoDB **breaks**
284 | Django's ``get_or_create`` when running at scale
285 |
286 | ::
287 |
288 | def get_or_create(self, **kwargs):
289 |
290 | try:
291 | return self.get(**lookup), False
292 | except self.model.DoesNotExist:
293 | try:
294 | obj = self.model(**params)
295 | obj.save(force_insert=True, using=self.db)
296 | return obj, True
297 | except IntegrityError, e:
298 | try:
299 | return self.get(**lookup), False
300 | except self.model.DoesNotExist:
301 | raise e
302 |
303 |
304 | .. _`django-dirtyfields`: http://pypi.python.org/pypi/django-dirtyfields/
305 | .. _`factory boy`: http://pypi.python.org/pypi/factory_boy
306 |
--------------------------------------------------------------------------------
/conf.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | #
3 | # Effective Django documentation build configuration file, created by
4 | # sphinx-quickstart on Sun Mar 4 11:02:15 2012.
5 | #
6 | # This file is execfile()d with the current directory set to its containing dir.
7 | #
8 | # Note that not all possible configuration values are present in this
9 | # autogenerated file.
10 | #
11 | # All configuration values have a default; values that are commented out
12 | # serve to show the default.
13 |
14 | import datetime
15 | import os
16 | import sys
17 |
18 | # If extensions (or modules to document with autodoc) are in another directory,
19 | # add these directories to sys.path here. If the directory is relative to the
20 | # documentation root, use os.path.abspath to make it absolute, like shown here.
21 | #sys.path.insert(0, os.path.abspath('.'))
22 |
23 | # -- General configuration -----------------------------------------------------
24 |
25 | # If your documentation needs a minimal Sphinx version, state it here.
26 | #needs_sphinx = '1.0'
27 |
28 | # Add any Sphinx extension module names here, as strings. They can be extensions
29 | # coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
30 | extensions = [
31 | 'sphinx.ext.doctest',
32 | 'sphinx.ext.todo',
33 | 'sphinxcontrib.blockdiag',
34 | 'hieroglyph',
35 | 'tut.sphinx',
36 | ]
37 |
38 | # Add any paths that contain templates here, relative to this directory.
39 | templates_path = ['_templates']
40 |
41 | # The suffix of source filenames.
42 | source_suffix = '.rst'
43 |
44 | # The encoding of source files.
45 | #source_encoding = 'utf-8-sig'
46 |
47 | # The master toctree document.
48 | master_doc = 'index'
49 |
50 | # General information about the project.
51 | project = u'Effective Django'
52 | copyright = u'2012-2013, Nathan Yergler'
53 |
54 | # The version info for the project you're documenting, acts as replacement for
55 | # |version| and |release|, also used in various other places throughout the
56 | # built documents.
57 | #
58 | # The short X.Y version.
59 | version = '0.1'
60 | # The full version, including alpha/beta/rc tags.
61 | release = '0.1'
62 |
63 | # The language for content autogenerated by Sphinx. Refer to documentation
64 | # for a list of supported languages.
65 | #language = None
66 |
67 | # There are two options for replacing |today|: either, you set today to some
68 | # non-false value, then it is used:
69 | #today = ''
70 | # Else, today_fmt is used as the format for a strftime call.
71 | #today_fmt = '%B %d, %Y'
72 |
73 | # List of patterns, relative to source directory, that match files and
74 | # directories to ignore when looking for source files.
75 | exclude_patterns = [
76 | 'README.rst',
77 | '_build',
78 | 'scratchpad',
79 |
80 | # sample code sub-module
81 | 'src',
82 |
83 | # Emacs temporary files
84 | '**.#*',
85 |
86 | # Buildout/virtualenv artifacts
87 | 'eggs',
88 | 'develop-eggs',
89 | 'lib',
90 | 'local',
91 | 'parts',
92 | ]
93 |
94 | # The reST default role (used for this markup: `text`) to use for all documents.
95 | #default_role = None
96 |
97 | # If true, '()' will be appended to :func: etc. cross-reference text.
98 | #add_function_parentheses = True
99 |
100 | # If true, the current module name will be prepended to all description
101 | # unit titles (such as .. function::).
102 | #add_module_names = True
103 |
104 | # If true, sectionauthor and moduleauthor directives will be shown in the
105 | # output. They are ignored by default.
106 | #show_authors = False
107 |
108 | # The name of the Pygments (syntax highlighting) style to use.
109 | pygments_style = 'sphinx'
110 |
111 | # A list of ignored prefixes for module index sorting.
112 | #modindex_common_prefix = []
113 |
114 | # -- Options for HTML 5 Slide output -------------------------------------------
115 |
116 | slide_theme = 'single-level'
117 | slide_theme_options = {'custom_css':'custom.css'}
118 |
119 | slide_link_html_to_slides = True
120 | slide_link_html_sections_to_slides = True
121 | slide_relative_path = "./slides/"
122 |
123 | slide_link_to_html = True
124 | slide_html_relative_path = "../"
125 |
126 | # -- Options for HTML output ---------------------------------------------------
127 |
128 | # The theme to use for HTML and HTML Help pages. See the documentation for
129 | # a list of builtin themes.
130 | html_theme = 'nature'
131 |
132 | # Theme options are theme-specific and customize the look and feel of a theme
133 | # further. For a list of options available for each theme, see the
134 | # documentation.
135 | #html_theme_options = {}
136 |
137 | # Add any paths that contain custom themes here, relative to this directory.
138 | #html_theme_path = []
139 |
140 | # The name for this set of Sphinx documents. If None, it defaults to
141 | # " v documentation".
142 | html_title = project
143 |
144 | # A shorter title for the navigation bar. Default is the same as html_title.
145 | # html_short_title = html_title
146 |
147 | # The name of an image file (relative to this directory) to place at the top
148 | # of the sidebar.
149 | #html_logo = None
150 |
151 | # The name of an image file (within the static path) to use as favicon of the
152 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
153 | # pixels large.
154 | #html_favicon = None
155 |
156 | # Add any paths that contain custom static files (such as style sheets) here,
157 | # relative to this directory. They are copied after the builtin static files,
158 | # so a file named "default.css" will overwrite the builtin "default.css".
159 | html_static_path = ['_static']
160 |
161 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
162 | # using the given strftime format.
163 | #html_last_updated_fmt = '%b %d, %Y'
164 |
165 | # If true, SmartyPants will be used to convert quotes and dashes to
166 | # typographically correct entities.
167 | #html_use_smartypants = True
168 |
169 | # Custom sidebar templates, maps document names to template names.
170 | #html_sidebars = {}
171 |
172 | # Additional templates that should be rendered to pages, maps page names to
173 | # template names.
174 | #html_additional_pages = {}
175 |
176 | # If false, no module index is generated.
177 | #html_domain_indices = True
178 |
179 | # If false, no index is generated.
180 | html_use_index = False
181 |
182 | # If true, the index is split into individual pages for each letter.
183 | #html_split_index = False
184 |
185 | # If true, links to the reST sources are added to the pages.
186 | #html_show_sourcelink = True
187 |
188 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
189 | #html_show_sphinx = True
190 |
191 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
192 | #html_show_copyright = True
193 |
194 | # If true, an OpenSearch description file will be output, and all pages will
195 | # contain a tag referring to it. The value of this option must be the
196 | # base URL from which the finished HTML is served.
197 | #html_use_opensearch = ''
198 |
199 | # This is the file name suffix for HTML files (e.g. ".xhtml").
200 | #html_file_suffix = None
201 |
202 | html_extra_path = [
203 | '.nojekyll',
204 | 'CNAME',
205 | ]
206 |
207 | # Output file base name for HTML help builder.
208 | htmlhelp_basename = 'EffectiveDjangodoc'
209 |
210 |
211 | # -- Options for LaTeX output --------------------------------------------------
212 |
213 | if 'latex' in sys.argv:
214 | release = ''
215 | latex_elements = {
216 | 'releasename': 'Build %s' % datetime.date.today().strftime('%Y.%m.%d'),
217 | 'date': datetime.date.today().strftime('%d %B %Y'),
218 | # The paper size ('letterpaper' or 'a4paper').
219 | #'papersize': 'letterpaper',
220 |
221 | # The font size ('10pt', '11pt' or '12pt').
222 | #'pointsize': '10pt',
223 |
224 | # Additional stuff for the LaTeX preamble.
225 | #'preamble': '',
226 | }
227 |
228 | # Grouping the document tree into LaTeX files. List of tuples
229 | # (source start file, target name, title, author, documentclass [howto/manual]).
230 | latex_documents = [
231 | ('tutorial/index', 'EffectiveDjango.tex', u'Effective Django',
232 | u'Nathan Yergler', 'manual'),
233 | ]
234 |
235 | # The name of an image file (relative to this directory) to place at the top of
236 | # the title page.
237 | #latex_logo = None
238 |
239 | # For "manual" documents, if this is true, then toplevel headings are parts,
240 | # not chapters.
241 | #latex_use_parts = False
242 |
243 | # If true, show page references after internal links.
244 | #latex_show_pagerefs = False
245 |
246 | # If true, show URL addresses after external links.
247 | latex_show_urls = True
248 |
249 | # Documents to append as an appendix to all manuals.
250 | #latex_appendices = []
251 |
252 | # If false, no module index is generated.
253 | #latex_domain_indices = True
254 |
255 |
256 | # -- Options for manual page output --------------------------------------------
257 |
258 | # One entry per manual page. List of tuples
259 | # (source start file, name, description, authors, manual section).
260 | man_pages = [
261 | ('index', 'effectivedjango', u'Effective Django',
262 | [u'Nathan Yergler'], 1)
263 | ]
264 |
265 | # If true, show URL addresses after external links.
266 | #man_show_urls = False
267 |
268 |
269 | # -- Options for Texinfo output ------------------------------------------------
270 |
271 | # Grouping the document tree into Texinfo files. List of tuples
272 | # (source start file, target name, title, author,
273 | # dir menu entry, description, category)
274 | texinfo_documents = [
275 | ('index', 'EffectiveDjango', u'Effective Django',
276 | u'Nathan Yergler', 'EffectiveDjango', 'One line description of project.',
277 | 'Miscellaneous'),
278 | ]
279 |
280 | # Documents to append as an appendix to all manuals.
281 | #texinfo_appendices = []
282 |
283 | # If false, no module index is generated.
284 | #texinfo_domain_indices = True
285 |
286 | # How to display URL addresses: 'footnote', 'no', or 'inline'.
287 | #texinfo_show_urls = 'footnote'
288 |
289 | # -- EPub options
290 |
291 | epub_author = 'Nathan Yergler'
292 |
293 | # -- DocTest Configuration ------------------------------------------------------
294 |
295 | trim_doctest_flags = True
296 | doctest_global_setup = """
297 |
298 | # setup the Django settings config
299 | import sys, os
300 | sys.path.append(os.getcwd())
301 | os.environ['DJANGO_SETTINGS_MODULE'] = 'settings'
302 |
303 | # import commonly needed items
304 | import rebar.testing
305 |
306 | # create a Request Factory
307 | from django.test.client import RequestFactory
308 | request_factory = RequestFactory()
309 |
310 | """
311 |
--------------------------------------------------------------------------------
/tutorial/authzn.rst:
--------------------------------------------------------------------------------
1 | .. tut::
2 | :path: /src
3 |
4 | =========================================
5 | Handling Authentication & Authorization
6 | =========================================
7 |
8 | .. warning::
9 |
10 | This page is a work in progress; errors may exist, and additional
11 | content is forthcoming.
12 |
13 | So far we've built a simple contact manager, and added support for a
14 | related model (Addresses). This has shown how to use many of the
15 | basics, but there are a few more things you'd want before exposing
16 | this to the outside world. One of those is authentication and
17 | authorization. Django includes support that works for many projects,
18 | which is what we'll use.
19 |
20 | Authentication
21 | ==============
22 |
23 | .. checkpoint:: authentication
24 |
25 | In order to use the included authentication support, the
26 | ``django.contrib.auth`` and ``django.contrib.sessions`` applications
27 | needs to be included in your project.
28 |
29 | Django enables thes by default when you create a project, as you can
30 | see in ``addressbook/settings.py``.
31 |
32 | .. literalinclude:: /src/addressbook/settings.py
33 | :lines: 118-130
34 |
35 | In addition to installing the application, the middleware needs to be
36 | installed, as well.
37 |
38 | .. literalinclude:: /src/addressbook/settings.py
39 | :lines: 96-104
40 |
41 |
42 | If you'll recall, during the first run of ``syncdb``, Django asked if
43 | we wanted to create a superuser account. It did so because we had the
44 | application installed already.
45 |
46 | The stock Django auth model supports Users_, Groups_, and
47 | Permissions_. This is usually sufficient unless you're integrating
48 | with an existing authentication backend.
49 |
50 | ``django.contrib.auth`` provides a set of views to support the basic
51 | authentication actions such as login, logout, password reset, etc.
52 | Note that it includes *views*, but not *templates*. We'll need to
53 | provide those for our project.
54 |
55 | For this example we'll just add support for login and logout views in
56 | our project. First, add the views to ``addressbook/urls.py``.
57 |
58 | .. literalinclude:: /src/addressbook/urls.py
59 | :lines: 7-9
60 |
61 | Both the login_ and logout_ view have default template names
62 | (``registration/login.html`` and ``registration/logged_out.html``,
63 | respectively). Because these views are specific to our project and not
64 | our re-usable Contacts application, we'll create a new
65 | ``templates/registration`` directory inside of ``addressbook``::
66 |
67 | $ mkdir -p addressbook/templates/registration
68 |
69 | And tell Django to look in that directory for templates by setting
70 | ``TEMPLATE_DIRS`` in ``addressbook/settings.py``.
71 |
72 | .. literalinclude:: /src/addressbook/settings.py
73 | :lines: 111-116
74 |
75 | Within that directory, first create ``login.html``.
76 |
77 | .. literalinclude:: /src/addressbook/templates/registration/login.html
78 | :language: html
79 |
80 | The login template inherits from our ``base.html`` template, and shows
81 | the login form provided by the view. The hidden ``next`` field allows
82 | the view to redirect the user to the page requested, if the login
83 | request was triggered by a permission failure.
84 |
85 | .. sidebar:: Why no name for the URL patterns?
86 |
87 | XXX
88 |
89 | The logout template, ``logged_out.html``, is simpler.
90 |
91 | .. literalinclude:: /src/addressbook/templates/registration/logged_out.html
92 | :language: html
93 |
94 | All it needs to do is provide a message to let the user know the
95 | logout was successful.
96 |
97 | .. sidebar:: Creating an Admin User
98 |
99 | XXX
100 |
101 | If you run your development server now using ``runserver`` and visit
102 | ``http://localhost:8000/login``, you'll see the login page. If you
103 | login with bogus credentials, you should see an error message. So
104 | let's try logging in with the super user credential you created earlier.
105 |
106 | .. image::
107 | /_static/tutorial/authz-login-pagenotfound.png
108 |
109 | Wait, what? Why is it visiitng ``/accounts/profile``? We never typed
110 | that. The login view wants to redirect the user to a fixed URL after a
111 | successful login, and the default is ``/accounts/profile``. To
112 | override that, we'll set the ``LOGIN_REDIRECT_URL`` value in
113 | ``addressbook/settings.py`` so that once a user logs in they'll be
114 | redirected to the list of contacts.
115 |
116 | .. literalinclude:: /src/addressbook/settings.py
117 | :lines: 161
118 |
119 | Now that we can log in and log out, it'd be nice to show the logged in
120 | user in the header and links to login/logout in the header. We'll add
121 | that to our ``base.html`` template, since we want that to show up
122 | everywhere.
123 |
124 | .. literalinclude:: /src/contacts/templates/base.html
125 | :language: html
126 | :lines: 8-17
127 |
128 |
129 | Authorization
130 | =============
131 |
132 | .. checkpoint:: authorization
133 |
134 | Having support for login and logout is nice, but we're not actually
135 | using it right now. So we want to first make our Contact views only
136 | available to authenticated users, and then we'll go on to associated
137 | contacts with specific Users, so the application could be used for
138 | multiple users.
139 |
140 | Django includes a suite a functions and decorators that help you guard
141 | a view based on authentication/authorization. One of the most commonly
142 | used is `login_required`_. Unfortunately, applying view decorators to
143 | class based views remains `a little cumbersome`_. There are
144 | essentially two methods: decorating the URL configuration, and
145 | decorating the class. I'll show how to decorate the class.
146 |
147 | Class based views have a ``dispatch()`` method that's called when an
148 | URL pattern matches. The ``dispatch()`` method looks up the
149 | appropriate method on the class based on the HTTP method and then
150 | calls it. Because we want to protect the views for all HTTP methods,
151 | we'll override and decorate that.
152 |
153 | In ``contacts/views.py`` we'll create a class mixin that ensures the
154 | user is logged in.
155 |
156 | .. literalinclude:: /src/contacts/views.py
157 | :lines: 1-2, 16-21
158 |
159 | This is a *mixin* because it doesn't provide a full implementation of
160 | a view on its own; it needs to be *mixed* with another view to have an
161 | effect.
162 |
163 | Once we have it, we can add it to the class declarations in
164 | ``contacts/views.py``. Each view will have our new ``LoggedInMixin``
165 | added as the first superclass. For example, ``ListContactView`` will
166 | look as follows.
167 |
168 | .. literalinclude:: /src/contacts/views.py
169 | :pyobject: ListContactView
170 |
171 | Just as ``LOGIN_REDIRECT_URL`` tells Django where to send people
172 | *after* they log in, there's a setting to control where to send them
173 | when they *need* to login. However, this can also be a view name, so
174 | we don't have to bake an explicit URL into the settings.
175 |
176 | .. literalinclude:: /src/addressbook/settings.py
177 | :lines: 162
178 |
179 | Checking Ownership
180 | ------------------
181 |
182 | Checking that you're logged in is well and good, but to make this
183 | suitable for multiple users we need to add the concept of ownership.
184 | There are three steps for
185 |
186 | #. Record the Owner of each Contact
187 | #. Only show Contacts the logged in user owns in the list
188 | #. Set the Owner when creating a new one
189 |
190 | First, we'll go ahead and add the concept of an Owner to the Contact
191 | model.
192 |
193 | In ``contacts/models.py``, we add an import and another field to our
194 | model.
195 |
196 | .. literalinclude:: /src/contacts/models.py
197 | :prepend: from django.contrib.auth.models import User
198 | ...
199 | :pyobject: Contact
200 |
201 | Because Django doesn't support migrations out of the box, we'll need
202 | to blow away the database and re-run syncdb.
203 |
204 | XXX Perfect segue for talking about South
205 |
206 | Now we need to limit the contact list to only the contacts the logged
207 | in User owns. This gets us into overriding methods that the base view
208 | classes have been handling for us.
209 |
210 | For the list of Contacts, we'll want to override the ``get_queryset``
211 | method, which returns the `Django QuerySet`_ of objects to be
212 | displayed.
213 |
214 | .. literalinclude:: /src/contacts/views.py
215 | :pyobject: ListContactView
216 |
217 |
218 | The remaining views are responsible for showing only a single object
219 | -- the Contact (or its addresses). For those we'll create another
220 | mixin that enforces authorization.
221 |
222 | .. literalinclude:: /src/contacts/views.py
223 | :prepend: from django.core.exceptions import PermissionDenied
224 | ...
225 | :pyobject: ContactOwnerMixin
226 |
227 | ``ContactOwnerMixin`` overrides the ``get_object()`` method, which is
228 | responsible for getting the object for a view to operate on. If it
229 | can't find one with the specified primary key and owner, it raises the
230 | ``PermissionDenied`` exception.
231 |
232 | .. note::
233 |
234 | This implementation will return HTTP 403 (Forbidden) whenever it
235 | cannot find the a Contact with the requested ID and owner. This
236 | will mask legitimate 404 (Not Found) errors.
237 |
238 | We'll use the ``ContactOwnerMixin`` in all of our views. For example,
239 | ``ContactView`` will look as follows:
240 |
241 | .. literalinclude:: /src/contacts/views.py
242 | :pyobject: ContactView
243 |
244 | Note that the order of inheritance is important: the superclasses
245 | (``LoggedInMixin``, ``ContactOwnerMixin``, ``DetailView``) will be
246 | checked in the order listed for methods. By placing ``LoggedInMixin``
247 | first, you're guaranteed that by the time execution reaches
248 | ``ContactOwnerMixin`` and ``DetailView``, you have a logged in,
249 | authenticated user.
250 |
251 | Review
252 | ======
253 |
254 | * XXX
255 |
256 | .. _`login_required`: https://docs.djangoproject.com/en/1.5/topics/auth/default/#django.contrib.auth.decorators.login_required
257 | .. _`a little cumbersome`: https://docs.djangoproject.com/en/1.5/topics/class-based-views/intro/#decorating-class-based-views
258 | .. _Users: https://docs.djangoproject.com/en/1.6/topics/auth/default/#user-objects
259 | .. _Groups: https://docs.djangoproject.com/en/1.6/topics/auth/default/#groups
260 | .. _Permissions: https://docs.djangoproject.com/en/1.6/topics/auth/default/#permissions-and-authorization
261 | .. _login: https://docs.djangoproject.com/en/1.6/topics/auth/default/#django.contrib.auth.views.login
262 | .. _logout: https://docs.djangoproject.com/en/1.6/topics/auth/default/#django.contrib.auth.views.login
263 | .. _`Django QuerySet`: https://docs.djangoproject.com/en/1.6/ref/class-based-views/mixins-multiple-object/#django.views.generic.list.MultipleObjectMixin.get_queryset
264 |
--------------------------------------------------------------------------------
/tutorial/getting-started.rst:
--------------------------------------------------------------------------------
1 | .. tut::
2 | :path: /src
3 |
4 | .. slideconf::
5 | :autoslides: True
6 | :theme: slides
7 |
8 | =================
9 | Getting Started
10 | =================
11 |
12 | Your Development Environment
13 | ============================
14 |
15 | .. slide:: The Environment
16 | :level: 3
17 |
18 | Three important factors for your environment:
19 |
20 | * Isolation
21 | * Determinism
22 | * Similarity
23 |
24 | .. ifnotslides::
25 |
26 | When thinking about your development environment, there are three
27 | important things to keep in mind: isolation, determinism, and
28 | similarity. They're each important, and they work in concert with one
29 | another.
30 |
31 | **Isolation** means that you're not inadvertently leveraging tools
32 | or packages installed outside the environment. This is particularly
33 | important when it comes to something like Python packages with C
34 | extensions: if you're using something installed at the system level
35 | and don't know it, you can find that when you go to deploy or share
36 | your code that it doesn't operate the way you expect. A tool like
37 | virtualenv_ can help create that sort of environment.
38 |
39 | Your environment is **deterministic** if you're confident about
40 | what versions of your dependencies you're relying on, and can
41 | reproduce that environment reliably.
42 |
43 | Finally, **similarity** to your production or deployment
44 | environment means you're running on the same OS, preferably the
45 | same release, and that you're using the same tools to configure
46 | your development environment that you use to configure your
47 | deployment environment. This is by no means a requirement, but as
48 | you build bigger, more complex software, it's helpful to be
49 | confident that any problem you see in production is reproducable in
50 | your development environment, and limit the scope of investigation
51 | to code you wrote.
52 |
53 | .. _virtualenv: http://www.virtualenv.org/
54 |
55 | Isolation
56 | ---------
57 |
58 | * We want to avoid using unknown dependencies, or unknown versions
59 | * virtualenv_ provides an easy way to work on a project without your
60 | system's ``site-packages``
61 |
62 | Determinism
63 | -----------
64 |
65 | * Determinism is all about dependency management
66 | * Choose a tool, use it in development and production
67 |
68 | * pip, specifically a `requirements files`_
69 | * buildout_
70 | * install_requires_ in setup.py
71 |
72 | * Identify specific versions of dependencies
73 |
74 | .. ifnotslides::
75 |
76 | You can specify versions either by the version for a package on
77 | PyPI, or a specific revision (SHA in git, revision number in
78 | Subversion, etc). This ensures that you're getting the exact
79 | version you're testing with.
80 |
81 | .. _`requirements files`: http://www.pip-installer.org/en/latest/requirements.html
82 | .. _buildout: http://www.buildout.org/
83 | .. _install_requires: http://pythonhosted.org/distribute/setuptools.html#declaring-dependencies
84 |
85 | Similarity
86 | ----------
87 |
88 | * Working in an environment similar to where you deploy eliminates
89 | variables when trying to diagnose an issue
90 | * If you're building something that requires additional services, this
91 | becomes even more important.
92 | * Vagrant_ is a tool for managing virtual machines, lets you easily
93 | create an environment separate from your day to day work.
94 |
95 | .. _Vagrant: http://vagrantup.com/
96 |
97 |
98 | Setting Up Your Environment
99 | ===========================
100 |
101 | .. checkpoint:: environment
102 |
103 | Create a Clean Workspace
104 | ------------------------
105 |
106 | ::
107 |
108 | $ mkdir tutorial
109 | $ virtualenv ./tutorial/
110 | New python executable in ./tutorial/bin/python
111 | Installing setuptools............done.
112 | Installing pip...............done.
113 | $ source ./tutorial/bin/activate
114 | (tutorial)$
115 |
116 | .. Alternately, start by cloning the `example repository`_::
117 |
118 | .. $ git clone git://github.com/nyergler/effective-django-tutorial.git
119 | .. $ cd effective-django-tutorial
120 | .. $ git checkout environment
121 | .. $ virtualenv .
122 | .. New python executable in ./bin/python
123 | .. Installing setuptools............done.
124 | .. Installing pip...............done.
125 | .. $ source ./bin/activate
126 | .. (effective-django-tutorial) $
127 |
128 | .. _`example repository`: https://github.com/nyergler/effective-django-tutorial
129 |
130 | Start a Requirements File
131 | -------------------------
132 |
133 | Create a ``requirements.txt`` in the ``tutorial`` directory with a
134 | single requirement in it.
135 |
136 | .. literalinclude:: /src/requirements.txt
137 |
138 | Installing Requirements
139 | -----------------------
140 |
141 | And then we can use pip_ to install the dependencies.
142 |
143 | ::
144 |
145 | (tutorial)$ pip install -U -r requirements.txt
146 |
147 | Downloading/unpacking Django==1.5.1
148 | Downloading Django-1.5.1.tar.gz (8.0MB): 8.0MB downloaded
149 | Running setup.py egg_info for package Django
150 |
151 | warning: no previously-included files matching '__pycache__' found under directory '*'
152 | warning: no previously-included files matching '*.py[co]' found under directory '*'
153 | Installing collected packages: Django
154 | Running setup.py install for Django
155 | changing mode of build/scripts-2.7/django-admin.py from 644 to 755
156 |
157 | warning: no previously-included files matching '__pycache__' found under directory '*'
158 | warning: no previously-included files matching '*.py[co]' found under directory '*'
159 | changing mode of /home/nathan/p/edt/bin/django-admin.py to 755
160 | Successfully installed Django
161 | Cleaning up...
162 |
163 | .. _pip: http://www.pip-installer.org/
164 |
165 | Beginning a Django Project
166 | ==========================
167 |
168 | .. ifnotslides::
169 |
170 | When a building is under construction, scaffolding is often used to
171 | support the structure before it's complete. The scaffolding can be
172 | temporary, or it can serve as part of the foundation for the
173 | building, but regardless it provides some support when you're just
174 | starting out.
175 |
176 | Django, like many web frameworks, provides scaffolding for your
177 | development efforts. It does this by making decisions and providing
178 | a starting point for your code that lets you focus on the problem
179 | you're trying to solve, and not how to parse an HTTP request.
180 | Django provides HTTP, as well as file system scaffolding.
181 |
182 | The HTTP scaffolding handles things like parsing an HTTP request
183 | into a Python object and providing tools to easily create a
184 | response. The file system scaffolding is a little different: it's a
185 | set of conventions for organizing your code. These conventions make
186 | it easier to add engineers to a project, since they
187 | (hypothetically) have some idea how the code is organized. In
188 | Django parlance, a **project** is the final product, and it
189 | assembles one or more **applications** together. Django 1.4 made a
190 | change to the way the `projects and applications are laid out on
191 | disk`_, which makes it easier to decouple and reuse applications
192 | between projects.
193 |
194 | .. _`projects and applications are laid out on disk`: https://docs.djangoproject.com/en/1.5/releases/1.4/#updated-default-project-layout-and-manage-py
195 |
196 | .. slide:: Scaffolding
197 | :level: 3
198 |
199 | * Django provides file system scaffolding just like HTTP scaffolding
200 | * Helps engineers understand where to find things when they go looking
201 | * Django 1.4 made a change that decouples apps from projects
202 | * In Django parlance, your **project** is a collection of
203 | **applications**.
204 |
205 | Creating the Project
206 | --------------------
207 |
208 | .. ifnotslides::
209 |
210 | Django installs a ``django-admin.py`` script for handling scaffolding
211 | tasks. We'll use ``startproject`` to create the project files. We
212 | specify the project name and the directory to start in; we're already
213 | in our isolated environment so we can just say ``.``
214 |
215 | ::
216 |
217 | (tutorial)$ django-admin.py startproject addressbook .
218 |
219 | ::
220 |
221 | manage.py
222 | ./addressbook
223 | __init__.py
224 | settings.py
225 | urls.py
226 | wsgi.py
227 |
228 | Project Scaffolding
229 | -------------------
230 |
231 | .. ifnotslides::
232 |
233 | * ``manage.py`` is a pointer back to ``django-admin.py`` with an
234 | environment variable set, pointing to your project as the one to
235 | read settings from and operate on when needed.
236 | * ``settings.py`` is where you'll configure your project. It has a
237 | few sensible defaults, but no database chosen when you start.
238 | * ``urls.py`` contains the URL to view mappings: we'll talk more about
239 | that shortly.
240 | * ``wsgi.py`` is a WSGI_ wrapper for your application. This is used
241 | by Django's development servers, and possibly other containers
242 | like mod_wsgi, uwsgi, etc. in production.
243 |
244 | .. ifslides::
245 |
246 | manage.py
247 | Wrapper around ``django-admin.py`` that operates on your project. You
248 | can run the tests or the development server using this.
249 |
250 | settings.py
251 | Your project configuration.
252 |
253 | urls.py
254 | URL definitions for your project
255 |
256 | wsgi.py
257 | A wrapper for running your project in a WSGI_ server.
258 |
259 | .. _WSGI: https://en.wikipedia.org/wiki/Web_Server_Gateway_Interface
260 |
261 | Creating the "App"
262 | ------------------
263 |
264 | ::
265 |
266 | (tutorial)$ python ./manage.py startapp contacts
267 |
268 | ::
269 |
270 | ./addressbook
271 | ./contacts
272 | __init__.py
273 | models.py
274 | tests.py
275 | views.py
276 |
277 | .. notslides::
278 |
279 | * Beginning in Django 1.4, *apps* are placed alongside *project*
280 | packages. This is a great improvement when it comes to
281 | deployment.
282 | * ``models.py`` will contain the Django ORM models for your app.
283 | * ``views.py`` will contain the View code
284 | * ``tests.py`` will contain the unit and integration tests you
285 | write.
286 |
287 | .. XXX: I wish this weren't so stupid; maybe this doc should just have
288 | .. autoslides turned off?
289 |
290 | .. ifnotslides::
291 |
292 | Review
293 | ======
294 |
295 | * Make sure your development environment is deterministic and as
296 | similar to where you'll deploy as possible
297 | * Specify explicit versions for your dependencies
298 | * Django organizes code into "Projects" and "Applications"
299 | * Applications are [potentially] reusable
300 |
301 | .. slide:: Review
302 | :level: 3
303 |
304 | * Make sure your development environment is deterministic and as
305 | similar to where you'll deploy as possible
306 | * Specify explicit versions for your dependencies
307 | * Django organizes code into "Projects" and "Applications"
308 | * Applications are [potentially] reusable
309 |
310 | * Next: :doc:`models`
311 |
--------------------------------------------------------------------------------
/tutorial/models.rst:
--------------------------------------------------------------------------------
1 | .. tut::
2 | :path: /src
3 |
4 | .. slideconf::
5 | :autoslides: False
6 | :theme: single-level
7 |
8 | =============
9 | Using Models
10 | =============
11 |
12 | .. slide:: Django Models
13 | :level: 1
14 |
15 | Storing and manipulating data with the Django ORM.
16 |
17 |
18 | .. checkpoint:: contact_model
19 |
20 | Configuring the Database
21 | ========================
22 |
23 | .. slide:: Configuring Databases
24 | :level: 2
25 |
26 | .. literalinclude:: /src/addressbook/settings.py
27 | :language: python
28 | :lines: 12-21
29 |
30 | Django includes support out of the box for MySQL, PostgreSQL, SQLite3,
31 | and Oracle. SQLite3_ is included with Python starting with version
32 | 2.5, so we'll use it for our project for simplicity. If you were going
33 | to use MySQL, for example, you'd need to add `mysql-python`_ to your
34 | ``requirements.txt`` file.
35 |
36 | To enable SQLite as the database, edit the ``DATABASES`` definition in
37 | ``addressbook/settings.py``. The ``settings.py`` file contains the
38 | Django configuration for our project. There are some settings that you
39 | must specify -- like the ``DATABASES`` configuration -- and others
40 | that are optional. Django fills in some defaults when it generates the
41 | project scaffolding, and the documentation contains a `full list of
42 | settings`_. You can also add your own settings here, if needed.
43 |
44 | For SQLite we need to set the engine and then give it a name. The
45 | SQLite backend uses the ``NAME`` as the filename for the database.
46 |
47 | .. literalinclude:: /src/addressbook/settings.py
48 | :language: python
49 | :lines: 12-21
50 |
51 | Note that the database engine is specified as a string, and not a
52 | direct reference to the Python object. This is because the settings
53 | file needs to be easily importable, without triggering any side
54 | effects. You should avoid adding imports to the settings file.
55 |
56 | You rarely need to import the settings file directly; Django imports
57 | it for you, and makes it available as ``django.conf.settings``. You
58 | typically import your settings from ``django.conf``::
59 |
60 | from django.conf import settings
61 |
62 | Creating a Model
63 | ================
64 |
65 | .. slide:: Defining Models
66 | :level: 2
67 |
68 | Models are created in the ``models`` module of a Django app and
69 | subclass Model_
70 |
71 | .. literalinclude:: /src/contacts/models.py
72 | :language: python
73 | :end-before: __str__
74 |
75 | Django models map (roughly) to a database table, and provide a place
76 | to encapsulate business logic. All models subclass the base Model_
77 | class, and contain field definitions. Let's start by creating a simple
78 | Contact model for our application in ``contacts/models.py``.
79 |
80 |
81 | .. literalinclude:: /src/contacts/models.py
82 | :language: python
83 |
84 | Django provides a set of fields_ that map to data types and different
85 | validation rules. For example, the ``EmailField`` here maps to the
86 | same column type as the ``CharField``, but adds validation for the
87 | data.
88 |
89 | Once you've created a model, you need to update your database with the
90 | new tables. Django's ``syncdb`` command looks for models that are
91 | installed and creates tables for them if needed.
92 |
93 | ::
94 |
95 | (tutorial)$ python ./manage.py syncdb
96 |
97 | Creating tables ...
98 | Creating table auth_permission
99 | Creating table auth_group_permissions
100 | Creating table auth_group
101 | Creating table auth_user_user_permissions
102 | Creating table auth_user_groups
103 | Creating table auth_user
104 | Creating table django_content_type
105 | Creating table django_session
106 | Creating table django_site
107 |
108 | ...
109 |
110 | Our contact table isn't anywhere to be seen. The reason is that we
111 | need to tell the Project to use the Application.
112 |
113 | The ``INSTALLED_APPS`` setting lists the applications that the project
114 | uses. These are listed as strings that map to Python packages. Django
115 | will import each and looks for a ``models`` module there. Add our
116 | Contacts app to the project's ``INSTALLED_APPS`` setting:
117 |
118 | .. literalinclude:: /src/addressbook/settings.py
119 | :language: python
120 | :lines: 111-123
121 |
122 | Then run ``syncdb`` again::
123 |
124 | (tutorial)$ python ./manage.py syncdb
125 | Creating tables ...
126 | Creating table contacts_contact
127 | Installing custom SQL ...
128 | Installing indexes ...
129 | Installed 0 object(s) from 0 fixture(s)
130 |
131 | Note that Django created a table named ``contacts_contact``: by
132 | default Django will name your tables using a combination of the
133 | application name and model name. You can override that with the
134 | `model Meta`_ options.
135 |
136 |
137 | Interacting with the Model
138 | ==========================
139 |
140 | .. slide:: Instantiating Models
141 | :level: 2
142 |
143 | ::
144 |
145 | nathan = Contact()
146 | nathan.first_name = 'Nathan'
147 | nathan.last_name = 'Yergler'
148 | nathan.save()
149 |
150 | ::
151 |
152 | nathan = Contact.objects.create(
153 | first_name='Nathan',
154 | last_name='Yergler')
155 |
156 | ::
157 |
158 | nathan = Contact(
159 | first_name='Nathan',
160 | last_name='Yergler')
161 | nathan.save()
162 |
163 | Now that the model has been synced to the database we can interact
164 | with it using the interactive shell.
165 |
166 | ::
167 |
168 | (tutorial)$ python ./manage.py shell
169 | Python 2.7.3 (default, Aug 9 2012, 17:23:57)
170 | [GCC 4.7.1 20120720 (Red Hat 4.7.1-5)] on linux2
171 | Type "help", "copyright", "credits" or "license" for more information.
172 | (InteractiveConsole)
173 | >>> from contacts.models import Contact
174 | >>> Contact.objects.all()
175 | []
176 | >>> Contact.objects.create(first_name='Nathan', last_name='Yergler')
177 |
178 | >>> Contact.objects.all()
179 | []
180 | >>> nathan = Contact.objects.get(first_name='Nathan')
181 | >>> nathan
182 |
183 | >>> print nathan
184 | Nathan Yergler
185 | >>> nathan.id
186 | 1
187 |
188 | There are a few new things here. First, the ``manage.py shell``
189 | command gives us a interactive shell with the Python path set up
190 | correctly for Django. If you try to run Python and just import your
191 | application, an Exception will be raised because Django doesn't know
192 | which ``settings`` to use, and therefore can't map Model instances to
193 | the database.
194 |
195 | Second, there's this ``objects`` property on our model class. That's
196 | the model's Manager_. If a single instance of a Model is analogous to
197 | a row in the database, the Manager is analogous to the table. The
198 | default model manager provides querying functionality, and can be
199 | customized. When we call ``all()`` or ``filter()`` or the Manager, a
200 | QuerySet is returned. A QuerySet is iterable, and loads data from the
201 | database as needed.
202 |
203 | Finally, there's this ``id`` field that we didn't define. Django adds
204 | an ``id`` field as the primary key for your model, unless you `specify
205 | a primary key`_.
206 |
207 |
208 | .. slide:: Model Managers
209 | :level: 2
210 |
211 | * A model instance maps to a row
212 | * The model Manager_ maps to the table
213 | * Every model has a default manager, ``objects``
214 | * Operations that deal with more than one instance, or at the
215 | "collection" level, usually map to the Manager
216 |
217 | .. slide:: Querying with Managers
218 | :level: 2
219 |
220 | * The ``filter`` Manager method lets you perform queries::
221 |
222 | Contact.objects.filter(last_name='Yergler')
223 |
224 | * ``filter`` returns a QuerySet_, an iterable over the result.
225 | * You can also assert you only expect one::
226 |
227 | Contact.objects.get(first_name='Nathan')
228 |
229 | * If more than one is returned, an Exception will be raised
230 | * The full query_ reference is pretty good on this topic.
231 |
232 | Writing a Test
233 | ==============
234 |
235 | .. slide:: Testing Models
236 | :level: 2
237 |
238 | * Business logic is usually added as methods on a Model.
239 | * Important to write unit tests for those methods as you add them.
240 | * We'll write an example test for the methods we add.
241 |
242 |
243 | We have one method defined on our model, ``__str__``, and this is a
244 | good time to start writing tests. The ``__str__`` method of a model
245 | will get used in quite a few places, and it's entirely conceivable
246 | it'd be exposed to end users. It's worth writing a test so we
247 | understand how we expect it to operate. Django creates a ``tests.py``
248 | file when it creates the application, so we'll add our first test to
249 | that file in the contacts app.
250 |
251 | .. literalinclude:: /src/contacts/tests.py
252 | :language: python
253 | :prepend: from contacts.models import Contact
254 | ...
255 | :pyobject: ContactTests
256 |
257 | .. slide:: Running the Tests
258 | :level: 2
259 |
260 | You can run the tests for your application using ``manage.py``::
261 |
262 | (tutorial)$ python manage.py test
263 |
264 |
265 | You can run the tests for your application using ``manage.py``::
266 |
267 | (tutorial)$ python manage.py test
268 |
269 | If you run this now, you'll see that around 420 tests run. That's
270 | surprising, since we've only written one. That's because by default
271 | Django runs the tests for all installed applications. When we added
272 | the ``contacts`` app to our project, there were several Django apps
273 | there by default. The extra 419 tests come from those.
274 |
275 | If you want to run the tests for a specific app, just specify the app
276 | name on the command line::
277 |
278 | (tutorial)$ python manage.py test contacts
279 | Creating test database for alias 'default'...
280 | ..
281 | ----------------------------------------------------------------------
282 | Ran 2 tests in 0.000s
283 |
284 | OK
285 | Destroying test database for alias 'default'...
286 | $
287 |
288 | One other interesting thing to note before moving on is the first and
289 | last line of output: "Creating test database" and "Destroying test
290 | database". Some tests need access to a database, and because we don't
291 | want to mingle test data with "real" data (for a variety of reasons,
292 | not the least of which is determinism), Django helpfully creates a
293 | test database for us before running the tests. Essentially it creates
294 | a new database, and runs ``syncdb`` on it. If you subclass from
295 | Django's ``TestCase`` (which we are), Django also resets any default
296 | data after running each TestCase, so that changes in one test won't
297 | break or influence another.
298 |
299 | .. rst-class:: include-as-slide, slide-level-2
300 |
301 | Review
302 | ======
303 |
304 | * Models define the fields in a table, and can contain business logic.
305 | * The ``syncdb`` manage command creates the tables in your database from
306 | models
307 | * The model Manager allows you to operate on the collection of
308 | instances: querying, creating, etc.
309 | * Write unit tests for methods you add to the model
310 | * The ``test`` manage command runs the unit tests
311 |
312 | .. ifslides::
313 |
314 | * Next: :doc:`views`
315 |
316 | .. _QuerySet: https://docs.djangoproject.com/en/1.5/ref/models/querysets/#django.db.models.query.QuerySet
317 | .. _query: https://docs.djangoproject.com/en/1.5/topics/db/queries/
318 | .. _SQLite3: http://docs.python.org/2/library/sqlite3.html
319 | .. _mysql-python: https://pypi.python.org/pypi/MySQL-python
320 | .. _`full list of settings`: https://docs.djangoproject.com/en/1.5/ref/settings/
321 | .. _Model: https://docs.djangoproject.com/en/1.5/ref/models/instances/#django.db.models.Model
322 | .. _Manager: https://docs.djangoproject.com/en/1.5/topics/db/managers/
323 | .. _`specify a primary key`: https://docs.djangoproject.com/en/1.5/topics/db/models/#automatic-primary-key-fields
324 | .. _fields: https://docs.djangoproject.com/en/1.5/ref/models/fields/
325 | .. _`model Meta`: https://docs.djangoproject.com/en/1.5/ref/models/options/
326 |
--------------------------------------------------------------------------------
/tutorial/forms.rst:
--------------------------------------------------------------------------------
1 | .. tut::
2 | :path: /src
3 |
4 | .. slideconf::
5 | :autoslides: False
6 | :theme: single-level
7 |
8 | =============
9 | Form Basics
10 | =============
11 |
12 | .. slide:: Django Forms
13 | :level: 1
14 |
15 | Validate user input and return Python objects
16 |
17 | .. slide:: Defining Forms
18 | :level: 2
19 |
20 | Forms are composed of fields, which have a widget.
21 |
22 | .. testcode::
23 |
24 | from django.utils.translation import gettext_lazy as _
25 | from django import forms
26 |
27 | class ContactForm(forms.Form):
28 |
29 | name = forms.CharField(label=_("Your Name"),
30 | max_length=255,
31 | widget=forms.TextInput,
32 | )
33 |
34 | email = forms.EmailField(label=_("Email address"))
35 |
36 | .. slide:: Instantiating a Form
37 | :level: 2
38 |
39 | Unbound forms don't have data associated with them, but they can
40 | be rendered::
41 |
42 | form = ContactForm()
43 |
44 | Bound forms have specific data associated, which can be
45 | validated::
46 |
47 | form = ContactForm(data=request.POST, files=request.FILES)
48 |
49 | .. slide:: Accessing Fields
50 | :level: 2
51 |
52 | Two ways to access fields on a Form instance
53 |
54 | - ``form.fields['name']`` returns the ``Field`` object
55 | - ``form['name']`` returns a ``BoundField``
56 | - ``BoundField`` wraps a field and value for HTML output
57 |
58 | .. slide:: Validating the Form
59 | :level: 2
60 |
61 | .. blockdiag::
62 |
63 | blockdiag {
64 | // Set labels to nodes.
65 | A [label = "Field Validation"];
66 | C [label = "Form Validation"];
67 |
68 | A -> C;
69 | }
70 |
71 | - Only bound forms can be validated
72 | - Calling ``form.is_valid()`` triggers validation if needed
73 | - Validated, cleaned data is stored in ``form.cleaned_data``
74 | - Calling ``form.full_clean()`` performs the full cycle
75 |
76 | .. slide:: Field Validation
77 | :level: 2
78 |
79 | .. blockdiag::
80 |
81 | blockdiag {
82 | // Set labels to nodes.
83 | A [label = "for each Field"];
84 |
85 | B [label = "Field.clean"];
86 | C [label = "Field.to_python"];
87 | D [label = "Field validators"];
88 |
89 | F [label = ".clean_fieldname()"];
90 |
91 | A -> B;
92 | B -> C;
93 | C -> D;
94 |
95 | A -> F;
96 | }
97 |
98 | - Three phases for Fields: To Python, Validation, and Cleaning
99 | - If validation raises an Error, cleaning is skipped
100 | - Validators are callables that can raise a ``ValidationError``
101 | - Django includes generic ones for some common tasks
102 | - Examples: URL, Min/Max Value, Min/Max Length, URL, Regex, email
103 |
104 | .. slide:: Field Cleaning
105 | :level: 2
106 |
107 | - ``.clean_fieldname()`` method is called after validators
108 | - Input has already been converted to Python objects
109 | - Methods can raise ``ValidationErrors``
110 | - Methods *must* return the cleaned value
111 |
112 | Up until this point we've been using forms without really needing to
113 | be aware of it. A `Django Form`_ is responsible for taking some user
114 | input, validating it, and turning it into Python objects. They also
115 | have some handy rendering methods, but I consider those sugar: the
116 | real power is in making sure that input from your users is what it
117 | says it is.
118 |
119 | The `Generic Views`_, specifically the ones we've been using, all
120 | operate on a particular model. Django is able to take the model
121 | definition that we've created and extrapolate a Form from it. Django
122 | can do this because both Models and Forms are constructed of fields
123 | that have a particular type and particular validation rules. Models
124 | use those fields to map data to types that your database understands;
125 | Forms use them to map input to Python types [1]_. Forms that map to a
126 | particular Model are called ModelForms_; you can think of them as
127 | taking user input and transforming it into an instance of a Model.
128 |
129 | .. [1] While I'm referring to them both as fields, they're really
130 | completely different implementations. But the analogy holds.
131 |
132 | Adding Fields to the Form
133 | -------------------------
134 |
135 | .. checkpoint:: confirm_contact_email
136 |
137 | So what if we want to add a field to our form? Say, we want to require
138 | confirmation of the email address. In that case we can create a new
139 | form, and override the default used by our views.
140 |
141 | First, in the ``contacts`` app directory, we'll create a new file,
142 | ``forms.py``.
143 |
144 | .. literalinclude:: /src/contacts/forms.py
145 | :end-before: def clean
146 |
147 | Here we're creating a new ``ModelForm``; we associate the form with
148 | our model in the ``Meta`` inner class.
149 |
150 | We're also adding an additional field, ``confirm_email``. This is an
151 | example of a field declaration in a model. The first argument is the
152 | label, and then there are additional keyword arguments; in this case,
153 | we simply mark it required.
154 |
155 | Finally, in the constructor we mutate the ``initial`` kwarg.
156 | ``initial`` is a dictionary of values that will be used as the default
157 | values for an `unbound form`_. Model Forms have another kwarg,
158 | ``instance``, that holds the instance we're editing.
159 |
160 | Overriding the Default Form
161 | ---------------------------
162 |
163 | We've defined a form with the extra field, but we still need to tell
164 | our view to use it. You can do this in a couple of ways, but the
165 | simplest is to set the ``form_class`` property on the View class.
166 | We'll add that property to our ``CreateContactView`` and
167 | ``UpdateContactView`` in ``views.py``.
168 |
169 | .. literalinclude:: /src/contacts/views.py
170 | :prepend: import forms
171 | ...
172 | :pyobject: CreateContactView
173 | :end-before: def get_success_url
174 |
175 | .. literalinclude:: /src/contacts/views.py
176 | :pyobject: UpdateContactView
177 | :end-before: def get_success_url
178 |
179 | If we fire up the server and visit the edit or create pages, we'll see
180 | the additional field. We can see that it's required, but there's no
181 | validation that the two fields match. To support that we'll need to
182 | add some custom validation to the Form.
183 |
184 | Customizing Validation
185 | ----------------------
186 |
187 | Forms have two different phases of validation: field and form. All the
188 | fields are validated and converted to Python objects (if possible)
189 | before form validation begins.
190 |
191 | Field validation takes place for an individual field: things like
192 | minimum and maximum length, making sure it looks like a URL, and date
193 | range validation are all examples of field validation. Django doesn't
194 | guarantee that field validation happens in any order, so you can't
195 | count on other fields being available for comparison during this
196 | phase.
197 |
198 | Form validation, on the other hand, happens after all fields have been
199 | validated and converted to Python objects, and gives you the
200 | opportunity to do things like make sure passwords match, or in this
201 | case, email addresses.
202 |
203 | Form validation takes place in a form's ``clean()`` method.
204 |
205 | .. literalinclude:: /src/contacts/forms.py
206 | :prepend: class ContactForm(forms.ModelForm):
207 | ...
208 | :language: python
209 | :pyobject: ContactForm.clean
210 | :end-before: inlineformset
211 |
212 | When you enter the ``clean`` method, all of the fields that validated
213 | are available in the ``cleaned_data`` dictionary. The ``clean`` method
214 | may add, remove, or modify values, but **must** return the dictionary
215 | of cleaned data. ``clean`` may also raise a ``ValidationError`` if it
216 | encounters an error. This will be available as part of the forms'
217 | ``errors`` property, and is shown by default when you render the form.
218 |
219 | Note that I said ``cleaned_data`` contains all the fields *that
220 | validated*. That's because form-level validation **always** happens,
221 | even if no fields were successfully validated. That's why in the clean
222 | method we use ``cleaned_data.get('email')`` instead of
223 | ``cleaned_data['email']``.
224 |
225 | If you visit the create or update views now, we'll see an extra field
226 | there. Try to make a change, or create a contact, without entering the
227 | email address twice.
228 |
229 | Controlling Form Rendering
230 | --------------------------
231 |
232 | .. checkpoint:: custom_form_rendering
233 |
234 | .. slide:: Rendering Forms
235 | :level: 2
236 |
237 | Three primary "whole-form" output modes:
238 |
239 | - ``form.as_p()``, ``form.as_ul()``, ``form.as_table()``
240 |
241 | ::
242 |
243 |
244 |
245 |
246 |
247 |
248 |
249 |
250 | Our templates until now look pretty magical when it comes to forms:
251 | the extent of our HTML tags has been something like::
252 |
253 |
260 |
261 | We're living at the whim of ``form.as_ul``, and it's likely we want
262 | something different.
263 |
264 | Forms have three pre-baked output formats: ``as_ul``, ``as_p``, and
265 | ``as_table``. If ``as_ul`` outputs the form elements as the items in
266 | an unordered list, it's not too mysterious what ``as_p`` and
267 | ``as_table`` do.
268 |
269 | .. slide:: Controlling Form Output
270 | :level: 2
271 |
272 | ::
273 |
274 | {% for field in form %}
275 | {{ field.label_tag }}: {{ field }}
276 | {{ field.errors }}
277 | {% endfor %}
278 | {{ form.non_field_errors }}
279 |
280 | Additional rendering properties:
281 |
282 | - ``field.label``
283 | - ``field.label_tag``
284 | - ``field.auto_id``
285 | - ``field.help_text``
286 |
287 | Often, though, you need more control. For those cases, you can take
288 | full control. First, a form is iterable; try replacing your call to
289 | ``{{form.as_ul}}`` with this::
290 |
291 | {% for field in form %}
292 | {{ field }}
293 | {% endfor %}
294 |
295 | As you can see, ``field`` renders as the input for each field in the
296 | form. When you iterate over a Form, you're iterating over a sequence
297 | of `BoundField`_ objects. A ``BoundField`` wraps the field definition
298 | from your Form (or derived from the ModelForm) along with any data and
299 | error state it may be bound to. This means it has some properties that
300 | are handy for customizing rendering.
301 |
302 | In addition to supporting iteration, you can access an individual
303 | BoundField directly, treating the Form like a dictionary::
304 |
305 | {{ form.email }}
306 |
307 | .. sidebar:: Dictionary!?!
308 |
309 | That may not look like a dictionary access, but remember that Django
310 | templates are quite restrictive in their syntax. Writing ``foo.bar``
311 | will look for a property ``bar`` on ``foo``, and if it's callable,
312 | call it. If it doesn't find a property, it'll map that to something
313 | like ``foo['bar']``. So when it comes to writing Django templates,
314 | dictionary elements act just like properties.
315 |
316 | Consider the following alternative to ``edit_contact.html``.
317 |
318 | .. literalinclude:: /src/contacts/templates/edit_contact_custom.html
319 | :language: html
320 |
321 | In this example we see a few different things at work:
322 |
323 | * ``field.auto_id`` to get the automatically generated field ID
324 | * Combining that ID with ``_container`` and ``_errors`` to give our
325 | related elements names that consistently match
326 | * Using ``field.label_tag`` to generate the label. ``label_tag`` adds
327 | the appropriate ``for`` property to the tag, too. For the
328 | ``last_name`` field, this looks like::
329 |
330 |
331 |
332 | * Using ``field.errors`` to show the errors in a specific place. The
333 | Django Form documentation has details on further customizing `how
334 | errors are displayed`_.
335 | * Finally, ``field.help_text``. You can specify a ``help_text``
336 | keyword argument to each field when creating your form, which is
337 | accessible here. Defining that text in the Form definition is
338 | desirable because you can easily mark it up for translation.
339 |
340 | Testing Forms
341 | -------------
342 |
343 | .. checkpoint:: contact_form_test
344 |
345 | It's easy to imagine how you'd use the ``LiveServerTestCase`` to write
346 | an integration test for a Form. But that wouldn't just be testing the
347 | Form, that'd be testing the View, the URL configuration, and probably
348 | the Model (in this case, at least). We've built some custom logic into
349 | our form's validator, and it's important to test that and that alone.
350 | Integration tests are invaluable, but when they fail there's more than
351 | one suspect. I like tests that fail with a single suspect.
352 |
353 | Writing unit tests for a Form usually means crafting some dictionary
354 | of form data that meets the starting condition for your test. Some
355 | Forms can be complex or long, so we can use a helper to generate the
356 | starting point from the Form's initial data.
357 |
358 | **Rebar** is a collection of utilities for working with Forms. We'll
359 | install Rebar so we can use the testing utilities.
360 |
361 | ::
362 |
363 | (tutorial)$ pip install rebar
364 |
365 | Then we can write a unit test that tests two cases: success (email
366 | addresses match) and failure (they do not).
367 |
368 | .. literalinclude:: /src/contacts/tests.py
369 | :prepend: from rebar.testing import flatten_to_dict
370 | from contacts import forms
371 | ...
372 | :pyobject: EditContactFormTests
373 |
374 | An interesting thing to note here is the use of the ``is_valid()``
375 | method. We could just as easily introspect the ``errors`` property
376 | that we used in our template above, but in this case we just need a
377 | Boolean answer: is the form valid, or not? Note that we do need to
378 | provide a first and last name, as well, since those are required
379 | fields.
380 |
381 | Review
382 | ------
383 |
384 | * Forms take user input, validate it, and convert it to Python objects
385 | * Forms are composed of Fields, just like Models
386 | * Fields have validation built in
387 | * You can customize per-field validation, as well as form validation
388 | * If you need to compare fields to one another, you need to implement
389 | the ``clean`` method
390 | * Forms are iterable over, and support dictionary-like access to, the
391 | bound fields
392 | * A Bound Field has properties and methods for performing fine-grained
393 | customization of rendering.
394 | * Forms are unit testable; Rebar has some utilities to help with
395 | testing large forms.
396 |
397 | .. _`Django Form`: https://docs.djangoproject.com/en/1.5/topics/forms/
398 | .. _`Generic Views`: https://docs.djangoproject.com/en/1.5/topics/class-based-views/
399 | .. _ModelForms: https://docs.djangoproject.com/en/1.5/topics/forms/modelforms
400 | .. _`unbound form`: https://docs.djangoproject.com/en/1.5/ref/forms/api/#ref-forms-api-bound-unbound
401 | .. _`BoundField`: https://docs.djangoproject.com/en/1.5/ref/forms/api/#django.forms.BoundField
402 | .. _`how errors are displayed`: https://docs.djangoproject.com/en/1.5/ref/forms/api/#how-errors-are-displayed
403 |
--------------------------------------------------------------------------------
/forms.rst:
--------------------------------------------------------------------------------
1 | ======================
2 | Effective Django Forms
3 | ======================
4 |
5 | Form Basics
6 | ===========
7 |
8 | Forms in Context
9 | ----------------
10 |
11 | .. Table::
12 | :class: context-table
13 |
14 | +-------------------------+---------------------------------+
15 | | **Views** | Convert Request to Response |
16 | +-------------------------+---------------------------------+
17 | | **Forms** | Convert input to Python objects |
18 | +-------------------------+---------------------------------+
19 | | **Models** | Data and business logic |
20 | +-------------------------+---------------------------------+
21 |
22 | .. Why use Forms?
23 | .. --------------
24 |
25 | .. - Data type coercion
26 | .. - Validation
27 | .. - Consistent HTML output
28 |
29 | Defining Forms
30 | --------------
31 |
32 | Forms are composed of fields, which have a widget.
33 |
34 | .. testcode::
35 |
36 | from django.utils.translation import gettext_lazy as _
37 | from django import forms
38 |
39 | class ContactForm(forms.Form):
40 |
41 | name = forms.CharField(label=_("Your Name"),
42 | max_length=255,
43 | widget=forms.TextInput,
44 | )
45 |
46 | email = forms.EmailField(label=_("Email address"))
47 |
48 | Instantiating a Form
49 | --------------------
50 |
51 | Unbound forms don't have data associated with them, but they can
52 | be rendered::
53 |
54 | form = ContactForm()
55 |
56 | Bound forms have specific data associated, which can be
57 | validated::
58 |
59 | form = ContactForm(data=request.POST, files=request.FILES)
60 |
61 | Accessing Fields
62 | ----------------
63 |
64 | Two ways to access fields on a Form instance
65 |
66 | - ``form.fields['name']`` returns the ``Field`` object
67 | - ``form['name']`` returns a ``BoundField``
68 | - ``BoundField`` wraps a field and value for HTML output
69 |
70 | Initial Data
71 | ------------
72 |
73 | .. testcode::
74 |
75 | form = ContactForm(
76 | initial={
77 | 'name': 'First and Last Name',
78 | },
79 | )
80 |
81 | .. doctest::
82 |
83 | >>> form['name'].value()
84 | 'First and Last Name'
85 |
86 |
87 | Validation
88 | ==========
89 |
90 | Validating the Form
91 | -------------------
92 |
93 | .. blockdiag::
94 |
95 | blockdiag {
96 | // Set labels to nodes.
97 | A [label = "Field Validation"];
98 | C [label = "Form Validation"];
99 |
100 | A -> C;
101 | }
102 |
103 | - Only bound forms can be validated
104 | - Calling ``form.is_valid()`` triggers validation if needed
105 | - Validated, cleaned data is stored in ``form.cleaned_data``
106 | - Calling ``form.full_clean()`` performs the full cycle
107 |
108 | Field Validation
109 | ----------------
110 |
111 | .. blockdiag::
112 |
113 | blockdiag {
114 | // Set labels to nodes.
115 | A [label = "for each Field"];
116 |
117 | B [label = "Field.clean"];
118 | C [label = "Field.to_python"];
119 | D [label = "Field validators"];
120 |
121 | F [label = ".clean_fieldname()"];
122 |
123 | A -> B;
124 | B -> C;
125 | C -> D;
126 |
127 | A -> F;
128 | }
129 |
130 | - Three phases for Fields: To Python, Validation, and Cleaning
131 | - If validation raises an Error, cleaning is skipped
132 | - Validators are callables that can raise a ``ValidationError``
133 | - Django includes generic ones for some common tasks
134 | - Examples: URL, Min/Max Value, Min/Max Length, URL, Regex, email
135 |
136 | Field Cleaning
137 | --------------
138 |
139 | - ``.clean_fieldname()`` method is called after validators
140 | - Input has already been converted to Python objects
141 | - Methods can raise ``ValidationErrors``
142 | - Methods *must* return the cleaned value
143 |
144 | ``.clean_email()``
145 | ------------------
146 |
147 | .. testcode::
148 |
149 | class ContactForm(forms.Form):
150 | name = forms.CharField(
151 | label=_("Name"),
152 | max_length=255,
153 | )
154 |
155 | email = forms.EmailField(
156 | label=_("Email address"),
157 | )
158 |
159 | def clean_email(self):
160 |
161 | if (self.cleaned_data.get('email', '')
162 | .endswith('hotmail.com')):
163 |
164 | raise ValidationError("Invalid email address.")
165 |
166 | return self.cleaned_data.get('email', '')
167 |
168 | Form Validation
169 | ---------------
170 |
171 | - ``.clean()`` performs cross-field validation
172 | - Called even if errors were raised by Fields
173 | - *Must* return the cleaned data dictionary
174 | - ``ValidationErrors`` raised by ``.clean()`` will be grouped in
175 | ``form.non_field_errors()`` by default.
176 |
177 | ``.clean()`` example
178 | --------------------
179 |
180 | .. testcode::
181 |
182 | class ContactForm(forms.Form):
183 | name = forms.CharField(
184 | label=_("Name"),
185 | max_length=255,
186 | )
187 |
188 | email = forms.EmailField(label=_("Email address"))
189 | confirm_email = forms.EmailField(label=_("Confirm"))
190 |
191 | def clean(self):
192 | if (self.cleaned_data.get('email') !=
193 | self.cleaned_data.get('confirm_email')):
194 |
195 | raise ValidationError("Email addresses do not match.")
196 |
197 | return self.cleaned_data
198 |
199 | Initial != Default Data
200 | -----------------------
201 |
202 | - Initial data is used as a starting point
203 | - It does not automatically propagate to ``cleaned_data``
204 | - Defaults for non-required fields should be specified when
205 | accessing the dict::
206 |
207 | self.cleaned_data.get('name', 'default')
208 |
209 | Passing Extra Information
210 | -------------------------
211 |
212 | - Sometimes you need extra information in a form
213 | - Pass as a keyword argument, and pop in __init__
214 |
215 | .. testcode::
216 |
217 | class MyForm(forms.Form):
218 | def __init__(self, *args, **kwargs):
219 | self._user = kwargs.pop('user')
220 | super(MyForm, self).__init__(*args, **kwargs)
221 |
222 | Tracking Changes
223 | ----------------
224 |
225 | - Forms use initial data to track changed fields
226 | - ``form.has_changed()``
227 | - ``form.changed_data``
228 | - Fields can render a hidden input with the initial value, as well::
229 |
230 | >>> changed_date = forms.DateField(show_hidden_initial=True)
231 | >>> print form['changed_date']
232 | ''
233 |
234 |
235 | Testing
236 | =======
237 |
238 | Testing Forms
239 | -------------
240 |
241 | - Remember what Forms are for
242 | - Testing strategies
243 |
244 | * Initial states
245 | * Field Validation
246 | * Final state of ``cleaned_data``
247 |
248 | Unit Tests
249 | ----------
250 |
251 | .. testcode::
252 |
253 | import unittest
254 |
255 | class FormTests(unittest.TestCase):
256 | def test_validation(self):
257 | form_data = {
258 | 'name': 'X' * 300,
259 | }
260 |
261 | form = ContactForm(data=form_data)
262 | self.assertFalse(form.is_valid())
263 |
264 | Test Data
265 | ---------
266 |
267 | .. testcode::
268 |
269 | from rebar.testing import flatten_to_dict
270 |
271 | form_data = flatten_to_dict(ContactForm())
272 | form_data.update({
273 | 'name': 'X' * 300,
274 | })
275 | form = ContactForm(data=form_data)
276 | assert(not form.is_valid())
277 |
278 |
279 | Rendering Forms
280 | ===============
281 |
282 | Idiomatic Form Usage
283 | --------------------
284 |
285 | .. testcode::
286 |
287 | from django.views.generic.edit import FormMixin, ProcessFormView
288 |
289 | class ContactView(FormMixin, ProcessFormView):
290 | form_class = ContactForm
291 | success_url = '/contact/sent'
292 |
293 | def form_valid(self, form):
294 | # do something -- save, send, etc
295 | pass
296 |
297 | def form_invalid(self, form):
298 | # do something -- log the error, etc -- if needed
299 | pass
300 |
301 | Form Output
302 | -----------
303 |
304 | Three primary "whole-form" output modes:
305 |
306 | - ``form.as_p()``, ``form.as_ul()``, ``form.as_table()``
307 |
308 | ::
309 |
310 |
311 |
312 |
313 |
314 |
315 |
316 |
317 |
318 |
319 | Controlling Form Output
320 | -----------------------
321 |
322 | ::
323 |
324 | {% for field in form %}
325 | {{ field.label_tag }}: {{ field }}
326 | {{ field.errors }}
327 | {% endfor %}
328 | {{ form.non_field_errors }}
329 |
330 | Additional rendering properties:
331 |
332 | - ``field.label``
333 | - ``field.label_tag``
334 | - ``field.html_name``
335 | - ``field.help_text``
336 |
337 | Customizing Rendering
338 | ---------------------
339 |
340 | You can specify additional attributes for widgets as part of the form
341 | definition.
342 |
343 | .. testcode::
344 |
345 | class ContactForm(forms.Form):
346 | name = forms.CharField(
347 | max_length=255,
348 | widget=forms.Textarea(
349 | attrs={'class': 'custom'},
350 | ),
351 | )
352 |
353 | You can also specify form-wide CSS classes to add for error and
354 | required states.
355 |
356 | .. testcode::
357 |
358 | class ContactForm(forms.Form):
359 | error_css_class = 'error'
360 | required_css_class = 'required'
361 |
362 |
363 | Customizing Error Messages
364 | --------------------------
365 |
366 | Built in validators have default error messages
367 |
368 | .. doctest::
369 |
370 | >>> generic = forms.CharField()
371 | >>> generic.clean('')
372 | Traceback (most recent call last):
373 | ...
374 | ValidationError: [u'This field is required.']
375 |
376 | ``error_messages`` lets you customize those messages
377 |
378 | .. doctest::
379 |
380 | >>> name = forms.CharField(
381 | ... error_messages={'required': 'Please enter your name'})
382 | >>> name.clean('')
383 | Traceback (most recent call last):
384 | ...
385 | ValidationError: [u'Please enter your name']
386 |
387 | Error Class
388 | -----------
389 |
390 | - ``ValidationErrors`` raised are wrapped in a class
391 | - This class controls HTML formatting
392 | - By default, ``ErrorList`` is used: outputs as ``
``
393 | - Specify the ``error_class`` kwarg when constructing the form to
394 | override
395 |
396 | Error Class
397 | -----------
398 |
399 | .. testcode::
400 |
401 | from django.forms.util import ErrorList
402 |
403 | class ParagraphErrorList(ErrorList):
404 | def __unicode__(self):
405 | return self.as_paragraphs()
406 |
407 | def as_paragraphs(self):
408 | return "
%s
" % (
409 | ",".join(e for e in self.errors)
410 | )
411 |
412 | form = ContactForm(data=form_data, error_class=ParagraphErrorList)
413 |
414 | Multiple Forms
415 | --------------
416 |
417 | Avoid potential name collisions with ``prefix``:
418 |
419 | .. testcode::
420 |
421 | contact_form = ContactForm(prefix='contact')
422 |
423 | Adds the prefix to HTML name and ID::
424 |
425 |
426 |
428 |
429 |
431 |
433 |
435 |
436 | Forms for Models
437 | ================
438 |
439 | Model Forms
440 | -----------
441 |
442 | - ModelForms map a Model to a Form
443 | - Validation includes Model validators by default
444 | - Supports creating and editing instances
445 | - Key differences from Forms:
446 |
447 | - A field for the Primary Key (usually ``id``)
448 | - ``.save()`` method
449 | - ``.instance`` property
450 |
451 | Model Forms
452 | -----------
453 |
454 | ::
455 |
456 | from django.db import models
457 | from django import forms
458 |
459 | class Contact(models.Model):
460 | name = models.CharField(max_length=100)
461 | email = models.EmailField()
462 | notes = models.TextField()
463 |
464 | class ContactForm(forms.ModelForm):
465 | class Meta:
466 | model = Contact
467 |
468 | Limiting Fields
469 | ---------------
470 |
471 | - You don't need to expose all the fields in your form
472 | - You can either specify fields to expose, or fields to exclude
473 |
474 | ::
475 |
476 | class ContactForm(forms.ModelForm):
477 |
478 | class Meta:
479 | model = Contact
480 | fields = ('name', 'email',)
481 |
482 |
483 |
484 | class ContactForm(forms.ModelForm):
485 |
486 | class Meta:
487 | model = Contact
488 | exclude = ('notes',)
489 |
490 | Overriding Fields
491 | -----------------
492 |
493 | - Django will generate fields and widgets based on the model
494 | - These can be overridden, as well
495 |
496 | ::
497 |
498 | class ContactForm(forms.ModelForm):
499 |
500 | name = forms.CharField(widget=forms.TextInput)
501 |
502 | class Meta:
503 | model = Contact
504 |
505 |
506 | Instantiating Model Forms
507 | -------------------------
508 |
509 | ::
510 |
511 | model_form = ContactForm()
512 |
513 | model_form = ContactForm(
514 | instance=Contact.objects.get(id=2)
515 | )
516 |
517 | ModelForm.is_valid()
518 | --------------------
519 |
520 | .. blockdiag::
521 |
522 | blockdiag {
523 | // Set labels to nodes.
524 | A [label = "Field Validation"];
525 | C [label = "Form Validation"];
526 | D [label = "_post_clean()"];
527 |
528 | A -> C -> D;
529 | }
530 |
531 | - Model Forms have an additional method, ``_post_clean()``
532 | - Sets cleaned fields on the Model instance
533 | - Called *regardless* of whether the form is valid
534 |
535 | Testing
536 | -------
537 |
538 | ::
539 |
540 | class ModelFormTests(unittest.TestCase):
541 | def test_validation(self):
542 | form_data = {
543 | 'name': 'Test Name',
544 | }
545 |
546 | form = ContactForm(data=form_data)
547 | self.assert_(form.is_valid())
548 | self.assertEqual(form.instance.name, 'Test Name')
549 |
550 | form.save()
551 |
552 | self.assertEqual(
553 | Contact.objects.get(id=form.instance.id).name,
554 | 'Test Name'
555 | )
556 |
557 |
558 | Form Sets
559 | =========
560 |
561 | Form Sets
562 | ---------
563 |
564 | - Handles multiple copies of the same form
565 | - Adds a unique prefix to each form::
566 |
567 | form-1-name
568 |
569 | - Support for insertion, deletion, and ordering
570 |
571 |
572 | Defining Form Sets
573 | ------------------
574 |
575 | .. testcode::
576 |
577 | from django.forms import formsets
578 |
579 | ContactFormSet = formsets.formset_factory(
580 | ContactForm,
581 | )
582 |
583 | .. testcode::
584 | :hide:
585 |
586 | request = request_factory.post(
587 | '/',
588 | rebar.testing.flatten_to_dict(ContactFormSet()),
589 | )
590 |
591 | .. testcode::
592 |
593 | formset = ContactFormSet(data=request.POST)
594 |
595 | Factory kwargs:
596 |
597 | - ``can_delete``
598 | - ``extra``
599 | - ``max_num``
600 |
601 | Using Form Sets
602 | ---------------
603 |
604 | ::
605 |
606 |
609 |
610 | Or more control over output::
611 |
612 |
618 |
619 | Management Form
620 | ---------------
621 |
622 | - ``formset.management_form`` provides fields for tracking the member
623 | forms
624 |
625 | - ``TOTAL_FORMS``
626 | - ``INITIAL_FORMS``
627 | - ``MAX_NUM_FORMS``
628 |
629 | - Management form data **must** be present to validate a Form Set
630 |
631 | formset.is_valid()
632 | ------------------
633 |
634 | .. blockdiag::
635 |
636 | blockdiag {
637 | // Set labels to nodes.
638 | A [label = "Clean Fields"];
639 | B [label = "Clean Form"];
640 | C [label = "Clean FormSet"];
641 |
642 | A -> B -> C;
643 | B -> A;
644 | }
645 |
646 | - Performs validation on each member form
647 | - Calls ``.clean()`` method on the FormSet
648 | - ``formset.clean()`` can be overridden to validate across Forms
649 | - Errors raised are collected in ``formset.non_form_errors()``
650 |
651 | FormSet.clean()
652 | ---------------
653 |
654 | .. testcode::
655 |
656 | from django.forms import formsets
657 |
658 | class BaseContactFormSet(formsets.BaseFormSet):
659 | def clean(self):
660 | names = []
661 | for form in self.forms:
662 | if form.cleaned_data.get('name') in names:
663 | raise ValidationError()
664 | names.append(form.cleaned_data.get('name'))
665 |
666 | ContactFormSet = formsets.formset_factory(
667 | ContactForm,
668 | formset=BaseContactFormSet
669 | )
670 |
671 | Insertion
672 | ---------
673 |
674 | - FormSets use the ``management_form`` to determine how many forms to
675 | build
676 | - You can add more by creating a new form and incrementing
677 | ``TOTAL_FORM_COUNT``
678 | - ``formset.empty_form`` provides an empty copy of the form with
679 | ``__prefix__`` as the index
680 |
681 | .. Insertion HTML
682 | .. --------------
683 |
684 | .. XXX
685 |
686 | Deletion
687 | --------
688 |
689 | - When deletion is enabled, additional ``DELETE`` field is added to
690 | each form
691 | - Forms flagged for deletion are available using the
692 | ``.deleted_forms`` property
693 | - Deleted forms are **not** validated
694 |
695 | ::
696 |
697 | ContactFormSet = formsets.formset_factory(
698 | ContactForm, can_delete=True,
699 | )
700 |
701 |
702 | Ordering Forms
703 | --------------
704 |
705 | - When ordering is enabled, additional ``ORDER`` field is added to
706 | each form
707 | - Forms are available (in order) using the ``.ordered_forms`` property
708 |
709 | ::
710 |
711 | ContactFormSet = formsets.formset_factory(
712 | ContactForm,
713 | can_order=True,
714 | )
715 |
716 | Testing
717 | -------
718 |
719 | - FormSets can be tested in the same way as Forms
720 | - Helpers to generate test form data:
721 |
722 | - ``flatten_to_dict`` works with FormSets just like Forms
723 | - ``empty_form_data`` takes a FormSet and index, returns a dict of data
724 | for an empty form:
725 |
726 | .. testcode::
727 |
728 | from rebar.testing import flatten_to_dict, empty_form_data
729 |
730 | formset = ContactFormSet()
731 | form_data = flatten_to_dict(formset)
732 | form_data.update(
733 | empty_form_data(formset, len(formset))
734 | )
735 |
736 |
737 | Model Form Sets
738 | ---------------
739 |
740 | - ModelFormSets:FormSets :: ModelForms:Forms
741 | - ``queryset`` argument specifies initial set of objects
742 | - ``.save()`` returns the list of saved instances
743 | - If ``can_delete`` is ``True``, ``.save()`` also deletes the models
744 | flagged for deletion
745 |
746 | Advanced & Miscellaneous Detritus
747 | =================================
748 |
749 | Localizing Fields
750 | -----------------
751 |
752 | - Django's i18n/l10n framework supports localized input formats
753 | - For example: 10,00 vs. 10.00
754 |
755 | Enable in ``settings.py``::
756 |
757 | USE_L10N = True
758 | USE_THOUSAND_SEPARATOR = True # optional
759 |
760 | Localizing Fields Example
761 | -------------------------
762 |
763 | And then use the ``localize`` kwarg
764 |
765 | .. testsetup:: l10n
766 |
767 | from django.conf import settings
768 | settings.USE_L10N = True
769 |
770 | .. doctest:: l10n
771 |
772 | >>> from django import forms
773 | >>> class DateForm(forms.Form):
774 | ... pycon_ends = forms.DateField(localize=True)
775 |
776 | >>> DateForm({'pycon_ends': '3/15/2012'}).is_valid()
777 | True
778 | >>> DateForm({'pycon_ends': '15/3/2012'}).is_valid()
779 | False
780 |
781 | >>> from django.utils import translation
782 | >>> translation.activate('en_GB')
783 | >>> DateForm({'pycon_ends':'15/3/2012'}).is_valid()
784 | True
785 |
786 | Dynamic Forms
787 | -------------
788 |
789 | - Declarative syntax is just sugar
790 | - Forms use a metaclass to populate ``form.fields``
791 | - After ``__init__`` finishes, you can manipulate ``form.fields``
792 | without impacting other instances
793 |
794 |
795 | State Validators
796 | ----------------
797 |
798 | - Validation isn't necessarily all or nothing
799 | - State Validators define validation for specific states, on top of
800 | basic validation
801 | - Your application can take action based on whether the form is valid,
802 | or valid for a particular state
803 |
804 |
805 | State Validators
806 | ----------------
807 |
808 | .. testcode::
809 |
810 | from django import forms
811 | from rebar.validators import StateValidator, StateValidatorFormMixin
812 |
813 | class PublishValidator(StateValidator):
814 | validators = {
815 | 'title': lambda x: bool(x),
816 | }
817 |
818 | class EventForm(StateValidatorFormMixin, forms.Form):
819 | state_validators = {
820 | 'publish': PublishValidator,
821 | }
822 | title = forms.CharField(required=False)
823 |
824 | State Validators
825 | ----------------
826 |
827 | ::
828 |
829 | >>> form = EventForm(data={})
830 | >>> form.is_valid()
831 | True
832 | >>> form.is_valid('publish')
833 | False
834 | >>> form.errors('publish')
835 | {'title': 'This field is required'}
836 |
--------------------------------------------------------------------------------
/tutorial/views.rst:
--------------------------------------------------------------------------------
1 | .. tut::
2 | :path: /src
3 |
4 | .. slideconf::
5 | :autoslides: False
6 | :theme: single-level
7 |
8 | ===============
9 | Writing Views
10 | ===============
11 |
12 | .. slide:: Writing Views
13 | :level: 1
14 |
15 | Handling HTTP Requests from users.
16 |
17 | .. rst-class:: include-as-slide, slide-level-2
18 |
19 | View Basics
20 | ===========
21 |
22 | Django Views take an `HTTP Request`_ and return an `HTTP Response`_ to
23 | the user.
24 |
25 | .. blockdiag::
26 |
27 | blockdiag {
28 | // Set labels to nodes.
29 | A [label = "User"];
30 | C [label = "View"];
31 |
32 | A -> C [label = "Request"];
33 | C -> A [label = "Response"];
34 | }
35 |
36 | Any Python callable can be a view. The only hard and fast requirement
37 | is that it takes the request object (customarily named ``request``) as
38 | its first argument. This means that a minimalist view is super
39 | simple::
40 |
41 |
42 |
43 | from django.http import HttpResponse
44 |
45 | def hello_world(request):
46 | return HttpResponse("Hello, World")
47 |
48 | Of course, like most frameworks, Django also allows you to pass
49 | arguments to the view from the URL. We'll cover this as we build up
50 | our application.
51 |
52 | .. _`HTTP Request`: https://docs.djangoproject.com/en/1.5/ref/request-response/#httprequest-objects
53 | .. _`HTTP Response`: https://docs.djangoproject.com/en/1.5/ref/request-response/#httpresponse-objects
54 |
55 | .. rst-class:: include-as-slide, slide-level-2
56 |
57 | Generic & Class Based Views
58 | ===========================
59 |
60 | * `Generic Views`_ have always provided some basic functionality:
61 | render a template, redirect, create or edit a model, etc.
62 | * Django 1.3 introduced `Class Based Views`_ (CBV) for the generic views
63 | * Provide higher levels of abstraction and composability
64 | * Also hide a lot of complexity, which can be confusing for the
65 | newcomer
66 | * Luckily the documentation is **much** better with Django 1.5
67 |
68 | .. ifnotslides::
69 |
70 | Django 1.3 introduced class based views, which is what we'll be
71 | focusing on here. Class based views, or CBVs, can eliminate a lot of
72 | boilerplate from your views, especially for things like an edit view
73 | where you want to take different action on a ``GET`` vs ``POST``. They
74 | give you a lot of power to compose functionality from pieces. The
75 | downside is that this power comes with some added complexity.
76 |
77 |
78 | .. rst-class:: include-as-slide, slide-level-2
79 |
80 | Class Based Views
81 | =================
82 |
83 | The minimal class based view subclasses View_ and implements methods
84 | for the HTTP methods it supports. Here's the class-based version of
85 | the minimalist "Hello, World" view we previously wrote.
86 |
87 | ::
88 |
89 | from django.http import HttpResponse
90 | from django.views.generic import View
91 |
92 | class MyView(View):
93 |
94 | def get(self, request, *args, **kwargs):
95 | return HttpResponse("Hello, World")
96 |
97 | In a class based view, HTTP methods map to class method names. In this
98 | case, we've defined a handler for ``GET`` requests with the ``get``
99 | method. Just like the function implementation, it takes ``request`` as
100 | its first argument, and returns an HTTP Response.
101 |
102 | .. sidebar:: Permissive Signatures
103 |
104 | You may notice that it has a couple of extra arguments in its
105 | signature, compared to the view we saw previously, specifically
106 | ``*args`` and ``**kwargs``. Class based views were first introduced
107 | as a way to make Django's "generic" views more flexible. That meant
108 | they were used in many different contexts, with potentially
109 | different arguments extracted from the URLs. As I've been writing
110 | class based views over the past year, I've continued to write them
111 | with permissive signatures, as I've found they're often useful in
112 | ways I didn't initially expect.
113 |
114 | Listing Contacts
115 | ================
116 |
117 | .. slide:: List Views
118 | :level: 2
119 |
120 | ListView_ provides a view of a set of objects.
121 |
122 | ::
123 |
124 | class ContactsList(ListView):
125 |
126 | model = Contact
127 | template_name = 'contact_list.html'
128 |
129 | def get_queryset(self):
130 | ... # defaults to model.objects.all()
131 |
132 | def get_context_object_name(self):
133 | ... # defaults to _list
134 |
135 | def get_context_data(self, **kwargs):
136 | ... # add anything else to the context
137 |
138 | def get_context_data(self, **kwargs):
139 | ... # add anything else to the context
140 |
141 | .. slide:: Edit Views
142 | :level: 2
143 |
144 | CreateView_, UpdateView_, DeleteView_ work on a model instance.
145 | ::
146 |
147 | class UpdateContact(UpdateView):
148 | model = Contact
149 | template_name = 'edit_contact.html'
150 |
151 | def get_object(self):
152 | ... # defaults to looking for a pk or slug kwarg, and
153 | # passing that to filter
154 |
155 | def get_context_object_name(self):
156 | ... # defaults to
157 | def get_context_data(self, **kwargs):
158 | ... # add anything else to the context
159 |
160 | def get_success_url(self):
161 | ... # where to redirect to on success
162 | # defaults to self.get_object().get_absolute_url()
163 |
164 | .. slide:: Detail Views
165 | :level: 2
166 |
167 | DetailView_ provides a view of a single object
168 |
169 | ::
170 |
171 | class ContactView(DetailView):
172 |
173 | model = Contact
174 | template_name = 'contact.html'
175 |
176 | def get_object(self):
177 | ... # defaults to looking for a pk or slug kwarg, and
178 | # passing that to filter
179 |
180 | def get_context_object_name(self):
181 | ... # defaults to
182 |
183 | def get_context_data(self, **kwargs):
184 | ... # add anything else to the context
185 |
186 | .. checkpoint:: contact_list_view
187 |
188 | We'll start with a view that presents a list of contacts in the
189 | database.
190 |
191 | The basic view implementation is shockingly brief. We can write the
192 | view in just a few lines in the ``views.py`` file in our ``contacts``
193 | application.
194 |
195 | .. literalinclude:: /src/contacts/views.py
196 | :language: python
197 | :end-before: template_name
198 |
199 | The ListView_ that we subclass from is itself composed of several
200 | mixins that provide some behavior, and that composition gives us a lot
201 | of power without a lot of code. In this case we set ``model =
202 | Contact``, which says that this view is going to list *all* the
203 | Contacts in our database.
204 |
205 |
206 | .. rst-class:: include-as-slide, slide-level-2
207 |
208 | Defining URLs
209 | =============
210 |
211 | The URL configuration tells Django how to match a request's path to
212 | your Python code. Django looks for the URL configuration, defined as
213 | ``urlpatterns``, in the ``urls.py`` file in your project.
214 |
215 | Let's add a URL mapping for our contact list view in
216 | ``addressbook/urls.py``.
217 |
218 | .. literalinclude:: /src/addressbook/urls.py
219 | :language: python
220 |
221 |
222 | * Use of the ``url()`` function is not strictly required, but I like
223 | it: when you start adding more information to the URL pattern, it
224 | lets you use named parameters, making everything more clear.
225 | * The first parameter is a regular expression. Note the trailing
226 | ``$``; why might that be important?
227 | * The second parameter is the view callable. It can either be the
228 | actual callable (imported manually), or a string describing it. If
229 | it's a string, Django will import the module (up to the final dot),
230 | and then calls the final segment when a request matches.
231 | * Note that when we're using a class based view, we *must* use the
232 | real object here, and not the string notation. That's because we
233 | have to call the class method ``as_view()``, which returns a wrapper
234 | around our class that Django's URL dispatch can call.
235 | * Giving a URL pattern a name allows you to do a reverse lookup
236 | * The URL name is useful when linking from one View to another, or
237 | redirecting, as it allows you to manage your URL structure in one
238 | place
239 |
240 | While the ``urlpatterns`` name **must** be defined, Django also allows
241 | you to define a few other values in the URL configuration for
242 | exceptional cases. These include ``handler403``, ``handler404``, and
243 | ``handler500``, which tell Django what view to use when an HTTP error
244 | occurs. See the `Django urlconf documentation`_ for details.
245 |
246 | .. _`Django urlconf documentation`: https://docs.djangoproject.com/en/1.5/ref/urls/#handler403
247 |
248 | .. admonition:: URL Configuration Import Errors
249 |
250 | Django loads the URL configuration very early during startup, and
251 | will attempt to import things it finds here. If one of the imports
252 | fails, however, the error message can be somewhat opaque. If your
253 | project stops working with an import-related exception, try to
254 | import the URL configuration in the interactive shell. That usually
255 | makes it clear where the problem lies.
256 |
257 |
258 | Creating the Template
259 | =====================
260 |
261 | .. slide:: Django Templates
262 | :level: 2
263 |
264 | * Django allows you to specify ``TEMPLATE_DIRS`` to look for templates
265 | in
266 | * By default it looks for a ``template`` subdirectory in each app
267 | * Keeping templates within an app makes creating reusable apps easier
268 |
269 | Now that we've defined a URL for our list view, we can try it out.
270 | Django includes a server suitable for development purposes that you
271 | can use to easily test your project::
272 |
273 | $ python manage.py runserver
274 | Validating models...
275 |
276 | 0 errors found
277 | Django version 1.4.3, using settings 'addressbook.settings'
278 | Development server is running at http://127.0.0.1:8000/
279 | Quit the server with CONTROL-C.
280 |
281 | If you visit the ``http://localhost:8000/`` in your browser, though,
282 | you'll see an error: ``TemplateDoesNotExist``.
283 |
284 | .. image::
285 | /_static/tutorial/TemplateDoesNotExist.png
286 |
287 | Most of Django's generic views (such as ``ListView`` which we're
288 | using) have a predefined template name that they expect to find. We
289 | can see in this error message that this view was expecting to find
290 | ``contact_list.html``, which is derived from the model name. Let's go
291 | and create that.
292 |
293 | By default Django will look for templates in applications, as well as
294 | in directories you specify in ``settings.TEMPLATE_DIRS``. The generic
295 | views expect that the templates will be found in a directory named
296 | after the application (in this case ``contacts``), and the filename
297 | will contain the model name (in this case ``contact_list.html``). This
298 | works very well when you're distributing a reusable application: the
299 | consumer can create templates that override the defaults, and they're
300 | clearly stored in a directory associated with the application.
301 |
302 | For our purposes, however, we don't need that extra layer of directory
303 | structure, so we'll specify the template to use explicitly, using the
304 | ``template_name`` property. Let's add that one line to ``views.py``.
305 |
306 | .. literalinclude:: /src/contacts/views.py
307 |
308 | Create a ``templates`` subdirectory in our ``contacts`` application,
309 | and create ``contact_list.html`` there.
310 |
311 | .. literalinclude:: /src/contacts/templates/contact_list.html
312 | :language: html
313 |
314 | Opening the page in the browser, we should see one contact there, the
315 | one we added earlier through the interactive shell.
316 |
317 | Creating Contacts
318 | =================
319 |
320 | .. checkpoint:: create_contact_view
321 |
322 | Adding information to the database through the interactive shell is
323 | going to get old fast, so let's create a view for adding a new
324 | contact.
325 |
326 | Just like the list view, we'll use one of Django's generic views. In
327 | ``views.py``, we can add the new view:
328 |
329 | .. literalinclude:: /src/contacts/views.py
330 | :prepend: from django.core.urlresolvers import reverse
331 | from django.views.generic import CreateView
332 | ...
333 | :pyobject: CreateContactView
334 |
335 | Most generic views that do form processing have the concept of the
336 | "success URL": where to redirect the user when the form is
337 | successfully submitted. The form processing views all adhere to the
338 | practice of POST-redirect-GET for submitting changes, so that
339 | refreshing the final page won't result in form re-submission. You can
340 | either define this as a class property, or override the
341 | ``get_success_url()`` method, as we're doing here. In this case we're
342 | using the ``reverse`` function to calculate the URL of the contact
343 | list.
344 |
345 | .. sidebar:: Context Variables in Class Based Views
346 |
347 | The collection of values available to a template when it's rendered
348 | is referred to as the Context. The Context is a combination of
349 | information supplied by the view and information from `context
350 | processors`_.
351 |
352 | When you're using built in generic views, it's not obvious what
353 | values are available to the context. With some practice you'll
354 | discover they're pretty consistent -- ``form``, ``object``, and
355 | ``object_list`` are often used -- but that doesn't help when you're
356 | just starting off. Luckily, the documentation for this is much
357 | improved with Django 1.5.
358 |
359 | In class based views, the ``get_context_data()`` method is used to
360 | add information to the context. If you override this method, you
361 | usually want to accept ``**kwargs``, and call the super class.
362 |
363 | The template is slightly more involved than the list template, but not
364 | much. Our ``edit_contact.html`` will look something like this.
365 |
366 | .. literalinclude:: /src/contacts/templates/edit_contact.html
367 | :language: html
368 |
369 | A few things to note:
370 |
371 | - The ``form`` in the context is the `Django Form`_ for our model.
372 | Since we didn't specify one, Django made one for us. How thoughtful.
373 | - If we just write ``{{ form }}`` we'll get table rows; adding
374 | ``.as_ul`` formats the inputs for an unordered list. Try ``.as_p``
375 | instead to see what you get.
376 | - When we output the form, it only includes our fields, not the
377 | surrounding ``