Django for ISchoolers: Difference between revisions

Undo revision 19296; minus URLs to avoid captcha
imported>Aldeka
(Undo revision 19296; minus URLs to avoid captcha)
 
(12 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 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 <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 <source lang="python">$ git init</source>.
Line 361 ⟶ 363:
)</source>
 
Suppose a visitor goes to http:// 127.0.0.1:8000/questions/23/.
# which regex pattern is tripped?
# what function is then called?
Line 797 ⟶ 799:
Ah. There’s no template yet. Let’s make one!
 
* Make a qandabear/templates/qandabear directory where templates will live., Rightright 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.
 
Edit qandabear/templates/qandabear/index.html to contain:
 
<source lang="python">
{% if latest_qs %}
<ul>
Line 857 ⟶ 863:
* 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="htmlhtml4strict"><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/]
Line 891 ⟶ 897:
 
= 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