Les vues (ou view en angalis) sont la deuxième notion du concept MVC (Modèle Vue Controleur).
A quoi sert une vue?
Les vues créent un contexte, intègre un template (ou gabarit ) puis retournent un objet de type HttpResponse .
Comment fonctionne une vue?
Tout d'abord votre serveur web reçoit une requête avec comme contenu: une adresse, un entête HTTP , divers données fournit par GET , POST , etc. Ensuite le serveur web demande à Django ce qu'il doit faire avec cette rêquete. L'adresse est comparée dans une sorte de table de routage ( url.py ) puis Django exécute la fonction associée au pattern de l'adresse . Cette fonction est donc une vue , puisqu'elle reçoit un contexte et renvoit un objet HttpResponse .
Passons à la pratique
Reprenons l'exemple du chapitre sur les applications Django:
eboutique/url.py
#!/usr/bin/python # coding: utf-8 from django.conf.urls import patterns, include, url from django.contrib import admin admin.autodiscover() urlpatterns = patterns('', # Examples: # url(r'^$', 'eboutique.views.home', name='home'), # url(r'^blog/', include('blog.urls')), url(r'^admin/', include(admin.site.urls)), url(r'^$', 'backoffice.views.home', name='home'), )
backoffice/views.py
#!/usr/bin/python # coding: utf-8 from django.shortcuts import render from django.http import HttpResponse # Create your views here. def home(request): return HttpResponse("Bonjour monde!")
résultat:
Première chose que l'on remarque c'est que la vue home dans notre fichier view.py n'est en réalité qu'une simple fonction qui récupère un objet request et qui retourne un objet HttpReponse
L'objet request c'est quoi?
En anglais request signifie "demande / sollicitation", ce sont donc des informations envoyées par un client et celui-ci attend une réponse en fonction des informations qu'il a envoyé.
Qui a-t-il dans un objet request?
Pour cela, rien de plus simple, ajouter la fonction dir dans votre vue:
backoffice/views.py
def home(request): print dir(request) return HttpResponse("Bonjour monde!")
résultat:
['COOKIES', 'FILES', 'GET', 'META', 'POST', 'REQUEST', '__class__', '__delattr__', '__dict__', '__doc__', '__format__', '__getattribute__', '__hash__', '__init__', '__iter__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', '_cookies', '_encoding', '_get_cookies', '_get_files', '_get_get', '_get_post', '_get_request', '_initialize_handlers', '_is_secure', '_load_post_and_files', '_mark_post_parse_error', '_messages', '_parse_content_type', '_post_parse_error', '_read_started', '_set_cookies', '_set_get', '_set_post', '_stream', '_upload_handlers', 'body', 'build_absolute_uri', 'csrf_processing_done', 'encoding', 'environ', 'get_full_path', 'get_host', 'get_signed_cookie', 'is_ajax', 'is_secure', 'method', 'parse_file_upload', 'path', 'path_info', 'read', 'readline', 'readlines', 'resolver_match', 'session', 'upload_handlers', 'user', 'xreadlines']
Pour voir les données associées à l'objet request:
backoffice/views.py
def home(request): print request.__dict__ return HttpResponse("Bonjour monde!")
Je ne peux pas copier coller le résultat -il y en a trop- je vous invite donc à effectuer cette manipulation sur votre machine. Mais vous verrez qu'il existe des données sur tout comme l' adresse IP du client , le navigateur utilisé, les données de session , le port utilisé, le language , le csrftoken , les données GET et POST , etc.
Récupérer GET et POST
Bon passons aux choses sérieuses. Comment lire les données GET et POST envoyées par le client?
backoffice/views.py
def home(request): string = request.GET['name'] return HttpResponse("Bonjour %s!" % string)
Réponse http
Une réponse où aucune erreur n'est à signaler possède le code 200 , vous pouvez le vérifier sur votre navigateur en lisant l'entête de la réponse:
Il est possible de retourner d'autres types de réponse, comme la fameuse 404 indiquant que le document est introuvable:
backoffice/views.py
from django.http import HttpResponse, HttpResponseNotFound def home(request): return HttpResponseNotFound("Erreur fichier introuvable")
Il existe d'autres réponses HTTP:
les réponses HTTP possibles
from django.http import * ------------------------------------ HttpResponse → 200 HttpResponseRedirect → 302 HttpResponsePermanentRedirect → 301 HttpResponseNotModified → 304 HttpResponseBadRequest → 400 HttpResponseNotFound → 404 HttpResponseForbidden → 403 HttpResponseNotAllowed → 405 HttpResponseGone → 410 HttpResponseServerError → 500
Http404 exception
Si vous voulez relever une erreur 404 standard, vous pouvez lever une exception:
backoffice/views.py
from django.http import HttpResponse, Http404 def home(request): if request.GET and request.GET["test"]: raise Http404 return HttpResponse("Bonjour Monde!")
L'erreur suivante devrait apparaître si vous ajouter une variable GET test :
Récupérer l'id dans l'url
Comme nous l'avons vu dans les chapitres précédents, l'url passe par une sorte de table de routage.
L'url peut contenir un id; comment récupérer cette information?
eboutique/urls.py
#!/usr/bin/python # coding: utf-8 from django.conf.urls import patterns, include, url from django.contrib import admin admin.autodiscover() urlpatterns = patterns('', # Examples: # url(r'^$', 'eboutique.views.home', name='home'), # url(r'^blog/', include('blog.urls')), url(r'^admin/', include(admin.site.urls)), url(r'^$', 'backoffice.views.home', name='home'), url(r'^commentaire/(?P<pk>\d+)/$', 'backoffice.views.commentaire'), )
Et ajoutons notre nouvelles vue:
backoffice/views.py
def commentaire(request, pk): return HttpResponse("id: {0}".format(pk, ) )
Résultat:
Nous avons utilisé le paramètre pk dans le fichier urls.py , mais vous pouvez choisir n'importe quel nom du moment qu'il est renseigné dans la vue.
Retourner un JSON
Si vous travaillez en Ajax il vous sera peu être utile de savoir retourner un JSON :
from django.http import JsonResponse def ma_vue(request): return return JsonResponse({'foo': 'bar'})
Templates
Comment exploiter les templates?
backoffice/views
from django.template.response import TemplateResponse def home(request): return TemplateResponse(request, 'home.html', {'name': 'olivier'})
Dans cet exemple, nous utilisons la fonction TemplateResponse qui envoit l'objet request, le nom du template et un dictionnaire (contexte). Vous pouvez mettre dans ce dictionnaire les données que vous voulez.
Et voici notre template :
backoffice/templates/home.html
Bonjour {{name}}
Les doubles crochets indiquent que name est une variable.
Les vues génériques
Très rapidement vous vous rendrez compte que vos vues se ressemblent toutes plus ou moins. Un peu comme dans la contrib Admin, finalement on voudra lister des modèles, puis pouvoir en créer, modifier et enfin supprimer.
Le concept de Django c'est de faciliter au maximum la conception d'un projet. Inutile de répéter du code, on se veut DRY et factoriser le code est aussi intéressant sur la performance du développeur que sur la maintenance.
TemplateView, la vue générique de base
Commençons par la vue générique de base: TemplateView . Cette vue se contente de renvoyer un fichier type static, pas de modèles, pas de requêtes en base de données.
eboutique/urls.py
from django.conf.urls import patterns, include, url from django.views.generic import * urlpatterns = patterns('', url(r'^$', TemplateView.as_view(template_name="home.html")), )
backoffice/templates/home.html
Bonjour Monde!
Dans cet exemple, le template home.html sera utilisé comme nous l'avons indiqué. Nul besoin d'instancier quoi que ce soit, la classe TemplateView ne nécessite que l'appel de la méthode as_view .
Les attributs de la classe TemplateView
content_type = None http_method_names = [u'get', u'post', u'put', u'patch', u'delete', u'head', u'options', u'trace'] response_class = <class 'django.template.response.TemplateResponse'> template_name = None
Les méthodes de la classe TemplateView
_allowed_methods(self) as_view(cls, **initkwargs) dispatch(self, request, *args, **kwargs) get(self, request, *args, **kwargs) get_context_data(self, **kwargs) get_template_names(self) http_method_not_allowed(self, request, *args, **kwargs) options(self, request, *args, **kwargs) render_to_response(self, context, **response_kwargs)
Vous pouvez dériver Templateview
pour mettre votre vue-classe dans la fichier views.py
eboutique/views.py
from django.views.generic import TemplateView class IndexView(TemplateView): template_name = "templates/index.html" def post(self, request, **kwargs): print "Action lors d'un post" return render(request, self.template_name)
eboutique/urls.py
from django.conf.urls import include, url urlpatterns = [ url(r'^$', IndexView.as_view()), ]
N'oubliez pas de renseigner tous les chemins vers vos templates dans le fichier de configuration de votre projet:
eboutique/settings.py
TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': [ '', 'test'], 'APP_DIRS': True, 'OPTIONS': { 'context_processors': [ 'django.template.context_processors.debug', 'django.template.context_processors.request', 'django.contrib.auth.context_processors.auth', 'django.contrib.messages.context_processors.messages', ], }, }, ]
ListeView
Cette vue a vocation à travailler sur une liste d'objets. Elle est l' une des vues génériques les plus utilisées
eboutique/urls.py
from django.conf.urls import patterns, include, url from django.views.generic import * from backoffice.models import * urlpatterns = patterns('', url(r'^$', ListView.as_view(model=Product)), )
Par défaut le template chargé sera dans notre cas nommé product_list.html
backoffice/product_liste.html
Bonjour Monde!
Vous pouvez bien évidemment choisir l'emplacement et le nom de votre template:
eboutique/urls.py
from django.conf.urls import patterns, include, url from django.views.generic import * from backoffice.models import * urlpatterns = patterns('', url(r'^$', ListView.as_view(model=Product, template_name="templates/product_list.html")), )
Vous pouvez surclasser la vue générique pour ajouter des options plus facilement:
eboutique/urls.py
from django.conf.urls import patterns, include, url from django.views.generic import * from backoffice.models import * class ProductsView(ListView): model = Product template_name = "template/product_list.html" urlpatterns = patterns('', url(r'^$', ProductsView.as_view()), )
Il nous manque plus que l'objet du contexte, c'est à dire nos produits:
class ProductsView(ListView): model = Product template_name = "template/product_list.html" context_object_name = "products"
et notre template:
{% for product in products %} {{product.id}} {% endfor %}
Il est possible de modifier la queryset qui liste les produits:
class ProductsView(ListView): model = Product template_name = "template/product_list.html" context_object_name = "products" queryset = Product.objects.filter(id__gt=400)
ou en passant par la méthode get_queryset :
class ProductsView(ListView): model = Product template_name = "template/product_list.html" context_object_name = "products" def get_queryset(self): return Product.objects.filter(id__gt=450)
Si vous voulez passer d'autres données, vous pouvez passer par la méthode :
class ProductsView(ListView): model = Product template_name = "template/product_list.html" context_object_name = "products" def get_context_data(self, **kwargs): context = super(ProductsView, self).get_context_data(**kwargs) context['name'] = "olivier" return context
Et dans notre template:
Bonjour {{name}}
Les attributs de la classe ListeView
allow_empty = True content_type = None context_object_name = None http_method_names = [u'get', u'post', u'put', u'patch', u'delete', u'head', u'options', u'trace'] model = None page_kwarg = 'page' paginate_by = None paginate_orphans = 0 paginator_class = <class 'django.core.paginator.Paginator'> queryset = None response_class = <class 'django.template.response.TemplateResponse'> template_name = None template_name_suffix = '_list'
Les méthodes de la classe ListeView :
dispatch(request, *args, **kwargs) → retourne un HttpResponse http_method_not_allowed(request, *args, **kwargs) → est exécuté si la méthode est interdite get_template_names(self) → retourne une liste de nom de templates get_queryset(self) → retourne la queryset get_context_object_name() → retourne l'objet en question get_context_data(self, **kwargs) → retourne le contexte render_to_response(context, **response_kwargs) → créer le contenu _allowed_methods(self) as_view(cls, **initkwargs) get(self, request, *args, **kwargs) get_allow_empty(self) get_paginate_by(self, queryset) get_paginate_orphans(self) get_paginator(self, queryset, per_page, orphans=0, allow_empty_first_page=True, **kwargs) options(self, request, *args, **kwargs) paginate_queryset(self, queryset, page_size)
DetailView
Tout est identique à ListView sauf qu'il s'agit que d'un seul item:
from django.conf.urls import patterns, include, url from django.views.generic import * from backoffice.models import * class ProductView(DetailView): model = Product context_object_name = "product" urlpatterns = patterns('', url(r'^product/(?P<pk>\d+)/$', ProductView.as_view()), )
et dans votre template:
{{product.id}} - {{product.name}}
Les arguments de la classe DetailView
content_type = None context_object_name = None http_method_names = [u'get', u'post', u'put', u'patch', u'delete', u'head', u'options', u'trace'] model = None pk_url_kwarg = 'pk' queryset = None response_class = <class 'django.template.response.TemplateResponse'> slug_field = 'slug' slug_url_kwarg = 'slug' template_name = None template_name_field = None template_name_suffix = '_detail'
Les méthodes de la classe DetailView
_allowed_methods(self) as_view(cls, **initkwargs) dispatch(self, request, *args, **kwargs) get(self, request, *args, **kwargs) get_allow_empty(self) get_context_data(self, **kwargs) get_context_object_name(self, object_list) get_paginate_by(self, queryset) get_paginate_orphans(self) get_paginator(self, queryset, per_page, orphans=0, allow_empty_first_page=True, **kwargs) get_queryset(self) get_template_names(self) http_method_not_allowed(self, request, *args, **kwargs) options(self, request, *args, **kwargs) paginate_queryset(self, queryset, page_size) render_to_response(self, context, **response_kwargs)
CreateView
CreateView est une vue générique qui vous aide à créer une page pour enregistrer un item.
eboutique/urls.py
from django.conf.urls import patterns, include, url from django.views.generic import * from backoffice.models import * urlpatterns = patterns('', url(r'^product/new/$', CreateView.as_view(model=Product)), )
Votre template:
<form action="" method="POST"> {% csrf_token %} {{form.as_ul}} <input type="submit" value="Créer"/> </form>
Résultat:
Les attributs de la classe CreateView :
content_type = None context_object_name = None fields = None form_class = None http_method_names = [u'get', u'post', u'put', u'patch', u'delete', u'head', u'options', u'trace'] initial = {} model = None pk_url_kwarg = 'pk' prefix = None queryset = None response_class = <class 'django.template.response.TemplateResponse'> slug_field = 'slug' slug_url_kwarg = 'slug' success_url = None template_name = None template_name_field = None template_name_suffix = '_form'
Les méthodes de la classe CreateView :
_allowed_methods(self) as_view(cls, **initkwargs) dispatch(self, request, *args, **kwargs) form_invalid(self, form) form_valid(self, form) get(self, request, *args, **kwargs) get_context_data(self, **kwargs) get_context_object_name(self, obj) get_form(self, form_class) get_form_class(self) get_form_kwargs(self) get_initial(self) get_object(self, queryset=None) get_prefix(self) get_queryset(self) get_slug_field(self) get_success_url(self) get_template_names(self) http_method_not_allowed(self, request, *args, **kwargs) options(self, request, *args, **kwargs) post(self, request, *args, **kwargs) put(self, *args, **kwargs) render_to_response(self, context, **response_kwargs)
UpdateView
Cette vue vous permet de modifier un item:
eboutique/urls.py
from django.conf.urls import patterns, include, url from django.views.generic import * from backoffice.models import * urlpatterns = patterns('', url(r'^product/upd/(?P<pk>\d+)/$', UpdateView.as_view(model=Product)), )
On reprend le même template que pour CreateView :
<form action="" method="POST"> {% csrf_token %} {{form.as_ul}} <input type="submit" value="Créer"/>
Résultat:
Les attributs de la classe UpdateView :
content_type = None context_object_name = None fields = None form_class = None http_method_names = [u'get', u'post', u'put', u'patch', u'delete', u'head', u'options', u'trace'] initial = {} model = None pk_url_kwarg = 'pk' prefix = None queryset = None response_class = <class 'django.template.response.TemplateResponse'> slug_field = 'slug' slug_url_kwarg = 'slug' success_url = None template_name = None template_name_field = None template_name_suffix = '_form'
Les méthodes de la classe UpdateView :
_allowed_methods(self) as_view(cls, **initkwargs) dispatch(self, request, *args, **kwargs) form_invalid(self, form) form_valid(self, form) get(self, request, *args, **kwargs) get_context_data(self, **kwargs) get_context_object_name(self, obj) get_form(self, form_class) get_form_class(self) get_form_kwargs(self) get_initial(self) get_object(self, queryset=None) get_prefix(self) get_queryset(self) get_slug_field(self) get_success_url(self) get_template_names(self) http_method_not_allowed(self, request, *args, **kwargs) options(self, request, *args, **kwargs) post(self, request, *args, **kwargs) put(self, *args, **kwargs) render_to_response(self, context, **response_kwargs)
DeleteView
Et enfin la vue qui propose de supprimer un item:
eboutique/urls.py
from django.conf.urls import patterns, include, url from django.views.generic import * from backoffice.models import * urlpatterns = patterns('', url(r'^product/delete/(?P<pk>\d+)/$', DeleteView.as_view(model=Product, success_url = "/products/")), )
Voici notre template:
<form action="" method="post">{% csrf_token %} <p>Supprimer "{{ object }}"?</p> <input type="submit" value="Confirmer" /> </form>
Et le résultat:
Les attributs de la classe DeleteView :
content_type = None context_object_name = None http_method_names = [u'get', u'post', u'put', u'patch', u'delete', u'head', u'options', u'trace'] model = None pk_url_kwarg = 'pk' queryset = None response_class = <class 'django.template.response.TemplateResponse'> slug_field = 'slug' slug_url_kwarg = 'slug' success_url = None template_name = None template_name_field = None template_name_suffix = '_confirm_delete'
Les méthodes de la classe DeleteView :
_allowed_methods(self) as_view(cls, **initkwargs) delete(self, request, *args, **kwargs) dispatch(self, request, *args, **kwargs) get(self, request, *args, **kwargs) get_context_data(self, **kwargs) get_context_object_name(self, obj) get_object(self, queryset=None) get_queryset(self) get_slug_field(self) get_success_url(self) get_template_names(self): http_method_not_allowed(self, request, *args, **kwargs) options(self, request, *args, **kwargs) post(self, request, *args, **kwargs) render_to_response(self, context, **response_kwargs)