让我们尝试实现 Docker 和 Django API
首先
最近我在学习使用Docker和Django REST框架,尝试创建一个Todo应用的API。我会在这里记录初学者的奋斗过程。(源代码在这里)
实现目标
-
- Docker を用いた開発環境構築
- Django REST framework を使った簡易な API 作成
环境
macOS Catalina 10.15.2
docker desktop community 2.3.0.3
docker version Client: 19.03.8
docker version Server: 19.03.8
输入
Django REST框架的概述
Django REST Framework是一个支持使用Django开发Web API的框架,而Django则是用于在Python中创建Web应用程序的框架。通过使用Django REST Framework,您可以轻松构建符合RESTful设计的API后端。有关RESTful API的详细信息,请参考此处。
Docker 的概述
Docker是一个简化容器虚拟化技术操作的软件。
与传统的虚拟环境构建相比,它可以利用最少的硬件资源来运行应用程序,应用程序启动更快,提供已经构建好的包含操作系统和应用程序等环境的容器映像,使得构建新应用程序更加简单。我发现这篇关于Docker的文章非常有参考价值,我多次阅读了它。
安装Docker
从Docker官网注册Docker账户并安装Docker。
进入安装页面,可能会看到”Get Stable”和”Get Edge”选项。
好像这是更新频率的不同,”Stable”每三个月更新一次,”Edge”每月更新一次。
这个选项之后可以随时更改,所以先无所谓选择哪个。
请参阅此文章以获取关于账户创建和安装的详细步骤。
让我们搭建环境。
在GitHub上创建代码库。
将克隆文件到本地电脑。
将URL复制到终端,然后输入git命令。
$ git clone https://github.com/Yi-Gaoqiao/todo-api.git
在克隆的代码库中切换到所需目录,并检查其内容后打开编辑器。
$ cd todo-api/ # 作業ディレクトリに移動
$ ls # 中身を確認
LICENSE README.md
$ code . # vscode起動
创建Dockerfile
我們將創建一個新的Dockerfile(用於構建Docker映像),並按照以下方式進行描述:
/todo-api/Dockerfile
FROM python:3.7-alpine # ベースとするDockerイメージを指定
LABEL architecture="Your Name" # 構築担当者をラベル付け
ENV PYTHONUNBUFFERD 1 # リアルタイムでログを見れるように環境変数を指定
COPY ./requirements.txt /requirements.txt # ローカルのrequirements.txtをコンテナにコピー
RUN pip install -r /requirements.txt # requirements.txtに従ってパッケージを一括でインストール
RUN mkdir /django-api # Djangoプロジェクトを置くディレクトリをコンテナ上に作成
WORKDIR /django-api # コンテナ上の作業ディレクトリを変更
COPY ./django-api /django-api # ローカルのdjango-apiディレクトリをコンテナにコピー
RUN adduser -D user # アプリケーションを実行するためのユーザを作成する
USER user # ユーザをrootから変更
为了不忘记这一点,我们会在本地创建一个名为django-api的目录。
创建 requirements.txt
请在这里列出所需的包和版本,并一次性安装它们。
本次所需的包是 Django 和 djangorestframework。可以在 PyPI 上搜索各个包的版本。
文件的内容如下所示。
/todo-api/requirements.txt 的要求文件
Django>=3.0.7,<3.1.0
djangorestframework>=3.11.0,<3.12.0
创建 docker-compose.yml 文件
docker-compose.yml 是一個記錄應用程式執行所需處理的文件。此外,它是一個非常方便的工具,可以輕鬆建立多個容器的應用程式 Docker 映像,並啟動和停止每個容器。
這次我們只想啟動一個名為 app 的容器。
/todo-api/docker-compose.yml 可以被重述为:/todo-api/docker-compose.yml 文件
version: "3"
services:
app:
build:
context: .
ports:
- "8000:8000"
volumes:
- ./django-api:/django-api
command: >
sh -c "python manage.py migrate &&
python manage.py runserver 0.0.0.0:8000"
以下是每个命令的说明。如果想了解更多,请参阅这篇文章。
services
この下にコンテナを定義。
app
コンテナの名前を指定。
build
Dockerfile が置いてあるディレクトリのパスを記述。
配布されているイメージ(公式イメージなど)を取得する場合は指定不要。
ports
ポートマッピングを指定。”ホスト側:コンテナ側”。
volumes
ディレクトリマウント。
ホスト側からファイルを配置したり編集したりできるようマウントする。
command
コンテナ起動時に実行されるコマンド。サーバー起動時にマイグレーションするよう記述しています。
使用 Dockerfile 构建 Docker 镜像。
在终端中运行以下命令。
$ docker-compose build
Building app
...
Successfully built f624466b62a4
Successfully tagged todo-api_app:latest
如果命令成功执行后,出现”Successfully tagged…”的话,那就完成了。
您可以使用下一条命令来确认创建的镜像。
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
todo-api_app latest 840de00a35b5 6 seconds ago 116MB
到目前为止看起来没有问题。
创建Django项目
请在终端中执行以下命令。
执行命令后,请确认在先前的 django-api 目录中是否创建了 todo_project。
$ docker-compose run --rm app sh -c "django-admin.py startproject todo_project ."
–rm
コンテナを起動したりイメージを build したりしていると、どんどん使っていないコンテナやイメージなどが溜まっていき、ディスク容量を圧迫してしまいます。それを防ぐために、コマンド実行時にコンテナを削除してしまいます。
sh -c “”
“”内に実行するシェルスクリプトを記述します。このコマンドは必須ではないみたいですが、個人的にどこからどこまでが docker-compose のコマンドか明確にしたいので使うようにしています。
创建Django应用程序
创建应用程序,请执行以下命令。应用程序名称可以根据您的喜好自由设定(我将使用 core 作为示例)。
$ docker-compose run --rm app sh -c "python manage.py startapp core"
创建自定义用户模型
在 Django 的标准 User 模型中,用户认证时使用的是用户名和密码。
对于大家认为在一般用户注册时应该使用电子邮件而不是用户名的声音,我们特别定制了 User 模型。
抱歉,这是个谎言。以下是来自 Django 官方文档的内容。
在开始一个新项目时,即使默认的用户模型已经足够,我们强烈建议创建一个自定义用户模型。这个模型和默认用户模型一样工作,但可以根据需要进行未来的定制。
因此,我会以以下方式进行描述。
/django-api/core/models.py :
/ Django-api /核心/模型.py
from django.db import models
from django.contrib.auth.models import AbstractBaseUser, BaseUserManager, PermissionsMixin
from django.conf import settings # あとで使います。
class UserManager(BaseUserManager):
def create_user(self, email, password=None, **extra_fields):
"""Creates and saves a new user"""
if not email:
raise ValueError('User must have an email address')
email = self.normalize_email(email)
user = self.model(email=email, **extra_fields)
user.set_password(password)
user.save(using=self._db)
return user
def create_superuser(self, email, password):
"""Creates and saves a new superuser"""
user = self.create_user(email, password)
user.is_staff = True
user.is_superuser = True
user.save(using=self._db)
return user
class User(AbstractBaseUser, PermissionsMixin):
"""Custom user model that supports using email instead of username"""
email = models.EmailField(max_length=255, unique=True)
name = models.CharField(max_length=255)
is_active = models.BooleanField(default=True)
is_staff = models.BooleanField(default=False)
objects = UserManager()
USERNAME_FIELD = 'email' # デフォルトは名前入力、今回はメールアドレスにカスタム
AbstractBaseUser
最低限のフィールド及び機能以外は持ち合わせていないので、動作を柔軟に定義したい際に利用。
パーミッション関連の機能を持ち合わせていないので、パーミッションの機能を利用したい場合は、PermissionMixin を同時に継承しておく必要がある。
BaseUserManager
マネージャーをカスタマイズする際に継承。
マネージャーに関してはこちらをご参照ください。
添加应用程序和声明使用自定义用户模型。
/django-api/todo-project/settings.py 可以做如下的中文翻译:
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'core', # 追加
]
~~~~
略
~~~~
STATIC_URL = '/static/'
AUTH_USER_MODEL = 'core.User' # ファイル最下部に記述
注意事项:在进行第一次迁移(init(0001))之前,务必先创建并注册自定义用户模型,以免后续出现不便之处,请注意。
迁移
$ docker-compose run --rm app sh -c "python manage.py makemigrations core"
Migrations for 'core':
core/migrations/0001_initial.py
- Create model User
启动第一个容器
来吧,各位,大家期待已久。现在是容器启动的时刻到了。
$ docker-compose up
...
app_1 | Watching for file changes with StatReloade
创建超级用户。
docker-compose run --rm app sh -c "python manage.py createsuperuser"
Email: superuser@dummy.co.jp
Password:
Password (again):
Superuser created successfully.
输入任意的电子邮件和密码(密码不会显示在屏幕上)。
在管理界面上注册模型。
为了在管理界面上能够查看先前创建的User模型,我们将在admin.py中进行更改。
由于我们对User模型进行了自定义,所以我们也需要对要显示在管理界面上的项目进行自定义。
/django-api/core/admin.py 的原生中文解释:
/django-api/core/admin.py 的中文解释
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
from django.utils.translation import gettext as _
from core import models
class UserAdmin(BaseUserAdmin):
ordering = ['id']
list_display = ['email', 'name']
fieldsets = (
(None, {'fields': ('email', 'password')}),
(_('Personal Info'), {'fields': ('name',)}),
(
_('Permissions'),
{'fields': ('is_active', 'is_staff', 'is_superuser')}
),
(_('Important dates'), {'fields': ('last_login',)})
)
admin.site.register(models.User, UserAdmin)
用户按照 ID 的顺序排列,并显示电子邮件和姓名。另外,在 fieldsets 中指定了每个用户的详细信息项。
BaseUserAdmin
デフォルトの UserAdmin を BaseUserAdmin としてインポートし、それを継承することでカスタマイズします。
gettext as _
gettext は多言語対応のために利用されます。慣習的に as _ としてインポートするみたいです。今回、言語設定に関してはデフォルトの英語のまま進めるので、翻訳に関して気になる方はこちらの記事をご参照ください。
用浏览器确认
由於僅查看代碼可能不容易形成具體的印象,所以讓我們實際上前往管理介面看看它是如何顯示的。
使用docker-compose up命令啟動容器,然後轉到http://127.0.0.1:8000/admin/,確認之前創建的超級用戶。
一旦確認完成,讓我們與admin.py文件中的內容進行對照。
用户创建支持模型的应用程序。
为了便于理解,将应用程序的名称命名为”user”。
docker-compose run --rm app sh -c "python manage.py startapp user"
完成应用之后,如同往常一样,将其注册到 settings.py 文件中。
/django-api/todo-project/settings.py的文件需要进行重新配置.
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'rest_framework', # これから使います
'rest_framework.authtoken', # これから使います
'core',
'user', # 追加
]
创建序列化器
序列化是将对象转换为字节流或字符流的过程。
将复杂数据,如查询集和模型实例,转换为可输出的格式,如JSON、XML等。类似于将Django模型翻译成不同格式的操作。将数据恢复到原始格式的过程称为反序列化。(参考:Django REST framework 官方文档)
由于默认情况下没有提供serializers.py文件,因此需要自己创建。路径为/django-api/user/serializers.py。
from django.contrib.auth import get_user_model
from rest_framework import serializers
class UserSerializer(serializers.ModelSerializer):
"""Serializer for the user object"""
class Meta:
model = get_user_model()
fields = ('email', 'password', 'name')
extra_kwargs = {'password': {'write_only': True, 'min_length': 8}}
def create(self, validated_data):
"""Create a new user with encrypted password and return it"""
user = get_user_model().objects.create_user(**validated_data)
return user
ModelSerializer
Django のモデルと紐づいています。モデルに基づいてフィールドとバリデータが自動的に Serializer にも適用されます。(Serializer はこれに限らず数種類用意されています。例えばただの Serializer は最も基本的なシリアライザで、対象が Django のモデルである必要はありません。このあと登場予定です。)
get_user_model()
get_user_model 関数は、そのプロジェクトで使用している User モデルを取得します。つまりデフォルトの User か、カスタムした User が返ります。汎用的な処理が書けるようになるので、ユーザーをインポートするときは get_user_model 関数を使うといいですね。
创建视图
观点是什么
View负责根据客户端(例如浏览器)的请求来决定要执行什么处理(提供哪些API)。这个实现非常简单。
/django-api/user/views.py的中文释义
from rest_framework import generics
from user.serializers import UserSerializer
class CreateUserView(generics.CreateAPIView):
"""Create a new user in the system"""
serializer_class = UserSerializer
在`serializer_class`中指定了先前创建的`UserSerializer`。到目前为止,创建了序列化器和视图,暂时没问题。现在我们将创建一个新的`urls.py`文件。
/Django-API/user/urls.py 可以被换成以下的中文表达方式:
/ Django-API / user / urls.py
from django.urls import path
from user import views
app_name = 'user'
urlpatterns = [
path('create/', views.CreateUserView.as_view(), name='create'),
]
.as_view
こちらは View の条件を満たす関数を自動で実行してくれる優れものでございます。リクエストメソッド(GET, POST など)に応じた条件分岐の記述を省略してくれます。
接下来,将/user/urls.py路由到/todo_project/urls.py。
把以下内容用汉语进行转述,只需提供一个选项:
/django-api/todo_project/urls.py
/Django-Api/todo_project/urls.py 的网址配置文件。
from django.contrib import admin
from django.urls import path, include # 追加
urlpatterns = [
path('admin/', admin.site.urls),
path('api/user/', include('user.urls')), # 追加
]
请使用浏览器进行确认。
似乎已成功注册。
令牌认证
令牌认证是指
-
- 客户端(在这里是浏览器)将用户信息(在这里是电子邮件地址和密码)发送到服务器。
-
- 服务器验证用户信息并颁发令牌。
- 通过将该令牌包含在HTTP请求的头部中,用户在以后的请求中得到认证。
比如说,如果之前创建的用户想要更改密码,我们要确保只有经过认证的用户(本人)才能进行更改。
首先,我们要实施令牌的发行。
以下是对/django-api/user/serializers.py的本地化汉语解释:
/django-api/user/serializers.py文件的内容如下。
from django.contrib.auth import get_user_model, authenticate # 追加
from rest_framework import serializers
class UserSerializer(serializers.ModelSerializer):
# 追加
class AuthTokenSerializer(serializers.Serializer):
"""Serializer for the user authentication object"""
email = serializers.CharField()
password = serializers.CharField(
style={'input_type': 'password'},
trim_whitespace=False
)
def validate(self, attrs):
"""Validate and authenticate the user"""
email = attrs.get('email')
password = attrs.get('password')
user = authenticate(
request=self.context.get('request'),
username=email,
password=password
)
if not user:
msg = ('Unable to authenticate with provided credentials')
raise serializers.ValidationError(msg, code='authentication')
attrs['user'] = user
return attrs
设定一个用于发放令牌所需的用户信息字段(包括电子邮件和密码),并在验证函数中进行验证。
如果输入的信息不匹配,将显示错误消息。
/django-api/user/views.py 的中文释义
from rest_framework import generics
from rest_framework.authtoken.views import ObtainAuthToken # 追加
from rest_framework.settings import api_settings # 追加
from user.serializers import UserSerializer, AuthTokenSerializer # 追加
class CreateUserView(generics.CreateAPIView):
# 追加
class CreateTokenView(ObtainAuthToken):
"""Create a new auth token for user, restrict who can see Todo"""
serializer_class = AuthTokenSerializer
renderer_classes = api_settings.DEFAULT_RENDERER_CLASSES
renderer_classes
ブラウザ上で発行されたトークンを確認することができます。
/django-api/user/urls.py 可以被翻译为《Django API 用户 URL 配置文件》。
from django.urls import path
from user import views
app_name = 'user'
urlpatterns = [
path('create/', views.CreateUserView.as_view(), name='create'),
path('token/', views.CreateTokenView.as_view(), name='token'), # 追加
]
请在浏览器上进行确认。
返回了一个不规则的神秘字符,那就是令牌。
更新用户信息
为经过认证的用户提供更新其注册信息的功能。
/餐馆网站/API/用户/序列化器.py
class UserSerializer(serializers.ModelSerializer):
"""Serializer for the user object"""
class Meta:
model = get_user_model()
fields = ('email', 'password', 'name')
extra_kwargs = {'password': {'write_only': True, 'min_length': 8}}
def create(self, validated_data):
"""Create a new user with encrypted password and return it"""
user = get_user_model().objects.create_user(**validated_data)
return user
# 追加
def update(self, instance, validated_data):
"""Update a user, setting the password correctly and return it"""
password = validated_data.pop('password', None)
user = super().update(instance, validated_data)
if password:
user.set_password(password)
user.save()
return user
.pop(‘password’)
更新前のパスワードを削除します。(無ければ None が返ってきます。)
以下是“django-api/user/views.py”的中国本土化重述。
/views.py是用户视图的文件,在django-api中。
from rest_framework import generics, authentication, permissions # 追加
from rest_framework.authtoken.views import ObtainAuthToken
from rest_framework.settings import api_settings
from user.serializers import UserSerializer, AuthTokenSerializer
class CreateUserView(generics.CreateAPIView):
class CreateTokenView(ObtainAuthToken):
# 追加
class ManageUserView(generics.RetrieveUpdateAPIView):
"""Manage the authenticated user"""
serializer_class = UserSerializer
authentication_classes = (authentication.TokenAuthentication,)
permission_classes = (permissions.IsAuthenticated,)
def get_object(self):
"""Retrieve and return authentication user"""
return self.request.user
authentication, permissions
読んで字の如く、認証と許可を司ります。ここでは認証方法はトークン認証を利用し、そして認証されたユーザーのみ閲覧・編集を許可する、ということであります。(状況に応じて、認証されていないユーザーは閲覧のみ許可、といった制限もできます。)
/django-api/user/urls.py 的翻译为中文如下:/django-api/用户/网址.py
from django.urls import path
from user import views
app_name = 'user'
urlpatterns = [
path('create/', views.CreateUserView.as_view(), name='create'),
path('token/', views.CreateTokenView.as_view(), name='token'),
path('update/', views.ManageUserView.as_view(), name='update'), # 追加
]
请在浏览器中确认。
辛苦了,以上是有关用户应用程序的描述。最后我们将创建一个待办事项应用程序。流程与用户应用程序几乎相同,所以我们会快速进行。
待办事项 API
通过以下流程进行:模型创建 → 迁移 → 应用创建 → 应用注册 → 序列化器 → 视图 → URL。
创建Todo模型
/django-api/core/models.py 的内容。
# 最下部に追加
class Todo(models.Model):
"""Todo object"""
user = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE
)
title = models.CharField(max_length=100)
content = models.CharField(max_length=255)
created_at = models.DateField(auto_now_add=True)
is_completed = models.BooleanField(default=False)
def __str__(self):
return self.title
settings.AUTH_USER_MODEL
繰り返しになりますが今回は User モデルをカスタマイズしたので、User モデルの参照時はそれを参照するよう指定する必要があります。
/django-api/core/admin.py 的内容请进行同义复述
admin.site.register(models.User, UserAdmin)
admin.site.register(models.Todo) # 最下部に追加
迁移 (Qian Yi)
当模型发生更改时,请不要忘记进行迁移。
$ docker-compose run --rm app sh -c "python manage.py makemigrations core"
Migrations for 'core':
core/migrations/0002_todo.py
- Create model Todo
创建一个待办事项应用程序
$ docker-compose run --rm app sh -c "python manage.py startapp todo"
在创建应用程序后,请确保注册到 settings.py 中。
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'rest_framework',
'rest_framework.authtoken',
'core',
'user',
'todo', # 追加
]
创建序列化器
/django-api/todo/serializers.py(新建)
from rest_framework import serializers
from core.models import Todo
from user.serializers import UserSerializer
class TodoSerializer(serializers.ModelSerializer):
"""Serializer for Todo objects"""
user = UserSerializer(read_only=True)
class Meta:
model = Todo
fields = ('id', 'user', 'title', 'content', 'created_at', 'is_completed')
read_only_fields = ('id', 'user',)
关于与Todo模型关联的User模型的”user”字段,将使用UserSerializer进行序列化。
创建视图和分页
以下是/views.py文件中的Django API代码。
请用一种方式将其用中文进行释义:
from rest_framework import viewsets, generics, pagination, response
from rest_framework.authentication import TokenAuthentication
from rest_framework.permissions import IsAuthenticated
from core.models import Todo
from todo import serializers
class TodoPagination(pagination.PageNumberPagination):
"""Get 2 Todo items in a page"""
page_size = 2
def get_paginated_response(self, data):
return response.Response({
'next': self.get_next_link(),
'previous': self.get_previous_link(),
'count': self.page.paginator.count,
'total_pages': self.page.paginator.num_pages,
'current_page': self.page.number,
'results': data,
'page_size': self.page_size,
'range_first': (self.page.number * self.page_size) - (self.page_size) + 1,
'range_last': min((self.page.number * self.page_size), self.page.paginator.count),
})
class TodoViewSet(viewsets.ModelViewSet):
"""Handles creating, reading and updating todo items"""
authentication_classes = (TokenAuthentication,)
permission_classes = (IsAuthenticated,)
serializer_class = serializers.TodoSerializer
queryset = Todo.objects.order_by('-created_at')
pagination_class = TodoPagination
def perform_create(self, serializer):
"""Create a new Todo item"""
serializer.save(user=self.request.user)
pagination.PageNumberPagination
デフォルトでは1ページで全件取得してしまうため、ページネーション機能を追加して1ページあたりの取得件数を制限します。
response.Response の中身は英字通りで、デフォルトよりちょっと見栄えがよくなります。
ModelViewSet
基本的な APIView が備わっています。とても便利です。
以下仅供参考。
.list() # 全件取得
.retrieve() # 1件取得
.create() # 作成
.update() # 更新
.partial_update() # 一部更新
.destroy() # 削除
创建网址
/django-api/todo/urls.py(创建新文件)
from django.urls import path, include
from rest_framework.routers import DefaultRouter
from todo import views
router = DefaultRouter()
router.register('todo', views.TodoViewSet)
app_name = 'todo'
urlpatterns = [
path('', include(router.urls))
]
-
- DefaultRouter
Router を登録できるのは ViewSet に限ります。user app で作成した generics の view とか登録しようもんならちゃんとエラー発生します。
Router はめちゃめちゃ便利で、詳細な API(/todo/1/ とか /todo/4/)を自動的に付加して URL 登録してくれます。わざわざ /todo/int:pk/ みたいなこと書かなくていいんです。すごい。
/django-api/todo_project/urls.py 的内容请转述为中文。
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path(‘admin/’, admin.site.urls),
path(‘api/user/’, include(‘user.urls’)),
path(‘api/’, include(‘todo.urls’)), # 追加
]
urlpatterns = [
path(‘admin/’, admin.site.urls),
path(‘api/user/’, include(‘user.urls’)),
path(‘api/’, include(‘todo.urls’)), # 加入
]
urlpatterns = [
path(‘admin/’, admin.site.urls),
path(‘api/user/’, include(‘user.urls’)),
path(‘api/’, include(‘todo.urls’)), # 包含
]
现在本次挑战的所有流程已经结束。
最后,让我们在浏览器中进行确认。
请使用浏览器确认(最终回)。
使用docker-compose up命令可以访问到http://127.0.0.1:8000/api/。
让我们试着跳转到”todo”: “http://127.0.0.1:8000/api/todo/”。
(再次强调,未经身份验证的用户无法查看。)
概况
这次我使用 Docker 搭建了开发环境,并尝试使用 Django REST framework 来实现 Todo 应用的 API。由于接触的新东西很多,所以不能完全理解,但是我能通过自己的研究完成它,感到非常满意。
请参考
书籍、编程和有时候迷你猪
Django兄弟博客
樱花の知识Docker入门
Narito博客
Django官方文档
Django REST框架官方网站
slideship Django REST框架实践入门
深入浅出地解释Docker是什么
使用Python和Django构建后端REST API- 初学者