【入門編】Python(Django)+MySQL+DockerでTODOアプリを作ってみよう!
前書き
本記事はDjangoを使用したアプリの開発を始めたいが、イメージがつかない人、 簡単にアプリの動きのイメージを掴みたい人向けに作成しました。
事前準備
・Terminalで下記コマンドを実行し、djangoをインストールしておきましょう pip install django
開発環境
参考程度で、開発に使用したPCスペック
・Mac Book Pro (Retina, 13-inch, Early 2015) ・プロセッサ 2.7GHz Intel Core i5
・メモリ 8GB 1867MHz DDR3
・OS macOS Mojave
事前に用意するフォルダ/ファイル
django
├── docker-compose.yml
├── mysql
├── sql
│ └── init.sql
├── nginx
│ ├── conf
│ │ └── app_nginx.conf
│ └── uwsgi_params
└── python
├── Dockerfile
└── requirements.txt
django/docker-compose.ymlを作成
db(MySQL)
コンテナに入ったデータは、起動を停止する際にすべて破棄されます。
データを永続化するため、ローカルのmysqlフォルダと同期しておきます。
commandはMySQLの文字コードの設定です(defaultはlatin1が入っており、日本語入力ができない) python
・commandの欄ですが、上述のuwsgiを使用してポート8001(任意のポート番号)を開放します。
これが、後のnginxへの連携の際に必要な処理となります。
・app.wsgiのappはDjangoのプロジェクト名です。
(例:django-admin startproject appのapp部分)
・--py-autoreload 1はDjangoアプリ開発の際に、ファイル等に変更があった際は自動リロードするための設定です。
・--logto /tmp/mylog.logはログを残すための記述です。
code:docker-compose.yml
version: '3'
services:
nginx:
image: nginx:1.13
ports:
- "8000:8000"
volumes:
- ./nginx/conf:/etc/nginx/conf.d
- ./nginx/uwsgi_params:/etc/nginx/uwsgi_params
- ./static:/static
depends_on:
- python
db:
image: mysql:5.7
command: mysqld --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci
ports:
- "3306:3306"
environment:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: todoList
MYSQL_USER: user
MYSQL_PASSWORD: password
TZ: 'Asia/Tokyo'
volumes:
- ./mysql:/var/lib/mysql
- ./sql:/docker-entrypoint-initdb.d
python:
build: ./python
command: uwsgi --socket :8001 --module app.wsgi --py-autoreload 1 --logto /tmp/mylog.log
volumes:
- ./src:/code
- ./static:/static
expose:
- "8001"
depends_on:
- db
django/sql/init.sqlを作成
docker-entrypoint-initdb.dにマウントされた.sqlに記載されたsql文は初回コンテナ起動時に実行されます。
後にdjangoでtestを実行する際にDB作成を許可するため、以下を記述します。
code:init.sql
GRANT ALL PRIVILEGES ON test_todoList.* TO 'user'@'%';
FLUSH PRIVILEGES;
django/nginx/conf/app_nginx.confを作成
・location / {以降の記述によって、nginxにアクセスのあったすべての情報がDjango側へ行くことになる
(静的ファイルの保管してある/staticを除く)
・仮にnginxのあるバージョンに脆弱性が発見された場合、ハッカーが該当のバージョンをResponse Headersから探し出し、 攻撃される恐れがあるため、server_tokens off;を設定し、Response Headersのnginxのバージョンを非表示にします。
code:app_nginx.conf
upstream django {
ip_hash;
server python:8001;
}
server {
listen 8000;
server_name 127.0.0.1;
charset utf-8;
location /static {
alias /static;
}
location / {
uwsgi_pass django;
include /etc/nginx/uwsgi_params;
}
}
server_tokens off;
django/nginx/uwsgi_paramsを作成
nginxのuWSGIモジュール用の設定ファイル
例えばアクセスのあったユーザのIPアドレスが知りたい場合、REMOTE_ADDRを使用します。
code:uwsgi-params
uwsgi_param QUERY_STRING $query_string;
uwsgi_param REQUEST_METHOD $request_method;
uwsgi_param CONTENT_TYPE $content_type;
uwsgi_param CONTENT_LENGTH $content_length;
uwsgi_param REQUEST_URI $request_uri;
uwsgi_param PATH_INFO $document_uri;
uwsgi_param DOCUMENT_ROOT $document_root;
uwsgi_param SERVER_PROTOCOL $server_protocol;
uwsgi_param REQUEST_SCHEME $scheme;
uwsgi_param HTTPS $https if_not_empty;
uwsgi_param REMOTE_ADDR $remote_addr;
uwsgi_param REMOTE_PORT $remote_port;
uwsgi_param SERVER_PORT $server_port;
uwsgi_param SERVER_NAME $server_name;
django/python/Dockerfileを作成
PYTHONUNBUFFEREDは、バッファが溜まってから出力するのを避けるための記述です。
パフォーマンスの観点からはバッファが溜まってからの出力の方が良いのですが、
今回は素早い反応を求めるため、設定を記述しておきます。
code:Dockerfile
FROM python:3.6
ENV PYTHONUNBUFFERED 1
RUN mkdir /code
WORKDIR /code
ADD requirements.txt /code/
RUN pip install -r requirements.txt
ADD . /code/
django/python/requirements.txtを作成
バージョンは環境によって変更します。
uwsgiはPythonでWebサービスを動かすためのアプリケーションサーバで、今回はnginxと連携するために使用します。 code:requirements.txt
Django==2.0.4
uwsgi==2.0.17
PyMySQL==0.8.0
Djangoプロジェクトの実行
docker-compose run python django-admin.py startproject app .
実行後は下記のディレクトリ構成になります
django
├── mysql
├── docker-compose.yml
├── sql
│ └── init.sql
├── nginx
│ ├── conf
│ │ └── app_nginx.conf
│ └── uwsgi_params
├── python
│ ├── Dockerfile
│ └── requirements.txt
├── src
│ ├── app
│ │ ├── __init__.py
│ │ ├── settings.py
│ │ ├── urls.py
│ │ └── wsgi.py
│ └── manage.py
└── static
django/src/app/settings.pyを編集
settings.pyに以下を追記
code:settings.py
"""
Django settings for app project.
Generated by 'django-admin startproject' using Django 2.0.4.
For more information on this file, see
For the full list of settings and their values, see
"""
import os
import pymysql
# connect mysql
pymysql.install_as_MySQLdb()
ホストのアクセス権限を付与します
code:settings.py
DATABASESの内容を変更
NAMEはMySQLのデータベース名です
code:settings.py
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'todoList',
'USER': 'user',
'PASSWORD': '',
'HOST': 'db',
'PORT': '3306',
}
}
マイグレーションの実施
docker-compose run python ./manage.py makemigrations
docker-compose run python ./manage.py migrate
管理者の設定
docker-compose run python ./manage.py createsuperuser
Username (leave blank to use 'root'): admin
Email address: admin@example.com
Password: **********
Password (again): **********
Superuser created successfully.
※adminのログイン画面で使用するUsernameとPassword
コンテナを起動し、ブラウザで確認する
・-dはバックグラウンドで実行を意味する
docker-compose up -d
下記画面が表示されれば成功です。
https://gyazo.com/84aebfbd9a062ea281c22bc846738321
CSSを適用させる
CSSが適用されていないのが確認できます。
https://gyazo.com/2f92935e9fdbe2d243b44dd7a96f7b4f
・settings.pyにSTATIC_ROOT = '/static'を追加します。
code:settings.py
# Static files (CSS, JavaScript, Images)
STATIC_URL = '/static/'
STATIC_ROOT = '/static'
・コマンドを実行
docker-compose run python ./manage.py collectstatic
https://gyazo.com/c1262533d10c2769dedb1f7299970a6d
アプリケーションの作成
docker-compose run python django-admin.py startapp todo
django/src/todoディレクトリとアプリケーションの雛形となるファイルが作成されます。
django/src/app/settings.pyを編集
プロジェクトの設定ファイルでこのアプリケーションを利用するように設定するため、
設定ファイル("django/src/app/settings.py")で、INSTALLED_APPSに"todo.apps.TodoConfig"を追加。
code:settings.py
INSTALLED_APPS = [
'todo.apps.TodoConfig',
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
]
django/src/app/urls.pyを編集
プロジェクト全体のURLルーティング設定を行います。
code:urls.py
from django.urls import include, path
from django.contrib import admin
from django.views.generic import RedirectView
urlpatterns = [
path('todo/', include('todo.urls')),
path('admin/', admin.site.urls),
path('', RedirectView.as_view(url='/todo/')),
]
django/src/todo/forms.pyを作成
送信フォームを扱うため、モデルフォームクラスPostFormを作成します。
code:forms.py
from django import forms
from .models import Post
class PostForm(forms.ModelForm):
class Meta:
model = Post
fields = ('body',)
django/src/todo/models.pyを編集
Djangoでは、テーブル作成はマイグレーションファイルでやります。 ファイルにテーブルの構造を明示的に記述することで、
テーブルの構造の確認、再作成、変更、ロールバックなどが簡単になり、管理しやすくなります。
また、プログラムからテーブルのフィールドを変数のように扱うことが可能となります。
以下では、Postという"post"テーブルに対応するモデルクラスを作成し、
その中で"body"というテキストフィールドを作成しています。
code:models.py
from django.db import models
class Post(models.Model):
body = models.CharField(max_length=200)
django/src/todo/urls.pyを作成
ToDoリストでは、一覧表示、ToDoの追加、ToDoの削除の3つの操作を行います。
それぞれ、以下のようなルーティングを割り当てます。
table:ルーティング
メソッド パス名 操作
GET /todo ToDo一覧表示
POST /todo/create ToDo追加
POST /todo/<int:id>/delete ToDo削除
code:urls.py
from django.urls import path
from . import views
app_name = 'todo'
urlpatterns = [
path('', views.index, name='index'),
path('create', views.create, name='create'),
path('<int:id>/delete', views.delete, name='delete'),
]
django/src/todo/views.pyを編集
ルーティングから呼び出されるビューのコードを記述します。
一覧表示、追加、削除に対応する、index()、create()、delete()関数を書きます。
code:views.py
from django.shortcuts import render, get_object_or_404
from django.http import HttpResponseRedirect, HttpResponse
from django.urls import reverse
from .models import Post
from .forms import PostForm
# Create your views here.
def index(request):
posts = Post.objects.all()
form = PostForm()
context = {'posts': posts, 'form': form, }
return render(request, 'todo/index.html', context)
def create(request):
form = PostForm(request.POST)
form.save(commit=True)
return HttpResponseRedirect(reverse('todo:index'))
def delete(request, id=None):
post = get_object_or_404(Post, pk=id)
post.delete()
return HttpResponseRedirect(reverse('todo:index'))
django/src/todo/templates/todo/base.htmlを作成
複数のページに共通の内容は共通のレイアウトファイルを用意した方が便利なので、レイアウトファイルを作成します。
見栄えがよくなるように、シンプルなMilligramという簡単なCSSフレームワークを設定しておきます。 code:base.html
{% load static %}
<!DOCTYPE html>
<html>
<head>
<title>Todo List</title>
<!-- CSS And JavaScript -->
<link rel="stylesheet"
href="//fonts.googleapis.com/css?family=Roboto:300,300italic,700,700italic">
<link rel="stylesheet"
href="//cdn.rawgit.com/necolas/normalize.css/master/normalize.css">
<link rel="stylesheet"
href="//cdn.rawgit.com/milligram/milligram/master/dist/milligram.min.css">
<link rel="stylesheet"
href="{% static 'css/todo.css' %}">
</head>
<body>
<div class="container">
{% block content %}
{% endblock %}
</div>
</body>
</html>
django/src/todo/templates/todo/index.htmlを作成
ToDo一覧表示と、ToDo追加、削除のためのHTMLファイルを作成しましょう。 code:index.html
{% extends 'todo/base.html' %}
{% block content %}
<h1 class="title">TODO管理</h1>
{% if error_message %}<p><strong>{{ error_message }}</strong></p>{% endif %}
<form action="{% url 'todo:create' %}" method="post">
{% csrf_token %}
<!-- Todo Name -->
<div class="form-group">
<label for="todo" class="col-sm-3 control-label">TODO追加フォーム</label>
<div class="col-sm-6">
{{ form.body }}
</div>
</div>
<!-- Add Todo Button -->
<div class="form-group">
<div class="col-sm-offset-3 col-sm-6">
<button type="submit" class="btn btn-default">追加</button>
</div>
</div>
</form>
<!-- Current Todos -->
<!--<h2 class="title">リスト</h2>-->
<table class="table table-striped todo-table">
<thead>
<tr>
<th>TODOリスト</th>
<th> </th>
</tr>
</thead>
<tbody>
{% for post in posts %}
<tr>
<!-- Todo Name -->
<td>
<div>{{ post.body }}</div>
</td>
<td>
<form action="{% url 'todo:delete' post.id %}" method="post">
{% csrf_token %}
<button class="delete">削除</button>
</form>
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endblock %}
django/static/css/todo.cssを作成
code:todo.css
h1.title {
margin: 50px 0 50px 0;
}
table {
margin: 50px 0 0 0;
}
button.delete {
margin: 15px 0 -10px 0;
}
アプリケーションの動作確認
・マイグレーションの実施
docker-compose run python ./manage.py makemigrations
docker-compose run python ./manage.py migrate
・サーバー起動
docker-compose run python ./manage.py runserver
・画面確認
https://gyazo.com/9f8605814b521826b58d83e3a46acb74
最終的なディレクトリ構成
※本記事で編集が不要なファイルは省略してます
django
├── docker-compose.yml
├── mysql
│ └── ※省略
├── sql
│ └── init.sql
├── nginx
│ ├── conf
│ │ └── app_nginx.conf
│ └── uwsgi_params
├── python
│ ├── Dockerfile
│ └── requirements.txt
├── src
│ ├── app
│ │ ├──models.py
│ │ ├── settings.py
│ │ ├── urls.py
│ │ └── wsgi.py
│ ├── todo
│ │ ├── templates
│ │ │ └── todo
│ │ │ ├──index.html
│ │ │ └── base.html
│ │ ├──forms.py
│ │ ├── models.py
│ │ ├── urls.py
│ │ └── views.py
│ └── manage.py
└── static