Django – #40 – REST API cz. 15 – Aplikacja Django korzystająca z API DRF – get()

Wprowadzenie.

Wyobraźmy sobie, że skończyliśmy prace nad naszym API lub też API jest stworzone w takim stopniu, że może być wykorzystane przez zewnętrzne aplikacje. W związku z powyższym kolejnym krokiem, jaki warto poznać, jest korzystanie z REST API stworzonego przy pomocy Django Rest Framework, poprzez zwykłą aplikacją w Django. Warto w tym miejscu zaznaczyć, że przy takiej architekturze mamy w pełni oddzieloną warstwę przechowywania danych od warstwy interfejsu użytkownika.

Zakres artykułu.

  • Aplikacja Django korzystająca z API DRF – get()

Aplikacja Django korzystająca z API DRF – get()

W celu uproszczenia zagadnienia, gdzie aplikacja będzie korzystała z REST API, postanowiłem, aby obydwie części należały do tego samego projektu, w związku z czym, odchodzi nam tworzenie nowego projektu Django od nowa.

W pierwszej kolejności stwórzmy szablon html o nazwie base.html. Jeżeli chcecie dowiedzieć się więcej na temat szablonów, to zachęcam do zapoznania się z pozostałymi wpisami o Django, które znajdują się na blogu.

<!doctype html>
<html lang="en">
<head>
<!-- Required meta tags -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<!-- Bootstrap CSS -->
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" integrity="sha384-JcKb8q3iqJ61gNV9KGb8thSsNjpSL0n8PARn9HuZOnIxN0hoP+VmmDGMN5t9UJ0Z" crossorigin="anonymous">
<link rel="shortcut icon" href="#" />
<title>REST API APP!</title>
</head>
<body>
<div class="d-flex flex-column flex-md-row align-items-center p-3 px-md-4 mb-3 bg-white border-bottom shadow-sm">
<h5 class="my-0 mr-md-auto font-weight-normal">REST API APP</h5>
</div>
<!-- Optional JavaScript -->
<!-- jQuery first, then Popper.js, then Bootstrap JS -->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.1/dist/umd/popper.min.js" integrity="sha384-9/reFTGAW83EW2RDu2S0VKaIzap3H66lZH81PoYlFhbGU+6BZp6G7niu735Sk7lN" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js" integrity="sha384-B4gt1jrGC7Jh4AgTPSdUtOBvfO8shuf57BaghqFfPlYxofvL8/KUEfYiJOMMV+rV" crossorigin="anonymous"></script>
{% block content %}{% endblock %}
</body>
</html>
<!doctype html> <html lang="en"> <head> <!-- Required meta tags --> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> <!-- Bootstrap CSS --> <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" integrity="sha384-JcKb8q3iqJ61gNV9KGb8thSsNjpSL0n8PARn9HuZOnIxN0hoP+VmmDGMN5t9UJ0Z" crossorigin="anonymous"> <link rel="shortcut icon" href="#" /> <title>REST API APP!</title> </head> <body> <div class="d-flex flex-column flex-md-row align-items-center p-3 px-md-4 mb-3 bg-white border-bottom shadow-sm"> <h5 class="my-0 mr-md-auto font-weight-normal">REST API APP</h5> </div> <!-- Optional JavaScript --> <!-- jQuery first, then Popper.js, then Bootstrap JS --> <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.1/dist/umd/popper.min.js" integrity="sha384-9/reFTGAW83EW2RDu2S0VKaIzap3H66lZH81PoYlFhbGU+6BZp6G7niu735Sk7lN" crossorigin="anonymous"></script> <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js" integrity="sha384-B4gt1jrGC7Jh4AgTPSdUtOBvfO8shuf57BaghqFfPlYxofvL8/KUEfYiJOMMV+rV" crossorigin="anonymous"></script> {% block content %}{% endblock %} </body> </html>
<!doctype html>
<html lang="en">

  <head>
    <!-- Required meta tags -->
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">

    <!-- Bootstrap CSS -->
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" integrity="sha384-JcKb8q3iqJ61gNV9KGb8thSsNjpSL0n8PARn9HuZOnIxN0hoP+VmmDGMN5t9UJ0Z" crossorigin="anonymous">
    <link rel="shortcut icon" href="#" />
    <title>REST API APP!</title>
  </head>
  <body>
    <div class="d-flex flex-column flex-md-row align-items-center p-3 px-md-4 mb-3 bg-white border-bottom shadow-sm">
      <h5 class="my-0 mr-md-auto font-weight-normal">REST API APP</h5>
    </div>

    <!-- Optional JavaScript -->
    <!-- jQuery first, then Popper.js, then Bootstrap JS -->
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.1/dist/umd/popper.min.js" integrity="sha384-9/reFTGAW83EW2RDu2S0VKaIzap3H66lZH81PoYlFhbGU+6BZp6G7niu735Sk7lN" crossorigin="anonymous"></script>
    <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js" integrity="sha384-B4gt1jrGC7Jh4AgTPSdUtOBvfO8shuf57BaghqFfPlYxofvL8/KUEfYiJOMMV+rV" crossorigin="anonymous"></script>
    {% block content %}{% endblock %}
    </body>
</html>

W drugim kroku stwórzmy kolejny plik html o nazwie home.html, który będzie rozszerzał plik base.html. W pliku tym będę zamieszczał wyniki pracy. Taka struktura szablonów ma celu oddzielenie części kodu, która będzie się zmieniała od części kodu, która jest stała. 

{% extends 'restapiapp/base.html' %}
{% block content %}
<div class="container">
</div>
{% endblock %}
{% extends 'restapiapp/base.html' %} {% block content %} <div class="container"> </div> {% endblock %}
{% extends 'restapiapp/base.html' %}

{% block content %}
  <div class="container">

  </div>
{% endblock %}

Teraz stwórzmy prostą funkcję widoku, która będzie renderowała szablon home.html. i wykorzystywała do tego dane ze słownika context.

def home(request):
context = {}
return render(request, 'restapiapp/home.html', context)
def home(request): context = {} return render(request, 'restapiapp/home.html', context)
def home(request):
    context = {}

    return render(request, 'restapiapp/home.html', context)

Następnie stwórzmy ścieżkę, która połączy adres URL z funkcją nowo stworzonego widoku. 

urlpatterns = [
path('admin/', admin.site.urls),
# API
path('api/authors/', views.AuthorList.as_view()),
path('api/books/', views.BookList.as_view()),
path('api/borrows/', views.BorrowList.as_view()),
path('api/borrows/<int:pk>/', views.BorrowRetrieveDestroy.as_view()),
path('api/borrows/<int:pk>/return/', views.BorrowReturnBookUpdate.as_view()),
path('api/borrows/<int:pk>/edit/', views.BorrowRetrieveUpdate.as_view()),
path('api/user/create', views.UserCreate.as_view()),
path('api/user/login', views.UserTokenList.as_view()),
path('api/activate/<int:pk>/<str:s>', views.UserAccountActivation.as_view()),
# DRF
path('api-auth/', include('rest_framework.urls')),
# APP
path('', views.home, name='home'), # new
]
urlpatterns = [ path('admin/', admin.site.urls), # API path('api/authors/', views.AuthorList.as_view()), path('api/books/', views.BookList.as_view()), path('api/borrows/', views.BorrowList.as_view()), path('api/borrows/<int:pk>/', views.BorrowRetrieveDestroy.as_view()), path('api/borrows/<int:pk>/return/', views.BorrowReturnBookUpdate.as_view()), path('api/borrows/<int:pk>/edit/', views.BorrowRetrieveUpdate.as_view()), path('api/user/create', views.UserCreate.as_view()), path('api/user/login', views.UserTokenList.as_view()), path('api/activate/<int:pk>/<str:s>', views.UserAccountActivation.as_view()), # DRF path('api-auth/', include('rest_framework.urls')), # APP path('', views.home, name='home'), # new ]
urlpatterns = [
    path('admin/', admin.site.urls),

    # API
    path('api/authors/', views.AuthorList.as_view()),
    path('api/books/', views.BookList.as_view()),
    path('api/borrows/', views.BorrowList.as_view()),
    path('api/borrows/<int:pk>/', views.BorrowRetrieveDestroy.as_view()),
    path('api/borrows/<int:pk>/return/', views.BorrowReturnBookUpdate.as_view()),
    path('api/borrows/<int:pk>/edit/', views.BorrowRetrieveUpdate.as_view()),

    path('api/user/create', views.UserCreate.as_view()),
    path('api/user/login', views.UserTokenList.as_view()),
    path('api/activate/<int:pk>/<str:s>', views.UserAccountActivation.as_view()),

    # DRF
    path('api-auth/', include('rest_framework.urls')),

    # APP
    path('', views.home, name='home'), # new
]

Na chwilę obecną może sprawdzić, czy strona ładuje się poprawnie.

Gdy wszystko do tej pory działa poprawnie, wówczas możemy przejść do właściwej części tego wpisu. W pierwszej kolejności do pliku zawierającego widoki views.py zaimportujmy moduł requests.

import requests
import requests
import requests

Do słownika context dodajmy nowy klucz ‘authors‘ i przypiszmy do niego wynik następującej funkcji requests.get(‘http://127.0.0.1:8000/api/authors/’).json(). Pod tym linkiem znajdziecie więcej informacji o module requests

def home(request):
context = {}
context['authors'] = requests.get('http://127.0.0.1:8000/api/authors/').json()
return render(request, 'restapiapp/home.html', context)
def home(request): context = {} context['authors'] = requests.get('http://127.0.0.1:8000/api/authors/').json() return render(request, 'restapiapp/home.html', context)
def home(request):
    context = {}

    context['authors'] = requests.get('http://127.0.0.1:8000/api/authors/').json()

    return render(request, 'restapiapp/home.html', context)

Następnie, aby zobaczyć wynik naszego zapytania, w szablonie home.html wywołajmy zmienną authors.

{% extends 'restapiapp/base.html' %}
{% block content %}
<div class="container">
{{ authors }}
</div>
{% endblock %}
{% extends 'restapiapp/base.html' %} {% block content %} <div class="container"> {{ authors }} </div> {% endblock %}
{% extends 'restapiapp/base.html' %}

{% block content %}
  <div class="container">
    {{ authors }}
  </div>
{% endblock %}

Sprawdźmy jak prezentuje się nasza strona.

Jak możemy zobaczyć, dane prezentowane są w formacie json

Teraz idźmy o krok dalej i wykonajmy kolejne zapytanie do serwera API, żebyśmy otrzymali listę książek. Listę tę przypiszmy do klucza w słowniku o nazwie books.

def home(request):
context = {}
context['authors'] = requests.get('http://127.0.0.1:8000/api/authors/').json()
context['books'] = requests.get('http://127.0.0.1:8000/api/books/').json()
return render(request, 'restapiapp/home.html', context)
def home(request): context = {} context['authors'] = requests.get('http://127.0.0.1:8000/api/authors/').json() context['books'] = requests.get('http://127.0.0.1:8000/api/books/').json() return render(request, 'restapiapp/home.html', context)
def home(request):
    context = {}

    context['authors'] = requests.get('http://127.0.0.1:8000/api/authors/').json()
    context['books'] = requests.get('http://127.0.0.1:8000/api/books/').json()

    return render(request, 'restapiapp/home.html', context)

Zmodyfikujmy teraz szablon i wyświetlmy zawartość zmiennej books

{% extends 'restapiapp/base.html' %}
{% block content %}
<div class="container">
{{ authors }}
<br><br><br><br>
{{ books }}
</div>
{% endblock %}
{% extends 'restapiapp/base.html' %} {% block content %} <div class="container"> {{ authors }} <br><br><br><br> {{ books }} </div> {% endblock %}
{% extends 'restapiapp/base.html' %}

{% block content %}
  <div class="container">
    {{ authors }}
    <br><br><br><br>
    {{ books }}
  </div>
{% endblock %}

Sprawdźmy wynik naszej pracy.

Obecnie nie posiadamy już ograniczenia w postacie danych zwracanych tylko z jednego zapytania do API. Oznacza to, że widoki stron / aplikacji mogą zawierać dane z wielu różnych zapytań.

Teraz możecie zadawać sobie pytanie, jak dobrać się do konkretnych danych w ciągu danych w formacie json. Temat ten już był poruszany w poprzednich wpisach, lecz dla przypomnienia napiszę tu jeszcze raz jak to zrobić. 

Jeżeli mamy zwracaną listę, to do konkretnych elementów wystarczy, jak po kropce dodamy liczbę. W ten sposób ograniczymy się do jednego zestawu danych (można powiedzieć do jednego rekordu).

{% extends 'restapiapp/base.html' %}
{% block content %}
<div class="container">
{{ authors }}
<br><br><br><br>
{{ books }}
<br><br>
{{ books.0 }}
</div>
{% endblock %}
{% extends 'restapiapp/base.html' %} {% block content %} <div class="container"> {{ authors }} <br><br><br><br> {{ books }} <br><br> {{ books.0 }} </div> {% endblock %}
{% extends 'restapiapp/base.html' %}

{% block content %}
  <div class="container">
    {{ authors }}
    <br><br><br><br>
    {{ books }}
    <br><br>
    {{ books.0 }}
  </div>
{% endblock %}

Sprawdźmy, jak teraz wygląda strona.

Efekt jest coraz lepszy, lecz co zrobić, aby wydobyć jedynie tytuł. W tym celu ponownie musimy zastosować kropkę i wpisać interesujący nas klucz, w moim przypadku będzie to title.

{% extends 'restapiapp/base.html' %}
{% block content %}
<div class="container">
{{ authors }}
<br><br><br><br>
{{ books }}
<br><br>
{{ books.0 }}
<br><br>
{{ books.0.title }}
</div>
{% endblock %}
{% extends 'restapiapp/base.html' %} {% block content %} <div class="container"> {{ authors }} <br><br><br><br> {{ books }} <br><br> {{ books.0 }} <br><br> {{ books.0.title }} </div> {% endblock %}
{% extends 'restapiapp/base.html' %}

{% block content %}
  <div class="container">
    {{ authors }}
    <br><br><br><br>
    {{ books }}
    <br><br>
    {{ books.0 }}
    <br><br>
    {{ books.0.title }}
  </div>
{% endblock %}

Ponownie sprawdźmy efekt tej modyfikacji.

Obecnie wiemy, jak poruszać się po otrzymanych zbiorach danych. Sprawdźmy teraz, co się stanie, jak będziemy chcieli pobrać listę pozycji wypożyczonych borrows

def home(request):
context = {}
context['authors'] = requests.get('http://127.0.0.1:8000/api/authors/').json()
context['books'] = requests.get('http://127.0.0.1:8000/api/books/').json()
context['borrows'] = requests.get('http://127.0.0.1:8000/api/borrows/').json()
return render(request, 'restapiapp/home.html', context)
def home(request): context = {} context['authors'] = requests.get('http://127.0.0.1:8000/api/authors/').json() context['books'] = requests.get('http://127.0.0.1:8000/api/books/').json() context['borrows'] = requests.get('http://127.0.0.1:8000/api/borrows/').json() return render(request, 'restapiapp/home.html', context)
def home(request):
    context = {}

    context['authors'] = requests.get('http://127.0.0.1:8000/api/authors/').json()
    context['books'] = requests.get('http://127.0.0.1:8000/api/books/').json()
    context['borrows'] = requests.get('http://127.0.0.1:8000/api/borrows/').json()

    return render(request, 'restapiapp/home.html', context)

Zmodyfikujmy odpowiednio plik home.html

{% extends 'restapiapp/base.html' %}
{% block content %}
<div class="container">
{{ authors }}
<br><br><br><br>
{{ books }}
<br><br>
{{ books.0 }}
<br><br>
{{ books.0.title }}
<br><br><br><br>
{{ borrows }}
</div>
{% endblock %}
{% extends 'restapiapp/base.html' %} {% block content %} <div class="container"> {{ authors }} <br><br><br><br> {{ books }} <br><br> {{ books.0 }} <br><br> {{ books.0.title }} <br><br><br><br> {{ borrows }} </div> {% endblock %}
{% extends 'restapiapp/base.html' %}

{% block content %}
  <div class="container">
    {{ authors }}
    <br><br><br><br>
    {{ books }}
    <br><br>
    {{ books.0 }}
    <br><br>
    {{ books.0.title }}
    <br><br><br><br>
    {{ borrows }}
  </div>
{% endblock %}

I sprawdźmy, co otrzymamy. 

W tej chwili otrzymaliśmy informację, że nie jesteśmy uwierzytelnieni. Jak pamiętacie z poprzednich wpisów, uwierzytelnianie odbywa się na dwa sposoby. Możemy dodać do aplikacji system logowania, lecz tę funkcjonalność już pokazywałem w poprzednich wpisach a z drugiej strony, gdy stworzymy oddzielną aplikację to metoda logowania, będzie bezużyteczna.

W tym oto momencie przychodzi nam z pomocą uwierzytelnianie przy pomocy Tokena. Stwórzmy słownik o nazwie headers, dla danych wysyłanych w nagłówku zapytania. Zmienną headers zamieśćmy jako kolejny argument metody request.get(), z tym że przypiszmy go do zmiennej o tej samej nazwie. 

def home(request):
context = {}
headers = {
'Authorization': 'Token 000c74d3b3b816d90e19ea42926e5a7f950366cc',
}
context['authors'] = requests.get('http://127.0.0.1:8000/api/authors/').json()
context['books'] = requests.get('http://127.0.0.1:8000/api/books/').json()
context['borrows'] = requests.get('http://127.0.0.1:8000/api/borrows/').json()
context['borrows2'] = requests.get('http://127.0.0.1:8000/api/borrows/', headers=headers ).json()
return render(request, 'restapiapp/home.html', context)
def home(request): context = {} headers = { 'Authorization': 'Token 000c74d3b3b816d90e19ea42926e5a7f950366cc', } context['authors'] = requests.get('http://127.0.0.1:8000/api/authors/').json() context['books'] = requests.get('http://127.0.0.1:8000/api/books/').json() context['borrows'] = requests.get('http://127.0.0.1:8000/api/borrows/').json() context['borrows2'] = requests.get('http://127.0.0.1:8000/api/borrows/', headers=headers ).json() return render(request, 'restapiapp/home.html', context)
def home(request):
    context = {}
    headers = {
        'Authorization': 'Token 000c74d3b3b816d90e19ea42926e5a7f950366cc',
    }

    context['authors'] = requests.get('http://127.0.0.1:8000/api/authors/').json()
    context['books'] = requests.get('http://127.0.0.1:8000/api/books/').json()
    context['borrows'] = requests.get('http://127.0.0.1:8000/api/borrows/').json()
    context['borrows2'] = requests.get('http://127.0.0.1:8000/api/borrows/', headers=headers ).json()

    return render(request, 'restapiapp/home.html', context)

W powyższym fragmencie kodu wartość tokena dla klucza Authorization zahardkodowałem, co jest oczywiście błędną praktyką. Token jest zmienną przypisaną do użytkownika, więc musi się zmienić w zależności, kto korzysta z aplikacji. Ponadto wartość tokena powinna być umieszczona w takim miejscu, aby nikt inny nie mógł jej podejrzeć, ponieważ dzięki temu kluczowi jest w stanie się pod nas podszywać, w zakresie takim, na jaki pozwala API

Standardowo zmodyfikujmy szablon home.html

{% extends 'restapiapp/base.html' %}
{% block content %}
<div class="container">
{{ authors }}
<br><br><br><br>
{{ books }}
<br><br>
{{ books.0 }}
<br><br>
{{ books.0.title }}
<br><br><br><br>
{{ borrows }}
<br><br>
{{ borrows2 }}
</div>
{% endblock %}
{% extends 'restapiapp/base.html' %} {% block content %} <div class="container"> {{ authors }} <br><br><br><br> {{ books }} <br><br> {{ books.0 }} <br><br> {{ books.0.title }} <br><br><br><br> {{ borrows }} <br><br> {{ borrows2 }} </div> {% endblock %}
{% extends 'restapiapp/base.html' %}

{% block content %}
  <div class="container">
    {{ authors }}
    <br><br><br><br>
    {{ books }}
    <br><br>
    {{ books.0 }}
    <br><br>
    {{ books.0.title }}
    <br><br><br><br>
    {{ borrows }}
    <br><br>
    {{ borrows2 }}
  </div>
{% endblock %}

I na koniec sprawdźmy efekt naszej pracy.

Autor artykułu
Dominik Bednarski

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany.