Opcjonalne parametry adresu URL w Django

161

Mam taki adres URL Django:

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

Problem w tym, że chcę, aby project_idparametr był opcjonalny.

Chcę /project_config/i /project_config/12345abdce/być równie ważne wzorców URL, tak, że jeśli project_id zostanie przyjęta, wtedy mogę go używać.

W tej chwili otrzymuję kod 404, gdy uzyskuję dostęp do adresu URL bez project_idparametru.

Darwin Tech
źródło

Odpowiedzi:

381

Istnieje kilka podejść.

Jednym z nich jest użycie nieprzechwytywanej grupy w wyrażeniu regularnym: (?:/(?P<title>[a-zA-Z]+)/)?
Ustalenie opcjonalnego tokenu adresu URL Regex Django

Innym, łatwiejszym do naśladowania sposobem jest posiadanie wielu reguł, które odpowiadają Twoim potrzebom, a wszystkie wskazują ten sam widok.

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

Pamiętaj, że w swoim widoku musisz również ustawić wartość domyślną dla opcjonalnego parametru adresu URL, w przeciwnym razie pojawi się błąd:

def foo(request, optional_parameter=''):
    # Your code goes here
Yuji „Tomita” Tomita
źródło
68
Głosuj na opcję wielu tras. +1
Burhan Khalid,
4
@Yuji - czy nie możesz rozwiązać problemu z cofaniem poprzez nazwanie każdego wzorca adresu URL?
Ted,
8
czy każdemu widokowi możemy nadać tę samą nazwę?
Eugene
2
@ Yuji'Tomita'Tomita Wiem, więc odpowiedź na pytanie eugene brzmi niestety, nie możemy normalnie mieć wielu widoków o tej samej nazwie, nawet jeśli implementujemy je jako sposób na uzyskanie opcjonalnych parametrów.
nnyby
2
@eugene Tak możemy mieć dwa adresy URL o tej samej nazwie, cofania będzie elegancko odebrać w zależności od przypadku w zależności od args
Arpit Singh
37

Możesz używać zagnieżdżonych tras

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

Jest to o wiele bardziej SUCHE (powiedzmy, że chcesz zmienić nazwę productkwarg na product_id, wystarczy zmienić wiersz 4, a wpłynie to na poniższe adresy URL.

Edytowane dla Django 1.8 i nowszych

Jacob Valenta
źródło
1
Zagnieżdżone jest dobre. Ponadto wyraźniej oddziela różne sekcje adresu URL w kodzie (ze względu na użycie wcięć)
Patrick
Problem z zagnieżdżonymi polega na tym, że jeśli masz wiele opcjonalnych parametrów, to ostatecznie nie jesteś DRY, ponieważ na przykład z 3 opcjonalnymi parametrami masz 8 różnych kombinacji możliwych adresów URL. Musisz obsłużyć wystąpienie parametru 1, parametr 1 nie występuje, ale parametr 2 występuje, a parametr 1 i 2 nie występuje, ale występuje parametr 3. Paragraf adresu URL będzie DUŻO trudniejszy do odczytania niż pojedynczy ciąg z wieloma opcjonalnymi parametrami. Użycie stałych symbolicznych dla opcjonalnych podciągów parametrów sprawiłoby, że byłby bardzo łatwy do odczytania i byłby tylko jeden adres URL.
Bogatyr
Myślę, że masz rację, ale to raczej wynik złego wyglądu / projektu adresu URL. Ten przykład można przerobić, aby był znacznie lepszy.
Jacob Valenta,
„mieszkanie jest lepsze niż zagnieżdżone”
pjdavis
30

Jeszcze prostsze jest użycie:

(?P<project_id>\w+|)

„(A | b)” oznacza a lub b, więc w twoim przypadku byłby to jeden lub więcej znaków wyrazu (\ w +) lub nic.

Więc wyglądałoby to tak:

url(
    r'^project_config/(?P<product>\w+)/(?P<project_id>\w+|)/$',
    'tool.views.ProjectConfig',
    name='project_config'
),
Juan José Brown
źródło
9
Podoba mi się prostota tego rozwiązania, ale uważaj: robiąc to, widok nadal otrzyma wartość argumentu, która będzie None. Oznacza to, że nie możesz w tym celu polegać na domyślnej wartości w sygnaturze widoku: musisz jawnie przetestować ją wewnątrz i przypisać w konsekwencji.
Anto,
To jest, którego szukałem =)
Mike Brian Olivera
3
a co z ostatnim ukośnikiem w przypadku braku project_id?
iamkhush
Możesz po prostu dodać? po ukośniku lub po prostu uwzględnij ukośnik we wzorcu project_id
Juan José Brown
18

Wersja Django> 2.0 :

Podejście jest zasadniczo identyczne z podejściem podanym w odpowiedzi Yuji „Tomita” Tomita . Problem dotyczy jednak składni:

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

Za pomocą path() można również przekazać dodatkowe argumenty do widoku z opcjonalnym argumentem kwargstypu dict. W tym przypadku widok nie potrzebuje wartości domyślnej dla atrybutu project_id:

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

Aby dowiedzieć się, jak to się robi w najnowszej wersji Django , zobacz oficjalną dokumentację dotyczącą wysyłania adresów URL .

jojo
źródło
1
Myślę, że pomyliłeś project_id i product_id w swoim kodzie, prawda?
Andreas Bergström
@ AndreasBergström wielkie dzięki za zwrócenie na to uwagi! masz rację! Poprawiłem to w pośpiechu, ale przyjrzymy się temu później. Mam nadzieję, że teraz jest dobrze! W project_idścieżce znajdował się również obrazek w przypadku domyślnego użycia pliku dict. Może to prowadzić do pozornie dziwnego zachowania, ponieważ argument podany w dicttestamencie będzie zawsze używany (o ile dobrze pamiętam).
jojo
@jojo Czy to oznacza, że ​​'project_config / foo / bar' w drugiej opcji automatycznie przekaże kwargi {'project_id': 'bar'} do widoku?
Oryginalny sos BBQ
9

Pomyślałem, że dodam trochę do odpowiedzi.

Jeśli masz wiele definicji adresów URL, musisz nadać im nazwy osobno. Więc tracisz elastyczność podczas wywoływania reverse, ponieważ jeden reverse będzie oczekiwał parametru, a drugi nie.

Inny sposób użycia wyrażenia regularnego w celu dostosowania opcjonalnego parametru:

r'^project_config/(?P<product>\w+)/((?P<project_id>\w+)/)?$'
tarequeh
źródło
2
W Django 1.6 rzuca to dla mnie wyjątek. Trzymałbym się z daleka od tegoReverse 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
źródło
0

Posługiwać się ? działa dobrze można sprawdzić na pythex . Pamiętaj, aby dodać parametry * args i ** kwargs w definicji metod widoku

url('project_config/(?P<product>\w+)?(/(?P<project_id>\w+/)?)?', tool.views.ProjectConfig, name='project_config')
franciscorode
źródło