Parámetros de URL opcionales de Django

161

Tengo una URL de Django como esta:

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

El problema es que quiero que el project_idparámetro sea opcional.

Quiero /project_config/y /project_config/12345abdce/para ser patrones de URL igualmente válidas, de manera que si project_id se pasa, a continuación, lo puedo usar.

Tal como está en este momento, obtengo un 404 cuando accedo a la URL sin el project_idparámetro.

Darwin Tech
fuente

Respuestas:

381

Hay varios enfoques.

Una es usar un grupo sin captura en la expresión regular: hacer opcional una señal de URL de Django de expresión regular (?:/(?P<title>[a-zA-Z]+)/)?

Otra forma más fácil de seguir es tener múltiples reglas que coincidan con sus necesidades, todas apuntando a la misma vista.

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),
)

Tenga en cuenta que, desde su punto de vista, también deberá establecer un valor predeterminado para el parámetro de URL opcional, o recibirá un error:

def foo(request, optional_parameter=''):
    # Your code goes here
Yuji 'Tomita' Tomita
fuente
68
Vota por la opción de rutas múltiples. +1
Burhan Khalid
44
@Yuji: ¿no puede resolver el problema de inversión nombrando cada patrón de URL?
Ted
8
¿podemos dar a cada vista el mismo nombre?
eugene
2
@ Yuji'Tomita'Tomita Lo sé, así que la respuesta a la pregunta de eugene es, lamentablemente, que no podemos tener varias vistas con el mismo nombre, incluso si las estamos implementando como una forma de obtener parámetros opcionales.
nnyby
2
@eugene Sí, podemos tener dos URL con el mismo nombre, la inversión se activará de forma inteligente, según corresponda, según el
argumento
37

Puedes usar rutas anidadas

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")
        ])),
    ])),
]

Esto es mucho más SECO (supongamos que desea cambiar el nombre de productkwarg a product_id, solo tiene que cambiar la línea 4, y afectará las siguientes URL.

Editado para Django 1.8 y superior

Jacob Valenta
fuente
1
Anidado es bueno. Además, separa las diferentes secciones de URL en su código más claramente (debido al uso de sangrías)
Patrick
El problema con los anidados es que si tiene múltiples parámetros opcionales, entonces terminará no SECO, ya que con, por ejemplo, 3 parámetros opcionales, tiene 8 combinaciones diferentes de URL posibles. Debe manejar el parámetro 1 que ocurre, el parámetro 1 no ocurre pero el parámetro 2 ocurre y los parámetros 1 y 2 no ocurren pero el parámetro 3 ocurre. El párrafo URL será MUCHO más difícil de leer que una sola cadena con múltiples parámetros opcionales. El uso de constantes simbólicas para las subcadenas de parámetros opcionales facilitaría la lectura, y solo habría una URL.
Bogatyr
Creo que tienes razón, pero eso se debe más al diseño deficiente de URL / vista. Este ejemplo podría ser modificado para ser mucho mejor.
Jacob Valenta
'plano es mejor que anidado'
pjdavis
30

Aún más simple es usar:

(?P<project_id>\w+|)

El "(a | b)" significa aob, por lo que en su caso sería uno o más caracteres de palabras (\ w +) o nada.

Entonces se vería así:

url(
    r'^project_config/(?P<product>\w+)/(?P<project_id>\w+|)/$',
    'tool.views.ProjectConfig',
    name='project_config'
),
Juan José Brown
fuente
9
Me gusta la simplicidad de esta solución, pero tenga cuidado: al hacerlo, la vista seguirá recibiendo un valor para el argumento, que será None. Esto significa que no puede confiar en un valor predeterminado en la firma de la vista para esto: debe probarlo explícitamente en su interior y asignarlo en consecuencia.
Anto
Esto es lo que estaba buscando =)
Mike Brian Olivera
3
¿Qué pasa con la última barra en caso de que project_id no esté presente?
iamkhush
¿Puedes agregar un? después de la barra o simplemente incluir la barra en el patrón project_id
Juan José Brown
18

Django> versión 2.0 :

El enfoque es esencialmente idéntico al dado en la respuesta de Yuji 'Tomita' Tomita . Sin embargo, se ve afectada la sintaxis:

# 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
    ...

El uso path()también puede pasar argumentos adicionales a una vista con el argumento opcional kwargsque es de tipo dict. En este caso, su vista no necesitaría un valor predeterminado para el atributo project_id:

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

Para saber cómo se hace esto en la versión más reciente de Django , consulte los documentos oficiales sobre el envío de URL .

jojo
fuente
1
Creo que mezcló project_id y product_id en su código, ¿verdad?
Andreas Bergström
@ AndreasBergström muchas gracias por señalar eso! tienes toda la razón sobre esto! Lo corrigió rápidamente, pero lo revisaremos más tarde. Espero que esté bien ahora! También estaba el project_idquieto en el camino en caso de que se usara por defecto dict. Esto puede conducir a un comportamiento aparentemente extraño, ya que el argumento proporcionado en el dictsiempre se utilizará (si no recuerdo mal).
jojo
@jojo ¿Eso significa que un 'project_config / foo / bar' en la segunda opción pasará automáticamente los {'project_id': 'bar'} kwargs a la vista?
Salsa BBQ original
9

Pensé que agregaría un poco a la respuesta.

Si tiene varias definiciones de URL, deberá nombrar cada una de ellas por separado. Por lo tanto, pierde la flexibilidad al llamar al reverso, ya que un reverso esperará un parámetro mientras que el otro no.

Otra forma de usar regex para acomodar el parámetro opcional:

r'^project_config/(?P<product>\w+)/((?P<project_id>\w+)/)?$'
tarequeh
fuente
2
En Django 1.6 esto arroja una excepción para mí. Me mantendría alejado de élReverse 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
fuente
0

Utilizar ? funciona bien, puedes comprobar en pythex . Recuerde agregar los parámetros * args y ** kwargs en la definición de los métodos de vista

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