Parâmetros de URL Opcionais do Django

161

Eu tenho uma URL do Django assim:

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

O problema é que eu quero que o project_idparâmetro seja opcional.

Eu quero /project_config/e /project_config/12345abdce/ser padrões de URL igualmente válidas, de modo que , se project_id for aprovada, então eu posso usá-lo.

Como está no momento, recebo um 404 quando acesso o URL sem o project_idparâmetro

Darwin Tech
fonte

Respostas:

381

Existem várias abordagens.

Uma é usar um grupo que não captura no regex: (?:/(?P<title>[a-zA-Z]+)/)?
Tornando opcional um token de URL do Regex Django

Outra maneira mais fácil de seguir é ter várias regras que atendam às suas necessidades, todas apontando para a mesma exibição.

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

Lembre-se de que, na sua visão, você também precisará definir um padrão para o parâmetro opcional do URL ou obterá um erro:

def foo(request, optional_parameter=''):
    # Your code goes here
Yuji 'Tomita' Tomita
fonte
68
Vote na opção de rotas múltiplas. +1
Burhan Khalid
4
@Yuji - você não pode resolver o problema de reversão nomeando cada padrão de URL?
Ted
8
podemos dar a cada visão o mesmo nome?
Eugene
2
@ Yuji'Tomita'Tomita Eu sei, então a resposta para a pergunta de eugene é, infelizmente, não, não podemos ter várias visualizações com o mesmo nome, mesmo que as estejam implementando como uma maneira de obter parâmetros opcionais.
nnyby
2
@eugene Sim nós podemos ter duas URLs com o mesmo nome, revertendo vai inteligentemente pegar o que for aplicável, dependendo dos argumentos
Arpit Singh
37

Você pode usar rotas aninhadas

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

Isso é muito mais seco ( productdigamos que você queira renomear o kwarg para product_id, você só precisa alterar a linha 4 e isso afetará os URLs abaixo.

Editado para Django 1.8 e superior

Jacob Valenta
fonte
1
Aninhado é bom. Além disso, ele separa diferentes seções de URL em seu código de forma mais clara (devido ao uso de travessões)
Patrick
O problema com o aninhado é que, se você tiver vários parâmetros opcionais, acabará não sendo SECO, pois, por exemplo, com 3 parâmetros opcionais, você terá 8 combinações diferentes de URLs possíveis. É necessário manipular o parâmetro 1 que ocorre, o parâmetro 1 não ocorre, mas o parâmetro 2 ocorre e os parâmetros 1 e 2 não ocorrem, mas o parâmetro 3 ocorre. O parágrafo da URL será MUITO mais difícil de ler do que uma única string com vários parâmetros opcionais. O uso de constantes simbólicas para as substrings de parâmetros opcionais facilitaria a leitura e haveria apenas um URL.
Bogatyr
Acho que você está certo, mas isso é mais resultado de um design deficiente de visualização / URL. Este exemplo pode ser reformulado para ser muito melhor.
Jacob Valenta
'flat é melhor do que aninhado'
pjdavis 07/07
30

Ainda mais simples é usar:

(?P<project_id>\w+|)

O "(a | b)" significa a ou b; portanto, no seu caso, seriam um ou mais caracteres da palavra (\ w +) ou nada.

Então seria assim:

url(
    r'^project_config/(?P<product>\w+)/(?P<project_id>\w+|)/$',
    'tool.views.ProjectConfig',
    name='project_config'
),
Juan José Brown
fonte
9
Eu gosto da simplicidade desta solução, mas cuidado: ao fazê-lo, a exibição ainda receberá um valor para o argumento, que será None. Significando que você não pode confiar em um valor padrão na assinatura da exibição para isso: você deve testá-lo explicitamente por dentro e atribuir em conseqüência.
Anto
Isso é que eu estava procurando =)
Mike Brian Olivera
3
e a última barra caso project_id não esteja presente?
iamkhush
Você pode apenas adicionar um? após a barra ou apenas inclua a barra no padrão project_id #
Juan José Brown
18

Django> versão 2.0 :

A abordagem é essencialmente idêntica à apresentada na resposta de Yuji 'Tomita' Tomita . Afetado, no entanto, é a sintaxe:

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

Usando path()você também pode passar argumentos extras para uma visão com o argumento opcional kwargsque é do tipo dict. Nesse caso, sua visualização não precisaria de um padrão para o atributo project_id:

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

Para saber como isso é feito na versão mais recente do Django , consulte os documentos oficiais sobre o envio de URL .

jojo
fonte
1
Eu acho que você misturou project_id e product_id no seu código, certo?
Andreas Bergström
@ AndreasBergström muito obrigado por apontar isso! você está certo sobre isso! Corrigido com pressa, mas terá uma segunda olhada mais tarde. Espero que esteja tudo bem agora! Também havia project_ido caminho ainda no caso do padrão usando a dict. Isso pode levar a um comportamento aparentemente estranho, pois o argumento fornecido no dictsempre será usado (se bem me lembro).
jojo
@jojo Isso significa que um 'project_config / foo / bar' na 2ª opção passará automaticamente os kwargs {'project_id': 'bar'} para a visualização?
Original BBQ Sauce
9

Pensei em acrescentar um pouco à resposta.

Se você tiver várias definições de URL, precisará nomear cada uma delas separadamente. Portanto, você perde a flexibilidade ao chamar reverso, pois um reverso espera um parâmetro enquanto o outro não.

Outra maneira de usar o regex para acomodar o parâmetro opcional:

r'^project_config/(?P<product>\w+)/((?P<project_id>\w+)/)?$'
tarequeh
fonte
2
No Django 1.6, isso gera uma exceção para mim. Eu ficaria longe dissoReverse 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
fonte
0

Usar ? funcionar bem, você pode verificar em pythex . Lembre-se de adicionar os parâmetros * args e ** kwargs na definição dos métodos de exibição

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