TP : Gestion d’une liste de tâches - Views et URLs (3/3)

date:2012-04-30 16:34
tags:django, python
category:Django
author:Rémy Hubscher

Récapitulatif

Nous avons vu dans le chapitre précédent comment créer les views et afficher notre liste de tâches.

Je vous rappelle que, dans un premier temps, nous avons une unique liste :

  1. Nous voulons ajouter des tâches dans la liste
  2. Nous voulons pouvoir vider la liste en fin de journée
  3. Nous voulons dire que la tâche est réalisée (la barrer)
  4. Nous voulons pouvoir marquer toutes les tâches comme terminée
  5. Nous voulons pouvoir supprimer une tâche

Nous allons maintenant en faire en sorte de pouvoir ajouter une tâche.

Ajouter une tâche

Nous allons mettre en place un form pour éditer notre modèle.

Créer un fichier forms.py et y ajouter ces informations.

forms.py

# -*- coding: utf-8 -*-
from django import forms
from todo.models import Task

class TaskForm(forms.ModelForm):
    class Meta:
        model = Task
        exclude = ('is_resolved',)

On va ensuite gérer ce formulaire dans une nouvelle vue

views.py

# -*- coding: utf-8 -*-

    # [...] On rajoute au document le code suivant

from django.views.generic import ListView, CreateView
from django.core.urlresolvers import reverse_lazy
from django.http import HttpResponseRedirect
    from todo.forms import TaskForm

class TaskCreateView(CreateView):
    form_class = TaskForm
    success_url  = reverse_lazy('tasks-list')

    def form_invalid(self, form):
        # Attention les erreurs du form ne seront pas affichées
        return HttpResponseRedirect(self.success_url)

Pour l’instant la seule erreur c’est que le champ soit vide. Dans notre cas, la tâche ne sera pas sauvegardée si on renvoit un content vide.

urls.py

On commence à avoir plusieurs urls pour notre module todo. Comme on souhaite qu’il soit réutilisable, on va mettre toutes les urls concernant les todos dans le fichier todo/urls.py

# -*- coding: utf-8 -*-
from django.conf.urls import patterns, include, url

from todo.views import *

urlpatterns = patterns('',
    url(r'^$', TasksView.as_view(), name='tasks-list'),

    url(r'^add/$', TaskCreateView.as_view(), name='task-create'),
)

Dans le fichier url de notre projet tuto_django/urls.py, on va inclure les urls des todo

# -*- coding: utf-8 -*-
from django.conf.urls import patterns, include, url
from django.views.generic import RedirectView

from django.contrib import admin
admin.autodiscover()

urlpatterns = patterns('',
    url(r'^$', RedirectView.as_view(url='todo/')),
    url(r'^admin/', include(admin.site.urls)),
    url(r'^todo/', include('todo.urls')),
)

On demande aussi de rediriger notre page d’accueil vers todo/

tasks-list.html

<header id="header">
        <h1>{% trans 'Todos' %}</h1>
        <form action="{% url task-create %}" method="post">
            {% csrf_token %}
            <input id="new-todo" placeholder="{% trans 'What needs to be done?' %}" name="content" autofocus>
        </form>
</header>

Le csrf_token est une sécurité de Django pour éviter qu’un script maveillant envoi notre formulaire sans le charger au préalable.

Le navigateur sait que lorsqu’on appuie sur ENTER il doit envoyer le formulaire.

Nous pouvons maintenant ajouter des tâches.

Supprimer les tâches terminées

views.py

TASK_LIST_URL = reverse_lazy('tasks-list')

def clear_resolved_tasks(request):
    if request.method == 'POST':
        # Modify an object in POST only
        Task.objects.filter(is_resolved=True).delete()
    return HttpResponseRedirect(TASK_LIST_URL)

Une toute petite fonction qui va récupérer les tâches terminées et les supprimer. On vérifie juste que la fonction a bien été appelé en POST car on modifie des données.

urls.py

# -*- coding: utf-8 -*-
from django.conf.urls import patterns, include, url

from todo.views import *

urlpatterns = patterns('',
    url(r'^$', TasksView.as_view(), name='tasks-list'),
    url(r'^clear/$', clear_resolved_tasks, name='tasks-clear'),
    url(r'^add/$', TaskCreateView.as_view(), name='task-create'),
)

tasks-list.html

<footer id="footer">
           <form action="{% url tasks-clear %}" method="post">
                   {% csrf_token %}
                   <button id="clear-completed" onclick="this.parentNode.submit();">{% trans 'Clear completed' %}</button>
           </form>
</footer>

Marquer une tâche comme terminée

views.py

from django.shortcuts import get_object_or_404

def toggle_task(request, task_id):
    if request.method == 'POST':
        # Modify an object in POST only
        task = get_object_or_404(Task, pk=task_id)

        task.is_resolved = not task.is_resolved
        task.save()

    return HttpResponseRedirect(TASK_LIST_URL)

urls.py

# -*- coding: utf-8 -*-
from django.conf.urls import patterns, include, url

from todo.views import *

urlpatterns = patterns('',
    url(r'^$', TasksView.as_view(), name='tasks-list'),
    url(r'^clear/$', clear_resolved_tasks, name='tasks-clear'),

    url(r'^add/$', TaskCreateView.as_view(), name='task-create'),
    url(r'^toggle/(?P<task_id>\d+)/$', toggle_task, name='task-toggle'),
)

tasks-list.html

<form method="post" action="{% url task-toggle task.id %}">
        {% csrf_token %}
        <input class="toggle" type="checkbox"{% if task.is_resolved %} checked="checked"{% endif %} onclick="this.parentNode.submit();">
</form>

Marquer toutes les tâches comme terminées

views.py

def toggle_tasks(request):
    if request.method == 'POST':
        # Modify an object in POST only
        try:
            task = Task.objects.all()[0]
        except IndexError:
            task = None

        if task is not None:
            status = not task.is_resolved
            Task.objects.all().update(is_resolved=status)

    return HttpResponseRedirect(TASK_LIST_URL)

urls.py

# -*- coding: utf-8 -*-
from django.conf.urls import patterns, include, url

from todo.views import *

urlpatterns = patterns('',
    url(r'^$', TasksView.as_view(), name='tasks-list'),
    url(r'^clear/$', clear_resolved_tasks, name='tasks-clear'),
    url(r'^toggle/$', toggle_tasks, name='tasks-toggle'),

    url(r'^add/$', TaskCreateView.as_view(), name='task-create'),
    url(r'^toggle/(?P<task_id>\d+)/$', toggle_task, name='task-toggle'),
)

tasks-list.html

<form method="post" action="{% url tasks-toggle %}">
        {% csrf_token %}
        <input id="toggle-all" type="checkbox" onclick="this.parentNode.submit();">
</form>

Supprimer une tâche

views.py

from django.views.generic import ListView, CreateView, DeleteView

class TaskDeleteView(DeleteView):
    model = Task
    success_url = TASK_LIST_URL

urls.py

# -*- coding: utf-8 -*-
from django.conf.urls import patterns, include, url

from todo.views import *

urlpatterns = patterns('',
    url(r'^$', TasksView.as_view(), name='tasks-list'),
    url(r'^clear/$', clear_resolved_tasks, name='tasks-clear'),
    url(r'^toggle/$', toggle_tasks, name='tasks-toggle'),

    url(r'^add/$', TaskCreateView.as_view(), name='task-create'),
    url(r'^toggle/(?P<task_id>\d+)/$', toggle_task, name='task-toggle'),
    url(r'^delete/(?P<pk>\d+)/$', TaskDeleteView.as_view(), name='task-delete'),
)

tasks-list.html

<form method="post" action="{% url task-delete task.id %}">
        {% csrf_token %}
        <button class="destroy" onclick="this.parentNode.submit();"></button>
</form>

Conclusion

C’est extrèmement simple de faire une application web avec Django, la plupart des briques sont là et il ne reste plus qu’à les utiliser.

Le code complet est disponible ici : http://bitbucket.org/natim/django-story/src/tip/demos/tuto_django

Vous pouvez tester cette application ici : http://django-story.ionyse.com/demos/todo/