Django for ISchoolers: Difference between revisions

Undo revision 19296; minus URLs to avoid captcha
imported>Aldeka
No edit summary
(Undo revision 19296; minus URLs to avoid captcha)
 
(27 intermediate revisions by 3 users not shown)
Line 14:
 
TODO: Actually write my own dang installation instructions.
 
On Debian Wheezy: aptitude install python git python-virtualenv python-django-south python-django python-pip
 
Prior to starting this tutorial, please install Python, git, pip, virtualenv, Django, and South on your computer. There's instructions for all of those at [http://pystar.org/setup_machine.html http://pystar.org/setup_machine.html].
Line 21 ⟶ 23:
Once you've got pip and virtualenv installed, in your terminal, go into the folder you want to keep this tutorial code in and type (one line at a time):
 
<source lang="python">
<code>
$ virtualenv --no-site-packages django-tutorial-env
 
Line 29 ⟶ 31:
 
(django-tutorial-env)$ pip install south
</codesource>
 
This will make and activate a virtual python environment for just this tutorial app. While this is a bit overkill if you only have one Django app at a time on your system, giving each Python app you work on its own environment is a good best practices just in case e.g. you're working on multiple apps, which use different versions of Django or other libraries, and you don't want them to conflict with each other.
Line 57 ⟶ 59:
=== Activate your environment in your terminal ===
 
If you haven't already, run <codesource lang="python">$ source django-tutorial-env/bin/activate</codesource> in your terminal window.
 
=== Set up your git repository ===
 
If you're following this tutorial in class, in your terminal, run <codesource lang="python">$ git clone git://github.com/aldeka/django-for-ischoolers.git</codesource>. This won't download hardly anything interesting, but it will make it easy for you to sync up with the class later.
 
We'll be using the master branch (the default name for the main branch of code, which you're in right now) for syncing up throughout the class in case you get lost. You'll need to make your own branches for playing with the code on your computer, so that it doesn't interfere w. re-syncing later.
Line 67 ⟶ 69:
Think of branches in git as alternate timelines--you "branch" off of the main timeline of changes to the code into a separate timeline. You can try out a new feature or code setup in a branch, without overwriting or getting in the way of yourself or anybody else working on the code as-is. Sometimes a new idea doesn't work out, and you delete the branch or just let it sit around. Sometimes the new feature works, though, and if so you can merge it into the "real" timeline. You can make as many branches as you want, and branches of branches. You can even share branches with others (though we won't be doing that today). Git's branching system (and its non-centralized architecture generally) make it easy to try out new ideas in code without having to ask permission of everyone else on the project first.
 
To make your first branch, be sure you've changed directories to the django-for-ischoolers directory and then type <codesource lang="python">$ git branch my-chunk-1</codesource> to make your new branch, and <codesource lang="python">$ git checkout my-chunk-1</codesource> to switch into that branch. Type <codesource lang="python">$ git branch</codesource> to confirm that you're in your new branch (and that you only have two branches so far).
 
If you're not following this tutorial in class, just run <codesource lang="python">$ git init</codesource>.
 
=== Create your Django project ===
Line 75 ⟶ 77:
Let's create your first Django project, called "mysite".
 
<codesource lang="python">$ django-admin.py startproject mysite</codesource>
 
You'll see we made a folder called "mysite", with some files in it. Let's check them out!
 
<source lang="python">
<code>$ cd mysite<br>
$ cd mysite
$ ls<br>
$ ls
__init__.py<br>
__init__.py
manage.py<br>
manage.py
settings.py<br>
settings.py
urls.py</code>
urls.py
</source>
 
These files are:
Line 99 ⟶ 103:
** Run the command:
 
<codesource lang="python">python manage.py runserver</codesource>
 
** Review the output in your terminal. It should look similar to:
 
<codesource lang="python">Validating models...<br>
0 errors found.<br>
 
Django version 1.2, using settings 'myproject.settings'<br>
Development server is running at http://127.0.0.1:8000/<br>
Quit the server with CONTROL-C.</codesource>
 
* Now that the server’s running, visit [http://127.0.0.1:8000/ http://127.0.0.1:8000/] with your Web browser. You’ll see a “Welcome to Django” page, in pleasant, light-blue pastel. It worked!
Line 114 ⟶ 118:
* Observe the logging that happens in the terminal where your server is running:
 
<codesource lang="python">[24/Mar/2011 11:50:18] "GET / HTTP/1.1" 200 2057</codesource>
 
which has the format:
 
<codesource lang="python">DATE METHOD URL PROTOCOL RESPONSE_CODE CONTENTSIZE</codesource>
 
* Navigate to http://127.0.0.1:8000/some/url/. What changes in the terminal log?
Line 134 ⟶ 138:
Each commit is a checkpoint in time, containing the '''diff'''--the "difference", all of the changes you made to your code base -- between that commit and the commit before it. Different branches in git share the commits made prior to the branching-off point, then each have their own commit history.
 
* To make a commit, first type <codesource lang="python">git status</codesource> into your terminal. This will let you know what changes git has noticed in your code and which of those changes, if any, are staged and ready to be committed.
 
<codesource lang="python">$ git status<br>
# On branch my-chunk-1
#
# Initial commit
#
# Untracked files:<br>
# (use "git add <file>..." to include in what will be committed)
#
# __init__.py<br>
# manage.py<br>
# settings.py<br>
# urls.py<br>
nothing added to commit but untracked files present (use "git add" to track)</codesource>
 
"Untracked files" means that git has noticed some new files inside its folder, but you haven't told git explicitly that you want it to "listen" for, and track, changes in those files.
 
* Add one file: <codesource lang="python">git add manage.py</codesource>. What does git status say now?
* Add all the files to the repo, in the local directory:
 
<codesource lang="python">$ git add *.py # all .py files, using a wildcard match.</codesource>
 
What does git status say now?
Line 161 ⟶ 165:
* git commit to commit those files:
 
<source lang="python">
<code># -m -> what is the 'message' for the commit<br>
# -m -> what is the 'message' for the commit
git commit -m "Initial commit of Django project from the IOLab Django workshop"</code>
git commit -m "Initial commit of Django project from the IOLab Django workshop"
</source>
 
* Look at your changes with <codesource lang="python">git log</codesource> to see your history. Is your commit message there?
 
Huzzah! We committed our changes so far. Now let's make some more changes!
Line 178 ⟶ 184:
* git add and commit it:
 
<source lang="python">
<code># even though git knows to "listen" to settings.py now, it still won't add it to the 'staging area' for your commit unless you tell it to explicitly with git add<br>
# even though git knows to "listen" to settings.py now, it still won't add it to the 'staging area' for your commit unless you tell it to explicitly with git add
git add settings.py<br>
git add settings.py
git commit -m "made myself an admin"</code>
git commit -m "made myself an admin"
</source>
 
===Set up the Database===
Line 186 ⟶ 194:
Keep looking at settings.py: The DATABASES variable is a dictionary (note the ‘{}’ characters) with one key: default.
 
<source lang="python">
<code> DATABASES = {<br>
DATABASES = {
'default': {<br>
'default': {
'ENGINE': 'django.db.backends', # Add 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'oracle'.<br>
'ENGINE': 'django.db.backends', # Add 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'oracle'.
'NAME': '', # Or path to database file if using sqlite3.<br>
'USERNAME': '', # NotOr usedpath withto database file if using sqlite3.<br>
'PASSWORDUSER': '', # Not used with sqlite3.<br>
'HOSTPASSWORD': '', # Set to empty string for 127.0.0.1. Not used with sqlite3.<br>
'PORTHOST': '', # Set to empty string for default127.0.0.1. Not used with sqlite3.<br>
'PORT': '', # Set to empty string for default. Not used with sqlite3.
}<br>
}
}</code>
}</source>
 
Notice that the value of default is itself another dictionary with information about the site’s default database.
Line 201 ⟶ 210:
* Set your app to use a sqlite database, in the ENGINE attribute. Sqlite is great for development because is stores its data in one normal file on your system and therefore is really simple to move around with your app. It's not sturdy enough to use for a website in production, though.
 
<codesource lang="python">'ENGINE': 'django.db.backends.sqlite3',</codesource>
 
* Set your app to use a file called 'database.db' to store information for this project.
 
<codesource lang="python">'NAME': 'database.db',</codesource>
 
Does database.db exist right now? (No, but that's okay. It'll get created automatically when it's needed.)
Line 213 ⟶ 222:
* Add South to our list of installed apps. (We'll need it later.)
 
<codesource lang="python">INSTALLED_APPS = (
'django.contrib.auth',
'django.contrib.contenttypes',
Line 224 ⟶ 233:
# 'django.contrib.admindocs',
'south',
)</codesource>
 
* Each of these applications makes use of at least one database table, so we need to create the tables in the database before we can use them. To do that, run the following command:
 
<codesource lang="python">python manage.py syncdb</codesource>
 
The syncdb command looks at the INSTALLED_APPS setting and creates any necessary database tables according to the database settings in your settings.py file. You’ll see a message for each database table it creates.
Line 256 ⟶ 265:
== Starting yer app ==
 
In this tutorial, we’ll create our pollQ&A app in the myproject directory for simplicity. In the future, when you decide that the world needs to be able to use your pollQ&A app and plug it into their own projects, and after you determine that your app plays nicely with other apps, you can publish that directory separately!
 
* Open your terminal and navigate into the mysite folder
* Make scaffolding for the app:
 
<codesource lang="python">$ python manage.py startapp qandabear</codesource>
 
That’ll create a directory qandabear to house our Q & A application.
Line 267 ⟶ 276:
* Verify what is new.
 
<codesource lang="python">$ git status<br>
# should show 'qandabear/' in 'untracked'</codesource>
 
* Examine the layout of qandabear (we will do more of this in following sections).
 
<codesource lang="python"># remember not to type the '$', it just means the prompt'.<br>
$ ls qandabear<br>
qandabear/<br>
__init__.py<br>
models.py<br>
tests.py<br>
views.py<br>
</codesource>
 
* Add and commit <codesource lang="python">qandabear/*py</codesource>.
* Install the Q&A app into the project. Edit the settings.py file again, and change the INSTALLED_APPS setting to include the string ‘qandabear’ as the last entry.
* Save and commit the settings.py file.
Line 289 ⟶ 298:
At the end of each 'chunk', I'm going to push our progress so far to the tutorial git repository. If you're doing this tutorial in class, please switch back to the master branch, then pull my changes.
 
<codesource lang="python">$ git checkout master<br>
$ git pull origin master</codesource>
 
This will make it so that everyone starts out each chunk with the same working code, so even if you get stuck or confused in one session, you can still keep up. (Since I don't have any TAs to help debug or answer questions, and we don't have that much time.)
Line 296 ⟶ 305:
Remember that *you* shouldn't be putting your modifications inside the master branch, though! (Otherwise, you'd have to overwrite them at the next chunk checkpoint, and that would be sad.) Instead, make a new branch for chunk 2 based off the master code you just downloaded, and let's get going!
 
<codesource lang="python">$ git branch my-chunk-2<br>
$ git checkout my-chunk-2</codesource>
 
<!-- == Test-driven development, part one ==
Line 335 ⟶ 344:
===Adding URLs to our urls.py===
 
When we ran <codesource lang="python">django-admin.py startproject mysite</codesource> to create the project, Django created a default URLconf file called `urls.py`.
 
* Write our URL mapping. Edit the file mysite/urls.py so it looks like this:
 
<codesource lang="python">urlpatterns = patterns('',<br>
(r'^questions/$', 'qandabear.views.index'),<br>
(r'^questions/(\d+)/$', 'qandabear.views.question'),<br>
(r'^questions/(\d+)/answers/(\d+)/edit/$', 'qandabear.views.edit_answer'),<br>
# Examples:<br>
# url(r'^$', 'myproject.views.home', name='home'),<br>
# url(r'^myproject/', include('myproject.foo.urls')),<br>
 
# Uncomment the admin/doc line below to enable admin documentation:<br>
# url(r'^admin/doc/', include('django.contrib.admindocs.urls')),<br>
 
# Uncomment the next line to enable the admin:<br>
# url(r'^admin/', include(admin.site.urls)),<br>
)</codesource>
 
Suppose a visitor goes to http:// 127.0.0.1:8000/questions/23/.
# which regex pattern is tripped?
# what function is then called?
Line 365 ⟶ 374:
* Save and commit.
 
Review: When somebody requests a page from your Web site – say, “/questions/23/”, Django will load the urls.py Python module, because it’s pointed to by the ROOT_URLCONF setting. It finds the variable named urlpatterns and traverses the regular expressions in order. When it finds a regular expression that matches – r'^pollsquestions/(\d+)/$' – it loads the function question() from qandabear/views.py. Finally, it calls that module’s detail() function like so:
 
<codesource lang="python">detail(request=<HttpRequest object>, '23')</codesource>
 
The ‘23’ part comes from (\d+). Using parentheses around a pattern “captures” the text matched by that pattern and sends it as an argument to the view function; the \d+ is a regular expression to match a sequence of digits (i.e., a number).
Line 376 ⟶ 385:
 
== views.py ==
 
* Start the development server: <source lang="python">python manage.py runserver</source>
* Fetch “http://127.0.0.1:8000/questions/” in your browser. You should get a pleasantly-colored error page with the following message:
 
<source lang="python">ViewDoesNotExist at /questions/
 
Tried index in module qandabear.views. Error was: 'module'
object has no attribute 'index'</source>
 
Recall this line: <source lang="python">(r'^questions/$', 'qandabear.views.index')</source>.
 
* Explore this using your django-shell: <source lang="python">python manage.py shell</source>
 
<source lang="python">>>> import qandabear # imports fine!
>>> import qandabear.views # imports fine also!
>>> dir(qandabear.views) # what is in there!
>>> 'index' in dir(qandabear.views)
False
>>> import inspect
>>> inspect.getsourcefile(qandabear.views)
# something like
'/Users/adalovelace/gits/mysite/questions/views.py'</source>
 
So, a mystery? Where is the view!? It’s nowhere! The URL parsing is going fine, but there is no one listening at the other end of the phone! This ViewDoesNotExist error happened because you haven’t written a function index() in the module questions/views.py.
 
Well, I guess we should do that!
 
* Write some views. Open qandabear/views.py and put the following Python code in it:
 
<source lang="python">from django.http import HttpResponse
 
def index(request):
return HttpResponse("Hello, world. You're at the questions index.")</source>
 
This is a very simple view.
 
* Save the views.py file, then go to [http://127.0.0.1:8000/questions/ http://127.0.0.1:8000/questions/] in your browser, and you should see that text.
 
* Add a few more views by adding to the views.py file. These views are slightly different, because they take an argument or two (which, remember, is passed in from whatever was captured by the regular expression in the URLconf):
 
<source lang="python">def question(request, question_id):
return HttpResponse("You're looking at question %s." % (question_id,))
 
def edit_answer(request, question_id, answer_id):
return HttpResponse("You're looking at the edit page for answer %s." % (answer_id,))</source>
 
* Save views.py.
 
* Navigate to [http://127.0.0.1:8000/questions/34/ http://127.0.0.1:8000/questions/34/]. It’ll run the question() method and display whatever ID you provide in the URL.
* Add a little html to the ‘question’ view. Wrap the question_id in <strong> </strong> tags and verify that the view is indeed bold!
* Add and commit your code. Remember to write a good commit message that mentioned what changed (in English) and more details below.
 
== Databases and the ORM (or: finally something that HTML/CSS/JS couldn't do for you on its own) ==
 
Okay, being able to design your URLs however you want, without them having to correspond to your actual file structure, is pretty neat. But besides that, we haven't done anything yet that you couldn't do with just HTML, CSS, and JavaScript (or just HTML, as it happens!).
 
Time to fix that.
 
ORM = Object Relational Mapper/Mapping. Translating Python classes -- real object-oriented programming stuff -- into representations in a database on your server for you.
 
(models.py --> ORM --> DB chart)
 
== models.py ==
 
Let's make it so we can store some real live data in our application!
 
In our simple Q&A app, we’ll create two models: Questions and Answers. As per our spec from the customer:
 
* A question has:
** The text of the question
** A publication date.
* An answer has:
** The text of the answer
** A publication date.
 
Each Answer is associated with a Question and each Question has associated Answers. We will respesent these concepts with python classes derived from django.db.models.
 
Edit the qandabear/models.py file so it looks like this:
 
<source lang="python">from django.db import models
 
class Question(models.Model):
text = models.CharField(max_length=200)
pub_date = models.DateTimeField(auto_now_add=True)
 
class Answer(models.Model):
question = models.ForeignKey(Question)
answer = models.TextField()
pub_date = models.DateTimeField(auto_now_add=True)</source>
 
* Save the models.py file.
 
All models in Django code are represented by a class that subclasses django.db.models.Model. Each model has a number of class variables, each of which represents a database field in the model. (cf: [http://docs.djangoproject.com/en/dev/topics/db/models/ http://docs.djangoproject.com/en/dev/topics/db/models/])
 
Each field is represented by an instance of a Field class – e.g., CharField for character fields and DateTimeField for datetimes. This tells Django what type of data each field holds.
 
The name of each Field instance (e.g. answer or pub_date) is the field’s name, in machine-friendly format. You’ll use this value in your Python code, and your database will use it as the column name.
 
Some Field classes have required elements. CharField, for example, requires that you give it a max_length. That’s used not only in the database schema, but in validation as well.
 
Most Field classes also have optional elements that you can set. For example, setting auto_now_add to True on a datetime field means that by default, that field will be set to the date and time that the object is first created.
 
Finally, note a relationship is defined, using ForeignKey. That tells Django each Answer is related to a single Question. Django supports all the common database relationships: many-to-ones, many-to-manys and one-to-ones.
 
== Database migrations and South, part one ==
 
When you create your models, you might not always know exactly what fields your models will need in advance. Maybe someday your qandabear app will have users, and you’ll want to keep track of the author of each question! Then you would want to add another field to the model to store that information. Maybe someday you'll decide that tracking all the pub_dates is silly, and want to delete that field.
 
Unfortunately, Django (and most database-using software) can’t figure out how to handle model changes very well on its own. Fortunately, a Django app called `south` can handle these changes–called ‘migrations’–for us.
 
Now that we’ve made our first version of our models file, let’s set up our qandabear app to work with South so that we can make migrations with it in the future!
 
* On the command line, write:
 
<source lang="python">$ python manage.py schemamigration qandabear --initial</source>
 
As you can see, that’s created a migrations directory for us, and made a new migration inside it.
 
* All we need to do now is apply our new migration:
 
<source lang="python">$ python manage.py migrate qandabear</source>
 
Great! Now our database file knows about qandabear and its new models, and if we need to change our models, South is set up to handle those changes. We’ll come back to South later.
 
'''IMPORTANT:''' You can't migrate an app if it's already been synced in the database using <source lang="python">python manage.py syncdb</source>. But you do need to run syncdb at least once before you use south (since south itself uses syncdb to give itself space in your database). That's why it's super important that when you run syncdb, south should be listed under INSTALLED_APPS, but '''none of your own apps should be''', and after you add your app to INSTALLED_APPS, you '''must not run syncdb again''' until after you've already set up migrations with that app.
 
* Add and commit all your work, including the migrations folder that South generated for you.
 
= Chunk 3 =
 
You know the drill. Switch back to the master branch, pull down all the new code, and create a new branch called my-chunk-3. (Or something else, if you like. Your branch names are up to you!)
 
== Let's add some data (via the Django shell)! ==
 
Now, let’s hop into the interactive Python shell and play around with the free API ("Application programming interface" -- APIs aren't just data doodads that hip web startups provide for you, they're an important concept in software architecture.) that Django gives you. To invoke the Python shell, use this command:
 
<source lang="python">python manage.py shell</source>
 
We’re using this instead of simply typing “python”, because manage.py sets up the project’s environment for you. “Setting up the environment” involves two things:
 
# Making sure qandabear (and any other apps you might have) are on the right path to be imported.
# Setting the DJANGO_SETTINGS_MODULE environment variable, which gives Django the path to your settings.py file.
 
Once you’re in the shell, explore the database API:
 
* Import the model classes we just wrote:
 
<source lang="python">
>>> from qandabear.models import Question, Answer</source>
 
* List all the current Questions:
 
<source lang="python">
>>> Question.objects.all()
[]</source>
 
How many questions is this?
Zen koan: Can there be an Answer for a Question that doesn’t yet exist?
 
=== Add our first Question ===
 
<source lang="python">
>>> q = Question(text="What is the Weirdest Cookbook Ever?")
# We could specify the value for pub_date as well here, but we don't have to since when we wrote its model definition, we specified that pub_date has a default value (of when the question is created)</source>
 
* Try getting the ID number of our new question 'q' by typing <source lang="python">q.id</source>. What happens?
 
* Save the Question instance into the database. You have to call save() explicitly.
 
<source lang="python">
>>> q.save()</source>
 
* Get the id of the Question instance. Because it’s been saved, it has an ID in the database now!
 
<source lang="python">
>>> q.id
1</source>
 
* Access the database columns (Fields, in Django parlance) as Python attributes:
 
<source lang="python">
>>> q.text
"What is the Weirdest Cookbook Ever?"
>>> q.pub_date
datetime.datetime(2011, 12, 1, 3, 3, 55, 841929)
</source>
 
* Send the Question back in time:
 
<source lang="python"># Change values by changing the attributes, then calling save().
>>> import datetime
>>> q.pub_date = datetime.datetime(2011, 4, 1, 0, 0)
>>> q.save()
>>> q.pub_date
datetime.datetime(2007, 4, 1, 0, 0)</source>
 
* Ask Django to show a list of all the Question objects available:
 
<source lang="python">>>> Question.objects.all()
[<Question: Question object>]</source>
 
===Fix The Hideous Default Representation===
 
Wait a minute! <Question: Question object> is an utterly unhelpful, truly wretched, beyond contemptable representation of this object. Let’s fix that by editing the Question model. Use your text editor to open the qandabear/models.py file and adding a __unicode__() method to both Question and Answer:
 
<source lang="python">class Question(models.Model):
# ...
def __unicode__(self):
return self.text
 
class Answer(models.Model):
# ...
def __unicode__(self):
# return the answer text, or if the text is longer than 37 characters, return a summary with an ellipsis
summary = self.text[:37]
if len(self.text) > 37:
summary = summary + '...'
return summary</source>
 
It’s important to add __unicode__() methods to your models, not only for your own sanity when dealing with the interactive prompt, but also because if you use Django's automatically-generated admin, these representations are used there too.
 
=== Adding custom methods to models ===
 
Enough of these normal python methods! Let's build in some useful functionality to our models in qandabear/models.py.
 
<source lang="python">import datetime
# ...
class Question(models.Model):
# ...
def was_published_today(self):
return self.pub_date.date() == datetime.date.today()</source>
 
Note the addition of <source lang="python">import datetime</source> to reference Python’s standard datetime module. This allows us to use the datetime library module in models.py by calling it with datetime.
 
* Save these changes to the models.py file.
 
=== Test that all those changes did something ===
 
Start a new Python interactive shell by running python manage.py shell:
 
<source lang="python">
>>> from qandabear.models import Question, Answer
Verify our __unicode__() addition worked:
>>> Question.objects.all()
[<Question: What is the Weirdest Cookbook Ever?>]
Search your database using the filter method on the objects attribute of Question.
>>> questions = Question.objects.filter(text="What is the Weirdest Cookbook Ever?")
>>> questions
[<Poll: What is the Weirdest Cookbook Ever?>]
>>> questions[0].id # remember python lists start with element 0.
1
</source>
 
If you try to search for a question that does not exist, filter will give you the empty list. The get method will always return one hit, or raise an exception.
 
<source lang="python">
>>> Question.objects.filter(text="Who framed Roger Rabbit?")
[]
>>> Question.objects.get(id=1)
<Question: What is the Weirdest Cookbook Ever?>
>>> Question.objects.get(id=2)
Traceback (most recent call last):
...
DoesNotExist: Question matching query does not exist.
</source>
 
===Add answers===
 
Observe: there is a Question in the database, but it has no Answers! Let's fix that.
 
<source lang="python">
>>> q = Question.objects.get(id=1)
>>> q.answer_set.all()
[]
Create three answers:
>>> q.answer_set.create(text='To Serve Man')
<Answer: To Serve Man>
>>> q.answer_set.create(text='The Original Road Kill Cookbook')
<Answer: The Original Road Kill Cookbook>
>>> a = q.answer_set.create(text='Mini-Mart A La Carte')
>>> a
<Answer: Mini-Mart A La Carte>
</source>
 
Go in reverse! Find the question a particular answer belongs to:
 
<source lang="python">
>>> a.question
<Question: What is the Weirdest Cookbook Ever?>
</source>
 
Because a Question can have more than one Answer, Django creates the answer_set attribute on each Question. You can use that to look at the list of available Answer, or to create them.
 
<source lang="python">
>>> q.answer_set.all()
[<Answer: To Serve Man>, <Answer: The Original Road Kill Cookbook>, <Answer: Mini-Mart A La Carte>]
>>> q.answer_set.count()
3
</source>
 
Can one be a Answer for a Question that doesn’t yet exist?:
 
<source lang="python">
>>> koan = Answer(text="Is this even an answer")
>>> koan.question_id
>>> koan.question
</source>
 
== Database migrations and South, part two ==
 
=== Changing your models ===
== Let's auto-populate some data (using a script in the shell)! ==
 
Oh no! Our client, LulzTech, has decided to change the spec for this prototype. They want to add a votes field to each Answer, to track how many upvotes it has. Which means we’re going to have to change our models.
 
* Open qandabear/models.py and edit the Answer class:
 
<source lang="python">
class Answer(models.Model):
question = models.ForeignKey(Question)
text = models.TextField()
votes = models.IntegerField(default=0)
pub_date = models.DateTimeField(auto_now_add=True)
</source>
 
The default parameter lets us set a default value for this field if an answer's vote count isn't explicitly specified. Most new answers are going to have zero votes, so we set our default to 0.
 
* Make a migration so we can let the database know that we added Answer.votes:
 
<source lang="python">
$ python manage.py schemamigration qandabear --auto
</source>
 
* Apply the migration.
 
<source lang="python">
$ python manage.py migrate qandabear
Running migrations for qandabear:
- Migrating forwards to 0002_auto__add_field_answer_votes.
> qandabear:0002_auto__add_field_answer_votes
- Loading initial data for qandabear.
No fixtures found.
</source>
 
Success!
 
===Save and commit===
 
You know the drill!
 
<!--
== Let's auto-populate some data (using a script in the shell)! == -->
 
== Views with actual data ==
 
In Django, each view is responsible for doing one of two things: returning an HttpResponse object containing the content for the requested page, or raise-ing an exception such as Http404.
 
Your view can read records from a database, or not. It can use a template system such as Django’s – or not. It can generate a PDF file, output XML, create a ZIP file on the fly, anything you want, using whatever Python libraries you want. All Django wants is that at the end, it gets an HttpResponse or an exception out of your view function.
 
Most of the Django views in the world use Django’s own database API, which was touched on in the discuss of models, to write or show dynamic data to the user in some fashion. Right now, our views are very simple, and don't use the data in our database at all. Let's fix that.
 
=== Write a better index() view ===
 
To match the spec, our index page should displays the latest 5 questions in the system, separated by commas, according to publication date.
 
* Edit views.py:
 
<source lang="python">
from qandabear.models import Question
from django.http import HttpResponse
 
def index(request):
latest_qs = Question.objects.all().order_by('-pub_date')[:5]
output = ', '.join([q.text for q in latest_qs])
return HttpResponse(output)
</source>
 
Restart the dev server, and navigate to [http://127.0.0.1:8000/questions/ http://127.0.0.1:8000/questions/]. You should see the text of the last 5 polls (or fewer than five, if you haven't made that many yet).
 
There’s a problem here, though: The page’s design is hard-coded in the view. If you want to change the way the page looks, you’ll have to edit this view code. That's silly.
 
* Use Django’s template system to separate the design from Python:
 
<source lang="python">
from django.shortcuts import render_to_response
from qandabear.models import Question
 
def index(request):
latest_qs = Question.objects.all().order_by('-pub_date')[:5]
context = {'latest_qs': latest_qs}
return render_to_response('qandabear/index.html', context)
</source>
 
To recap what this does:
 
* Creates a variable called latest_poll_list. Django queries the database for all Poll objects, ordered by pub_date with most recent first, and uses slicing to get the first five.
* Creates a variable called context that is a dictionary with one key.
* Evaluates the render_to_response function with two arguments, and returns whatever that returns.
render_to_response loads the template called polls/index.html and passes it a value as context. The context is a dictionary mapping template variable names to Python objects.
 
If you can read this this view function without being overwhelmed, then you understand the basics of Django views. Now is a good time to reflect and make sure you do:
 
# What would you have to change to get 10 polls?
# What if you wanted the first 10 by name instead of by publication date?
 
* Reload [http://127.0.0.1:8000/questions/ http://127.0.0.1:8000/polls/]. Now you’ll see an error:
 
<source lang="python">
TemplateDoesNotExist at /questions/
polls/index.html
</source>
 
Ah. There’s no template yet. Let’s make one!
 
* Make a qandabear/templates/qandabear directory where templates will live, right alongside the views.py for the qandabear app. This is what I would do:
 
<source lang="python">
mkdir -p qandabear/templates/qandabear
</source>
 
Edit qandabear/templates/qandabear/index.html to contain:
 
<source lang="python">
{% if latest_qs %}
<ul>
{% for question in latest_qs %}
<li><a href="/questions/{{ question.id }}/">{{ question.text }}</a></li>
{% endfor %}
</ul>
{% else %}
<p>No questions are available.</p>
{% endif %}
</source>
 
* Edit TEMPLATE_DIRS in settings.py to have the full path to the templates folder inside your new app. On my computer, this looks like:
 
<source lang="python">
TEMPLATE_DIRS = (
# Put strings here, like "/home/html/django_templates" or "C:/www/django/templates".
# Always use forward slashes, even on Windows.
# Don't forget to use absolute paths, not relative paths.
'/karen/Code/django-for-ischoolers/mysite/qandabear/templates',
)
</source>
 
* Reload [http://127.0.0.1:8000/questions/ http://127.0.0.1:8000/questions/] . You should see a bulleted-list containing up to five of your questions. There should also be link pointing to the question's detail page.
 
* Save and commit.
 
===Fix the detail view and handle user errors using a 404 ===
 
Now, let’s tackle the question detail view – the page that displays one single question.
 
* Edit the views.py file. This view uses Python exceptions:
 
<source lang="python">
from django.http import Http404
# ...
def detail(request, question_id):
try:
q = Question.objects.get(id=question_id)
except Question.DoesNotExist:
raise Http404
return render_to_response('qandabear/detail.html', {'question': q})
</source>
 
Notice that view raises the Http404 exception if a poll with the requested ID doesn’t exist.
 
* Create qandabear/templates/qandabear/detail.html with:
 
<source lang="python">
{{ question }}
</source>
 
* Verify your “detail” view works. Try it: [http://127.0.0.1:8000/questions/1/ http://127.0.0.1:8000/questions/1/]
 
* Try visiting [http://127.0.0.1:8000/questions/9000/ http://127.0.0.1:8000/questions/9000/]. Oops, we forgot to make a template for our lovely 404 error! Create qandabear/templates/404.html (the qandabear template root dir) as:
 
<source lang="html4strict"><p>You have a 404. Go back and try again.</p></source>
 
* Load a question page that does not exist, to test out the pretty 404 error: [http://127.0.0.1:8000/questions/9000/ http://127.0.0.1:8000/questions/9000/]
 
* What? It says DEBUG has to be False? All right, set it (in settings.py), and try again! (note: Chrome ‘eats’ the 404. Safari will show our created page.)
 
* Change DEBUG back to True
 
* Save and commit.
 
=== Add more detail to the details! ===
 
Great, we've got a question detail page. But it isn't really that useful. We don't just want to see the question text -- we want to see the question's answers, too!
 
* Edit the qandabear/detail.html template to add a question variable. question points the particular instance of the Question class that our view sent to the template.
 
<source lang="python">
<h1>{{ question.text }}</h1>
<ul>
{% for answer in question.answer_set.all %}
<li>{{ answer.text }} -- <em>Answered at {{ answer.pub_date }} -- {{ answer.votes }} votes</em></li>
{% endfor %}
</ul>
</source>
 
The django.template system uses dot-lookup syntax to access variable attributes. Django’s template language is a bit looser than standard python. In pure Python, the . (dot) only lets you get attributes from objects, and we would need to use [] to access parts of list, tuple or dict objects. In this example, we are just doing attribute lookup, but in general if you’re not sure how to get data out of an object in django.templates, try dot.
 
Method-calling happens in the {% for %} loop: question.answer_set.all is interpreted as the Python code question.answer_set.all(), which returns a sequence of Answer objects and is suitable for use in the {% for %} template tag.
 
* Reload [http://127.0.0.1:8000/questions/1/ http://127.0.0.1:8000/questions/1/]. Observe that the question's answers now appear, with a little metadata!
 
* Save and commit your changes.
 
= Chunk 4 =
 
<source lang="python">$ git checkout master
$ git pull origin master
$ git branch my-chunk-4
$ git checkout my-chunk-4</source>
 
== Django templates 101 ==
 
Okay, we've done a bit with Django's templating language already when we created those templates in the previous chunk. Now let's look at templating a bit more in-depth.
== Hooking up views to our templates ==
 
=== Oh,Tags CRUD!and (plus, ModelForms)variables ===
 
Django comes with its own templating language. A template is (mostly) valid HTML, with some special templating tags thrown in to handle dynamic content. Some of those tags you've already seen, and resemble basic Python operators -- e.g. if statements and for loops. Programmatic statements such as these are always found inside block tags -- {% %} -- which separate them from the surrounding HTML.
 
<source lang="python">
<div class="question {% if question.was_published_today %}new-question-highlight{% endif %}"></div>
{% comment %}This tag checks to see if the given question was published today (using the question model's class function). If it was, the div gets an additional class applied to it, 'new-question-highlight'.{% endcomment %}
</source>
 
Additionally, you have variables, which are not programming statements but rather print data that your view fed to the template so that it appears in the HTML code. Variables can have special filters applied to them, which allow you to e.g. lowercase a given piece of text, but that's the extent of their power. These variables are found inside variable tags -- {{ }}.
 
Example:
 
<source lang="python">
<h1>{{ question.text|upper }}</h1>
{% comment %}This makes the text of the question appear inside the <h1> tag, and filters it so that it appears in all uppercase.{% endcomment %}
</source>
 
A reference to Django template filters and tags can be found here: [https://docs.djangoproject.com/en/dev/ref/templates/builtins/ https://docs.djangoproject.com/en/dev/ref/templates/builtins/]
 
Note: One important thing to keep in mind is that the Django templating language is ''NOT'' as powerful as Python or Django proper! There are many functions that Python has that the templating language does not support. For instance, you can't add or subtract within the templating language. Similarly, there are limits to what data the templating language can access in the database -- it has to be passed that data by the view. This is ''on purpose''. You should NOT be doing heavy-duty computing or data slicing and dicing in your templates! That logic should be in your views.py file, or a helper script of some kind if necessary. Remember M-V-C separation?
 
=== Template inheritance ===
 
When you make a web app with multiple pages, there are going to be a lot of elements shared by each of the pages. For example, each of the pages should have the same header and navigation UI, and the same footer. However, right now, we have two templates -- index.html and detail.html -- and each of them are entirely separate from the other. If we wanted to add a header to index.html, we'd have to add it separately to detail.html -- and every other template we have. If we ever decided to change our header, we'd be copying and pasting the whole day long. That's silly! And it violates Django's principle of DRY: Don't Repeat Yourself.
 
Fortunately, Django's template language helps us avoid this problem, by supporting ''template inheritance''.
 
* In qandabear/templates/, create a file named base.html. Put this code in the file:
 
<source lang="html4strict">
<!DOCTYPE html>
<html>
<head>
<title>QandaBear</title>
</head>
 
<body>
<div id="header">
<h1>QandaBear</h1>
<span class="subheader">All your questions answered</span>
</div>
 
<div id="content">
{% block content %}
<h2>Welcome to QandaBear!</h2>
{% endblock content %}
</div>
 
<div id="footer">
This web app is powered by Django!
</div>
</body>
</html>
</source>
 
What did we just do here? We made an HTML skeleton for our app including a header area, a content area, and a footer area. We also used the Django 'block' tag to make a block inside our content div, conveniently named 'content'.
 
* Now that we've got this skeleton, let's modify index.html to inherit from this template.
 
<source lang="html4strict">
{% extends "base.html" %}
 
{% block content %}
{% if latest_qs %}
<ul>
{% for question in latest_qs %}
<li><a href="/questions/{{ question.id }}/">{{ question.text }}</a></li>
{% endfor %}
</ul>
{% else %}
<p>No questions are available.</p>
{% endif %}
{% endblock content %}
</source>
 
What did we just do? We added an 'extends' tag to the top of our template, which tells Django that this template inherits from another one. We also put our code inside of a set of block tags, a block named 'content'. This tells Django, "Overwrite whatever the base template had inside this block and put this code there instead!"
 
Save your work and check out [http://127.0.0.1:8000/questions/ http://127.0.0.1:8000/questions/]! Now your header and footer appear on the page, along with the list of questions inside your content div.
 
(We should modify detail.html to inherit from our base template as well. That's left as an exercise to the reader.)
 
You can have multiple layers of inheritance -- for instance, if you had some pages with a single-column layout and other pages with a two-column layout, you could have a base template, which is inherited by single-column.html and two-column.html, and each of those could be inherited by other templates. Each template can only have one immediate parent, however -- you couldn't tell a template to inherit both from single-column.html *and* two-column.html.
 
By default, calling a block inside a child template will overwrite any content inside that block in the parent template. Sometimes you don't want to do this, though. For instance, if you had all of your javascript code and import statements inside a block, and wanted to some additional javascript that applied to just one template, you wouldn't want to overwrite everything. Fortunately, there's a tag for that, too!
 
* Add "<code>{{ block.super }}</code>" to your index.html file's content block:
 
<source lang="html4strict">
{% block content %}
{{ block.super }}
{% if latest_qs %}
<ul>
{% for question in latest_qs %}
<li><a href="/questions/{{ question.id }}/">{{ question.text }}</a></li>
{% endfor %}
</ul>
{% else %}
<p>No questions are available.</p>
{% endif %}
{% endblock content %}
</source>
 
* Save your work and check out [http://127.0.0.1:8000/questions/ http://127.0.0.1:8000/questions/]. What did this variable do?
 
* Commit your work.
 
=== Includes ===
 
There's another way that Django enables you to reuse elements: the 'include' tag. Generally speaking, you should use template inheritance for the most common kinds of reuse. Using includes for everything would get pretty messy! However, sometimes you have a widget that you want to use on multiple pages, and there isn't a clean inheritance relationship between them.
 
* In templates/qandabear, create a file called 'subscriber_widget.html'.
 
<source lang="html4strict">
<div class="widget subscriber-widget">
This web app is totally awesome, don't you agree? <a href="#">Subscribe to our newsletter!</a>
</div>
</source>
 
* Edit index.html's content block:
 
<source lang="html4strict">
{% block content %}
{{ block.super }}
{% if latest_qs %}
<ul>
{% for question in latest_qs %}
<li><a href="/questions/{{ question.id }}/">{{ question.text }}</a></li>
{% endfor %}
</ul>
{% else %}
<p>No questions are available.</p>
{% endif %}
{% include "qandabear/subscriber_widget.html" %}
{% endblock content %}
</source>
 
* Save your work and check out [http://127.0.0.1:8000/questions/ http://127.0.0.1:8000/questions/]. Oh hey, it's our subscriber widget! (Not much of a widget right now, but you get the idea.)
 
* Commit your work.
 
== Oh, CRUD! <!---(plus, ModelForms)--> ==
 
So, we've got a basic web app, with some templates and dynamic data. But right now the only way we have to edit that data -- add, edit, or delete questions or answers -- is from the command line. Our web app's users can't do it at all! Let's fix that.
 
A common acronym for these sorts of basic data operations is CRUD -- Create, Read, Update, and Delete. We've already got 'Read' covered. Time to do the rest!
 
===Create the form===
 
Recall that the prototype spec allows users to add answers to questions. We are going to use a form for that functionality. As an alternative, we could have used AJAX Requests or some other mechanism.
 
* Update our question detail template (qandabear/detail.html) to contain an HTML <form> element:
 
<source lang="html4strict">
{% extends "base.html" %}
{% block content %}
<h1>{{ question.text }}</h1>
 
<ul>
{% for answer in question.answer_set.all %}
<li>{{ answer.text }} -- <em>Answered at {{ answer.pub_date }} -- {{ answer.votes }} votes</em></li>
{% endfor %}
</ul>
 
<h2>Add new answer<h2>
<form action="/questions/{{ question.id }}/answer/" method="post">
{% csrf_token %}
<textarea name="answer"></textarea>
<input type="submit" value="Enter" />
</form>
{% endblock content %}
</source>
 
There is a lot going on there. A quick rundown:
 
* The above template displays a textarea below the list of previously-submitted answers, with an 'Enter' button.
* We set the form’s action to '/questions/{{ question.id }}/answer/ method="post"'. Normal web pages are requested using GET, but the standards for HTTP indicate that if you are changing data on the server, you must use the POST method. (Whenever you create a form that alters data server-side, use method="post". This tip isn’t specific to Django; it’s just good Web development practice!)
* Since we’re creating a POST form (which can have the effect of modifying data), we need to worry about Cross Site Request Forgeries. Thankfully, you don’t have to worry too hard, because Django comes with a very easy-to-use system for protecting against it. In short, all POST forms that are targeted at internal URLs should have the {% csrf_token %} template tag inside the form.
* The {% csrf_token %} tag requires information from the request object, which is not normally accessible from within the template context. To fix this, a small adjustment needs to be made to the detail view in the views.py file.
 
* Fix views.py to protect against CSRF hacking:
 
<source lang="python">
from django.template import RequestContext
from django.shortcuts import get_object_or_404, render_to_response
# ...
def detail(request, question_id):
q = get_object_or_404(Question, pk=question_id)
return render_to_response('qandabear/detail.html', {'question': q}, context_instance=RequestContext(request))
</source>
 
Notice we also added a function that checks if a 404 is returned for us. This is a common pattern, so there is a pre-built shortcut function for it so we can use fewer lines of code! The details of how the RequestContext works are explained in the documentation for RequestContext.
 
* Review your work at [http://127.0.0.1:8000/questions/1/ http://127.0.0.1:8000/questions/1/] .
* Save and commit.
 
===Process the form===
 
* Add <source lang="python">(r'^questions/(?P<question_id>\d+)/answer/$', 'add_answer'),</source> to urls.py
* Add add_answer() function to views.py:
 
<source lang="python">
from django.shortcuts import get_object_or_404, render_to_response
from django.http import HttpResponseRedirect, HttpResponse
from django.core.urlresolvers import reverse
from django.template import RequestContext
from qandabear.models import Answer, Question
# ...
def add_answer(request, question_id):
q = get_object_or_404(Question, pk=question_id)
answer_text = q.answer_set.get(pk=request.POST['answer'])
a = Answer(text=answer_text, question=q)
a.save()
# Always return an HttpResponseRedirect after successfully dealing
# with POST data. This prevents data from being posted twice if a
# user hits the Back button.
return HttpResponseRedirect(reverse('qandabear.views.detail', args=(q.id,)))
</source>
 
This code includes a few things we haven’t covered yet in this tutorial:
 
* request.POST is a dictionary-like object that lets you access submitted data by key name. In this case, request.POST['answer'] returns the contents of our textarea (named 'answer'), as a string. request.POST values are always strings.
* Note that Django also provides request.GET for accessing GET data in the same way – but we’re explicitly using request.POST in our code, to ensure that data is only altered via a POST call.
* After making and saving our new Answer, the code returns an HttpResponseRedirect rather than a normal HttpResponse. HttpResponseRedirect takes a single argument: the URL to which the user will be redirected (see the following point for how we construct the URL in this case).
* As the Python comment above points out, you should always return an HttpResponseRedirect after successfully dealing with POST data. This tip isn’t specific to Django; it’s just good Web development practice. That way, if the web surfer hits reload, they get the success page again, rather than re-doing the action.
* We are using the reverse() function in the HttpResponseRedirect constructor in this example. This function helps avoid having to hardcode a URL in the view function. It is given the name of the view that we want to pass control to and the variable portion of the URL pattern that points to that view. In this case, using the URLconf we set up in urls.py, this reverse() call will return a string like <source lang="python">'/questions/3/'</source> where the 3 is the value of q.id. This redirected URL will then call the results view to display the final page. Note that you need to use the full name of the view here (including the prefix).
 
* Restart your dev server.
* Navigate to [http://127.0.0.1:8000/questions/1/ http://127.0.0.1:8000/questions/1/] in your browser and answer the question. You should see your new answer appear in the list of answers.
* Save and commit.
 
=== Exercises for the reader ===
* We just made it so users could add answers. Make another form, on the index page, to let users add questions!
* Add an 'edit' link to each answer on the detail page, which when clicked takes you to another page where you can edit the answer using a form. When the edit form is filled out, it should take you back to the detail page for the question you came from.
* Add a 'delete' link to each answer on the detail page that, when clicked, deletes the answer and takes you back to that detail page. Huzzah -- we now have full CRUD functionality for Answers!
* Add a try/except to your view to check to see if the answer textarea is empty. If so, instead of making a new answer, return the user to the detail page right away with a warning message that their answer was invalid.
* With more complex models, [https://docs.djangoproject.com/en/dev/topics/forms/modelforms/ ModelForms] are super helpful in generating a form that will fill out your model without having to match up all the fields from scratch yourself. Rewrite QandaBear to use ModelForms!
 
= Bonus points =
 
== Static files (aka: Django for designers) ==
 
TODO: Write this!
 
[https://docs.djangoproject.com/en/1.3/howto/static-files/ https://docs.djangoproject.com/en/1.3/howto/static-files/]
 
== AJAX and Django ==
 
TODO: Write this!
 
For now, [http://webcloud.se/log/AJAX-in-Django-with-jQuery/ http://webcloud.se/log/AJAX-in-Django-with-jQuery/]
 
== Test-driven development, part two ==
 
TODO: (walk through how to write tests for a new feature)
 
[http://dougalmatthews.com/articles/2010/jan/20/testing-your-first-django-app/ http://dougalmatthews.com/articles/2010/jan/20/testing-your-first-django-app/]
 
[https://docs.djangoproject.com/en/dev/topics/testing/ https://docs.djangoproject.com/en/dev/topics/testing/]
 
== Authentication and Users (and reusing apps) ==
 
[https://docs.djangoproject.com/en/dev/topics/auth/ https://docs.djangoproject.com/en/dev/topics/auth/]
 
== Deploying to a real live server ==
 
TODO
Anonymous user