URL pattern design, model design, view design, template design, form design.
Transition design, using transition diagrams.
This occurs very frequently in all Web applications. It is a way of displaying a list of items so that each list item is a link to the details of that item. The general pattern is that a URL of the form /items/ is handled by a view (item_list()) that renders an HTML template of the form:
<ul>
...
<li><a href="/items/n/">Item n</a></li>
...
</ul>
Of course, you would never actually write /items/n/ in a template, you would write {{ item.get_absolute_url }} or {% url item_detail item.id %} or something similar instead. Similarly, Item n would be written {{ item.name }} or something similar.
Then, the URL pattern /items/n/ is handled by a view (item_detail()) that renders an HTML template to display the detail of the item (with id) n.
See my tutorial solution (~s352978/pub/tutorial.zip) for an example.
See the discussion group project (~s352978/pub/groups.zip, not yet available) for another example, one that uses the list-detail pattern twice.
This pattern is concerned with allowing (authorised) users, not admins, to create, retrieve, update and delete instances of some class.
A general principle is the following:
Every view that successfully updates the database must terminate by redirecting to a page that shows the effect of the update.
Otherwise, refreshing the resulting page may result in redoing the database update. (This called the reload-redo problem.)
One example of how to create instances is in the guestbook
example. This example uses one URL /messages/ to display the
add-new-message form (and other stuff) and a second URL
/messages/add/ to update the database from the posted form
data. (Note that forms used to update the database must use method "post",
never method "get".)
A common alternative is to use a single URL pattern such as .../items/add is used to call a view to either display the add-new-item form (alone) or update the database from the posted form data.
def add(request):
if request.method == "GET":
# Display add-new-item form
return render(request, 'add.html', {"form": ItemForm()})
else:
# Process posted form data
form = ItemForm(request.POST)
if form.is_valid():
# Add new item to database
form.save()
return redirect(reverse('item_list'))
else:
# Redisplay add-item form with error messages
return render(request, 'add.html', {"form": form})
Normally, after adding a new item, it is preferable to render the page that displays details of the newly-added item (so the user can check the addition was performed successfully). In this case, we need to redirect as follows:
item = form.save()
return redirect(reverse('item_detail', args=[item.id]))
To update an instance, it's always better to use the second alternative above. Use a URL pattern such as .../items/n/update/ to call a view to either display the update-item form or update the database from the posted form data.
def update(request, item_id):
item = get_object_or_404(Item, pk=item_id)
if request.method == "GET":
# Display update-item form
form = ItemForm(instance=item)
return render(request, 'update.html', {"form": form})
else:
# Process posted form data
form = ItemForm(request.POST, instance=item)
if form.is_valid():
# Update item in database
form.save()
return redirect(reverse('item_detail', args=[item_id]))
else:
# Redisplay update-item form with error messages
return render(request, 'update.html', {"form": form})
To delete an item (with a given id) is straightforward and left as an exercise.
To retrieve those items that match given criteria deserves a separate section.
See Making queries in the documentation.
The basic idea is to use a query set:
from django.db.models import Q
def search(request):
# Get the query string from the query form
query = request.GET.get('query','')
if query:
# Return all items that match the query
qset = (
Q(summary__icontains=query) |
Q(description__icontains=query)
)
item_list = Item.objects.filter(qset).distinct().order_by('-pub_date')
else:
# Return all items
item_list = Item.objects.all().order_by('-pub_date')
return render(request, "results.html",
{"item_list": item_list, "query": query})
Here, I've used a separate results.html template, but it's
much better to use the same template for both browsing and displaying
query results. You just have to provide some extra context to distinguish
the two cases and to identify the query. (Every page that displays the
results of a search should also display the query used in the
search.)
Pagination is the process of displaying a long list of results (using the list-detail pattern) one page at a time, with navigation links between pages.
See Pagination in the documentation.
See the guestbook with queries project (~s352978/pub/guestbook_queries.zip on dwarf) which illustrates how queries can be expressed, how queries are processed, and how result lists (and answers to queries) are paginated.
Uses the Python datetime module. See datetime in the Python documentation.
The main classes are date, time and datetime. The main constructors are date(year, month, day), datetime(year, month, day, hours, minutes, seconds), today() (returns a date) and now() (returns a datetime). (There doesn't seem to be a convenient input function analogous to PHP's function strtodate().) The main output function is strftime().
In Django models, you can set a field to store the time the object was created or the time the object was last updated as follows:
time_created = models.DateTimeField(auto_now_add=True)
time_updated = models.DateTimeField(auto_now=True)
In Django templates, you can use the filter date as described in the templates documentation to format date values, e.g., if value is a datetime object, {{ value|date:"D d M Y" }} could display as 'Wed 09 Jan 2008'.
Generic views are system-provided views for common patterns such as the list-detail pattern. They allow programmers to omit views they would otherwise have to write.
See Class-based generic views in the documentation and part 4 of the tutorial.
There are several main classes of generic views, including:
We describe list-detail views as an example: Classes DetailView and ListVieware defined in the module django.views.generic. Each class contains a view, as_view, that renders a specific instance (or a list of instances) of a given class, using a given template, which refers to the instance (or list of instances). These details are normally specified in a URLconf file. The point is that there is no need to write your own views. So, in the tutorial example, the URLconf file urls.py in the pollsapplication contains the following:
from django.conf.urls.defaults import patterns, url
from django.views.generic import DetailView, ListView
from polls.models import Poll
urlpatterns = patterns('',
url(r'^$',
ListView.as_view(queryset=Poll.objects.order_by('-pub_date')[:5],
context_object_name='latest_poll_list',
template_name='polls/index.html'),
name='index'),
url(r'^(?P\d+)/$',
DetailView.as_view(model=Poll,
template_name='polls/detail.html'),
# by default, the context object name is "poll"
name='detail'),
url(r'^(?P\d+)/results/$',
DetailView.as_view(model=Poll,
template_name='polls/results.html'),
name='results'),
url(r'^(?P\d+)/vote/$', 'polls.views.vote', name='vote'),
)
This file comes from the alternative version of the tutorial project (~s352978/pub/tutorial_generic.zip).
As in this example, it's important to make templates more readable, by giving to give the instance (or list) variable in templates an application-dependent name instead of the default name object (or object_list).
(This section needs to be checked after PIL is installed on dwarf to see if anything has changed in Django 1.3.)
See file uploads and image fields in the documentation. If you are using images on your own computer, you need to install the Python Imaging Library (PIL).
In the media directory, create a world-writable subdirectory images.
In the Item model containing the image:
image = models.ImageField(upload_to='images', blank=True, height_field='height', width_field='width') height = models.IntegerField(blank=True, null=True) width = models.IntegerField(blank=True, null=True)
In forms.py:
class ItemForm(ModelForm):
class Meta:
model = Item
exclude = ['width','height']
This prevents the width and height attributes being shown in the form derived from Item.
In the view to handle posted form data, including an image file:
if request.method == "POST":
form = ItemForm(request.POST, request.FILES)
if form.is_valid():
item = form.save()
...
In the template to display an item's details:
{% if item.image %}
<img alt="{{ item.image.name }}"
src="{{ item.image.url }}"
width="{{ item.width }}"
height="{{ item.height }}" />
{% endif %}
In practice, we also need to be able to resize images, create thumbnail images, etc. It should be possible to do this using a save() method in the class definition.
See the guestbook with images project (~s352978/pub/guestbook_images.zip) on grue for an example.
See also File management and File handling reference in the documentation.