<< précédentsuivant >>

Chapitre 8 : Vues et URLconfs avancés

Au chapitre 3, nous avons expliqué les bases des fonctions view et des URLconfs sous Django. Ce chapitre aborde plus en détails les fonctionnalités avancées de ces deux composants du framework.

Astuces dans les URLconfs

Il n’y a rien de « particulier » au sujet des URLconfs — comme pour tout le reste avec Django, il s’agit juste de code Python. Vous pouvez tirer parti de cela de plusieurs façons, comme décrit dans le chapitre qui suit.

Rationnaliser les imports de fonction

Considérez cet URLconf, construit sur l’exemple du chapitre 3 :

from django.conf.urls.defaults import *
from mysite.views import current_datetime, hours_ahead, hours_behind,
now_in_chicago, now_in_london

urlpatterns = patterns('',
    (r'^now/$', current_datetime),
    (r'^now/plus(\d{1,2})hours/$', hours_ahead),
    (r'^now/minus(\d{1,2})hours/$', hours_behind),
    (r'^now/in_chicago/$', now_in_chicago),
    (r'^now/in_london/$', now_in_london),
)

Comme enseigné au chapitre 3, chaque entrée de l’URLconf inclut sa fonction view associée, passée directement en tant qu’objet fonction. Cela signifie qu’il est nécessaire d’importer les fonctions views au début de notre module.

Mais lorsque une application Django augmente en complexité, son URLconf grandit aussi, et garder ces imports peut devenir fastidieux à gérer. (Pour chaque nouvelle vue, vous devez vous souvenir de l’importer, et l’instruction d’importation tend à devenir exagérément longue si vous utilisez cette approche). Il est possible d’éviter cette contrainte en important le module views lui même. Cet exemple d’URLconf est équivalent au précédent :

from django.conf.urls.defaults import *
from mysite import views

urlpatterns = patterns('',
    (r'^now/$', views.current_datetime),
    (r'^now/plus(\d{1,2})hours/$', views.hours_ahead),
    (r'^now/minus(\d{1,2})hours/$', views.hours_behind),
    (r'^now/in_chicago/$', views.now_in_chicago),
    (r'^now/in_london/$', views.now_in_london),
)

Django offre une autre manière de spécifier la vue pour un patron particulier de l’URLconf : vous pouvez préciser une chaîne de caractère contenant le nom du module et le nom de la fonction plutôt que l’objet fonction lui même. En continuant avec l’exemple actuel :

from django.conf.urls.defaults import *

urlpatterns = patterns('',
    (r'^now/$', 'mysite.views.current_datetime'),
    (r'^now/plus(\d{1,2})hours/$', 'mysite.views.hours_ahead'),
    (r'^now/minus(\d{1,2})hours/$', 'mysite.views.hours_behind'),
    (r'^now/in_chicago/$', 'mysite.views.now_in_chicago'),
    (r'^now/in_london/$', 'mysite.views.now_in_london'),
)

(Notez les guillemets autour des noms de vue. Nous utilisons 'mysite.views.current_datetime' — avec des guillemets — à la place de mysite.views.current_datetime).

En utilisant cette technique, il n’est plus nécessaire d’importer les fonctions view; Django importe automatiquement la vue appropriée lorsqu’il en a besoin la première fois, d’après la chaîne décrivant le nom et le chemin de la vue.

Un raccourci supplémentaire que vous pouvez prendre lorsque vous utilisez la technique des chaînes de caractères consiste à factoriser un « préfixe de vue » commun. Dans notre exemple d’URLconf, chaque chaîne débute par monsite.views, ce qui est redondant. Nous pouvons factoriser ce préfixe commun et le passer en premier argument de patterns(), comme ceci :

from django.conf.urls.defaults import *

urlpatterns = patterns('mysite.views',
    (r'^now/$', 'current_datetime'),
    (r'^now/plus(\d{1,2})hours/$', 'hours_ahead'),
    (r'^now/minus(\d{1,2})hours/$', 'hours_behind'),
    (r'^now/in_chicago/$', 'now_in_chicago'),
    (r'^now/in_london/$', 'now_in_london'),
)

Notez que vous ne placez pas de point final (".") dans le préfixe, ni de point initial dans les chaînes de vue. Django ajoute ceux-ci automatiquement.

Avec ces deux approches à l’esprit, laquelle est la meilleure ? Cela dépend réellement de votre style de code et de vos besoins.

Les avantages de l’approche « chaîne de caractère » sont les suivants :

  • C’est plus compact, parce que cela ne vous oblige pas à importer les vues.
  • Vous obtenez un URLconf plus lisible et plus facilement gérable si vos vues sont réparties dans divers modules Python.

Les avantages de l’approche « objet » sont les suivants :

  • elle autorise un « emballage » facile des vues.. Consultez la partie « encapsuler les vues » ci-après dans ce chapitre.
  • C’est plus « Pythonique » — c’est à dire que cela correspond plus aux traditions de Python, tel le passage de fonctions en tant qu’objet.

Les deux approches sont valides, et vous pouvez même les mélanger à l’intérieur d’un unique URLconf. Vous avez le choix.

Utilisation des préfixes de vue multiple

En pratique, si vous utilisez la technique des chaînes, vous finirez probablement par mélanger les vues au point que les vues de votre URLconf n’aurons pas de préfixe communs. Cependant, vous pouvez continuer à tirer parti du raccourci de préfixe dans les vues pour supprimer la duplication. Ajoutez simplement ensemble de multiples objets patterns(), comme ceci :

Avant :

from django.conf.urls.defaults import *

urlpatterns = patterns('',
    (r'^/?$', 'mysite.views.archive_index'),
    (r'^(\d{4})/([a-z]{3})/$', 'mysite.views.archive_month'),
    (r'^tag/(\w+)/$', 'weblog.views.tag'),
)

Après :

from django.conf.urls.defaults import *

urlpatterns = patterns('mysite.views',
    (r'^/?$', 'archive_index'),
    (r'^(\d{4})/([a-z]{3})/$', 'archive_month'),
)

urlpatterns += patterns('weblog.views',
    (r'^tag/(\w+)/$', 'tag'),
)

Tout ce qui importe au framework est qu’il y ait une variable appelée urlpatterns au niveau du module. Cette variable peut être générée dynamiquement, comme nous le faisons dans cet exemple.

Cas des URLs spécifiques au mode Débug

À propos de la construction dynamique des urlpatterns, vous pourriez vouloir tirer parti de cette technique pour modifier le comportement de votre URLconf lorsque vous êtes en mode debug. Pour ce faire, vérifiez simplement la valeur du paramètre DEBUG à l’exécution, comme ceci :

from django.conf.urls.defaults import*
from django.conf import settings

urlpatterns = patterns('',
    (r'^$', 'mysite.views.homepage'),
    (r'^(\d{4})/([a-z]{3})/$', 'mysite.views.archive_month'),
)

if settings.DEBUG:
    urlpatterns += patterns('',
        (r'^debuginfo$', 'mysite.views.debug'),
    )

Dans cet exemple, l’URL /debuginfo/ sera disponible uniquement si votre paramètre DEBUG est fixé à True, Vrai.

Utilisation des groupes nommés

Jusqu’à présent, dans tous nos exemples d’URLconf, nous avons utilisé de simples groupes d’expressions rationnelles anonymes — autrement dit, nous plaçons des parenthèses autour de morceaux d’URL que nous voulons capturer, et Django passe ce texte capturé à la vue en tant qu’argument positionnel. Pour un usage plus avancé, il est possible d’utiliser des groupes nommés d’expressions rationnelles pour capturer les composants de l’URL et les passer en tant qu’arguments clefs à une vue.

Arguments mot-clefs contre arguments positionnels

Une fonction Python peut être appelée en utilisant des arguments mot-clefs ou des arguments positionnels — et, dans certains cas, les deux à la fois. Dans un appel « argument mot-clef », vous spécifiez le nom de ces arguments avec les valeurs que vous êtes en train de passer. Dans un appel « argument positionnel », vous passez simplement les arguments sans explicitement préciser quelles sont les arguments correspondants aux valeurs; l’association est implicite d’après l’ordre des arguments.

Par exemple, considérez cette simple fonction :

def sell(item, price, quantity):
    print "Selling %s unit(s) of %s at %s" % (quantity, item, price)

Pour l’appeler avec des arguments positionnels, vous spécifiez les arguments dans l’ordre où ils sont listés dans la définition de la fonction :

sell('Socks', '$2.50', 6)

Pour l’appeler avec des arguments mot-clefs, vous précisez les noms des arguments en même temps que les valeurs. Les instructions suivantes sont équivalentes :

sell(item='Socks', price='$2.50', quantity=6)
sell(item='Socks', quantity=6, price='$2.50')
sell(price='$2.50', item='Socks', quantity=6)
sell(price='$2.50', quantity=6, item='Socks')
sell(quantity=6, item='Socks', price='$2.50')
sell(quantity=6, price='$2.50', item='Socks')

Enfin, vous pouvez mixer arguments mot-clefs et positionnels, dès l’instant où tous les arguments positionnels sont listés avant les arguments mot-clefs. Les instructions suivantes sont équivalentes à celles de l’exemple précédent :

sell('Socks', '$2.50', quantity=6)
sell('Socks', price='$2.50', quantity=6)
sell('Socks', quantity=6, price='$2.50')

Dans les expressions rationnelles Python, la syntaxe pour les groupes nommés est (?P<name>pattern), où name correspond au nom du groupe et pattern correspond au patron à mettre en correspondance.

Voici un exemple d’URLconf qui utilise des groupes anonymes :

from django.conf.urls.defaults import *
from mysite import views

urlpatterns = patterns('',
    (r'^articles/(\d{4})/$', views.year_archive),
    (r'^articles/(\d{4})/(\d{2})/$', views.month_archive),
)

Voici le même URLconf, réécrit pour utiliser les groupes nommés :

from django.conf.urls.defaults import *
from mysite import views

urlpatterns = patterns('',
    (r'^articles/(?P<year>\d{4})/$', views.year_archive),
    (r'^articles/(?P<year>\d{4})/(?P<month>\d{2})/$',
    views.month_archive),
)

Ceci donne exactement la même chose que l’exemple précédent, avec une subtile différence : les valeurs capturées sont transmises aux vues comme des arguments mot-clefs plutôt que comme des arguments positionnels.

Par exemple, avec les groupes anonymes, une requête vers /articles/2006/03/ produira un appel de fonction équivalent à ceci :

month_archive(request, '2006', '03')

Alors qu’avec les groupes nommés, la même requête produira cet appel de fonction :

month_archive(request, year='2006', month='03')

En pratique, l’utilisation des groupes nommés rends vos URLconfs plus explicites et moins sujets aux bugs liés à l’ordre des arguments — sans compter que vous pouvez réorganiser les arguments dans les definitions de vos vues. Suivant l’exemple précédent, si nous avions voulu modifier les URLs pour y inclure le mois avant l’année, et que nous avions utilisé des groupes anonymes , nous aurions du nous souvenir de changer l’ordre des arguments dans la vue month_archive. Si nous avions utilisé les groupes nommés, changer l’ordre des paramètres capturés dans l’URL n’aurait pas eu d’effet sur la vue.

Bien sur, les bénéfices des groupes nommés se font au détriment de la brièveté; certains développeurs trouvent la syntaxe des groupes nommés laide et trop verbeuse. Toutefois, un autre avantage des groupes nommés est la lisibilité, spécialement pour ceux qui ne sont pas intimement familiers des expressions rationnelles ou de votre application Django particulière. Il est facile de voir ce qui se passe, d’un seul coup d’oeil, dans un URLconf qui utilise les groupes nommés.

Comprendre l’algorithme Matching/Grouping

Une opposition à l’usage des groupes nommés dans un URLconf consiste à dire qu’un unique patron d’URLconf ne peut contenir à la fois des groupes nommés et des groupes anonymes. Si vous faites cela, Django ne lèvera aucune erreur, mais vous trouverez probablement que vos URLs ne répondrons pas à vos attentes. Spécifiquement, voici l’algorithme suivi par l’analyseur d’URLconf, selon la présence ou non de groupes nommés dans une expression rationnelle :

  • S’il y a des arguments nommés, il les utilisera, ignorant les arguments anonymes.
  • Autrement, il passera tous les arguments anonymes comme s’il s’agissait d’arguments positionnels.
  • Dans les deux cas, il passera toute option supplémentaire comme arguments mot-clef. Consultez la prochaine partie pour avoir plus d’information.

Passer des options supplémentaires aux vues

Parfois vous vous surpprendrez à écrire des fonctions, dans les vues, qui sont assez similaires, avec seulement de petites différences. Par exemple, admettons que vous ayez deux vues au contenu identique à l’exception du gabarit qu’elles utilisent :

# urls.py

from django.conf.urls.defaults import *
from mysite import views

urlpatterns = patterns('',
    (r'^foo/$', views.foo_view),
    (r'^bar/$', views.bar_view),
)

# views.py

from django.shortcuts import render_to_response
from mysite.models import MyModel

def foo_view(request):
    m_list = MyModel.objects.filter(is_new=True)
    return render_to_response('template1.html', {'m_list':
    m_list})

def bar_view(request):
    m_list = MyModel.objects.filter(is_new=True)
    return render_to_response('template2.html', {'m_list':
    m_list})

Nous nous répétons dans ce code, et ce n’est pas élégant. À première vue, vous pourriez penser à supprimer la redondance en utilisant la même vue pour les deux URLs, en plaçant des parenthèses autour des URL afin de les capturer, et en vérifiant l’URL contenu dans la vue pour déterminer le gabarit, comme ceci :

# urls.py

from django.conf.urls.defaults import *
from mysite import views

urlpatterns = patterns('',
    (r'^(foo)/$', views.foobar_view),
    (r'^(bar)/$', views.foobar_view),
)

# views.py

from django.shortcuts import render_to_response
from mysite.models import MyModel

def foobar_view(request, url):
    m_list = MyModel.objects.filter(is_new=True)
    if url == 'foo':
        template_name = 'template1.html'
    elif url == 'bar':
        template_name = 'template2.html'
    return render_to_response(template_name, {'m_list': m_list})

Le problème avec cette solution, cependant, est qu’il couple vos URLs avec votre code. Si vous décidez de renommer /foo/ en /fooey/, vous aurez à vous souvenir de changer le code de la vue.

La solution élégante implique un paramètre optionnel dans l’URLconf. Chaque patron d’un URLconf peut inclure un troisième élément : un dictionnaire d’arguments mot-clefs à transmettre à la fonction vue.

Ayant ceci à l’esprit, nous pouvons réécrire notre exemple comme ceci :

# urls.py

from django.conf.urls.defaults import *
from mysite import views

urlpatterns = patterns('',
    (r'^foo/$', views.foobar_view, {'template_name':
    'template1.html'}),
    (r'^bar/$', views.foobar_view, {'template_name':
    'template2.html'}),
)

# views.py

from django.shortcuts import render_to_response
from mysite.models import MyModel

def foobar_view(request, template_name):
    m_list = MyModel.objects.filter(is_new=True)
    return render_to_response(template_name, {'m_list': m_list})

Comme vous pouvez le voir, l’URLconf de cet exemple précise un template_name dans l’URLconf. La vue le traite comme tout autre paramètre.

Cette technique d’options d’URLconf additionnels est une très bonne façon d’envoyer des informations supplémentaires à vos vues avec un minimum de chichi. En tant que telle, elle est utilisée par une paire d’applications embarquées dans Django, et plus particulièrement pour son système de vues génériques, que nous détaillons au chapitre 9.

Les parties qui suivent contiennent plusieurs idées sur la façon dont vous pouvez utiliser cette technique d’options additionnelles dans les URLconfs au sein de vos propres projets.

Simuler les valeurs capturées dans l’URLconf

Admettons que vous possédiez, parmi un jeu de vues qui correspondent à un patron, un autre URL qui ne réponde pas à ce patron mais dont la logique de vue est la même. Dans ce cas, vous pouvez « simuler » la capture des valeurs d’URL en utilisant les options d’URLconf supplémentaires pour prendre en charge cette URL additionnelle avec la même vue.

Par exemple, vous pouvez avoir une application qui affiche des données pour une journée particulière, avec des URLs comme celles-ci :

/mydata/jan/01/
/mydata/jan/02/
/mydata/jan/03/
# ...
/mydata/dec/30/
/mydata/dec/31/

C’est assez simple à gérer — vous pouvez capturer celles-ci dans un URLconf du genre (en utilisant la syntaxe de groupe nommé) :

urlpatterns = patterns('',
    (r'^mydata/(?P<month>\w{3})/(?P<day>\d\d)/$', views.my_view),
)

La signature de la fonction vue devrait ressembler à ceci :

def ma_view(request, month, day):
    # ....

Cette approche est très simple — rien que vous n’ayez déjà vu auparavant. L’astuce arrive lorsque vous voulez ajouter un nouvel URL qui utilise ma_view mais dont l’URL n’inclut pas de month ou de day.

Par exemple, vous pourriez vouloir ajouter un autre URL, /mydata/birthday/, qui serait équivalent à /mydata/jan/06/. Vous pouvez tirer parti des options supplémentaires de l’URLconf comme ceci :

urlpatterns = patterns('',
    (r'^mydata/birthday/$', views.my_view, {'month': 'jan',
    'day': '06'}),
    (r'^mydata/(?P<month>\w{3})/(?P<day>\d\d)/$', views.my_view),
)

Ici, l’aspect sympathique est que vous n’avez pas du tout besoin de modifier votre vue. La fonction vue s’occupe uniquement de savoir s’il y a des paramètres month et day — elle se moque que ceux-ci proviennent de la capture d’URL elle même ou des paramètres additionnels.

Faire une vue générique

C’est une bonne pratique de programmation de « factoriser » les parties communes du code. Par exemple, avec ces deux fonctions Python :

def say_hello(person_name):
    print 'Hello, %s' % person_name

def say_goodbye(person_name):
    print 'Goodbye, %s' % person_name

nous pouvons factoriser les salutations pour en faire des paramètres :

def greet(person_name, greeting):
    print '%s, %s' % (greeting, person_name)

Vous pouvez appliquer cette même philosophie à vos vues Django en utilisant les paramètres additionnels des URLconf.

Avec ceci en tête, vous pouvez commencer à faire abstraction de vos vues à un plus haut niveau. Au lieu de vous dire, « cette vue affiche une liste d’objets Event », et «cette vue affiche une liste d’objets BLogEntry », réalisez qu’elles sont toutes deux des cas spéciaux de « vue qui affiche une liste d’objets, où le type d’objet est variable ». Prenez par exemple ce code :

# urls.py

from django.conf.urls.defaults import *
from mysite import views

urlpatterns = patterns('',
    (r'^events/$', views.event_list),
    (r'^blog/entries/$', views.entry_list),
)

# views.py

from django.shortcuts import render_to_response
from mysite.models import Event, BlogEntry

def event_list(request):
    obj_list = Event.objects.all()
    return render_to_response('mysite/event_list.html',
    {'event_list': obj_list})

def entry_list(request):
    obj_list = BlogEntry.objects.all()
    return render_to_response('mysite/blogentry_list.html',
    {'entry_list': obj_list})

Les deux vues font essentiellement la même chose : elles affichent une liste d’objets. Factorisons donc le type d’objet qu’elles affichent :

# urls.py

from django.conf.urls.defaults import *
from mysite import models, views

urlpatterns = patterns('',
    (r'^events/$', views.object_list, {'model': models.Event}),
    (r'^blog/entries/$', views.object_list, {'model':
    models.BlogEntry}),
)

# views.py

from django.shortcuts import render_to_response

def object_list(request, model):
    obj_list = model.objects.all()
    template_name = 'mysite/%s_list.html' %
    model.__name__.lower()
    return render_to_response(template_name, {'object_list':
    obj_list})

Grâce à ces petits changements, nous obtenons soudainement une vue réutilisable, indépendante du modèle ! À partir de maintenant, à chaque fois que nous aurons besoin d’une vue qui liste un jeu d’objets, nous pouvons simplement réutiliser cette vue object_list plutôt que d’écrire du code destiné à une vue. Voici quelques notes sur ce que nous avons réalisés :

  • Nous passons directement les classes de modèle, comme le paramètre model. Le dictionnaire des options supplémentaires dans l’URLconf peut transmettre tout type d’objet Python — pas seulement des chaînes de caractères.
  • La ligne model.objects.all() est un exemple de duck typing : « Si cela marche comme un canard et caquette comme un canard, nous pouvons le considérer comme un canard ». Notez que le code ne sait pas de quel type est l’objet model. La seule chose requise est que model possède un attribut objects, qui à son tour possède une méthode all().
  • Nous utilisons model.__name__.lower() pour déterminer le nom du template. Chaque classe Python présente un attribut __name__ qui retourne le nom de la classe. Cette fonctionnalité est pratique dans de tels cas, lorsque nous ne connaissons pas le type de classe avant l’exécution. Par exemple, le __name__ de la classe BlogEntry est la chaîne de caractères BlogEntry.
  • Une légère différence entre cet exemple et l’exemple précédent est que nous passons le nom de la variable générique object_list au template. Nous pouvons facilement changer ce nom de variable en blogentry_list ou en event_list, mais nous laissons cela à titre d’exercice pour le lecteur.

Parce que les sites web basés sur les bases de données ont plusieurs patrons en commun, Django est fournit avec un jeu de « vues génériques » qui utilisent précisément cette technique pour vous épargner du temps. Nous abordons les vues génériques embarquées par Django au chapitre suivant.

Donner des options de configuration à une vue

Si vous diffusez une application Django, il y a des chances que vos utilisateurs voudrons quelques possibilités de configuration. Dans ce cas, c’est une bonne idée d’ajouter des raccourcis à vos vues pour toutes les options de configuration que vous pensez rendre modifiables par les utilisateurs. Vous pouvez utilisez les paramètres additionnels de l’URLconf pour ce faire.

Une façon courante de rendre une application configurable passe par le nom du gabarit :

def my_view(request, template_name):
    var = do_something()
    return render_to_response(template_name, {'var': var})
Comprendre la prévalence des valeurs capturées vs. les options supplémentaires

Lorsqu’il y a conflit, les paramètres supplémentaires de l’URLconf sont prioritaires sur les paramètres capturés. Autrement dit, si votre URLconf capture une variable de groupe nommé et qu’un paramètre supplémentaire inclut une variable avec le même nom, la valeur du paramètre supplémentaire de l’URLconf sera utilisée.

Considérons par exemple cet URLConf :

from django.conf.urls.defaults import *

urlpatterns = patterns('',
    (r'^mydata/(?P<id>\d+)/$', views.my_view, {'id': 3}),
)

Ici, l’expression régulière et le dictionnaire supplémentaire incluent un id. L’id codé en dur est prioritaire. Cela signifie que toute requête (par exemple, /mydata/2/ ou /mydata/432432/) sera traitée comme si id était fixé à 3, indépendamment de la valeur capturée dans l’URL.

Les lecteurs astucieux noterons que dans ce cas, c’est un gaspillage de temps et de saisie pour capturer l’id de l’expression régulière, parceque sa valeur sera toujours écrasée par la valeur du dictionnaire. C’est exact; Nous précisons cela uniquement pour vous éviter de faire l’erreur.

Utiliser les arguments par défaut pour les vues

Une autre astuce pratique est de spécifier les paramètres par défaut pour les arguments de la vue. Cela indique à la vue quelle est la valeur à utiliser par défaut pour un paramètre si aucun n’est indiqué.

En voici un exemple :

# urls.py

from django.conf.urls.defaults import *

urlpatterns = patterns('',
    (r'^blog/$', views.page),
    (r'^blog/page(?P<num>\d+)/$', views.page),
)

# views.py

def page(request, num="1"):
    # Output the appropriate page of blog entries, according to
    num.
    # ...

Ici, les deux patrons d’URL pointent sur la même vue — views.page — mais le premier patron ne capture rien de l’URL. Si le premier patron correspond, la fonction page() utilisera son argument par défaut pour num, "1". Si le second patron correspond, page() utilisera n’importe quelle valeur num capturée par l’expression rationnelle.

Il est courant d’utiliser cette technique en conjonction avec les options de configuration, comme expliqué précédemment. Cet exemple apporte une légère amélioration à celui de la section « Donner des options de configuration à une vue » en fournissant une valeur par défaut pour template_name :

def my_view(request, template_name='mysite/my_view.html'):
    var = do_something()
    return render_to_response(template_name, {'var': var})

Vues particulières

Parfois vous aurez un patron dans votre URLConf qui gère un grand nombre d’URL, mais vous voudrez traiter différement l’un d’entre eux. Dans ce cas, tirez parti du traitement linéaire de l’URLConf et placez le cas particulier en premier.

Par exemple, les pages « add an objet » (ajouter un objet) de l’interface d’administration de Django sont représentées par cette ligne de l’URLConf :

urlpatterns = patterns('',
    # ...
    ('^([^/]+)/([^/]+)/add/$',
    'django.contrib.admin.views.main.add_stage'),
    # ...
)

Ceci extrait les URLs tels que /myblog/entries/add/ et /auth/groups/add/. Cependant, la page « add » pour un objet utilisateur (/auth/user/add/) est un cas particulier — elle n’affiche pas tous les champs de formulaire, elle affiche deux champs mot de passe, et ainsi de suite. Nous pouvons résoudre ce problème par un traitement spécifique dans la vue, comme ceci :

def add_stage(request, app_label, model_name):
    if app_label == 'auth' and model_name == 'user':
        # do special-case code
    else:
        # do normal code

mais ceci n’est pas élégant pour une raison simple que nous avons déjà abordé de multiples fois dans ce chapitre : cela place la logique de l’URL dans la vue. Une solution plus élégante consiste à tirer parti du fait que les traitements des URLConfs s’effectuent de haut en bas :

urlpatterns = patterns('',
    # ...
    ('^auth/user/add/$',
    'django.contrib.admin.views.auth.user_add_stage'),
    ('^([^/]+)/([^/]+)/add/$',
    'django.contrib.admin.views.main.add_stage'),
    # ...
)

Avec ceci en place, une requête vers /auth/user/add/ sera prise en charge par la vue user_add_stage. Malgré que l’URL corresponde au second patron, elle correspond d’abord au premier. (C’est la logique du court-circuit).

Capturer du texte dans les URLS

Chaque argument capturé est envoyé à la vue comme une chaîne de caractères entière Python, indépendamment de la correspondance faite par l’expression régulière. Par exemple, dans cette ligne d’URLConf :

(r'^articles/(?P<year>\d{4})/$', views.year_archive),

l’argument year vers views.year_archive() sera une chaîne, pas un entier, même si \d{4} correspondra uniquement à un entier.

C’est important de garder ceci à l’esprit lorsque vous écrivez le code d’une vue. Beaucoup de fonctions embarquées dans Python sont strictes (à juste titre) et n’acceptent que des objets d’un certain type. Une erreur courante est d’essayer de créer un objet datetime.date avec des valeurs de chaîne de caractère à la place d’entiers :

>>> import datetime
>>> datetime.date('1993', '7', '9')
Traceback (most recent call last):
    ...
TypeError: an integer is required
>>> datetime.date(1993, 7, 9)
datetime.date(1993, 7, 9)

Traduite dans un URLConf et dans une vue, l’erreur ressemble à ceci :

# urls.py

from django.conf.urls.defaults import *

urlpatterns = patterns('',
    (r'^articles/(\d{4})/(\d{2})/(\d{2})/$', views.day_archive),
)

# views.py

import datetime

def day_archive(request, year, month, day)
    # The following statement raises a TypeError!
    date = datetime.date(year, month, day)

Au lieu de cela, day_archive() peut être correctement écrite, comme ceci :

def day_archive(request, year, month, day)
    date = datetime.date(int(year), int(month), int(day))

Notez que int() lui même lève une ValueError lorsque vous lui transmettez une chaîne qui n’est pas composée uniquement de chiffres. Nous évitons cette erreur dans ce cas parce que l’expression rationnelle de notre URLConf a vérifié que seules les chaînes contenant des chiffres sont transmises à la vue.

Déterminer ce que l’URLconf cherche

Lorsqu’une requête arrive, Django tente de mettre en correspondance les patrons d’URLConf avec l’URL requise, comme une chaîne Python standard (pas une chaîne Unicode). Ceci n’inclu pas les paramètres GET ou POST, ni le nom de domaine. Ceci n’inclu pas non plus le slash initial, puisque tout URL a un slash initial.

Par exemple, dans une requête vers http://www.example.com/myapp/, Django tentera de trouver myapp/. Dans une requête vers http://www.example.com/myapp/?page=3, Django tentera de trouver myapp/.

La méthode de requête (par exemple, POST, GET, HEAD) n’est pas prise en compte lors du parcours de l’URLConf. Autrement dit, toute méthode de requête sera dirigée vers la même fonction pour un URL identique. C’est de la responsabilité d’une vue de faire les branchements basés sur la méthode de requête.

Inclure d’autre URLconfs

Si vous désirez que votre code soit utilisé sur de multiples sites basés sur Django, vous devez considérer vos URLConfs de façon à ce qu’ils autorisent l’« inclusion ».

Quelque soit l’endroit, votre URLconf peut « inclure » d’autre modules d’URLconf. Ceci classe un jeu d’URLs de façon arborescente. Par exemple, cet URLConf inclut d’autres URLConfs :

from django.conf.urls.defaults import *

urlpatterns = patterns('',
    (r'^weblog/', include('mysite.blog.urls')),
    (r'^photos/', include('mysite.photos.urls')),
    (r'^about/$', 'mysite.views.about'),
)

Il y a ici un écueil important : l’expression rationnelle de cet exemple qui pointe vers un include() n’a pas de $ (correspondance avec un charactère de fin de chaîne) mais inclut un slash final. Chaque fois que Django rencontre include(), il tranche toute partie de l’URL qui ne correspond plus après ce point et transmet la chaîne restante à l’URLConf inclus pour un traitement complémentaire.

Continuons l’exemple avec l’URLconf mysite.blog.urls :

from django.conf.urls.defaults import *

urlpatterns = patterns('',
    (r'^(\d\d\d\d)/$', 'mysite.blog.views.year_detail'),
    (r'^(\d\d\d\d)/(\d\d)/$', 'mysite.blog.views.month_detail'),
)

Avec ces deux URLconfs, voici comment quelques exemples de requêtes seront pris en charge :

  • /weblog/2007/ : Dans le premier URLconf, la patron r'^weblog/' correspond. Puisqu’il s’agit d’un include(), Django coupe tout le texte correspondant, ici 'weblog/'. La partie restante de l’URL est 2007/, qui correspond à la première ligne de l’URLConf mysite.blog.urls.
  • /weblog//2007/ : Dans le premier URLconf, le patron r'^weblog/' correspond. Puisque c’est un include(), Django coupe tout le texte correspondant, ici 'weblog/'. La partie restante de l’URL est /2007/ (avec un slash initial), qui ne correspond à aucune des lignes de l’URLConf mysite.blog.urls.
  • /about/ : Ceci correspond à la vue mysite.views.about du premier URLconf, démontrant que vous pouvez mélanger les patrons include() avec les patrons non-include().

Comment fonctionnent les paramètres capturés avec include()

Un URLconf inclus reçoit tous les paramètres capturés par son URLConf parent, par exemple :

# root urls.py

from django.conf.urls.defaults import *

urlpatterns = patterns('',
    (r'^(?P<username>\w+)/blog/', include('foo.urls.blog')),
)

# foo/urls/blog.py

from django.conf.urls.defaults import *

urlpatterns = patterns('',
    (r'^$', 'foo.views.blog_index'),
    (r'^archive/$', 'foo.views.blog_archive'),
)

Dans cet exemple, la variable capturée username est transmise à l’URLConf inclus et, ainsi, à chaque fonction de la vue dans cette URLconf.

Notez que les paramètres capturés seront toujours transmis à chaque ligne de l’URLConf inclus, indépendamment du fait que la ligne valide ces paramètres.

Pour cette raison, cette technique est utile uniquement si vous êtes certains que chaque vue de l’URLConf inclus accepte les paramètres que vous transmettez.

Comment les options supplémentaires de l’URLConf fonctionnent avec include()

De la même façon, vous pouvez passer des options supplémentaire d’URLConf à include(), tout comme vous pouvez passer des options supplémentaires d’URLconf à une vue normale — sous la forme d’un dictionnaire. Lorsque vous faites cela, chaque ligne de l’URLconf inclu transmettra les options supplémentaires.

Par exemple, les deux jeux d’URLconf suivants sont fonctionnellement identiques.

Premier jeu :

# urls.py

from django.conf.urls.defaults import *

urlpatterns = patterns('',
    (r'^blog/', include('inner'), {'blogid': 3}),
)

# inner.py

from django.conf.urls.defaults import *

urlpatterns = patterns('',
    (r'^archive/$', 'mysite.views.archive'),
    (r'^about/$', 'mysite.views.about'),
    (r'^rss/$', 'mysite.views.rss'),
)

Second jeu :

# urls.py

from django.conf.urls.defaults import *

urlpatterns = patterns('',
    (r'^blog/', include('inner')),
)

# inner.py

from django.conf.urls.defaults import *

urlpatterns = patterns('',
    (r'^archive/$', 'mysite.views.archive', {'blogid': 3}),
    (r'^about/$', 'mysite.views.about', {'blogid': 3}),
    (r'^rss/$', 'mysite.views.rss', {'blogid': 3}),
)

Comme dans le cas des paramètres capturés, (expliqué dans la section précédente), les options supplémentaires seront toujours transmises à chaque ligne de l’URLconf inclus, indépendamment du fait que la ligne valide ces paramètres. Pour cette raison, cette technique est utile uniquement si vous êtes certain que chaque vue de l’URLconf inclus accepte les options supplémentaires que vous passez.

Et ensuite ?

L’un des objectifs principaux de Django est de réduire le volume de code que les développeurs doivent écrire, aussi avons-nous suggéré comment raccourcir le code de vos vues et de vos URLconfs au cours de ce chapitre.

La prochaine étape logique dans l’élimination de code est de supprimer le besoin d’écrire des vues entières. C’est le sujet du chapitre suivant.

<< précédentsuivant >>

Dernière modification: 2010-05-25 17:46:47.525920