Django

Request to Response - views

9 lutego 2016 21:00
Paweł Kucmus

Ten artykuł jest kontynuacją czteroczęściowego bloku o Django. Jeśli jeszcze tego nie zrobiłeś powinieneś najpierw odwiedzić: Wprowadzenie do Django oraz Django i bazy danych

Od żądania (request) po odpowiedź (response)

Obsługując żądania HTTP - czyli robiąc to czym powinna zajmować się aplikacja webowa - interesują nas generalnie tylko owe żądania i co na ich podstawie zwrócić. Bardzo uprościmy temat budowy poszczególnych elementów wymiany danych protokołem HTTP ale poznane tu podstawy pozwolą nam na swobodną pracę z aplikacjami webowymi np. zbudowanymi przy użyciu Django. Po raz kolejny wyróżnić chcę dwie podstawowe rzeczy:

Request

Czyli nasze żądanie, które wysyłamy z klienta HTTP (np. Firefox) do serwera HTTP (np. serwera PyPiła). W żądaniu wyróżniamy kila podstawowych rzeczy:

  • metodę żądania: GET, POST, PUT, DELETE i inne
  • ścieżkę, np.: / czy /blog/django-introduction
  • parametry czyli wartości po znaku ? w ścieżce
  • payload, możliwy w przypadku POST i PUT
  • nagłówki, np.: AUTHORIZATION
  • typ zawartości, o tym później

Response

Odpowiedzi zbudowane na podstawie tego co zostało zażądane i odesłane z serwera HTTP z powrotem do klienta HTTP, najważniejsze elementy to:

  • status: 200 - OK, 404 - not found, 500 - server error i więcej
  • zawartość, np.: treść HTML ze stroną bloga
  • typ zawartości, np.: określający, że zwracamy text/html

Cykl życia żądania

W praktyce, kiedy wpisujecie w przeglądarce adres http://pypila.stxnext.pl i wciskacie enter, dzieją się następujące rzeczy:

  • wasza przeglądarka buduje żądanie i kieruje je na adres naszego bloga
  • adres zostaje przetłumaczony z tekstu pypila.stxnext.pl na publiczny adres IP naszego serwera
  • żądanie dociera do naszej aplikacji Django
  • nasza aplikacja na podstawie tego o co została poproszona zwraca to lub nie przeglądarce
  • przeglądarka wyświetla (renderuje) zwróconą zawartość

Implementacja pierwszego widoku

Function based

Generalnie przyjdzie edytować nam dwa pliki views.py i urls.py. W pierwszym piszemy widok:

from django.http import HttpResponse

def hello_world(request):
    return HttpResponse('Hello world!')

jest to prosta funkcja przyjmująca obiekt request (budowana przez Django reprezentacja żądania) i zwracająca zawsze ten sam tekst. Teraz w urls.py - pliku domyślnie znajdującym się w katalogu głównym (obok settings.py):

from django.conf.urls import url
from django.contrib import admin

from blog import views

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^', views.hello_world),
]

Uruchamiamy runserver, z przeglądarki wchodzimy na http://127.0.0.1:8000/ (jest to zapytanie GET) i widzimy nasze "Hello World!".


Możemy do widoku podać parametry (URL params):

# urls.py
urlpatterns = [
    # ...
    url(r'^(?P<name>\w+)/', views.hello_name),
]

# views.py def hello_name(request, name): return HttpResponse('Hello {}!'.format(name))

I wchodzimy na http://127.0.0.1:8000/pawel/


Wyciągać dane z bazy przy pomocy modeli:

# urls.py
urlpatterns = [
    # ...
    url(r'^(?P<id>\d+)/', views.hello_family),
]

# views.py from blog.models import CzlonekRodziny

def hello_family(request, id): person = CzlonekRodziny.objects.get(pk=id) return HttpResponse('Hello {person.imie} {person.nazwisko}!'.format(person=person))

Class based

We wcześniejszym przykładzie bazowaliśmy na metodach, które były mapowane do odpowiednich adresów. Do tworzenia widoków możemy również wykorzystać klasy. W tym celu edytujemy plik views.py oraz urls.py

from django.http import HttpResponse
from django.views.generic import View

from blog.models import CzlonekRodziny
# Create your views here.


class CzlonkowieRodziny(View):

    def get(self, request, **kwargs):
        members = CzlonekRodziny.objects.all()

        return HttpResponse(members)

# urls.py
from django.conf.urls import url
from django.contrib import admin

from blog import views


urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^czlonkowie_rodziny', views.CzlonkowieRodziny.as_view())
]

Lista członków rodziny

Wiemy już jak tworzyć widoki i wyświetlać na nich różne dane. Utwórzmy widok, który będzie wyświetlał nam listę ze wszystkimi członkami rodziny. Poprzednie rozwiązania są wystarczające, gdy chcemy wyświetlić prosty tekst zawierające małą ilość danych. Przy wyświetlaniu większej ilości danych zaczyna się robić bałagan w naszym kodzie i zarządzanie elementami, które chcemy wyświetlić staje się coraz trudniejsze. Z pomocą przychodzą nam szablony. Szablony zawierają statyczne części naszego HTML oraz specjalny kod który określa jak wyświetlać dynamiczne dane. Django domyślnie w szablonach używa Django template language. Nasze szablony dodajemy w katalogu templates w naszej aplikacji (<app_name>/templates/<template_name>).

Czas na przykład:

Tworzymy nowy szablon o nazwie czlonkowie_rodziny.html

# czlonkowie_rodziny.html
<html>
  <head>
    <title></title>
  </head>
  <body>
    <ul>
      {% for member in members %}
        <li>{{ member.imie }} {{ member.nazwisko }}</li>
      {% endfor %}
    </ul>
</body>
</html>


Następnie edytujemy views.py

from django.shortcuts import render
from django.views.generic import View

from blog.models import CzlonekRodziny

class CzlonkowieRodziny(View): def get(self, request): context = { 'members': CzlonekRodziny.objects.all() } return render(request, 'lista_czlonkow.html', context=context)

Więcej o szablonach dowiemy się na kolejnych zajęciach.

Własne API

Zainstalujcie narzędzie o nazwie Postman.

# decorators.py

import json


def parse_json(func):
    def parse(self, request, *args, **kwargs):
        request.data = json.loads(request.read())
        return func(self, request, *args, **kwargs)
    return parse

# views.py

from django.http import JsonResponse
from django.views.generic import View

from blog.models import CzlonekRodziny
from blog.decorators import parse_json
# Create your views here.


class CzlonkowieRodzinyAPIView(View):

    def filter_queryset(self, request, pk):
        qs = CzlonekRodziny.objects.filter()
        if pk:
            qs = qs.filter(pk=pk)
        else:
            if 'imie' in request.GET:
                qs = qs.filter(imie__in=request.GET.getlist('imie'))
            if 'nazwisko' in request.GET:
                qs = qs.filter(nazwisko__in=request.GET.getlist('nazwisko'))
            if 'wiek' in request.GET:
                qs = qs.filter(wiek__in=request.GET.getlist('wiek'))

        return qs

    def get(self, request, pk=None, *args, **kwargs):
        result = self.filter_queryset(request, pk)

        return JsonResponse({'result': list(result.values())})

    @parse_json
    def post(self, request, *args, **kwargs):
        result = {}
        try:
            CzlonekRodziny.objects.create(**request.data)
            status = 201
        except Exception as e:
            result = {'errors': e.message}
            status = 400
        finally:
            return JsonResponse(result, status=status)

    @parse_json
    def put(self, request, pk, *args, **kwargs):
        result = self.filter_queryset(request, pk)
        result.update(**request.data)

        return JsonResponse({'result': list(result.values())})

    def delete(self, request, pk, *args, **kwargs):
        status = 200
        result = self.filter_queryset(request, pk)
        if result:
            result.delete()
        else:
            status = 400

        return JsonResponse({}, status=status)

# urls.py
from django.conf.urls import url
from django.contrib import admin

from django.views.decorators.csrf import csrf_exempt
from blog import views
urlpatterns = [
    ...
    url(
        r'^api/czlonkowie_rodziny/$',
        csrf_exempt(views.CzlonkowieRodzinyAPI.as_view())
    ),
    url(
        r'^api/czlonkowie_rodziny/(?P<pk>\d+)/$',
        csrf_exempt(views.CzlonkowieRodzinyAPI.as_view())
    )
]

Comments