Эта статья является исчерпывающим руководством для начинающих программистов, желающих разрабатывать проекты с RESTful интерфейсами, используя при этом Python, Django и Django Rest Framework.
Введение
- Django — это веб-фреймворк, написанный на Python.
- Python — это высокоуровневый интерпретируемый язык программирования общего назначения.
- API (Application Programming Interface, интерфейс приложения) — это набор правил и механизмов, посредством которых приложение или его части взаимодействуют с другими приложениями.
- REST (Representational State Transfer) — это программная архитектура интерфейсов REST.
Как сказано в диссертации Роя Филдинга: «REST — это архитектурный стиль, который в основном использует существующие технологии и протоколы интернета. Попросту говоря, это представление данных для клиента в подходящем для него формате.»
Следовательно, RESTful + API (интерфейс) является широко используемой терминологией для реализации такой архитектуры и связанных с ней ограничений (например, в веб-сервисах).
Вот пример запроса GET
из интерфейса Github:
$ curl https://api.github.com/users/joshuadeguzman
Вы получите примерно следующее:
{ "login": "joshuadeguzman", "id": 20706361, "node_id": "MDQ6VXNlcjIwNzA2MzYx", "avatar_url": "https://avatars1.githubusercontent.com/u/20706361?v=4", "gravatar_id": "", "url": "https://api.github.com/users/joshuadeguzman", "html_url": "https://github.com/joshuadeguzman", "followers_url": "https://api.github.com/users/joshuadeguzman/followers", "following_url": "https://api.github.com/users/joshuadeguzman/following{/other_user}", "gists_url": "https://api.github.com/users/joshuadeguzman/gists{/gist_id}", "starred_url": "https://api.github.com/users/joshuadeguzman/starred{/owner}{/repo}", "subscriptions_url": "https://api.github.com/users/joshuadeguzman/subscriptions", "organizations_url": "https://api.github.com/users/joshuadeguzman/orgs", "repos_url": "https://api.github.com/users/joshuadeguzman/repos", "events_url": "https://api.github.com/users/joshuadeguzman/events{/privacy}", "received_events_url": "https://api.github.com/users/joshuadeguzman/received_events", "type": "User", "site_admin": false, "name": "Joshua de Guzman", "company": "@freelancer", "blog": "https://joshuadeguzman.me", "location": "Manila, PH", "email": null, "hireable": true, "bio": "Android Engineer at @freelancer. Building tools for humans.", "public_repos": 75, "public_gists": 2, "followers": 38, "following": 10, "created_at": "2016-07-28T15:19:54Z", "updated_at": "2019-06-16T10:26:39Z" }
Получен набор данных в формате JSON.
JSON ( JavaScript Object Notation) — это открытый файловый формат, в котором обычный текст используется для передачи объектов данных, состоящих из пар атрибут-значение и типов данных массива. Конечно, существуют и другие форматы, такие как XML, INI, CSV и так далее. Но на сегодняшний день, JSON, благодаря своей интуитивной и понятной структуре, применяется крайне широко вне зависимости от того, какой язык программирования используется.
Python и Django
Python, согласно его создателю Гвидо ван Россуму, это высокоуровневый язык программирования, и основой его философии является читаемость кода и синтаксис, благодаря которому можно выразить любую концепцию в нескольких строчках кода.
В языке Python используется английское представление слов (например, для методов, резервных ключевых слов и управляющих команд), что позволяет любому новичку легко начать программировать. Система типов в Python динамическая: он проверяет типы данных во время выполнения программы. Python также осуществляет автоматическое управление памятью.
print(5 + 5) # This will result to 10
Django — это высокоуровневый веб-фреймворк, который дает возможность разработчикам быстро и элегантно выполнять проекты.
Его основными чертами являются дизайн для быстрой разработки, безопасность и масштабируемость.
Краткий обзор Django
В Django стандартным способом для внесения изменений в базу данных является использование модулей миграции.
Возьмем пользовательскую модель User
:
from django.db import models class User(models.Model): first_name = models.CharField(max_length=50) middle_name = models.CharField(max_length=50) last_name = models.CharField(max_length=50) def __str__(self): return self.name
Если в вашу модель были внесены какие-либо изменения, запустите модуль makemigrations
.
$ python manage.py makemigrations
Затем вы можете синхронизировать базу данных с набором моделей и миграций.
$ python manage.py migrate
Создание REST интерфейсов при помощи Django Rest Framework
Django REST Framework (DRF) — это мощный и гибкий инструмент для создания веб-интерфейсов (web APIs). Он дает возможность разработчикам не изобретать колесо, самостоятельно создавая сложный и громоздкий REST-интерфейс с нуля. Чем сложнее будут становиться ваши проекты, тем лучше вы будете осознавать необходимость использования DRF или других полезных REST-фреймворков.
1. Инсталляция и настройка проекта
Создаем директорию проекта
$ mkdir djangoapi
Устанавливаем пакет virtualenv при помощи pip.
Виртуальное окружение позволяет проекту иметь свои дополнительные библиотеки, а также обновлять пакеты внутри окружения, не затрагивая глобальное окружение или другие проекты.
pip это менеджер пакетов. С его помощью можно устанавливать пакеты, написанные на языке Python, и далее управлять ими.
$ pip install virtualenv
Создаем папку виртуального окружения в директории нашего проекта.
$ cd djangoapi $ virtualenv venv
Активируем виртуальное окружение.
$ source venv/bin/activate
Чтобы отменить изменения, просто запустите команду deactivate
. Подробнее об этом — в документации virtualenv.
Устанавливаем библиотеки django, djangorestframework.
$ pip install django $ pip install djangorestframework
Создаем наш проект в django.
$ django-admin startproject blog
Запускаем наш проект.
$ python manage.py runserver System check identified no issues (0 silenced). You have 15 unapplied migration(s). Your project may not work properly until you apply the migrations for app(s): admin, auth, contenttypes, sessions. Run 'python manage.py migrate' to apply them. August 16, 2018 - 09:58:36 Django version 2.1, using settings 'blog.settings' Starting development server at http://127.0.0.1:8000/ Quit the server with CONTROL-C.
Непримененными миграциями (unapplied migration(s)) являются файлы, включенные при создании проекта django.
Чтобы их синхронизировать, просто запустите команду migrate
.
$ python manage.py migrate Running migrations: Applying contenttypes.0001_initial... OK Applying auth.0001_initial... OK Applying admin.0001_initial... OK Applying admin.0002_logentry_remove_auto_add... OK Applying admin.0003_logentry_add_action_flag_choices... OK Applying contenttypes.0002_remove_content_type_name... OK Applying auth.0002_alter_permission_name_max_length... OK Applying auth.0003_alter_user_email_max_length... OK Applying auth.0004_alter_user_username_opts... OK Applying auth.0005_alter_user_last_login_null... OK Applying auth.0006_require_contenttypes_0002... OK Applying auth.0007_alter_validators_add_error_messages... OK Applying auth.0008_alter_user_username_max_length... OK Applying auth.0009_alter_user_last_name_max_length... OK Applying sessions.0001_initial... OK
В качестве базы данных нашего проекта установлена SQLite база данных под названием db.sqlite3
.
Создаем приложение в нашем проекте.
$ cd blog $ python manage.py startapp posts
Структура проекта на данный момент должна иметь следующий вид:
$ find . ./posts ./posts/migrations ./posts/migrations/__init__.py ./posts/models.py ./posts/__init__.py ./posts/apps.py ./posts/admin.py ./posts/tests.py ./posts/views.py ./db.sqlite3 ./blog ./blog/__init__.py ./blog/__pycache__ ./blog/__pycache__/settings.cpython-36.pyc ./blog/__pycache__/wsgi.cpython-36.pyc ./blog/__pycache__/__init__.cpython-36.pyc ./blog/__pycache__/urls.cpython-36.pyc ./blog/settings.py ./blog/urls.py ./blog/wsgi.py ./manage.py
2. Модель
Каждый экземпляр модели является определенным источником информации о ваших данных. В общем и целом, каждая модель относится к определенной таблице в вашей базе данных.
# djangoapi/blog/posts/models.py from django.db import models # Создаем здесь нашу модель. class Post(models.Model): title = models.CharField(max_length=255) content = models.TextField() is_featured = models.BooleanField(default=False) def __str__(self): return self.name
Метод __str__
вызывается при помощи встроенной функции str()
, а также при выполнении инструкции print()
для вычисления неформального строкового представления объекта.
Если вы сейчас запустите команду makemigrations
, Django пока никаких изменений не заметит.
$ No changes detected
Чтобы решить эту проблему, нужно в файле setting.py
добавить наше приложение post
в список установленных приложений.
# djangoapi/blog/blog/settings.py INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'posts' # Add it here ]
Теперь можно применить миграцию.
$ python manage.py makemigrations Migrations for 'posts': posts/migrations/0001_initial.py - Create model Post $ python manage.py migrate Operations to perform: Apply all migrations: admin, auth, contenttypes, posts, sessions Running migrations: Applying posts.0001_initial... OK
3. Сериализация
Сериализаторы позволяют переводить структуру данных или объекта в формат, который можно сохранить или передать, а впоследствии восстановить обратно.
Создадим файлы serializers.py
и views.py
и разместим их следующим образом:
# posts/api posts/api/serializers.py posts/api/views.py # posts/migrations posts/migrations/ # posts posts/admin.py posts/apps.py posts/models.py posts/tests.py posts/views.py # posts/api/serializers.py from ..models import Post from rest_framework import serializers class PostSerializer(serializers.ModelSerializer): class Meta: model = Post fields = ('title', 'content', 'is_featured') # if not declared, all fields of the model will be shown
В нашем руководстве мы используем класс ModelSerializer
, более подробно о нем можно прочитать в документации.
4. Представления (views)
Функция представления, или для краткости просто представление, является функцией Python. Она принимает на вход веб-запрос и возвращает ответ.
# posts/api/views.py from ..models import Post from . import serializers from rest_framework import generics, status from rest_framework.response import Response class PostListView(generics.ListAPIView): queryset = Post.objects.all() serializer_class = serializers.PostSerializer
Как видно из данного кода, ListAPIView
используется только для чтения, чтобы представлять коллекцию экземпляров модели.
В данном отрывке кода мы использовали методы представлений из класса generics
, который находится в модуле rest_framework
. Более подробно о них можно прочитать в документации.
5. URLs
Здесь мы настраиваем наши маршруты или URL-пути к нашим представлениям, от которых мы ожидаем определенных ответов на каждый запрос.
# posts/urls.py from django.urls import path from . import views from .api import views urlpatterns = [ path('', views.PostListView.as_view(), name=None) ]
6. Завершаем настройку
Давайте убедимся, что модуль rest_framework
добавлен в список приложений нашего проекта.
# djangoapi/blog/blog/settings.py INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'rest_framework', # Add it here 'posts' ]
7. Админка Django
Так как мы пока не настроили наши POST-запросы, будем пополнять нашу базу данных через панель администратора Django.
Для этого создадим аккаунт admin
с паролем 1234password
и правами супеюзера.
$ python manage.py createsuperuser --email admin@example.com --username admin Password: Password (again): This password is too common. Bypass password validation and create user anyway? [y/N]: y Superuser created successfully.
Теперь зарегистрируем модель Post
в панели администратора.
# posts/admin.py from django.contrib import admin from .models import Post # Регистрируйте модели здесь. admin.site.register(Post)
Вот и все. Теперь зайдите в панель администратора и обновите записи в модели Post. Более подробно об этом можно прочитать в документации.
8. Тестируем наш интерфейс (API)
$ python manage.py runserver GET /api/v1/posts/ HTTP 200 OK Allow: GET, HEAD, OPTIONS Content-Type: application/json Vary: Accept [ { "title": "Example Post #1", "content": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.", "is_featured": false }, { "title": "Example Post #2", "content": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.", "is_featured": true } ]
Великолепно! Теперь настало время обновить наши представления и на этом закончить со стандартными CRUD-операциями.
9. Добавляем новые представления (views)
POST
— это метод, используемый для создания новых ресурсов в нашей базе данных либо для обновления старых.
# posts/api/views.py from ..models import Post from . import serializers from rest_framework import generics, status from rest_framework.response import Response class PostCreateView(generics.CreateAPIView): queryset = Post.objects.all() serializer_class = serializers.PostSerializer def create(self, request, *args, **kwargs): super(PostCreateView, self).create(request, args, kwargs) response = {"status_code": status.HTTP_200_OK, "message": "Successfully created", "result": request.data} return Response(response)
Когда мы хотим передать список данных, тем самым предотвращая соответствующий POST-запрос, или создаем запись в базе данных для конкретного представления списка, то, как правило, мы разделяем классы представлений List
и Create
.
Варианты использования для разных приложений всегда разнятся, для объединения логики набора связанных представлений вы вольны использовать ListCreateAPIView или даже ViewSets.
Примечание. Поскольку мы хотим отображать данные более систематически, мы можем переопределить метод create
и отобразить наш собственный обработчик пользовательских ответов.
Добавим дополнительные представления с методами GET
, PATCH
, и DELETE
.
class PostDetailView(generics.RetrieveUpdateDestroyAPIView): queryset = Post.objects.all() serializer_class = serializers.PostSerializer def retrieve(self, request, *args, **kwargs): super(PostDetailView, self).retrieve(request, args, kwargs) instance = self.get_object() serializer = self.get_serializer(instance) data = serializer.data response = {"status_code": status.HTTP_200_OK, "message": "Successfully retrieved", "result": data} return Response(response) def patch(self, request, *args, **kwargs): super(PostDetailView, self).patch(request, args, kwargs) instance = self.get_object() serializer = self.get_serializer(instance) data = serializer.data response = {"status_code": status.HTTP_200_OK, "message": "Successfully updated", "result": data} return Response(response) def delete(self, request, *args, **kwargs): super(PostDetailView, self).delete(request, args, kwargs) response = {"status_code": status.HTTP_200_OK, "message": "Successfully deleted"} return Response(response)
10. Обновление URL
# posts/urls.py from django.urls import path from . import views from .api import views urlpatterns = [ path('', views.PostListView.as_view(), name=None), path('create/', views.PostCreateView.as_view(), name=None), path('<int:pk>/', views.PostDetailView.as_view(), name=None) ]
Теперь мы можем отправлять запросы к нашему API через Postman
, наше приложение, или делать GET-запросы из нашего браузера. Например:
POST /api/v1/posts/create/ HTTP 200 OK Allow: POST, OPTIONS Content-Type: application/json Vary: Accept { "status_code": 200, "message": "Successfully created", "result": { "csrfmiddlewaretoken": "rnSUN3XOIghnXA0yKghnQgxg0do39xhorYene5ALw3gWGThK5MjG6YjL8VUb7v2h", "title": "Creating a resource", "content": "Howdy mate!" } } GET /api/v1/posts/1/ HTTP 200 OK Allow: GET, PUT, PATCH, DELETE, HEAD, OPTIONS Content-Type: application/json Vary: Accept { "status_code": 200, "message": "Successfully retrieved", "result": { "title": "Sample Post", "content": "Sample Post Content", "is_featured": false } }
Вот и все. Мы ухитрились создать RESTful-интерфейс при помощи DRF. Ура!
Исходный код
Исходный код доступен на Github.