Additional Generic Views§

Edit Views§

In addition to creating Contacts, we’ll of course want to edit them. As with the List and Create views, Django has a generic view we can use as a starting point.

from django.views.generic import UpdateView
...
class UpdateContactView(UpdateView):

    model = Contact
    template_name = 'edit_contact.html'

    def get_success_url(self):
        return reverse('contacts-list')
  • we can re-use the same template
  • but how does it know which contact to load?
  • we need to either: provide a pk/slug, or override get_object().
  • we’ll provide pk in the URL
    url(r'^edit/(?P<pk>\d+)/$', contacts.views.UpdateContactView.as_view(),
        name='contacts-edit',),

We’ll update the contact list to include an edit link next to each contact.

{% extends "base.html" %}

{% block content %}
<h1>Contacts</h1>

<ul>
  {% for contact in object_list %}
    <li class="contact">{{ contact }}
      (<a href="{% url "contacts-edit" pk=contact.id %}">edit</a>)
    </li>
  {% endfor %}
</ul>

<a href="{% url "contacts-new" %}">add contact</a>
{% endblock %}

Note the use of pk=contact.id in the {% url %} tag to specify the arguments to fill into the URL pattern.

If you run the server now, you’ll see an edit link. Go ahead and click it, and try to make a change. You’ll notice that instead of editing the existing record, it creates a new one. Sad face.

If we look at the source of the edit HTML, we can easily see the reason: the form targets /new, not our edit URL. To fix this – and still allow re-using the template – we’re going to add some information to the template context.

The template context is the information available to a template when it’s rendered. This is a combination of information you provide in your view – either directly or indirectly – and information added by context processors, such as the location for static media and current locale. In order to use the same template for add and edit, we’ll add information about where the form should redirect to the context.

class CreateContactView(CreateView):

    model = Contact
    template_name = 'edit_contact.html'

    def get_success_url(self):
        return reverse('contacts-list')

    def get_context_data(self, **kwargs):

        context = super(CreateContactView, self).get_context_data(**kwargs)
        context['action'] = reverse('contacts-new')

        return context
class UpdateContactView(UpdateView):

    model = Contact
    template_name = 'edit_contact.html'

    def get_success_url(self):
        return reverse('contacts-list')

    def get_context_data(self, **kwargs):

        context = super(UpdateContactView, self).get_context_data(**kwargs)
        context['action'] = reverse('contacts-edit',
                                    kwargs={'pk': self.get_object().id})

        return context

We also update the template to use that value for the action and change the title based on whether or not we’ve previously saved.

{% if contact.id %}
<h1>Edit Contact</h1>
{% else %}
<h1>Add Contact</h1>
{% endif %}

<form action="{{ action }}" method="POST">

You may wonder where the contact value in the contact comes from: the class based views that wrap a single object (those that take a primary key or slug) expose that to the context in two different ways: as a variable named object, and as a variable named after the model class. The latter often makes your templates easier to read and understand later. You can customize this name by overriding get_context_object_name on your view.

Deleting Contacts§

The final view for our basic set of views is delete. The generic deletion view is very similar to the edit view: it wraps a single object and requires that you provide a URL to redirect to on success. When it processes a HTTP GET request, it displays a confirmation page, and when it receives an HTTP DELETE or POST, it deletes the object and redirects to the success URL.

We add the view definition to views.py:

from django.views.generic import DeleteView
...
class DeleteContactView(DeleteView):

    model = Contact
    template_name = 'delete_contact.html'

    def get_success_url(self):
        return reverse('contacts-list')

And create the template, delete_contact.html, in our templates directory.

{% extends "base.html" %}

{% block content %}

<h1>Delete Contact</h1>

<p>Are you sure you want to delete the contact {{ contact }}?</p>

<form action="{% url "contacts-delete" pk=contact.id %}" method="POST">
  {% csrf_token %}

  <input type="submit" value="Yes, delete." />
  <a href="{% url "contacts-list" %}">No, cancel.</a>
</form>

{% endblock %}

Of course we need to add this to the URL definitions:

    url(r'^delete/(?P<pk>\d+)/$', contacts.views.DeleteContactView.as_view(),
        name='contacts-delete',),

And we’ll add the link to delete to the edit page.

{% if contact.id %}
<a href="{% url "contacts-delete" pk=contact.id %}">Delete</a>
{% endif %}

Detail View§

Finally, let’s go ahead and add a detail view for our Contacts. This will show the details of the Contact: not much right now, but we’ll build on this shortly. Django includes a generic DetailView: think of it as the single serving ListView.

from django.views.generic import DetailView
...
class ContactView(DetailView):

    model = Contact
    template_name = 'contact.html'

Again, the template is pretty straight forward; we create contact.html in the templates directory.

{% extends "base.html" %}

{% block content %}

<h1>{{ contact }}</h1>

<p>Email: {{ contact.email }}</p>

{% endblock %}

And add the URL mapping:

    url(r'^(?P<pk>\d+)/$', contacts.views.ContactView.as_view(),
        name='contacts-view',),

We’re also going to add a method to our Contact model, get_absolute_url. get_absolute_url is a Django convention for obtaining the URL of a single model instance. In this case it’s just going to be a call to reverse, but by providing this method, our model will play nicely with other parts of Django.

class Contact(models.Model):
...
    def get_absolute_url(self):

        return reverse('contacts-view', kwargs={'pk': self.id})

And we’ll add the link to the contact from the contact list.

  {% for contact in object_list %}
    <li class="contact">
      <a href="{{ contact.get_absolute_url }}">{{ contact }}</a>
      (<a href="{% url "contacts-edit" pk=contact.id %}">edit</a>)
    </li>
  {% endfor %}