Django：プロジェクト作成時にやることまとめ

プロジェクト作成は頻度が低く都度調べてしまうので、コピペ用と、順番で抜け漏れでないようにするためのメモです。
他の言語・フレームワークから乗り換えて利用する方にはそこそこ有益かもしれません。

1. プロジェクト作成から基本設定
1. 基本プラグインの導入
1. 管理画面テンプレート導入(AdminLTE)
1. アカウント系アプリケーションの追加とカスタムユーザーモデル
1. よく使うテンプレまとめ

# プロジェクト作成から基本設定

1. プロジェクト作成
1. staticルート設定
1. テンプレートフォルダ設定
1. データベース設定
1. ローカライゼーション設定
1. キャッシュのデータベース化設定

## プロジェクト作成
```sh
django-admin startproject [プロジェクト名]
```

## staticルート設定

staticルートフォルダを設定しておきます。webから直接アクセスできるフォルダです。

```python:[プロジェクト名]/settings.py
STATIC_URL='static/'
STATIC_ROOT = os.path.join(BASE_DIR, "static")
```

## テンプレートフォルダ設定

テンプレートフォルダは各アプリケーションの下にtemplatesフォルダを作ってそこに配置するように説明しているサイトが多いのですが、プロジェクトフォルダ直下のtemplatesフォルダを用意して、全体で使うテンプレートはこちらに配置するようにします。

```python:[プロジェクト名]/settings.py
TEMPLATES = [
    ...
        'DIRS': [
          os.path.join(BASE_DIR, 'templates'),
        ],
```

## データベース変更

とりあえずmysqlとpostgresの記述をコピペできるように。

```python:[プロジェクト名]/settings.py
DATABASES = {
    'default': {
        # mysqlの場合
        'ENGINE': 'django.db.backends.mysql',
        'PORT': '3306',
        # Postgresの場合
        'ENGINE': 'django.db.backends.postgresql_psycopg2',
        'PORT': '5432',
        # 共通
        'NAME': 'your db name',
        'USER': 'user',
        'PASSWORD': 'password',
        'HOST': 'localhost',
    }
}
```

## ローカライゼーション設定

セッションミドルウェアの後に、ロケールミドルウェアを追加してローカライゼーションファイルを作成するフォルダを作っておきます。多言語対応しないとしても、リソースを外部ファイルにするという観点から基本的に全部のプロジェクトでやるようにします。

settings.pyのimportにローカライゼーション用のgettext_lazyとosを追加しておきます。

```python:[プロジェクト名]/settings.py
import os
from django.utils.translation import gettext_lazy as _
```

ミドルウェア追加。セッションミドルウェアの後にLocaleMiddlewareを追加します。

```python:[プロジェクト名]/settings.py
MIDDLEWARE = [
    ...
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.locale.LocaleMiddleware',
    ...
]
```

ローカライゼーションの言語ファイルを配置するフォルダの設定と作成をしておきます。

```python:[プロジェクト名]/settings.py
LANGUAGE_CODE = 'ja'

TIME_ZONE = 'Asia/Tokyo'

USE_I18N = True

USE_TZ = True

LANGUAGES = [
    ('en', _('English')),
    ('ja', _('Japanese')),
]
LOCALE_PATHS = (
    os.path.join(BASE_DIR, 'locale'),
)
```

フォルダ作成
```sh
mkdir -p locale/ja/LC_MESSAGES
```

urls.pyのURLパターンにi18n_patternsをインポートして適用しておきます。

```python:[プロジェクト名]/urls.py
from django.conf.urls.i18n import i18n_patterns
```
```python:[プロジェクト名]/urls.py
# urlpatterns = [
#    ...
#]
urlpatterns = i18n_patterns(
    ...
    prefix_default_language=False
)
```


## キャッシュ利用設定

テンプレートやページのキャッシュについては後で本番化で検討で良いのですが、プログラム中で何かとキャッシュを使う機会は多いものですので、基本的に全てのプロジェクトで、まずはデータベースのキャッシュテーブルに保存するようにしておきます。パフォーマンスが必要な場合はmemcachedやradisを使う方が良いのですが、ローカル開発や新規webアプリケーションの場合はこのままで。

```python:[プロジェクト名]/settings.py
CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.db.DatabaseCache',
        'LOCATION': 'キャッシュテーブル名',
    }
}
```

# 必須プラグインの導入

## インストール
よく使うプラグインをとりあえず入れておきます。この４つは使わないことはあまりないかと思います。

- environ ... 設定の外部ファイル化（必須）
- django_boost ... データモデルの論理削除導入
- django-mathfilter ... テンプレートで算術演算するためのフィルタ
- sorl-thumbnail ... アップロード画像のサムネイルを簡単生成

pipコマンドでローカルインストールしておきます。

```sh
pip install environ
pip install django_boost
pip install django-mathfilters
pip install sorl-thumbnail
```

## 設定ファイル変更

インストールしたら
アプリケーションフォルダに.envファイルを作成し
setting.pyのINSTALLED_APPSに追加した上で、
environで.envファイルから設定を読み込めるようにしておきます。

```python:[プロジェクト名]/settings.py

import os
import environ

...(略)...

# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent

# envファイルのパスを追加
env = environ.Env()                         
env.read_env(os.path.join(BASE_DIR, '.env'))

...(略)...

# installed appsに追加
INSTALLED_APPS = [
    ...
    'django_boost',
    'mathfilters',
    'stdimage',
]
```

environを入れたら、SECRET_KEY, DEBUG, データベース接続情報系は全てenvにこの段階で追い出しておいた方が良いです。

# 管理画面テンプレート導入

AdminLTE3を入れます。バージョンがやや古くBootstrap4で、5に対応していないため、Django Bootsrap5 Volt　を入れるか悩みどころですが、AdminLTE3ならパッケージで入れられつつ、工数も少なく済むため、リソースや予算が潤沢でなければ管理画面はフロントエンドエンジニアを別リソースで絡められることはあまりないため、AdminLTEでそのままやります。

パッケージがインストールされていないならインストール。
```sh
pip install django-adminlte-3
```


インストール済みアプリにsetting.pyにadminlte3アプリとテーマを追加。

```python:[プロジェクト名]/setting.py
INSTALLED_APPS = [
    ...
    'adminlte3',       
    'adminlte3_theme',                                                                                     
]
```

静的ファイルをプロジェクト内にコピー。
```sh
python manage.py collectstatic
```

# アカウント系アプリケーションだけ切り分け - カスタムユーザークラス

**必ずmigrationする前にやっておきましょう。後からユーザーモデルクラスを変更するとかなり面倒な手順が必要になってしまいます。**
会員登録や会員の情報登録は割とよく使いまわせるものなので、accountアプリケーションだけ本体と別で最初に作ります。使いまわせるコードが手持ちである場合は追加したアプリケーションのコードを丸ごとコピーやgitのサブモジュールで持って来れたりします。

```sh
django-admin startapp account
```

新規にアカウント系の機能を作る場合はカスタムユーザーモデルの雛形だけ作成して、追加してDjango管理画面から操作できるようにしておきます。
CustomUserクラスのソースコードからコピペした下記コードをコピペして必要に応じて変更するのが楽です。以下のコードはローカライゼーションの設定が終わっていることが前提です。

```python:account/models.py
from django.db import models
from django.contrib.auth.base_user import AbstractBaseUser
from django.contrib.auth.validators import UnicodeUsernameValidator
from django.contrib.auth.models import PermissionsMixin, UserManager
from django_boost.models.mixins import LogicalDeletionMixin
from django.utils.translation import gettext_lazy as _
from django.utils import timezone
from django.core.mail import send_mail

# Create your models here.
class User(AbstractBaseUser, PermissionsMixin, LogicalDeletionMixin):
    username_validator = UnicodeUsernameValidator()
    username = models.CharField(verbose_name=_("username"),max_length=150,unique=True,validators=[username_validator])
    first_name = models.CharField(_("first name"), max_length=150, blank=True)
    last_name = models.CharField(_("last name"), max_length=150, blank=True)
    email = models.EmailField(_("email address"), blank=True)
    date_joined = models.DateTimeField(_("date joined"), default=timezone.now)
    is_staff = models.BooleanField(
        _("staff status"),
        default=False,
        help_text=_("Designates whether the user can log into this admin site."),
    )
    is_active = models.BooleanField(
        _("active"),
        default=True,
        help_text=_(
            "Designates whether this user should be treated as active. "
            "Unselect this instead of deleting accounts."
        ),
    )
    
    USERNAME_FIELD = "username"
    EMAIL_FIELD = "email"
    REQUIRED_FIELDS = []

    objects = UserManager()
    class Meta:
        verbose_name = _("user")
        verbose_name_plural = _("users")

    def clean(self):
        super().clean()
        self.email = self.__class__.objects.normalize_email(self.email)

    def get_full_name(self):
        """
        Return the first_name plus the last_name, with a space in between.
        """
        full_name = "%s %s" % (self.first_name, self.last_name)
        return full_name.strip()

    def get_short_name(self):
        """Return the short name for the user."""
        return self.first_name

    def email_user(self, subject, message, from_email=None, **kwargs):
        """Send an email to this user."""
        send_mail(subject, message, from_email, [self.email], **kwargs)
```

Djang管理画面から操作できるようにadmin.pyに上記モデルを追加しておきます。

```python:account/admin.py
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin
from .models import User

# Register your models here.
class CustomUserAdmin(UserAdmin):
  list_display = ('username','first_name','last_name','email')
  ordering = ('-date_joined',)

admin.site.register(User,CustomUserAdmin)
```

accountをsettings.pyのインストール済みアプリケーションに追加しておきます。上記のモデルでは論理削除を利用するdjango_boostを使っているので、django_boostも追加しておきます。

```python:[プロジェクト名]/setting.py
INSTALLED_APPS = [
    ...
    'adminlte3',       
    'adminlte3_theme',
    'django_boost',
    'account.apps.AccountConfig',
]
```

settings.pyにユーザー認証モデルとして行を追加しておきます。ついでにログイン・ログアウト後のURLも追加します。
場所はどこでも大丈夫です。

```python:[プロジェクト名]/settings.py
# User model
AUTH_USER_MODEL = 'account.User'

# Login
LOGIN_URL = '/login'
LOGIN_REDIRECT_URL = '/'
LOGOUT_REDIRECT_URL='/login'

```
# ソーシャルログイン導入（必要な場合のみ）

```
pip install social-auth-app-django
```

```python:[プロジェクト名]/settings.py
INSTALLED_APPS = [
    ...
    'social_django',
    ]

MIDDLEWARE = [
    ...
    'social_django.middleware.SocialAuthExceptionMiddleware',
    ]

...

AUTHENTICATION_BACKENDS = (
    'social_core.backends.open_id.OpenIdAuth',
    'social_core.backends.google.GoogleOpenId',
    'social_core.backends.google.GoogleOAuth2',
    'social_core.backends.github.GithubOAuth2',
    'social_core.backends.twitter.TwitterOAuth',
    'social_core.backends.facebook.FacebookOAuth2',
    'social_core.backends.linkedin.LinkedinOAuth2',
    'django.contrib.auth.backends.ModelBackend',
)

# ソーシャルログインキー/シークレット
SOCIAL_AUTH_GOOGLE_OAUTH2_KEY = env('SOCIAL_AUTH_GOOGLE_OAUTH2_KEY')  # クライアントID
SOCIAL_AUTH_GOOGLE_OAUTH2_SECRET = env('SOCIAL_AUTH_GOOGLE_OAUTH2_SECRET') # クライアント シークレット
SOCIAL_AUTH_FACEBOOK_KEY = env('SOCIAL_AUTH_FACEBOOK_KEY')  # アプリID
SOCIAL_AUTH_FACEBOOK_SECRET = env('SOCIAL_AUTH_FACEBOOK_SECRET')  # app secret
SOCIAL_AUTH_TWITTER_KEY = env('SOCIAL_AUTH_TWITTER_KEY') # Consumer Key
SOCIAL_AUTH_TWITTER_SECRET = env('SOCIAL_AUTH_TWITTER_SECRET') # Consumer Secret
SOCIAL_AUTH_GITHUB_KEY = env('SOCIAL_AUTH_GITHUB_KEY') # Client ID
SOCIAL_AUTH_GITHUB_SECRET = env('SOCIAL_AUTH_GITHUB_SECRET') # Client Secret
SOCIAL_AUTH_LINKEDIN_OAUTH2_KEY = env('SOCIAL_AUTH_LINKEDIN_OAUTH2_KEY')
SOCIAL_AUTH_LINKEDIN_OAUTH2_SECRET = env('SOCIAL_AUTH_LINKEDIN_OAUTH2_SECRET')

```

# マイグレーション

アカウント系のデータモデルの調整が終わってからマイグレーションします。キャッシュテーブルも作ります。

```sh
python manage.py createcachetable
python manage.py makemigrations
python manage.py migrate
```

# 管理者作成と稼働

```sh
python manage.py createsuperuser
```

```sh
python manage.py runserver
```