Django for ISchoolers: Difference between revisions

Content added Content deleted
imported>Aldeka
imported>Aldeka
No edit summary
Line 21: Line 21:
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):
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):


<syntaxhighlight lang="python">
<source lang="python">
$ virtualenv --no-site-packages django-tutorial-env
$ virtualenv --no-site-packages django-tutorial-env


Line 29: Line 29:


(django-tutorial-env)$ pip install south
(django-tutorial-env)$ pip install south
</source>
</syntaxhighlight>


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.
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: Line 57:
=== Activate your environment in your terminal ===
=== Activate your environment in your terminal ===


If you haven't already, run <syntaxhighlight lang="python">$ source django-tutorial-env/bin/activate</syntaxhighlight> in your terminal window.
If you haven't already, run <source lang="python">$ source django-tutorial-env/bin/activate</source> in your terminal window.


=== Set up your git repository ===
=== Set up your git repository ===


If you're following this tutorial in class, in your terminal, run <syntaxhighlight lang="python">$ git clone git://github.com/aldeka/django-for-ischoolers.git</syntaxhighlight>. This won't download hardly anything interesting, but it will make it easy for you to sync up with the class later.
If you're following this tutorial in class, in your terminal, run <source lang="python">$ git clone git://github.com/aldeka/django-for-ischoolers.git</source>. 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.
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: Line 67:
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.
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, type <syntaxhighlight lang="python">$ git branch my-chunk-1</syntaxhighlight> to make your new branch, and <syntaxhighlight lang="python">$ git checkout my-chunk-1</syntaxhighlight> to switch into that branch. Type <syntaxhighlight lang="python">$ git branch</syntaxhighlight> to confirm that you're in your new branch (and that you only have two branches so far).
To make your first branch, type <source lang="python">$ git branch my-chunk-1</source> to make your new branch, and <source lang="python">$ git checkout my-chunk-1</source> to switch into that branch. Type <source lang="python">$ git branch</source> 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 <syntaxhighlight lang="python">$ git init</syntaxhighlight>.
If you're not following this tutorial in class, just run <source lang="python">$ git init</source>.


=== Create your Django project ===
=== Create your Django project ===
Line 75: Line 75:
Let's create your first Django project, called "mysite".
Let's create your first Django project, called "mysite".


<syntaxhighlight lang="python">$ django-admin.py startproject mysite</syntaxhighlight>
<source lang="python">$ django-admin.py startproject mysite</source>


You'll see we made a folder called "mysite", with some files in it. Let's check them out!
You'll see we made a folder called "mysite", with some files in it. Let's check them out!
Line 101: Line 101:
** Run the command:
** Run the command:


<syntaxhighlight lang="python">python manage.py runserver</syntaxhighlight>
<source lang="python">python manage.py runserver</source>


** Review the output in your terminal. It should look similar to:
** Review the output in your terminal. It should look similar to:


<syntaxhighlight lang="python">Validating models...
<source lang="python">Validating models...
0 errors found.
0 errors found.


Django version 1.2, using settings 'myproject.settings'
Django version 1.2, using settings 'myproject.settings'
Development server is running at http://127.0.0.1:8000/
Development server is running at http://127.0.0.1:8000/
Quit the server with CONTROL-C.</syntaxhighlight>
Quit the server with CONTROL-C.</source>


* 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!
* 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 116: Line 116:
* Observe the logging that happens in the terminal where your server is running:
* Observe the logging that happens in the terminal where your server is running:


<syntaxhighlight lang="python">[24/Mar/2011 11:50:18] "GET / HTTP/1.1" 200 2057</syntaxhighlight>
<source lang="python">[24/Mar/2011 11:50:18] "GET / HTTP/1.1" 200 2057</source>


which has the format:
which has the format:


<syntaxhighlight>DATE METHOD URL PROTOCOL RESPONSE_CODE CONTENTSIZE</syntaxhighlight>
<source>DATE METHOD URL PROTOCOL RESPONSE_CODE CONTENTSIZE</source>


* Navigate to http://127.0.0.1:8000/some/url/. What changes in the terminal log?
* Navigate to http://127.0.0.1:8000/some/url/. What changes in the terminal log?
Line 136: Line 136:
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.
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 <syntaxhighlight lang="python">git status</syntaxhighlight> 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.
* To make a commit, first type <source lang="python">git status</source> 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.


<syntaxhighlight lang="python">$ git status
<source lang="python">$ git status
# On branch my-chunk-1
# On branch my-chunk-1
#
#
Line 150: Line 150:
# settings.py
# settings.py
# urls.py
# urls.py
nothing added to commit but untracked files present (use "git add" to track)</syntaxhighlight>
nothing added to commit but untracked files present (use "git add" to track)</source>


"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.
"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: <syntaxhighlight lang="python">git add manage.py</syntaxhighlight>. What does git status say now?
* Add one file: <source lang="python">git add manage.py</source>. What does git status say now?
* Add all the files to the repo, in the local directory:
* Add all the files to the repo, in the local directory:


<syntaxhighlight lang="python">$ git add *.py # all .py files, using a wildcard match.</syntaxhighlight>
<source lang="python">$ git add *.py # all .py files, using a wildcard match.</source>


What does git status say now?
What does git status say now?
Line 163: Line 163:
* git commit to commit those files:
* git commit to commit those files:


<syntaxhighlight lang="python">
<source lang="python">
# -m -> what is the 'message' for the commit
# -m -> what is the 'message' for the commit
git commit -m "Initial commit of Django project from the IOLab Django workshop"
git commit -m "Initial commit of Django project from the IOLab Django workshop"
</source>
</syntaxhighlight>


* Look at your changes with <syntaxhighlight lang="python">git log</syntaxhighlight> to see your history. Is your commit message there?
* Look at your changes with <source lang="python">git log</source> to see your history. Is your commit message there?


Huzzah! We committed our changes so far. Now let's make some more changes!
Huzzah! We committed our changes so far. Now let's make some more changes!
Line 182: Line 182:
* git add and commit it:
* git add and commit it:


<syntaxhighlight lang="python">
<source lang="python">
# 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
# 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
git add settings.py
git commit -m "made myself an admin"
git commit -m "made myself an admin"
</source>
</syntaxhighlight>


===Set up the Database===
===Set up the Database===
Line 192: Line 192:
Keep looking at settings.py: The DATABASES variable is a dictionary (note the ‘{}’ characters) with one key: default.
Keep looking at settings.py: The DATABASES variable is a dictionary (note the ‘{}’ characters) with one key: default.


<syntaxhighlight lang="python">
<source lang="python">
DATABASES = {
DATABASES = {
'default': {
'default': {
Line 202: Line 202:
'PORT': '', # Set to empty string for default. Not used with sqlite3.
'PORT': '', # Set to empty string for default. Not used with sqlite3.
}
}
}</syntaxhighlight>
}</source>


Notice that the value of default is itself another dictionary with information about the site’s default database.
Notice that the value of default is itself another dictionary with information about the site’s default database.
Line 208: Line 208:
* 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.
* 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.


<syntaxhighlight lang="python">'ENGINE': 'django.db.backends.sqlite3',</syntaxhighlight>
<source lang="python">'ENGINE': 'django.db.backends.sqlite3',</source>


* Set your app to use a file called 'database.db' to store information for this project.
* Set your app to use a file called 'database.db' to store information for this project.


<syntaxhighlight lang="python">'NAME': 'database.db',</syntaxhighlight>
<source lang="python">'NAME': 'database.db',</source>


Does database.db exist right now? (No, but that's okay. It'll get created automatically when it's needed.)
Does database.db exist right now? (No, but that's okay. It'll get created automatically when it's needed.)
Line 220: Line 220:
* Add South to our list of installed apps. (We'll need it later.)
* Add South to our list of installed apps. (We'll need it later.)


<syntaxhighlight lang="python">INSTALLED_APPS = (
<source lang="python">INSTALLED_APPS = (
'django.contrib.auth',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.contenttypes',
Line 231: Line 231:
# 'django.contrib.admindocs',
# 'django.contrib.admindocs',
'south',
'south',
)</syntaxhighlight>
)</source>


* 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:
* 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:


<syntaxhighlight lang="python">python manage.py syncdb</syntaxhighlight>
<source lang="python">python manage.py syncdb</source>


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.
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 268: Line 268:
* Make scaffolding for the app:
* Make scaffolding for the app:


<syntaxhighlight lang="python">$ python manage.py startapp qandabear</syntaxhighlight>
<source lang="python">$ python manage.py startapp qandabear</source>


That’ll create a directory qandabear to house our Q & A application.
That’ll create a directory qandabear to house our Q & A application.
Line 274: Line 274:
* Verify what is new.
* Verify what is new.


<syntaxhighlight lang="python">$ git status
<source lang="python">$ git status
# should show 'qandabear/' in 'untracked'</syntaxhighlight>
# should show 'qandabear/' in 'untracked'</source>


* Examine the layout of qandabear (we will do more of this in following sections).
* Examine the layout of qandabear (we will do more of this in following sections).


<syntaxhighlight lang="python"># remember not to type the '$', it just means the prompt'.
<source lang="python"># remember not to type the '$', it just means the prompt'.
$ ls qandabear
$ ls qandabear
qandabear/
qandabear/
Line 286: Line 286:
tests.py
tests.py
views.py
views.py
</source>
</syntaxhighlight>


* Add and commit <syntaxhighlight lang="python">qandabear/*py</syntaxhighlight>.
* Add and commit <source lang="python">qandabear/*py</source>.
* 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.
* 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.
* Save and commit the settings.py file.
Line 296: Line 296:
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.
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.


<syntaxhighlight lang="python">$ git checkout master
<source lang="python">$ git checkout master
$ git pull origin master</syntaxhighlight>
$ git pull origin master</source>


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.)
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 303: Line 303:
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!
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!


<syntaxhighlight lang="python">$ git branch my-chunk-2
<source lang="python">$ git branch my-chunk-2
$ git checkout my-chunk-2</syntaxhighlight>
$ git checkout my-chunk-2</source>


<!-- == Test-driven development, part one ==
<!-- == Test-driven development, part one ==
Line 342: Line 342:
===Adding URLs to our urls.py===
===Adding URLs to our urls.py===


When we ran <syntaxhighlight lang="python">django-admin.py startproject mysite</syntaxhighlight> to create the project, Django created a default URLconf file called `urls.py`.
When we ran <source lang="python">django-admin.py startproject mysite</source> 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:
* Write our URL mapping. Edit the file mysite/urls.py so it looks like this:


<syntaxhighlight lang="python">urlpatterns = patterns('',
<source lang="python">urlpatterns = patterns('',
(r'^questions/$', 'qandabear.views.index'),
(r'^questions/$', 'qandabear.views.index'),
(r'^questions/(\d+)/$', 'qandabear.views.question'),
(r'^questions/(\d+)/$', 'qandabear.views.question'),
Line 359: Line 359:
# Uncomment the next line to enable the admin:
# Uncomment the next line to enable the admin:
# url(r'^admin/', include(admin.site.urls)),
# url(r'^admin/', include(admin.site.urls)),
)</syntaxhighlight>
)</source>


Suppose a visitor goes to http://127.0.0.1:8000/questions/23/.
Suppose a visitor goes to http://127.0.0.1:8000/questions/23/.
Line 374: Line 374:
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'^questions/(\d+)/$' – it loads the function question() from qandabear/views.py. Finally, it calls that module’s detail() function like so:
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'^questions/(\d+)/$' – it loads the function question() from qandabear/views.py. Finally, it calls that module’s detail() function like so:


<syntaxhighlight lang="python">detail(request=<HttpRequest object>, '23')</syntaxhighlight>
<source lang="python">detail(request=<HttpRequest object>, '23')</source>


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).
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 384: Line 384:
== views.py ==
== views.py ==


* Start the development server: <syntaxhighlight lang="python">python manage.py runserver</syntaxhighlight>
* 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:
* Fetch “http://127.0.0.1:8000/questions/” in your browser. You should get a pleasantly-colored error page with the following message:


<syntaxhighlight lang="python">ViewDoesNotExist at /questions/
<source lang="python">ViewDoesNotExist at /questions/


Tried index in module qandabear.views. Error was: 'module'
Tried index in module qandabear.views. Error was: 'module'
object has no attribute 'index'</syntaxhighlight>
object has no attribute 'index'</source>


Recall this line: <syntaxhighlight lang="python">(r'^questions/$', 'qandabear.views.index')</syntaxhighlight>.
Recall this line: <source lang="python">(r'^questions/$', 'qandabear.views.index')</source>.


* Explore this using your django-shell: <syntaxhighlight lang="python">python manage.py shell</syntaxhighlight>
* Explore this using your django-shell: <source lang="python">python manage.py shell</source>


<syntaxhighlight lang="python">>>> import qandabear # imports fine!
<source lang="python">>>> import qandabear # imports fine!
>>> import qandabear.views # imports fine also!
>>> import qandabear.views # imports fine also!
>>> dir(qandabear.views) # what is in there!
>>> dir(qandabear.views) # what is in there!
Line 404: Line 404:
>>> inspect.getsourcefile(qandabear.views)
>>> inspect.getsourcefile(qandabear.views)
# something like
# something like
'/Users/adalovelace/gits/mysite/questions/views.py'</syntaxhighlight>
'/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.
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.
Line 412: Line 412:
* Write some views. Open qandabear/views.py and put the following Python code in it:
* Write some views. Open qandabear/views.py and put the following Python code in it:


<syntaxhighlight lang="python">from django.http import HttpResponse
<source lang="python">from django.http import HttpResponse


def index(request):
def index(request):
return HttpResponse("Hello, world. You're at the questions index.")</syntaxhighlight>
return HttpResponse("Hello, world. You're at the questions index.")</source>


This is a very simple view.
This is a very simple view.
Line 423: Line 423:
* 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):
* 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):


<syntaxhighlight lang="python">def question(request, question_id):
<source lang="python">def question(request, question_id):
return HttpResponse("You're looking at question %s." % (question_id,))
return HttpResponse("You're looking at question %s." % (question_id,))


def edit_answer(request, question_id, answer_id):
def edit_answer(request, question_id, answer_id):
return HttpResponse("You're looking at the edit page for answer %s." % (answer_id,))</syntaxhighlight>
return HttpResponse("You're looking at the edit page for answer %s." % (answer_id,))</source>


* Save views.py.
* Save views.py.
Line 462: Line 462:
Edit the qandabear/models.py file so it looks like this:
Edit the qandabear/models.py file so it looks like this:


<syntaxhighlight lang="python">from django.db import models
<source lang="python">from django.db import models


class Question(models.Model):
class Question(models.Model):
Line 471: Line 471:
question = models.ForeignKey(Question)
question = models.ForeignKey(Question)
answer = models.TextField()
answer = models.TextField()
pub_date = models.DateTimeField(auto_now_add=True)</syntaxhighlight>
pub_date = models.DateTimeField(auto_now_add=True)</source>


* Save the models.py file.
* Save the models.py file.
Line 497: Line 497:
* On the command line, write:
* On the command line, write:


<syntaxhighlight lang="python">$ python manage.py schemamigration qandabear --initial</syntaxhighlight>
<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.
As you can see, that’s created a migrations directory for us, and made a new migration inside it.
Line 503: Line 503:
* All we need to do now is apply our new migration:
* All we need to do now is apply our new migration:


<syntaxhighlight lang="python">$ python manage.py migrate qandabear</syntaxhighlight>
<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.
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 <syntaxhighlight lang="python">python manage.py syncdb</syntaxhighlight>. 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.
'''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.
* Add and commit all your work, including the migrations folder that South generated for you.
Line 519: Line 519:
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:
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:


<syntaxhighlight lang="python">python manage.py shell</syntaxhighlight>
<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:
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:
Line 530: Line 530:
* Import the model classes we just wrote:
* Import the model classes we just wrote:


<syntaxhighlight lang="python">
<source lang="python">
>>> from qandabear.models import Question, Answer</syntaxhighlight>
>>> from qandabear.models import Question, Answer</source>


* List all the current Questions:
* List all the current Questions:


<syntaxhighlight lang="python">
<source lang="python">
>>> Question.objects.all()
>>> Question.objects.all()
[]</syntaxhighlight>
[]</source>


How many questions is this?
How many questions is this?
Line 545: Line 545:
=== Add our first Question ===
=== Add our first Question ===


<syntaxhighlight lang="python">
<source lang="python">
>>> q = Question(text="What is the Weirdest Cookbook Ever?")
>>> 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)</syntaxhighlight>
# 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 <syntaxhighlight lang="python">q.id</syntaxhighlight>. What happens?
* 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.
* Save the Question instance into the database. You have to call save() explicitly.


<syntaxhighlight lang="python">
<source lang="python">
>>> q.save()</syntaxhighlight>
>>> q.save()</source>


* Get the id of the Question instance. Because it’s been saved, it has an ID in the database now!
* Get the id of the Question instance. Because it’s been saved, it has an ID in the database now!


<syntaxhighlight lang="python">
<source lang="python">
>>> q.id
>>> q.id
1</syntaxhighlight>
1</source>


* Access the database columns (Fields, in Django parlance) as Python attributes:
* Access the database columns (Fields, in Django parlance) as Python attributes:


<syntaxhighlight lang="python">
<source lang="python">
>>> q.text
>>> q.text
"What is the Weirdest Cookbook Ever?"
"What is the Weirdest Cookbook Ever?"
>>> q.pub_date
>>> q.pub_date
datetime.datetime(2011, 12, 1, 3, 3, 55, 841929)
datetime.datetime(2011, 12, 1, 3, 3, 55, 841929)
</source>
</syntaxhighlight>


* Send the Question back in time:
* Send the Question back in time:


<syntaxhighlight lang="python"># Change values by changing the attributes, then calling save().
<source lang="python"># Change values by changing the attributes, then calling save().
>>> import datetime
>>> import datetime
>>> q.pub_date = datetime.datetime(2011, 4, 1, 0, 0)
>>> q.pub_date = datetime.datetime(2011, 4, 1, 0, 0)
>>> q.save()
>>> q.save()
>>> q.pub_date
>>> q.pub_date
datetime.datetime(2007, 4, 1, 0, 0)</syntaxhighlight>
datetime.datetime(2007, 4, 1, 0, 0)</source>


* Ask Django to show a list of all the Question objects available:
* Ask Django to show a list of all the Question objects available:


<syntaxhighlight lang="python">>>> Question.objects.all()
<source lang="python">>>> Question.objects.all()
[<Question: Question object>]</syntaxhighlight>
[<Question: Question object>]</source>


===Fix The Hideous Default Representation===
===Fix The Hideous Default Representation===
Line 589: Line 589:
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:
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:


<syntaxhighlight lang="python">class Question(models.Model):
<source lang="python">class Question(models.Model):
# ...
# ...
def __unicode__(self):
def __unicode__(self):
Line 601: Line 601:
if len(self.text) > 37:
if len(self.text) > 37:
summary = summary + '...'
summary = summary + '...'
return summary</syntaxhighlight>
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.
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.
Line 609: Line 609:
Enough of these normal python methods! Let's build in some useful functionality to our models in qandabear/models.py.
Enough of these normal python methods! Let's build in some useful functionality to our models in qandabear/models.py.


<syntaxhighlight lang="python">import datetime
<source lang="python">import datetime
# ...
# ...
class Question(models.Model):
class Question(models.Model):
# ...
# ...
def was_published_today(self):
def was_published_today(self):
return self.pub_date.date() == datetime.date.today()</syntaxhighlight>
return self.pub_date.date() == datetime.date.today()</source>


Note the addition of <syntaxhighlight lang="python">import datetime</syntaxhighlight> to reference Python’s standard datetime module. This allows us to use the datetime library module in models.py by calling it with datetime.
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.
* Save these changes to the models.py file.
Line 624: Line 624:
Start a new Python interactive shell by running python manage.py shell:
Start a new Python interactive shell by running python manage.py shell:


<syntaxhighlight lang="python">
<source lang="python">
>>> from qandabear.models import Question, Answer
>>> from qandabear.models import Question, Answer
Verify our __unicode__() addition worked:
Verify our __unicode__() addition worked:
Line 635: Line 635:
>>> questions[0].id # remember python lists start with element 0.
>>> questions[0].id # remember python lists start with element 0.
1
1
</source>
</syntaxhighlight>


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.
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.


<syntaxhighlight lang="python">
<source lang="python">
>>> Question.objects.filter(text="Who framed Roger Rabbit?")
>>> Question.objects.filter(text="Who framed Roger Rabbit?")
[]
[]
Line 648: Line 648:
...
...
DoesNotExist: Question matching query does not exist.
DoesNotExist: Question matching query does not exist.
</source>
</syntaxhighlight>


===Add Answers===
===Add Answers===
Line 654: Line 654:
Observe: there is a Question in the database, but it has no Answers! Let's fix that.
Observe: there is a Question in the database, but it has no Answers! Let's fix that.


<syntaxhighlight lang="python">
<source lang="python">
>>> q = Question.objects.get(id=1)
>>> q = Question.objects.get(id=1)
>>> q.answer_set.all()
>>> q.answer_set.all()
Line 666: Line 666:
>>> a
>>> a
<Answer: Mini-Mart A La Carte>
<Answer: Mini-Mart A La Carte>
</source>
</syntaxhighlight>


Go in reverse! Find the question a particular answer belongs to:
Go in reverse! Find the question a particular answer belongs to:


<syntaxhighlight lang="python">
<source lang="python">
>>> a.question
>>> a.question
<Question: What is the Weirdest Cookbook Ever?>
<Question: What is the Weirdest Cookbook Ever?>
</source>
</syntaxhighlight>


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.
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.


<syntaxhighlight lang="python">
<source lang="python">
>>> q.answer_set.all()
>>> q.answer_set.all()
[<Answer: To Serve Man>, <Answer: The Original Road Kill Cookbook>, <Answer: Mini-Mart A La Carte>]
[<Answer: To Serve Man>, <Answer: The Original Road Kill Cookbook>, <Answer: Mini-Mart A La Carte>]
>>> q.answer_set.count()
>>> q.answer_set.count()
3
3
</source>
</syntaxhighlight>


Can one be a Answer for a Question that doesn’t yet exist?:
Can one be a Answer for a Question that doesn’t yet exist?:


<syntaxhighlight lang="python">
<source lang="python">
>>> koan = Answer("Is this even an answer")
>>> koan = Answer("Is this even an answer")
>>> koan.question_id
>>> koan.question_id
>>> koan.question
>>> koan.question
</source>
</syntaxhighlight>


<!--- Adding pre-written data would go here. -->
<!--- Adding pre-written data would go here. -->
Line 700: Line 700:
* Open qandabear/models.py and edit the Question class:
* Open qandabear/models.py and edit the Question class:


<syntaxhighlight lang="python">
<source lang="python">
class Question(models.Model):
class Question(models.Model):
question = models.CharField(max_length=200)
question = models.CharField(max_length=200)
</source>
</syntaxhighlight>


* Edit the Answer class too:
* Edit the Answer class too:


<syntaxhighlight lang="python">
<source lang="python">
class Answer(models.Model):
class Answer(models.Model):
question = models.ForeignKey(Question)
question = models.ForeignKey(Question)
Line 713: Line 713:
votes = models.IntegerField(default=0)
votes = models.IntegerField(default=0)
pub_date = models.DateTimeField(auto_now_add=True)
pub_date = models.DateTimeField(auto_now_add=True)
</source>
</syntaxhighlight>


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.
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.
Line 719: Line 719:
* Make a migration so the database knows we deleted Question.pub_date and added Answer.votes:
* Make a migration so the database knows we deleted Question.pub_date and added Answer.votes:


<syntaxhighlight lang="python">
<source lang="python">
$ python manage.py schemamigration qandabear --auto
$ python manage.py schemamigration qandabear --auto
</source>
</syntaxhighlight>


You should get a warning message that looks like this:
You should get a warning message that looks like this:


<syntaxhighlight lang="python">
<source lang="python">
? The field 'Question.pub_date' does not have a default specified, yet is NOT NULL.
? The field 'Question.pub_date' does not have a default specified, yet is NOT NULL.
? Since you are removing this field, you MUST specify a default
? Since you are removing this field, you MUST specify a default
Line 732: Line 732:
? 2. Specify a one-off value to use for existing columns now
? 2. Specify a one-off value to use for existing columns now
? 3. Disable the backwards migration by raising an exception.
? 3. Disable the backwards migration by raising an exception.
</source>
</syntaxhighlight>


South insists on having a default value for this field just in case, even though we're about to remove it. So, let's do that. Enter '2'.
South insists on having a default value for this field just in case, even though we're about to remove it. So, let's do that. Enter '2'.


<syntaxhighlight lang="python">
<source lang="python">
? Please enter Python code for your one-off default value.
? Please enter Python code for your one-off default value.
? The datetime module is available, so you can do e.g. datetime.date.today()
? The datetime module is available, so you can do e.g. datetime.date.today()
</source>
</syntaxhighlight>


pub_date is supposed to be a date of some kind. The default may as well be today!
pub_date is supposed to be a date of some kind. The default may as well be today!


<syntaxhighlight lang="python">
<source lang="python">
? Please enter Python code for your one-off default value.
? Please enter Python code for your one-off default value.
? The datetime module is available, so you can do e.g. datetime.date.today()
? The datetime module is available, so you can do e.g. datetime.date.today()
>>> datetime.date.today()
>>> datetime.date.today()
</source>
</syntaxhighlight>


South will then happily finish creating your migration.
South will then happily finish creating your migration.
Line 753: Line 753:
* Apply the migration.
* Apply the migration.


<syntaxhighlight lang="python">
<source lang="python">
$ python manage.py migrate qandabear
$ python manage.py migrate qandabear
Running migrations for qandabear:
Running migrations for qandabear:
Line 760: Line 760:
- Loading initial data for qandabear.
- Loading initial data for qandabear.
No fixtures found.
No fixtures found.
</source>
</syntaxhighlight>


Success!
Success!