Djangoのプロジェクトを始める

# はじめに

この記事では、Djangoのプロジェクトの作成から、ホーム画面とアカウント作成画面、ログイン画面を表示までを行います。
ほとんど、個人用のメモですが、参考になれば幸いです。動作の保証はできません。

認証は、django-allauthを用いています。
django-allauthをによる画面の編集、テストは、[Django allauthにおけるログイン画面の作成](https://qiita.com/kwashi/items/8421cf45bfaee9e6e238)に記載しています。

<!-- more -->

# インストール/登録

+ [pythonのインストール](https://www.python.org/downloads/)
+ [stripeへ登録](https://stripe.com/jp) 使わない場合しなくても良いです。

# プロジェクトを始める

以下のコマンドでdjangoのプロジェクトの場所を作成する。

```javascript
mkdir ec-django
```

pythonの仮想環境の作成し、必要なパッケージをインストールする。

```javascript
cd ec-django/

python -m venv venv

//仮想環境へ入る
source venv/bin/activate

//仮想環境内でパッケージインストール 
pip install Django==2.2
pip install django-allauth==0.39.1 //ログイン機能などのauthentication機能
pip install django-environ==0.4.5 //環境変数を.envファイルから読み込むため　
pip install mysqlclient==1.4.4 //Mysql データベース用
//pip install stripe==2.35.0 //stripeによる決済機能

```

プロジェクトを作成する。

```javascript

django-admin startproject config .
```

ユーザを管理するアプリ(main)を作成する。

```javascript

./manage.py  startapp main
```

その他必要なディレクトリを作成する。

```javascript
//簡単な処理を行うpythonファイルをディレクトリ
mkdir script
touch ./script/__init__.py

// templatesファイル(.htmlなど)を置くディレクトリ
mkdir templates

//静的ファイルを置くディレクトリ
mkdir static
````

# envファイルによる環境変数の設定

.envファイルの作成

```javascript

touch .env
```

以下の.envファイルに機能追加の都度、環境変数を追加していく。
もし、gitなどネットに公開する場合、アップロードしないように注意する。

```javascript:.env
DEBUG=Ture

SECRET_KEY=<django-secret-key> //./config/setting.pyのsecret key

//以下データベースに関する設定である。（記事後半で設定するDBに合わせる。）
DB_NAME=databese-name
DB_USER=user-name
DB_PASS=password
```

環境変数を読み込むプログラムを作成する。
以下のプログラムでは、本記事の後半で設定する、DB情報に関するものと、Djangoプロジェクトのベースとなるディレクトリのパスも読み込んでいる。

```python
import environ
import os
from config.settings.base import BASE_DIR #後ほど設定

import logging
logger = logging.getLogger(__file__)

def get_env_dict(env_path):
    env = {}
    try:
        read_env = environ.Env()
        environ.Env.read_env(env_path)

        #以下に、.envファイルから読み込むパラメータについて書く。
        if read_env('DEBUG') == 'True' or True:
            env['DEBUG'] = True
        elif read_env('DEBUG') == 'False' or False:
            env['DEBUG'] = False
    
        #secret keys
        env['SECRET_KEY'] = read_env('SECRET_KEY')

        #DB setting
        env['DB_NAME'] = read_env('DB_NAME')
        env['DB_USER'] = read_env('DB_USER')
        env['DB_PASS'] = read_env('DB_PASS')

    except environ.ImproperlyConfigured as e:
        logger.info('設定されていないkeyが設定されている: {}'.format(e))
        
    except Exception as e:
        logger.info('環境変数設定のエラー: {e}'.format(e))
    
    return env
        

env_path = os.path.join(BASE_DIR, '.env')
env = get_env_dict(env_path)
```

# MySQLデータベースを設定

以下を参考にしました。
[いつもDjangoでMySQL(utf8mb4)を利用するときに行っているDjangoのDATABASE設定](https://qiita.com/shirakiya/items/71861325b2c8988979a2)
[Mac での MySQL セットアップ](https://qiita.com/itooww/items/13055c8bb1d226ee5844)

```javascript
brew install mysql 
//mysql --version
//mysql  Ver 14.14 Distrib 5.7.25, for osx10.14 (x86_64) using  EditLine wrapper

mysql.server start

mysql -u root -p

//DB名 database-name, ユーザー名 user-name, パスワード passwordとすると

mysql> CREATE DATABASE database-name;
mysql> CREATE USER 'username'@'localhost' IDENTIFIED BY 'password';
mysql> GRANT ALL PRIVILEGES ON database-name.* TO 'user-name'@'localhost';
mysql> FLUSH PRIVILEGES;

//もしDjangoにおいて、テストを実行する場合
mysql> GRANT ALL PRIVILEGES ON test_django_test.* TO 'mysiteuser'@'localhost';
```

# Djangoのパラメータの設定

設定ファイルを作成する。
設定ファイルは。./config/settings.pyであるが、以下のように開発用の設定ファイルを作成する。

```javascript
mkdir ./config/settings
touch ./config/settings/__init__.py
touch ./config/settings/base.py
touch ./config/settings/development.py
```

### 基本パラメータを設定

```python:./config/settings/base.py
import os

###############
# Build paths #
###############

BASE_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) #ファイル変更により修正
PROJECT_NAME = os.path.basename(BASE_DIR)


############
# Security #
############

DEBUG = False

ALLOWED_HOSTS = []

#################
# Core settings #
#################

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'django.contrib.sites',

    #django-allauth
    'allauth',
    'allauth.account',
    'allauth.socialaccount',

    #main アプリ
    'main.apps.MainConfig',
]

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
ROOT_URLCONF = 'config.urls'

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [
            os.path.join(BASE_DIR, 'templates', 'allauth'), #ログインテンプレート等などを置く場所
            os.path.join(BASE_DIR, 'templates'), #テンプレートを置く場所
        ],
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]


WSGI_APPLICATION = 'config.wsgi.application'

##################
# Authentication #
##################

AUTH_USER_MODEL = "main.User" #main/model.pyで設定予定のUserモデルを認証に使用するため

#ログイン処理後に遷移するURL
LOGIN_REDIRECT_URL = 'home'

#ログアウト処理後に遷移するURL
ACCOUNT_LOGOUT_REDIRECT_URL = 'home'

AUTHENTICATION_BACKENDS = (
    'django.contrib.auth.backends.ModelBackend',
    'allauth.account.auth_backends.AuthenticationBackend', #CustomUserにおいてEmail設定を行っているため設定が必要
)

# 認証方法をメールアドレスにする
ACCOUNT_AUTHENTICATION_METHOD = 'email' 

# メールアドレスをアカウント作成時に要求する
ACCOUNT_EMAIL_REQUIRED = True 

#ユーザー登録確認メールは送信しない　:送信する場合は'option'
ACCOUTN_EMAIL_VERIFICATION = 'none'

#USER名を使用しない（以下をコメントアウトしていので、今回のプロジェクトでは、アカウント作成時に要求する：デフォルト）
#ACCOUNT_USERNAME_REQUIRED = False 

#ログアウトボタンにより、ログアウト画面に遷移せず、即座にリダイレクト先へ遷移
ACCOUNT_LOGOUT_ON_GET = True 

#all-authがサイトを判別するために使用
SITE_ID = 1

############
# Database #
############

DATABASES = {}


############
# Messages #
############

MESSAGE_STORAGE = 'django.contrib.messages.storage.session.SessionStorage'

###########
# Logging #
###########

LOGGING = {}

#######################
# Password validation #
#######################

AUTH_PASSWORD_VALIDATORS = [
    {
        'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
    },
]


########################
# Internationalization #
########################

LANGUAGE_CODE = 'ja' #日本用に設定

TIME_ZONE = 'Asia/Tokyo' #日本用に設定

USE_I18N = True

USE_L10N = True

USE_TZ = True


################
# Static files #
################

#静的ファイルを置く場所を設定
STATIC_URL = '/static/'
STATICFILES_DIRS = [os.path.join(BASE_DIR, 'static')]
STATIC_ROOT = '/var/www/{}/static'.format(PROJECT_NAME)

#メディアファイルを置く場所を設定
MEDIA_URL = '/media/'
MEDIA_ROOT = '/var/www/{}/media'.format(PROJECT_NAME)

```

### 開発用のパラメータを設定

```python:./config/settings/development.py
from .base import *
from script.get_env_dict import env 

#####################
# Security settings #
#####################

DEBUG = True

SECRET_KEY = env['SECRET_KEY']

ALLOWED_HOSTS = ['*']


############
# Database #
############

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': env['DB_NAME'], #　作成したデータベース名
        'USER': env['DB_USER'], # ログインユーザー名
        'PASSWORD': env['DB_PASS'],
        'HOST': 'localhost',
        'PORT': '3306',
        'ATOMIC_REQUESTS': True, #トランザクション
        'OPTIONS': {
            'charset': 'utf8mb4',
            'sql_mode': 'TRADITIONAL,NO_AUTO_VALUE_ON_ZERO,ONLY_FULL_GROUP_BY',
        },
        "TEST": {
            'NAME' : 'test_django_test'
        }
    }
}

###########
# Logging #
###########

LOGGING = {
    # バージョンは「1」固定
    'version': 1,
    # 既存のログ設定を無効化しない
    'disable_existing_loggers': False,
    # ログフォーマット
    'formatters': {
        # 開発用
        'develop': {
            'format': '%(asctime)s [%(levelname)s] %(pathname)s:%(lineno)d ' '%(message)s'
        },
    },
    # ハンドラ
    'handlers': {
        # コンソール出力用ハンドラ
        'console': {
            'level': 'DEBUG',
            'class': 'logging.StreamHandler',
            'formatter': 'develop',
        },
    },
    # ロガー
    'loggers': {
        # 自作アプリケーション全般のログを拾うロガー
        '': {
            'handlers': ['console'],
            'level': 'DEBUG',
            'propagate': False,
        },
        # Django本体が出すログ全般を拾うロガー
        'django-test': {
            'handlers': ['console'],
            'level': 'INFO',
            'propagate': False,
        },
    },
}

################
# Static files #
################

STATIC_ROOT = os.path.join(BASE_DIR, 'static-root')
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')

##################
# Email settings #
##################

EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'

```

.envファイルのDEBUGがTrueのとき、development.pyが選択されるように./manage.pyを変更する。

```python:./manage.py

#!/usr/bin/env python
"""Django's command-line utility for administrative tasks."""
import os
import sys
from script.get_env_dict import env

def main():
#.envファイルのDEBUGがTrueのとき、development.pyを読み込む
    if env['DEBUG'] == True:
        os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings.development')
        
    elif env['DEBUG'] == False:
        os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings.development') ## TODO : 本番環境に関する設定は、別途記載 

        
    from django.db.backends.mysql.schema import DatabaseSchemaEditor
    DatabaseSchemaEditor.sql_create_table += " ROW_FORMAT=DYNAMIC"

    try:
        from django.core.management import execute_from_command_line
    except ImportError as exc:
        raise ImportError(
            "Couldn't import Django. Are you sure it's installed and "
            "available on your PYTHONPATH environment variable? Did you "
            "forget to activate a virtual environment?"
        ) from exc
    execute_from_command_line(sys.argv)


if __name__ == '__main__':
    main()
```

# ホームページを表示してみる!!

### URLのルーティングを作成
ここでは、プロジェクト管理画面、django-allauthにおけるログインなどの画面、main/urls.pyに関する画面へのURLパターンを設定する。

```python:./config/urls.py
from django.contrib import admin
from django.urls import path, include
from script.get_env_dict import env
from django.conf import settings
from django.conf.urls.static import static

urlpatterns = [
    path('admin/', admin.site.urls), #プロジェクト管理画面
    path('accounts/', include('allauth.urls')), #ログインなどのアカウント管理機能
    path('', include('main.urls')), #home画面などmainアプリに関するもの
]+ static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
```

mainアプリに関する画面のurlを設定する。

./mainアプリ内には、urls.pyが含まれていないため、urls.pyを作成

User名の登録時に、validationの機能として、正規表現による制限(username_regex)と、最小文字数の制限(MinLengthValidator)を追加している。

```javascript
touch ./main/urls.py
```
ホーム画面へのパス設定する。

```python:urls.py
from django.contrib import admin
from django.views.generic import TemplateView
from django.urls import path, include
from main import views

urlpatterns = [
    path('', TemplateView.as_view(template_name="main/home.html"), name='home'),
]
```
### ログイン機能のためのユーザーモデルを作成

Userモデルとして、アカウント作成に必要な、username, emailを設定している。
ここでは、usernameに使用できる文字を制限している。

```python:./main/models.py
from django.db import models
from django.contrib.auth.models import AbstractUser
from django.contrib.auth.validators import UnicodeUsernameValidator
from django.core.validators import MinLengthValidator, RegexValidator
from django.utils.translation import gettext_lazy as _ #翻訳機能など国際化のため


class User(AbstractUser):
    """Custom user to perform authentication by email """
    username_validator = UnicodeUsernameValidator()
    username_regex = RegexValidator(regex=r'^[a-zA-Z0-9][a-zA-Z0-9\-]+', message="ユーザネームに使用できない文字が指定されています。")
    username = models.CharField(_('username'), max_length=150, unique=True,
        help_text=_('Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.'),
        validators=[username_validator, username_regex, MinLengthValidator(5)],
        error_messages={
            'unique': _("A user with that username already exists."),
        },
    )
    email = models.EmailField('emial address', unique=True)
```

### データベースマイグレーション

models.pyにUserモデルを設定したため、マイグレーションを行う。

```javascript

//DBの起動
mysql.server start

//マイグレーションファイルの作成
./manage.py makemigrations

//DBにマイグレート
./manage.py migrate
```

もし、モデルを間違えてDBのマイグレートをやり直した場合、かつ、開発中でDBを消して良い場合、以下のコマンドで初期化できる。

```javascript

//マイグレーションファイルを削除
find . -path "*/migrations/0*.py" -delete
find . -path "*/migrations/__pycache__/0*.pyc" -delete

//データベースを削除・再設定
mysql -u root -p

mysql> drop database database-name;
mysql>create database database-name;
mysql>exit;

mysql.server restart
```

# homeテンプレートの作成

./main/urls.pyで設定した、main/home.htmlを作成する。

```javascript
mkdir ./templates/main
touch ./templates/main/home.html
```

```html
<!DOCTYPE html>
<html lang="js">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
    <h1>Hello djanog !!</h1>
</body>
</html>
```

# ホームページを表示

以下のコマンドでサーバを起動

```javascript
./manage.py runserver
```

ブラウザで、http://localhost:8000/ にアクセスすると、Hello djanog !!と表示された画面が表示されるはずです。
また、http://localhost:8000/accounts/signup/ にアクセスした場合、アカウント作成画面が表示されます。
また、http://localhost:8000/accounts/login/ にアクセスした場合、ログイン画面が表示されます。

例えば、以下のような画面が表示されます。
![スクリーンショット 2019-08-16 23.41.31.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/481395/bf255c39-d937-74b3-d998-df0ca7d918f6.png)

# 参考文献

本記事の多くの部分は、以下の教科書を参考にしました。
おそらくこの記事のみでは、補完できないと思うので、以下の本を買いましょう!!
めっちゃいい本です。

+ [現場で使えるDjangoの教科書 <<基礎編>>](https://www.amazon.co.jp/%E7%8F%BE%E5%A0%B4%E3%81%A7%E4%BD%BF%E3%81%88%E3%82%8B-Django-%E3%81%AE%E6%95%99%E7%A7%91%E6%9B%B8%E3%80%8A%E5%9F%BA%E7%A4%8E%E7%B7%A8%E3%80%8B-%E6%A8%AA%E7%80%AC-%E6%98%8E%E4%BB%81/dp/4802094744)