初めに
こんにちは。
Django + React でツイートアプリみたいなのを作り
学んだことをアウトプットしていこうと思います。
長くなると思うので、投稿は何個かに分けます。
転職活動も始まり、ポートフォリオとして活用できる頑張ります。
間違ってる部分などあればコメントで教えてください。
目次
ざっくりこんな感じで進めていきますが、
追加機能の追加など変わっていくと思います。
1.リポジトリを新規作成
2.Docker / Devcontainer セットアップ
3.Djangoプロジェクト初期化
4.User関連をセットアップ
5.JWT 認証 + CORS 設定
リポジトリを新規作成
この辺は説明いりませんね。
tweetapp_ver1みたいな名前つけて作ってください。
Gitの初期化
git init
このコマンドで、このフォルダがGitリポジトリとして扱われるようになります。
.git/ という隠しフォルダが作られて、そこにGitの管理情報が入ります。
ここから先、ファイルを作ったり修正したら「git add」「git commit」でバージョン管理できるようになます。
.gitignore というファイルを作る
tweetapp_ver1/ touch .gitignore
このファイルに「Gitで追跡したくないファイルやフォルダのパターン」を書いていく。
いったんこんな感じで。
# python関連
__pycache__/
*.pyc
db.sqlite3
# Devcontainer関連
.devcontainer/*.log
# Node関連
node_modules/
環境変数ファイル
.env
Docker / Devcontainer セットアップ
Visual Studio CodeのDevContainer機能を使う場合、.devcontainer/ というディレクトリを作り、その中に設定ファイルをまとめましょう。
tweetapp_ver1/ mkdir .devcontainer
cd .devcontainer
tweetapp_ver1/.devcontainer/ touch devcontainer.json
// .devcontainer/devcontainer.json
{
"name": "TweetApp Devcontainer", //なんでもいい
"build": {
"dockerfile": "Dockerfile",
// Dockerfileのパス (ここ読んでbuildします)
// (相対パス指定なので同じ.devcontainerフォルダ内にあるDockerfileを使う)
"context": ".."
// Dockerイメージをビルドするときのコンテキスト(作業ディレクトリ)
// ".." を指定することで、1つ上のフォルダ(=tweetapp_ver1ルート)全体がビルド対象になる
},
"customizations": {
"vscode": {
// VSCode拡張機能やエディタ設定をコンテナ内で使えるようにする
"extensions": [
"ms-python.python",
"ms-python.vscode-pylance",
"dbaeumer.vscode-eslint",
"msjsdiag.vscode-react-native"
]
}
},
"forwardPorts": [
8000,
3000
],
// コンテナ内のポート 8000 (Django) と 3000 (React開発サーバー) を
// ホスト側にフォワードするための設定
"remoteUser": "vscode"
// VSCode推奨のコンテナ内ユーザー
}
tweetapp_ver1/.devcontainer/ touch Docekrfile
# .devcontainer/Dockerfile
FROM mcr.microsoft.com/vscode/devcontainers/python:3.10
# DevContainer用のPython3.10イメージをベースにする
# apt-get で必要なものを入れる
RUN apt-get update && apt-get install -y \
curl \
iputils-ping \
# Redisなどを使うならここでredis-serverもインストール可
&& curl -fsSL https://deb.nodesource.com/setup_18.x | bash - \
&& apt-get install -y nodejs
# Pythonパッケージをインストール
RUN pip install --upgrade pip
RUN pip install django \
djangorestframework \
django-cors-headers \
djangorestframework-simplejwt \
channels \
channels-redis
# 必要に応じてnpmでグローバルインストールするものがあればここで
# たとえば、create-react-appとかyarnとか
# RUN npm install -g yarn
# ホントはここで "COPY ./frontend/package*.json /tmp/frontend/" して
# RUN cd /tmp/frontend && npm install
# みたいな記述を入れることもあるけど、
# DevContainer内で直接 "cd frontend && npm install" でもOK。(僕はこれ)
apt-getは、Debianとその派生物(Ubuntu、Linux Mintなど)で使用されるコマンドラインツールで、パッケージの管理を行うために使用されます。
apt-getを使用すると、ソフトウェアパッケージを検索、インストール、更新、削除することができます。また、依存関係の解決やパッケージのバージョン管理などの機能も提供されます。
Curlとは、コマンドラインからHTTPリクエストを送信するためのオープンソースのライブラリやツールのことです。
apt-get install -y nodejs
Node.jsも入れる。Reactの開発やnpm実行に必要。
pip install django ... channels-redis
Django本体やRESTFramework、JWT、Channelsなどを一括インストール。
channels-redis があるとRedisを使ったWebSocket通信のバックエンドをサポート。
コミットタイミングの例
ここまでの手順が終わったら、いったんGitにコミットしましょう。
現場のチーム開発では、ある程度意味のある単位でコミットするのがおすすめらしいです。
今回「プロジェクト初期化 + DevContainer設定」が1つの大きなまとまり。
tweetapp/ git add .
tweetapp/ git commit -m "chore: initialize project and devcontainer setup"
chore:
は機能変更ではないが、環境や設定に関する変更をまとめるときに使われる。
もし feat:
, fix:
, docs:
など機能追加やバグ修正、ドキュメント更新ならそちらを。
"initialize project and devcontainer setup" → やったことを簡潔に要約。
コミットメッセージは英語にしている現場も多いけど、日本語で書くチームもある。
そこはプロジェクトの方針に合わせてOK。
大事なのは**「何をやったか」**がパッと分かるようにすること。
ここまで出来たら vscodeの左下にある><
みたいなマークから『新しい開発コンテンツ』を押してコンテナをbuildしてください。コンテナ内に入り開発していきましょう!
Djangoプロジェクト初期化
tweetapp_ver1/ mkdir backend
tweetapp_ver1/ cd backend
tweetapp_ver1/backend/ django-admin startproject backend .
すると以下のような構造になるはずです。
twitter-clone/
├─ .devcontainer/
├─ backend/
│ ├─ backend/
│ │ ├─ __init__.py
│ │ ├─ asgi.py
│ │ ├─ settings.py
│ │ ├─ urls.py
│ │ └─ wsgi.py
│ ├─ manage.py
└─ ... (その他は後で追加)
settings.pyの基本設定
backend/backend/settings.py を開きます。
ここにDjangoのメイン設定が書かれています。
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
# ここに追加予定
# 'rest_framework',
# 'corsheaders',
# 'channels',
# など
]
以下にも corsheaders.middleware.CorsMiddleware を追加予定。
CORS(クロスオリジン)を扱うときに必須。ReactとDjangoが別ポートで動く場合など。
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
# ここに 'corsheaders.middleware.CorsMiddleware', など
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
以下についても、デフォルトはSQLite。試験的に使うなら十分。
本番運用するならPostgreSQLやMySQLを選ぶことが多い。
とりあえず開発ではSQLiteでOK。
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': BASE_DIR / 'db.sqlite3',
}
}
ユーザーがアップロードした画像を保存するときに MEDIA_ROOT を指定する。
MEDIA_URL でURLのパスを定義している。
STATIC_URL = '/static/'
STATIC_ROOT = BASE_DIR / 'staticfiles'
MEDIA_URL = '/media/'
MEDIA_ROOT = BASE_DIR / 'media'
Django Channels を使うときに必要。
ここで 'backend.asgi.application' をASGIエントリポイントとして設定する。
WSGI_APPLICATION = "backend.wsgi.application" #これは初めからある
ASGI_APPLICATION = 'backend.asgi.application'
初回マイグレーション & 動作確認
設定が終わったら、一旦マイグレーションしてDBを準備します。
DevContainerが起動していれば以下コマンドをコンテナ内ターミナルで叩けるはずです。
tweetapp_ver1/backend/ python manage.py migrate
開発サーバーを起動してみましょう
tweetapp_ver1/backend/ python manage.py runserver 0.0.0.0:8000
ブラウザ開いて以下のように表示されたら成功です。
Git commitしよう
今回のステップだと、「Djangoプロジェクトを初期化して最低限の設定を入れた」 がまとまりになります。
tweetapp_ver1/ git add .
tweetapp_ver1/ git commit -m "feat: create Django project." #みたいな
User関連をセットアップ
この辺りでGitHubに共有もわすれずに
GitHubリポジトリの作成 & ローカル連携
まずはGitHubリポジトリをつくって、
今作業しているローカルリポジトリをプッシュできるようにしましょう。
1.1 GitHubリポジトリを作る
- GitHubにアクセスしてログイン
- Repositories から "New" をクリック
- "Repository name" に "twitter-clone" とか「好きなリポジトリ名」を入力
- "Public" or "Private"を選択
- "Create repository" ボタンを押す
- すると、「リモートリポジトリのURL」が表示される
- だいたい https://github.com/ユーザー名/リポジトリ名.git みたいなやつ
1.2 ローカルリポジトリをGitHubにPush
今のローカルプロジェクトを、作ったリポジトリにPushする。
# 1) ローカルで現在いるブランチを確認
git branch
# 2) GitHubのリポジトリを"origin"という名前で追加
git remote add origin "https://github.com/ユーザー名/リポジトリ名.git"
# 3) プッシュ
git push -u origin main
DjangoでカスタムUserモデルを作るための準備
新しいアプリを作る
慣例的に"users"とか"accounts"といった名前でアプリを作ることが多いです。
わたしはusersですすめます。
tweetapp_ver1/backend/ python manage.py startapp users
settings.py で INSTALLED_APPS に追加
backend/backend/settings.py の INSTALLED_APPS に "users" を追加。
AUTH_USER_MODELの設定
デフォルトの django.contrib.auth.models.User を使わず、
独自Userを使うときは、settings.py に
# backend/backend/settings.py
INSTALLED_APPS = [
"django.contrib.admin",
"django.contrib.auth",
...
"users", # カスタムユーザーアプリ
]
AUTH_USER_MODEL = "users.User"
これは「ユーザーモデルはusersアプリのUserクラスを使いますよ」という宣言。
注意: プロジェクトで最初のマイグレーションより前に設定しておく必要があります。
既にマイグレート済みの場合でも、まだ実際にUserを使っていないなら大丈夫だけど、
本番運用後に変えるのは本当に面倒になります。
AbstractUserを継承したUserモデルの実装
# backend/users/models.py
from django.contrib.auth.models import AbstractUser
from django.db import models
# Create your models here.
class User(AbstractUser):
# DjangoのAbstractUserを継承したカスタムUserモデル。
# AbstractUserは username, email, first_name, last_name, password など
# すでに多くのフィールドが定義されている。
# 追加でプロフィール画像や自己紹介欄などを1つのモデルにまとめてもOK
# 例:
# profile_image = models.ImageField(upload_to='profiles/', null=True, blank=True)
# bio = models.TextField(blank=True)
# ここに追加フィールドを書ける
pass
def __str__(self):
return self.username
#のように書くと、このUserインスタンス(レコード)を文字列として扱う際には
# "ユーザー名(self.username)" が返ってくるということになる。
#もし__str__を定義しないと、User object (1)みたいな、ちょっと味気ない表記になる。
#__str__を定義すると、管理画面やデバッグ時のprintなどに、
# usernameが表示されるようになり、分かりやすいというメリットがある。
なぜAbstractUserを使うかというと
- AbstractUser は DjangoのデフォルトUser機能(認証、パスワードハッシュ、権限管理など)をまとめて持っている
- それを継承するだけで「username」「email」「password」などのフィールドが使えるし、さらに自由にフィールド追加できる
- AbstractBaseUser を使う場合は「usernameやpasswordなどを自分で定義する」必要があるため、より細かい制御ができる代わりに設定が大変
- 今回は「標準のusernameとか使いたい+プロフィール情報を同じテーブルにまとめたい」ならAbstractUserが楽
- django.contrib.auth.modelsの標準Userでもよいが、後で追加フィールドするときにsignal作ったりと面倒に感じたので、今回はCustomで試す
admin.py の編集
# backend/users/admin.py
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin
from.models import User
#.は同じ階層のという意味
# Register your models here.
@admin.register(User)
class CustomUserAdmin(UserAdmin):
# カスタムUserモデルを管理画面で扱うためのAdmin設定
# Django標準のUserAdminを継承すると、基本的なUIが確保される。
pass
こうするとDjango管理画面でもusers.Userが表示されるようになり
あとは管理画面にアクセスしたときに、標準Userと同じような画面が利用可能です。
マイグレーション & テスト実行
rm db.sqlite3
python manage.py makemigrations
python manage.py migrate
スーパーユーザー作成
管理画面で確認するためにスーパーユーザーを1人作っておきましょう。
python manage.py createsuperuser
フォローする情報(username, email, passwordなど)を聞かれるので答えます。
emailは無視。
成功すると、管理画面(http://localhost:8000/admin/)にログイン。
Git コミットしておきましょう
JWT 認証 + CORS 設定
ここでは以下をやります。
Django REST Framework と JWT を settings.py に追加
まずは backend/backend/settings.py を開いて、下記のように修正します。
INSTALLED_APPS = [
# 既存のDjango公式アプリ
"django.contrib.admin",
"django.contrib.auth",
"django.contrib.contenttypes",
"django.contrib.sessions",
"django.contrib.messages",
"django.contrib.staticfiles",
# 追加するアプリ
"rest_framework", # Django REST Framework
"corsheaders", # CORSヘッダ処理
"rest_framework_simplejwt", # JWT
# 自作アプリ
"users",
]
MIDDLEWARE = [
"django.middleware.security.SecurityMiddleware",
"django.contrib.sessions.middleware.SessionMiddleware",
# CORS (フロントからのリクエストを許可)
"corsheaders.middleware.CorsMiddleware",
"django.middleware.common.CommonMiddleware",
"django.middleware.csrf.CsrfViewMiddleware",
"django.contrib.auth.middleware.AuthenticationMiddleware",
"django.contrib.messages.middleware.MessageMiddleware",
"django.middleware.clickjacking.XFrameOptionsMiddleware",
]
なぜここに書くのか
- INSTALLED_APPS に書くことで、Django がそれらのアプリを認識してマイグレーションなどを正しく処理できる。
- MIDDLEWARE は、HTTPリクエストが Django に届いたとき、どんな順番で処理するかを指定するリスト
- corsheaders.middleware.CorsMiddleware を入れると、別ドメイン・別ポートからのアクセスを受け入れたり、ヘッダーに Access-Control-Allow-Origin: ... を含められるようになる
この記事を読んでCORSを理解してください。
REST_FRAMEWORK の設定
同じく settings.py の下のほうに、REST_FRAMEWORK の設定をしましょう。
from datetime import timedelta
REST_FRAMEWORK = {
"DEFAULT_AUTHENTICATION_CLASSES": [
"rest_framework_simplejwt.authentication.JWTAuthentication",
],
"DEFAULT_PERMISSION_CLASSES": [
"rest_framework.permissions.IsAuthenticatedOrReadOnly",
],
}
SIMPLE_JWT = {
"ACCESS_TOKEN_LIFETIME": timedelta(minutes=30),
"REFRESH_TOKEN_LIFETIME": timedelta(days=1),
}
- "DEFAULT_AUTHENTICATION_CLASSES" で JWT 認証をメインに使う宣言
- "DEFAULT_PERMISSION_CLASSES" は「認証されてない場合は読み取り専用だけ許可、書き込み系は不可」にする設定
- API 全体に対してのデフォルト方針なので、後ほど個別ビューで上書きできる
- "SIMPLE_JWT" でアクセストークンとリフレッシュトークンの有効期限を定義
- ここではアクセストークン 30分、リフレッシュトークン 1日としているけど、好みに合わせて変えてOK
CORSの設定
# backend/backend/settings.py
CORS_ALLOW_ALL_ORIGINS = False
CORS_ALLOWED_ORIGINS = [
"http://localhost:3000",
"http://localhost:3001",
]
- CORS_ALLOW_ALL_ORIGINS = True だと「あらゆるオリジンからのリクエストを許可」
- ポート3000でReactを動かすことが多いので、必要なら上記のように localhost:3000 だけ許可
ユーザー登録 & JWTトークン発行のエンドポイントを作る
"カスタムUserモデル" を使っているので、「新規ユーザーを登録するView」を1つ作ります。
# backend/users/views.py
from rest_framework import generics, permissions
from.models import User
from.serializers import UserCreateSerializer
# Create your views here.
class UserCreateView(generics.CreateAPIView):
# 新規ユーザーを作成するだけのAPI
queryset = User.objects.all()
serializer_class = UserCreateSerializer
permission_classes = [permissions.AllowAny]
- generics.CreateAPIView は「POST でデータを作るAPI」のための汎用ビュー
- 下でUserCreateSerializer というシリアライザーを後で作る
- permission_classes = [permissions.AllowAny] で、認証なしでもユーザー登録できるようにする
この記事読むとDRFの全体像なんとなくつかめますよ
# backend/users/serializers.py
from rest_framework import serializers
from.models import User
class UserCreateSerializer(serializers.ModelSerializer):
password = serializers.CharField(write_only=True)
class Meta:
model = User
fields = ["id", "username", "email", "password"]
def create(self, validated_data):
# create_user() でパスワードをハッシュ化して登録できる
user = User.objects.create_user(
username = validated_data["username"],
email = validated_data.get("email", ""), #emailは任意。メソッド呼び出し
password = validated_data["password],
)
return user
ルーティング設定
# backend/users/urls.py
from django.urls import path
from.views import UserCreateView
urlpatterns = [
path('users/register/', UserCreateView.as_view(), name="user_register"),
]
- "api/users/register/" というURLで登録できるようにするイメージ。
実際には "backend/urls.py" で include("users.urls") してあげる必要がある。
JWT発行APIの設定
"rest_framework_simplejwt" には、以下のようにトークン発行用の標準ビューがあります。
# backend/backend/urls.py
from django.contrib import admin
from django.urls import path, include
from rest_framework_simplejwt.views import TokenObtainPairView, TokenRefreshView
urlpatterns = [
path("admin/", admin.site.urls),
path("api/", include("users.urls")),
path("api/token/", TokenObtainPairView.as_view(), name="token_obtain_pair"),
path("api/token/refresh/", TokenRefreshView.as_view(), name="token_refresh"),
]
- "api/token/" に POST すると "username" と "password" を送って access token と refresh token が返ってくる
- "api/token/refresh/" に POST すると、リフレッシュトークンを使って新しいアクセストークンを取得できる
Gitコミット忘れずに!
今日はここまで。ありがとうございました。