"
This article is part of in the series
Last Updated: Thursday 12th December 2013

In the previous article, we wrote an index view to show a list of posts that are created less than two days ago and a detail view to show the detailed content of a post. In this article, we're going to write a view that allows the user to upload a post and improve our existing index and detail with Django's built-in generic views.

The Upload View

Similar to what we have done in the previous article, in order to write the upload view we need to write an URL pattern in myblog/urls.py first:

[python]
from django.conf.urls import patterns, include, url

# Uncomment the next two lines to enable the admin:
from django.contrib import admin
admin.autodiscover()

urlpatterns = patterns('',
url(r'^$', 'myblog.views.index', name='index'),
url(r'^post/(?P<post_id>\d+)/detail.html$',
'myblog.views.post_detail', name='post_detail'),
# Link the view myblog.views.post_upload to URL post/upload.html
url(r'^post/upload.html$', 'myblog.views.post_upload', name='post_upload'),

# Uncomment the admin/doc line below to enable admin documentation:
url(r'^admin/doc/', include('django.contrib.admindocs.urls')),

# Uncomment the next line to enable the admin:
url(r'^admin/', include(admin.site.urls)),
)
[/python]

The previous URL pattern specifies that myblog.views.post_upload should process any request towards the URL post/upload.html. Now let's write the actual view:

[python]
# Add the following import statements to the top of myblog/views.py
from datetime import datetime
from django.core.urlresolvers import reverse
from django.http import HttpResponseRedirect

# Add the following function to the end of myblog/views.py
def post_upload(request):
if request.method == 'GET':
return render(request, 'post/upload.html', {})
elif request.method == 'POST':
post = m.Post.objects.create(content=request.POST['content'],
created_at=datetime.utcnow())
# No need to call post.save() at this point -- it's already saved.
return HttpResponseRedirect(reverse('post_detail', kwargs={'post_id': post.id}))
[/python]

post_upload handles two possible cases. If the request is a GET request, post_upload simply returns a HTML page. If it's a POST request, post_upload() creates a new Post object in the database and returns a HTML page with the new Post object in the context.

Next, we're going to write the template myblog/templates/post/upload.html for our view. This template is going to present an HTML form to the user.

[html]
<form action="{% url 'post_upload' %}" method="post">
{% csrf_token %}
<label for="content"></label>
<input type="text" name="content" id="content" />
<input type="submit" />
</form>
[/html]

The previous template file uses Django's template language to specify the content of a page. Whenever the template is rendered along with a context by Django, the code between {% and %} is processed by the template renderer and anything outside of the {% %} tags gets returned as normal string. In order to see the actual output of the rendered template, you can inspect its source code in a browser:

<form action="/post/upload.html" method="post" _lpchecked="1">
  <input type="hidden" name="csrfmiddlewaretoken" value="AGGNpA4NcmbuPachX2zrksQXg4PQ7NW0">
  <label for="content"></label>
  <input type="text" name="content" id="content">
  <input type="submit">
</form>

Now you can start the server with the python manage.py runserver shell command, and access the URL http://127.0.0.1:8000/post/upload.html:

Upload a Post

Now you can type the content of the new Post object:

Django Type Content of New Post

And click "submit" that will redirect you to your new post:

Django New Post

Generic Views

So far, we wrote three simple views in our Django application: an index view that shows a list of Posts; a post_detail view that shows a page about the details of a Post object; and a post_upload view that allows a user to upload a Post onto the database. These views represent a common case of web development: getting data from the database, rendering the data using a template and returning the rendered template as a HTTP response back to the user. Since these kinds of views are so common, Django provides a suite of generic views that help reduce the boilerplate code when writing similar views.

First, we will modify the URL configurations in myblog/urls.py:

[python]
from django.conf.urls import patterns, include, url

# Uncomment the next two lines to enable the admin:
from django.contrib import admin
admin.autodiscover()

from myblog import views

urlpatterns = patterns('',
# Uncomment the admin/doc line below to enable admin documentation:
url(r'^admin/doc/', include('django.contrib.admindocs.urls')),

# Uncomment the next line to enable the admin:
url(r'^admin/', include(admin.site.urls)),

# Use generic views to replace the previous post_index and post_detail viwes
url(r'^$', views.PostIndexView.as_view(), name='post_index'),
url(r'^post/(?P<pk>\d+)/detail.html$',
views.PostDetailView.as_view(),
name='post_detail'),
url(r'^post/upload.html', views.post_upload, name='post_upload'),
)
[/python]

Notice that post_detail accepts one argument pk which is required by generic.DetailView to fetch the Post object whose primary key is the passed-in argument.

Then, we modify myblog/views.py:

[python]
from datetime import datetime, timedelta
from django.core.urlresolvers import reverse
from django.http import HttpResponseRedirect
from django.views import generic

from myblog import models as m

def post_upload(request):
if request.method == 'GET':
return render(request, 'post/upload.html', {})
elif request.method == 'POST':
post = m.Post.objects.create(content=request.POST['content'],
created_at=datetime.utcnow())
# No need to call post.save() at this point -- it's already saved.
return HttpResponseRedirect(reverse('post_detail', kwargs={'post_id': post.id}))

class PostIndexView(generic.ListView):
template_name = 'index.html'
context_object_name = 'post_list'
model = m.Post

def get_queryset(self):
''' Return posts that are created less than two days ago. '''
two_days_ago = datetime.utcnow() - timedelta(days=2)
return m.Post.objects.filter(created_at__gt=two_days_ago).all()

class PostDetailView(generic.DetailView):
template_name = 'post/detail.html'
model = m.Post
[/python]

Notice how much more cleaner the code is, compared to the previous version. generic.ListView provides the concept of displaying a list of objects, while generic.DetailView cares about "displaying one specific object". Every generic view needs to know which model it's depending on, so the model attribute is required in every generic view subclass.

Because generic.ListView and generic.DetailView accept sensible defaults such as context_object_name to be used in the template and model to be queried against the database, our code becomes shorter and more straightforward instead of caring too much about boilerplate code such as loading and rendering templates. The generic views are a bunch of Python classes that are meant to sub-classed to suit developers' different needs.

Now you can run the server again to see the exact same output as our previous implementation.

Summary and Suggestions

In this article, we learned how to write a view to handle the POST request, as well as how to use generic views to clean up and improve our views' code quality. Django is a strong proponent of the Don't Repeat Yourself (DRY) principle which enforces that every piece of information should have a single, unambiguous and authoritative representation within a system. In Django's case, it means that there should be only one function, one class or one module in charge of one specific feature. For example, each of the generic views handle only one type of view and encapsulates the functionality essential to that specific type of view, so that the subclasses can re-use the core functionality everywhere. Django's own implementation and API closely follow the DRY principle to minimize duplication and unnecessary boilerplate code. We should also following the same principle and re-use Django's generic views whenever we can. When you face a problem where sub-classing a generic view is not enough, I recommend you to read Django's source code regarding how to implement the generic views and try to write re-usable views instead of mindlessly writing boilerplate code.

About The Author