due at 23:59 on
Again, the following commands will update your repository to the latest code that I may have added:
liucs:~$ cd cs164/bookswap liucs:~/cs164/bookswap$ git pull
Before continuing, make sure you are caught up on Milestone 3.
Here is how you can abandon your changes and continue your project using mine. Do this only if you feel you need it to make progress, or if I advise you to! After finishing this sequence of commands, send me an email to let me know how it went (or report anything that looks like an error).
-X
in step 2.## Use these commands ONLY if you are stuck on M3 liucs:~/cs164/bookswap$ git commit -am "unsaved changes" liucs:~/cs164/bookswap$ git merge -X theirs origin/solution liucs:~/cs164/bookswap$ git push liucs:~/cs164/bookswap$ rm -f dbfile liucs:~/cs164/bookswap$ python manage.py syncdb --noinput liucs:~/cs164/bookswap$ python manage.py loaddata sample
After adding the sample data, your admin interface username is admin
and the password is admin
.
To use templates, we have to specify a template directory in the settings.py
file. Create the directory in the shell:
liucs:~/cs164/bookswap$ mkdir templates
and then find the section for TEMPLATE_DIRS
in settings.py
(around line 105) and make it look like this:
from os.path import dirname, join
CWD = dirname(__file__) # double underscores
TEMPLATE_DIRS = (
join(CWD, 'templates'),
# Put strings here, like "/home/html/django_templates"
# Always use forward slashes, even on Windows.
# Don't forget to use absolute paths, not relative paths.
)
This code automatically creates an absolute path to your templates
directory, by substituting in the directory of the settings.py
file itself.
Now let’s try the simplest possible template. In urls.py
, add an entry like this:
url(r'^template-test/', 'app.views.template_test'),
If you load http://localhost:8000/template-test/
in your browser on the VM (of course, you need to have runserver
working, as usual), you should see an error ViewDoesNotExist
.
In app/views.py
, add these imports at the top:
from django.shortcuts import render_to_response
from django.template import RequestContext
from django.db.models import Q
and this simple view function:
def template_test(request):
return render_to_response('test.html', {'a': 42})
Reload the template-test
URL and you should see the error message change to “Template not found.”
Finally, create a new file test.html
in your templates
directory, with these contents:
<h1>Test</h1>
The value of A is {{a}}.
Reload the template-test
URL and it should say “The value of A is 42.”
As discussed in class, we’ll save the search form for later, but we can still do the search results. The URL will look like this:
http://localhost:8000/search?isbn=1234&title=Photo&authors=Chris
and so on for other search fields. The rule here is that, after the question mark, you have multiple key=value
pairs, separated by &
.
Add the following to the URL configuration:
url(r'^search', 'app.views.search'),
Start your function in app/views.py
:
def search(request):
return render_to_response('search.html')
And then create an initial template in templates/search.html
:
<h1>Search Results</h1>
Verify that all that works and displays the “Search Results” header:
Now we’ll handle the various search fields, one at a time. Let’s start with isbn
. We’ll use a filter
along with a so-called “Q
” object (explained below), and pass the results
to the template.
def search(request):
results = Book.objects.filter(Q(isbn=request.GET['isbn']))
return render_to_response('search.html', {'results': results})
The template should be modified accordingly:
<h1>Search Results</h1>
<ul>
{% for r in results %}
<li>{{r.title}} by {{r.authors}} ({{r.isbn}})
</li>
{% endfor %}
</ul>
Set isbn=
to an actual ISBN from your sample data, and you’ll get that result back:
Q
objectOkay, now about that Q
notation. This gives us a very flexible way to combine search queries, including Boolean and (&
), or (|
), not (~
). So if I want to find books matching the ISBN or the title, it looks like this:
results = Book.objects.filter(
Q(isbn = request.GET['isbn']) |
Q(title__contains = request.GET['title']))
Here’s a sample result using this code:
We get the Chemistry book because it is ISBN 3888472910, and the other two because the title contains “Photo”.
Try adding some other search fields, such as authors
. Should this also be combined with the others using or (|
), or would the Boolean and make more sense?
Next, try searching by course, including the prefix, number, and title. This is a little more complex. You can still add them to the Q
object, like this:
results = Book.objects.filter(
Q(isbn=request.GET['isbn']) |
Q(title__contains=request.GET['title']) |
Q(course__prefix=request.GET['prefix']))
However, because course
is a ManyToManyField
, we end up getting back several matches:
This is easy to fix by adding .distinct()
to the end of the filter function:
results = Book.objects.filter(
Q(isbn=request.GET['isbn']) |
Q(title__contains=request.GET['title']) |
Q(course__prefix=request.GET['prefix'])).distinct()
If you can get authors
to work but are having too much trouble with course
, it’s fine to move on.
Next we’ll do a page that displays detailed information about one book, including all the buy/sell listings for that book.
If you don’t already have it, add this to your URL conf:
url(r'^book/(\d+)/', 'app.views.showbook'),
As described in class, the parentheses in the URL pattern indicate there is a parameter there. The \d
matches any digit (0–9), and because there is a +
after it, that means one or more digits. So the URL will match /book/1/
or /book/932/
, but not /book/9a/
. For this parameter, we’ll use the id
field automatically assigned to every book, rather than the ISBN.
In app/views.py
, your showbook
function will have an extra parameter that is supplied by the number from the URL. Here’s an initial version:
def showbook(request, id):
b = Book.objects.get(id=id)
return HttpResponse(
"ISBN: %s<br> Title: %s<br> Authors: %s<br> Publisher: %s<br>" %
(b.isbn, b.title, b.authors, b.publisher))
You can test that and see one of your books:
Using what you learned about templates and render_to_response
, create a book.html
template that displays the same information after you change showbook
as follows:
def showbook(request, id):
b = Book.objects.get(id=id)
return render_to_response('book.html', {'book': b})
You may also want to play with the formatting in the HTML template. Use <h1>
to make the title a heading, for example.
Now, we want to list buyers and sellers on the template page. You will:
issue the appropriate Django query commands in the showbook
function in app/views.py
pass the results of those queries to the template using the dictionary parameter of render_to_response
:
{'book': b, 'buyers': … , 'sellers': … }
display them in the template using the {% for … %}
loop notation.
Try to make it look something like this:
How did I get the count of people in each category? For any query result, you can just add .count
(if it’s in a template), or call .count()
(if it’s in Python code).
<h2>{{buyers.count}} people want to buy</h2>
Notice that, in the screen shot, it says “1 people want to sell,” which is a little awkward. You can fix stuff like that with the pluralize
filter. It takes a number on the left side, and a string with two alternatives (singular, then plural) on the right:
<h2>{{sellers.count}}
{{sellers.count|pluralize:"person wants,people want"}}
to sell
</h2>
Now if sellers.count
is 1, it says “1 person wants to sell.” Otherwise it will use “N people want to sell.”
We want the search results page to link to the Here’s the syntax for an HTML hyperlink:
<a href="/book/{{r.id}}/">{{r.title}}</a>
Verify that it connects you from the search results to the appropriate book page.
For this one, you’re more on your own. We want the user names in the selling and listing pages to link to a user profile page containing their contact information. For now, that probably just means their user name and email address. Later on we’ll look at how to add a more detailed user profile, which could contain phone numbers, preferences, etc.
The fields of the User
object are called
username
first_name
last_name
email
Make sure that the links on the book page take you to the correct user profile:
That’s it, but feel free to embellish! Ask questions by email or on the forum about anything fancier you’d like to try.
To submit, first try git status
and look for any untracked files. These are files you created that did not exist before, such as your templates.
liucs:~/cs164/bookswap$ git status # On branch master # Your branch is ahead of 'league/master' by 4 commits. # # Changes not staged for commit: # (use "git add..." to update what will be committed) # (use "git checkout -- ..." to discard changes) # # modified: app/views.py # modified: urls.py # # Untracked files: # (use "git add ..." to include in what will be committed) # # templates/book.html # templates/user.html no changes added to commit (use "git add" and/or "git commit -a")
Before committing, you will have to add all of these new files:
liucs:~/cs164/bookswap$ git add templates/*.html
Finally, you can do the usual two-step commit
and push
:
liucs:~/cs164/bookswap$ git commit -am "my milestone 4" liucs:~/cs164/bookswap$ git pushcomments powered by Disqus
©2012 Christopher League · some rights reserved · CC by-sa