Paramètres d'URL facultatifs de Django

162

J'ai une URL Django comme celle-ci:

url(
    r'^project_config/(?P<product>\w+)/(?P<project_id>\w+)/$',
    'tool.views.ProjectConfig',
    name='project_config'
),

views.py:

def ProjectConfig(request, product, project_id=None, template_name='project.html'):
    ...
    # do stuff

Le problème est que je veux que le project_idparamètre soit facultatif.

Je veux /project_config/et /project_config/12345abdce/être des modèles d'URL aussi valides, de sorte que si project_id est passé, alors je peux l' utiliser.

Dans l'état actuel des choses, j'obtiens un 404 lorsque j'accède à l'URL sans le project_idparamètre.

Darwin Tech
la source

Réponses:

382

Il existe plusieurs approches.

La première consiste à utiliser un groupe non capturant dans l'expression régulière: (?:/(?P<title>[a-zA-Z]+)/)?
Rendre un jeton d'URL Django Regex facultatif

Un autre moyen plus simple à suivre consiste à avoir plusieurs règles qui correspondent à vos besoins, toutes pointant vers la même vue.

urlpatterns = patterns('',
    url(r'^project_config/$', views.foo),
    url(r'^project_config/(?P<product>\w+)/$', views.foo),
    url(r'^project_config/(?P<product>\w+)/(?P<project_id>\w+)/$', views.foo),
)

Gardez à l'esprit que dans votre vue, vous devrez également définir une valeur par défaut pour le paramètre d'URL facultatif, sinon vous obtiendrez une erreur:

def foo(request, optional_parameter=''):
    # Your code goes here
Yuji 'Tomita' Tomita
la source
68
Votez pour l'option multi-routes. +1
Burhan Khalid
4
@Yuji - ne pouvez-vous pas résoudre le problème d'inversion en nommant chaque modèle d'URL?
Ted
8
pouvons-nous donner à chaque vue le même nom?
eugene
2
@ Yuji'Tomita'Tomita Je sais, donc la réponse à la question d'Eugene est malheureusement, non, nous ne pouvons pas correctement avoir plusieurs vues avec le même nom, même si nous les implémentons comme moyen d'obtenir des paramètres optionnels.
nnypar
2
@eugene Oui, nous pouvons avoir deux URL avec le même nom, l'inversion ramassera intelligemment ce qui est applicable en fonction des arguments
Arpit Singh
37

Vous pouvez utiliser des itinéraires imbriqués

Django <1,8

urlpatterns = patterns(''
    url(r'^project_config/', include(patterns('',
        url(r'^$', ProjectConfigView.as_view(), name="project_config")
        url(r'^(?P<product>\w+)$', include(patterns('',
            url(r'^$', ProductView.as_view(), name="product"),
            url(r'^(?P<project_id>\w+)$', ProjectDetailView.as_view(), name="project_detail")
        ))),
    ))),
)

Django> = 1,8

urlpatterns = [
    url(r'^project_config/', include([
        url(r'^$', ProjectConfigView.as_view(), name="project_config")
        url(r'^(?P<product>\w+)$', include([
            url(r'^$', ProductView.as_view(), name="product"),
            url(r'^(?P<project_id>\w+)$', ProjectDetailView.as_view(), name="project_detail")
        ])),
    ])),
]

C'est beaucoup plus DRY ( productdisons que vous vouliez renommer le kwarg en product_id, il vous suffit de changer la ligne 4, et cela affectera les URL ci-dessous.

Édité pour Django 1.8 et supérieur

Jacob Valenta
la source
1
Nested est bon. En outre, il sépare plus clairement les différentes sections d'URL de votre code (en raison de l'utilisation de retraits)
Patrick
Le problème avec l'imbrication est que si vous avez plusieurs paramètres optionnels, vous finissez par ne pas être DRY, car avec, par exemple, 3 paramètres optionnels, vous avez 8 combinaisons différentes d'URL possibles. Vous devez gérer le paramètre 1 se produisant, le paramètre 1 ne se produisant pas mais le paramètre 2 se produisant, et les paramètres 1 et 2 ne se produisant pas mais le paramètre 3 se produisant. Le paragraphe URL sera BEAUCOUP plus difficile à lire qu'une seule chaîne avec plusieurs paramètres facultatifs. L'utilisation de constantes symboliques pour les sous-chaînes de paramètres facultatives rendrait la lecture très facile et il n'y aurait qu'une seule URL.
Bogatyr le
Je pense que vous avez raison, mais c'est davantage le résultat d'une mauvaise conception de la vue / URL. Cet exemple pourrait être retravaillé pour être bien meilleur.
Jacob Valenta
'flat is better than nested'
pjdavis
30

Encore plus simple est d'utiliser:

(?P<project_id>\w+|)

Le "(a | b)" signifie a ou b, donc dans votre cas ce serait un ou plusieurs caractères de mot (\ w +) ou rien.

Cela ressemblerait donc à:

url(
    r'^project_config/(?P<product>\w+)/(?P<project_id>\w+|)/$',
    'tool.views.ProjectConfig',
    name='project_config'
),
Juan José Brown
la source
9
J'aime la simplicité de cette solution, mais attention: ce faisant, la vue recevra toujours une valeur pour l'argument, qui sera None. Cela signifie que vous ne pouvez pas vous fier à une valeur par défaut dans la signature de la vue pour cela: vous devez la tester explicitement à l'intérieur et l'assigner en conséquence.
Anto
C'est ce que je cherchais =)
Mike Brian Olivera
3
Qu'en est-il de la dernière barre oblique au cas où project_id n'est pas présent?
iamkhush
Vous pouvez simplement ajouter un? après la barre oblique ou simplement inclure la barre oblique dans le modèle project_id
Juan José Brown
18

Django> version 2.0 :

L'approche est essentiellement identique à celle donnée dans la réponse de Yuji 'Tomita' Tomita . La syntaxe est cependant affectée:

# URLconf
...

urlpatterns = [
    path(
        'project_config/<product>/',
        views.get_product, 
        name='project_config'
    ),
    path(
        'project_config/<product>/<project_id>/',
        views.get_product,
        name='project_config'
    ),
]


# View (in views.py)
def get_product(request, product, project_id='None'):
    # Output the appropriate product
    ...

En utilisant, path()vous pouvez également passer des arguments supplémentaires à une vue avec l'argument facultatif kwargsde type dict. Dans ce cas, votre vue n'aurait pas besoin d'une valeur par défaut pour l'attribut project_id:

    ...
    path(
        'project_config/<product>/',
        views.get_product,
        kwargs={'project_id': None},
        name='project_config'
    ),
    ...

Pour savoir comment cela se fait dans la version la plus récente de Django , consultez la documentation officielle sur la répartition des URL .

jojo
la source
1
Je pense que vous avez mélangé project_id et product_id dans votre code, non?
Andreas Bergström
@ AndreasBergström merci beaucoup pour l'avoir signalé! vous avez tout à fait raison à ce sujet! Corrigé à la hâte, mais j'y reviendrai plus tard. J'espère que ça va maintenant! Il y avait aussi le project_idtoujours dans le chemin dans le cas de la valeur par défaut en utilisant un dict. Cela peut conduire à un comportement apparemment étrange, car l'argument fourni dans le dictsera toujours utilisé (si je me souviens bien).
jojo
@jojo Cela signifie-t-il qu'un 'project_config / foo / bar' dans la deuxième option passera automatiquement les {'project_id': 'bar'} kwargs à la vue?
Sauce barbecue originale du
9

J'ai pensé que j'ajouterais un peu à la réponse.

Si vous avez plusieurs définitions d'URL, vous devrez nommer chacune d'elles séparément. Ainsi, vous perdez la flexibilité lors de l'appel inverse car un inverse attendra un paramètre tandis que l'autre ne le fera pas.

Une autre façon d'utiliser regex pour accueillir le paramètre facultatif:

r'^project_config/(?P<product>\w+)/((?P<project_id>\w+)/)?$'
tarequeh
la source
2
Dans Django 1.6, cela jette une exception pour moi. Je resterais loin de çaReverse for 'edit_too_late' with arguments '()' and keyword arguments '{'pk': 128}' not found. 1 pattern(s) tried: ['orders/cannot_edit/((?P<pk>\\d+)/)?$']
Patrick
2

Django = 2,2

urlpatterns = [
    re_path(r'^project_config/(?:(?P<product>\w+)/(?:(?P<project_id>\w+)/)/)?$', tool.views.ProjectConfig, name='project_config')
]
AzizAhmad
la source
0

Utilisation ? fonctionne bien, vous pouvez vérifier sur pythex . N'oubliez pas d'ajouter les paramètres * args et ** kwargs dans la définition des méthodes de vue

url('project_config/(?P<product>\w+)?(/(?P<project_id>\w+/)?)?', tool.views.ProjectConfig, name='project_config')
franciscorode
la source