Pisanie pierwszej aplikacji Django, część 3.

Ten tutorial zaczyna się, gdzie skończył się Tutorial 2. Kontynuujemy z aplikacją web-ankiet i skupimy się na tworzeniu publicznego interfejsu – „widoków”.

Przegląd

Widok jest „typem” strony internetowej w tworzej aplikacji Django, który w ogólności służy określonej funkcji i ma określony szablon. Na przykład w aplikacji blogowej, możesz mieć następujące widoki:

  • Strona główna bloga – wyświetla kilka najnowszych wpisów.
  • Strona „szczegółów” wpisu – strona na pojedynczy wpis.
  • Roczna strona archiwum – wyświetla wszystkie miesiące z wpisami w danym roku.
  • Miesięczna strona archiwum – wyświetla wszystkie dni z wpisami w danym miesiącu.
  • Dzienna strona archiwum – wyświetla wszystkie wpisy z danego dnia.
  • Komentowanie – obsługuje dodawanie komentarzy do danego wpisu.

W naszej aplikacji ankietowej będziemy mieć następujące cztery widoki:

  • Strona „główna” pytań – wyświetla klika najnowszych pytań.
  • Strona „szczegółów” pytania – wyświetla treść pytania, bez wyników, ale z formularzem do głosowania.
  • Strona „wyników” pytania – wyświetla wyniki dla wybranego pytania.
  • Akcja głosowania – obsługuje głosowanie na konkretny wybór w wybranym pytaniu.

W Django strony i inna treść są dostarczane przez widoki. Każdy widok jest reprezentowany przez prostą funkcję Pythona (lub metodę, w przypadku widoków opartych na klasach). Django wybierze widok sprawdzając URL, który jest zażądany (aby być dokładnym, część URL-a po nazwie domeny).

Teraz w swoim czasie w Internecie można natknąć się na takie piękności jak „ME2/Sites/dirmod.asp?sid=&type=gen&mod=Core+Pages&gid=A6CD4967199A42D9B65B1B”. Będziesz zadowolony wiedząc, że Django pozwala nam na znacznie bardziej eleganckie wzorce adresów URL niż ten.

Wzorzec URL jest po prostu ogólną formą URL-a – na przykład: /newsarchive/<year>/<month>/.

Aby przejść z URL-a do widoku, Django używa czegoś, co znane jest jako «URLconfs». URLconf mapuje wzorce URL na widoki.

Ten tutorial zawiera podstawowe instrukcje użytkowania URLconfów. Możesz przejść do URL dispatcher po więcej informacji.

Pisanie kolejnych widoków

Teraz dodajmy kilka więcej widoków do polls/views.py. Te widoki są nieco inne, bo biorą argument:

polls/views.py
def detail(request, question_id):
    return HttpResponse("You're looking at question %s." % question_id)

def results(request, question_id):
    response = "You're looking at the results of question %s."
    return HttpResponse(response % question_id)

def vote(request, question_id):
    return HttpResponse("You're voting on question %s." % question_id)

Podłącz te nowe widoki do modułu polls.urls dodając następujące wywołania path():

polls/urls.py
from django.urls import path

from . import views

urlpatterns = [
    # ex: /polls/
    path('', views.index, name='index'),
    # ex: /polls/5/
    path('<int:question_id>/', views.detail, name='detail'),
    # ex: /polls/5/results/
    path('<int:question_id>/results/', views.results, name='results'),
    # ex: /polls/5/vote/
    path('<int:question_id>/vote/', views.vote, name='vote'),
]

Spójrz na „/polls/34/” w swojej przeglądarce. Uruchomi to metodę detail() i wyświetli jakiekolwiek ID podasz w URL-u. Spróbuj też „/polls/34/results/” i „/polls/34/vote/” – te wyświetlą strony wyników i głosowania.

Kiedy ktoś żąda strony z twojej witryny – powiedzmy „/polls/34/”, Django załaduje moduł mysite.urls Pythona, ponieważ jest wskazany przez ustawienie ROOT_URLCONF. Odnajduje on zmienną nazwaną urlpatterns i trawersuje po kolei wzorce. Po odnalezieniu dopasowania w 'polls/', odcina pasujący tekst („polls/”) i wysyła pozostały tekst – „34/” – do URLconfa «polls.urls» do dalszego przetwarzania. Tam dopasowuje go do '<int:question_id>/', co kończy się wywołaniem widoku detail() w ten sposób:

detail(request=<HttpRequest object>, question_id=34)

Część question_id=34 pochodzi z <int:question_id>. Użycie nawiasów trójkątnych „wyłapuje” część URL-a i wysyła ją jako argument do funkcji widoku. Część :question_id> ciągu znaków określa nazwę, która zostanie użyta, aby zidentyfikować dopasowany wzorzec, a część <int: jest konwerterem, który określa jakie wzorce powinny pasować do tej części ścieżki URL.

Nie ma potrzeby dodawać wstawek do URL-i typu .html – chyba że tego chcesz, w takim przypadku możesz zrobić coś takiego:

path('polls/latest.html', views.index),

Ale nie rób tego. To głupie.

Pisanie widoków, które faktycznie coś robią

Każdy widok jest odpowiedzialny za zrobienie jednej z dwóch rzeczy: zwrócenie obiektu HttpResponse zawierającego treść dla żądanej strony lub rzucenie wyjątku takiego jak Http404. Reszta zależy od ciebie.

Twój widok może czytać rekordy z bazy danych lub nie. Może używać systemu szablonów takiego jak Django – lub innego systemu szablonów Python – lub nie. Może generować plik PDF, zwracać XML, tworzyć w locie plik ZIP, cokolwiek chcesz, używając dowolnych bibliotek Pythona, których chcesz.

Wszystko, czego chce Django, to ten HttpResponse. Lub wyjątek.

Użyjmy własnego bazodanowego API Django, które omówiliśmy w Tutorialu 2, ponieważ jest wygodne. Tu jest próba w nowym widoku index(), która wyświetla ostatnie 5 pytań ankietowych w systemie, oddzielone przecinkami, według daty publikacji:

polls/views.py
from django.http import HttpResponse

from .models import Question


def index(request):
    latest_question_list = Question.objects.order_by('-pub_date')[:5]
    output = ', '.join([q.question_text for q in latest_question_list])
    return HttpResponse(output)

# Leave the rest of the views (detail, results, vote) unchanged

Chociaż mamy tutaj problem: design strony jest zahardcodowany w widoku. Jeśli chcesz zmienić sposób, w jaki wygląda strona, będziesz musiał zmienić ten kod Pythona. Użyjmy systemu szablonów Django, aby oddzielić design od Pythona tworząc szablon, który będzie mógł użyć widok.

Najpierw stwórzmy katalog o nazwie templates w twoim katalogu polls. Django będzie tam szukał szablonów.

Ustawienie TEMPLATES twojego projektu opisuje jak Django będzie ładował i renderował szablony. Domyślny plik ustawień konfiguruje backend DjangoTemplates, którego opcja APP_DIRS jest ustawiona na True. Według konwencji DjangoTemplates szuka podkatalogu „templates” w każdej z INSTALLED_APPS.

W katalogu templates, który właśnie stworzyłeś, stwórz kolejny katalog o nazwie polls i w tym katalogu stwórz plik o nazwie index.html. Innymi słowy, twój szablon powinien być pod polls/templates/polls/index.html. Ponieważ loader szablonów app_directories działa jak opisano powyżej, możesz odwoływać się do tego szablonu w Django po prostu polls/index.html.

Przestrzeń nazw szablonu

Teraz moglibyśmy poradzić sobie umieszczając nasze szablony bezpośrednio w polls/templates (zamiast tworzyć kolejny podkatalog polls), ale w rzeczywistości byłoby to złym pomysłem. Django wybierze pierwszy szablon, którego nazwa się zgadza i jeśli miałbyś szablon o takiej samej nazwie w innej aplikacji, Django nie byłoby w stanie ich odróżnić. Musimy być w stanie wskazać Django odpowiedni szablon i najprostszym sposobem, aby to zapewnić jest znamespace’owanie ich. To znaczy umieszczenie tych szablonów w innym katalogu nazwanym dla samej aplikacji.

Umieść następujący kod w tym szablonie:

polls/templates/polls/index.html
{% if latest_question_list %}
    <ul>
    {% for question in latest_question_list %}
        <li><a href="/polls/{{ question.id }}/">{{ question.question_text }}</a></li>
    {% endfor %}
    </ul>
{% else %}
    <p>No polls are available.</p>
{% endif %}

Teraz zaktualizujmy nasz widok index w polls/views.py, aby używał szablonu:

polls/views.py
from django.http import HttpResponse
from django.template import loader

from .models import Question


def index(request):
    latest_question_list = Question.objects.order_by('-pub_date')[:5]
    template = loader.get_template('polls/index.html')
    context = {
        'latest_question_list': latest_question_list,
    }
    return HttpResponse(template.render(context, request))

Ten kod ładuje szablon o nazwie polls/index.html i przekazuje mu kontekts. Kontekst jest słownikiem mapującym nazwy zmiennych szablonu do obiektów Pythona.

Załaduj stronę kierując swoją przeglądarkę na „/polls/”. Powinnieneś zobaczyć wypunktowaną listę zawierającą pytanie „What’s up” z Tutoriala 2. Link kieruje do strony szczegółów pytania.

Skrót: render()

To popularny idiom do ładowania szablonu, wypełniania kontekstu i zwracania obiektu HttpResponse z wynikiem wyrenderowanego szablonu. Djano daje skrót. Tutaj jest pełen widok index(), napisany na nowo:

polls/views.py
from django.shortcuts import render

from .models import Question


def index(request):
    latest_question_list = Question.objects.order_by('-pub_date')[:5]
    context = {'latest_question_list': latest_question_list}
    return render(request, 'polls/index.html', context)

Zwróć uwagę, że kiedy zrobiliśmy to w tych wszystkich widokach, nie potrzebujemy już importować loader i HttpResponse (będziesz chciał zatrzymać HttpResponse jeśli wciąż masz zaczątki metod dla detail, results i vote).

Funkcja render() bierze obiekt request jako swój pierwszy argument, nazwę szablonu jako drugi argument i słownik jako swój opcjonalny trzeci argument. Zwraca obiekt HttpResponse danego szablonu wyrenderowany z danym kontekstem.

Zgłaszanie błędu 404

Teraz zawalczmy z widokiem szczegółów pytania – strony, która wyświetla treść pytania dla danej ankiety. Tutaj jest widok:

polls/views.py
from django.http import Http404
from django.shortcuts import render

from .models import Question
# ...
def detail(request, question_id):
    try:
        question = Question.objects.get(pk=question_id)
    except Question.DoesNotExist:
        raise Http404("Question does not exist")
    return render(request, 'polls/detail.html', {'question': question})

Nowy koncept tutaj: Widok zgłasza wyjątek Http404 jeśli pytanie o żądanym ID nie istnieje.

Omówimy trochę później co mógłbyś umieścić w tym szablonie polls/detail.html, ale jeśli chcesz, aby powyższy przykład szybko zaczął działać, plik zawierający tylko:

polls/templates/polls/detail.html
{{ question }}

pomoże ci wystartować na teraz.

Skrót: get_object_or_404()

To popularny idiom do używania get() i zgłaszania Http404 jeśli obiekt nie istnieje. Django daje skrót. Tutaj jest widok detail() przepisany:

polls/views.py
from django.shortcuts import get_object_or_404, render

from .models import Question
# ...
def detail(request, question_id):
    question = get_object_or_404(Question, pk=question_id)
    return render(request, 'polls/detail.html', {'question': question})

Funkcja get_object_or_404() bierze model Django jako swój pierwszy argument i wyznaczoną wcześniej liczbę arguementów nazwanych, które przekazuje do funkcji get() menadżera modelu. Zgłasza Http404 jeśli obiekt nie istnieje.

Filozofia

Dlaczego używamy funkcji pomocniczej get_object_or_404() zamiast automatycznie wyłapywać wyjątki ObjectDoesNotExist na wyższym poziomie lub powodując, że API modelu będzie zgłaszać Http404 zamiast ObjectDoesNotExist?

Ponieważ to związałoby warstwę modelu z warstwą widoku. Jednym z najważniejszych celów projektowych Django było utrzymywania luźnych więzów. Trochę kontrolowanego wiązania jest wprowadzone w module django.shortcuts.

Jest też funkcja get_list_or_404(), która działa tak samo jak get_object_or_404() – z wyjątkiem używania filter() zamiast get(). Zgłasza wyjątek Http404 gdy lista jest pusta.

Użyj systemu szablonów

Wracamy do widoku detail() naszej aplikacji ankietowej. Mając zmienną kontekstową question, tak mógłby wyglądać nasz szablon polls/detail.html:

polls/templates/polls/detail.html
<h1>{{ question.question_text }}</h1>
<ul>
{% for choice in question.choice_set.all %}
    <li>{{ choice.choice_text }}</li>
{% endfor %}
</ul>

System szablonów używa składni kropkowego lookupu, aby dostać się do atrybutów zmiennej. W przykładzie {{ question.question_text }}, najpierw Django robi słownikowy lookup na obiekcie question. Gdy to się nie uda, próbuje atrybutowego lookupu – który w tym przypadku działa. Gdyby atrybutowy lookup się nie powiódł, spróbowałoby lookupu przez indeks listy.

Wywoływanie metod dzieje się w pętli {% for %}: question.choice_set.all jest interpretowane jako kod Pythona question.choice_set.all(), który zwraca iterator obiektów Choice i jest odpowiedni do użycia w tagu {% for %}.

Zobacz przewodnik po szablonach po więcej informacji na temat szablonów.

Usuwanie wstawionych na sztywno URL-i z szablonów

Pamiętasz, kiedy wpisaliśmy link do pytania w szablonie polls/index.html, link był częściowo wpisany na sztywno, w ten sposób:

<li><a href="/polls/{{ question.id }}/">{{ question.question_text }}</a></li>

Problem z tym podejściem z hardkodowaniem jest taki, że staje się niezwykle trudna zmiana URL-i w projektach z dużą liczbą szablonów. Jednakże, odkąd zdefiniowałeś nazwany argument w funkcjach path() w module polls.urls, możesz usunąć poleganie na poszczególnych ścieżkach URL zdefiniowanych w twoich konfiguracjach URL-i używając szablonowego taga {% url %}:

<li><a href="{% url 'detail' question.id %}">{{ question.question_text }}</a></li>

Działa to przez sprawdzenie definicji URL-a według specyfikacji w module polls.urls. Możesz zobaczyć poniżej, gdzie dokładnie nazwa «detail» jest zdefiniowana w URL-u:

...
# the 'name' value as called by the {% url %} template tag
path('<int:question_id>/', views.detail, name='detail'),
...

Jeśli chcesz zmienić URL widoku szczegółów ankiety na jakiś inny, na przykład na coś w stylu polls/specifics/12/, zamiast robić to w szablonie (lub szablonach), zmieniłbyś to w polls/urls.py:

...
# added the word 'specifics'
path('specifics/<int:question_id>/', views.detail, name='detail'),
...

Przestrzenie nazw dla nazw URL-i

Projekt tutorialowy ma tylko jedną aplikację, polls. W prawdziwych projektach Django może być pięć, dziesięć, dwadzieścia lub więcej aplikacji. Jak Django odróżnia nazwy URL pomiędzy nimi? Na przykład aplikacja polls ma widok detail i tak samo mogłaby mieć aplikacja blogowa w tym samym projekcie. Jak zrobić, aby Django wiedziało, której aplikacji widok stworzyć dla urla w przypadku użycia taga {% url %}?

Odpowiedzią jest dodanie przestrzeni nazw do twojego URLconfa. W pliku polls/urls.py dodaj app_name, aby ustawić przestrzeń nazw aplikacji:

polls/urls.py
from django.urls import path

from . import views

app_name = 'polls'
urlpatterns = [
    path('', views.index, name='index'),
    path('<int:question_id>/', views.detail, name='detail'),
    path('<int:question_id>/results/', views.results, name='results'),
    path('<int:question_id>/vote/', views.vote, name='vote'),
]

Teraz zmień swój szablon polls/index.html z:

polls/templates/polls/index.html
<li><a href="{% url 'detail' question.id %}">{{ question.question_text }}</a></li>

aby wskazywał na widok szczegółów w przestrzeni nazw:

polls/templates/polls/index.html
<li><a href="{% url 'polls:detail' question.id %}">{{ question.question_text }}</a></li>

Jeśli czujesz się dobrze z pisaniem widoków, przeczytaj część czwartą tego tutoriala, aby dowiedzieć się na temat przetwarzania prostych formularzy i widokach generycznych.

Back to Top