【 Docker＋Nginx＋Django＋RDS】WEBアプリができるまで③カスタムユーザーを作ってadminにたどり着く

# 前置き
独学で、子供の成長アプリを作った時のことを、記録として残していきます。
間違っているところなどあれば、ご連絡お願いします。
　①[Djangoのようこそページへたどり着くまで](https://qiita.com/quantity82/items/e8c2392bf982efb55f80)
　②[NginxでDjangoのようこそページへたどり着くまで](https://qiita.com/quantity82/items/88a6348135e25c661feb)
　③[カスタムユーザーを作ってadminにたどり着く](https://qiita.com/quantity82/items/b3cd6ded5bbb5f3726f3)　<--ここです
　④[ログインログアウトをしよう](https://qiita.com/quantity82/items/8e4b7ce2ef56e60ad392)
　⑤[ユーザー登録（サインイン）機能を作ろう](https://qiita.com/quantity82/items/bbfc7e72d4a65c71e24c)
　⑥[ユーザーごとのデータ登録できるようにする〜CRU編](https://qiita.com/quantity82/items/4d019a4ee475208a5a26)
　⑦[ユーザーごとのデータ登録できるようにする〜削除編](https://qiita.com/quantity82/items/c48fee71b5f8eb766dae)
　⑧[画像ファイルのアップロード](https://qiita.com/quantity82/items/9bcbd25902f6878ee325)
　⑨[身長体重を記録する＠一括削除機能つき](https://qiita.com/quantity82/items/c8e58742eeaa8c0fc954)
　⑩[成長曲線グラフを描いてみよう](https://qiita.com/quantity82/items/619f5ed2a6cd45f7f592)
　⑪[本番環境へデプロイ＋色々手直し](https://qiita.com/quantity82/items/6e91b7ee7a2d7e0fab32)

# Goal
カスタムユーザー設定を入れて、admin画面までたどり着く

# その前に・・・
Djangoの設定を日本版にする。

```python:setting.py
LANGUAGE_CODE = 'ja'
TIME_ZONE = 'Asia/Tokyo'
```
# まずはユーザー管理用のアプリを作る
startappでusersアプリを作る。

```
docker-compose run web python ./manage.py startapp users
```

すると、こんな感じになる。

```
.
├── docker-compose.yml
├── nginx
│   ├── conf
│   │   └── mysite_nginx.conf
│   └── uwsgi_params
├── src
│   ├── manage.py
│   ├── mysite
│   │   ├── __init__.py
│   │   ├── __pycache__
│   │   │   ├── __init__.cpython-37.pyc
│   │   │   ├── settings.cpython-37.pyc
│   │   │   ├── urls.cpython-37.pyc
│   │   │   └── wsgi.cpython-37.pyc
│   │   ├── settings.py
│   │   ├── urls.py
│   │   └── wsgi.py
│   ├── static
│   └── users
│       ├── __init__.py
│       ├── admin.py
│       ├── apps.py
│       ├── migrations
│       │   └── __init__.py
│       ├── models.py
│       ├── tests.py
│       └── views.py
└── web
    ├── Dockerfile
    └── requirements.txt
```

Djangoでアプリを作ったら、settingに作ったことを教えてあげる。

```python:settings.py
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'users.apps.UsersConfig', #追加
]
```

カスタムユーザーを作ったので、そのことを教えてあげる。

```python:settings.py
AUTH_USER_MODEL = 'users.User' #追加
```

# ユーザーカスタマイズ
AbstractBaseUserでデフォルトのユーザークラスをオーバーライド。
デフォルトだとID＆PASSだが、mailアドレスをID扱いにしている。

```python:users/model.py
from django.db import models
from django.core.mail import send_mail
from django.contrib.auth.models import PermissionsMixin
from django.contrib.auth.base_user import AbstractBaseUser
from django.utils.translation import ugettext_lazy as _
from django.utils import timezone
from django.contrib.auth.base_user import BaseUserManager

class UserManager(BaseUserManager):
    """ユーザーマネージャー."""

    use_in_migrations = True

    def _create_user(self, email, password, **extra_fields):
        """Create and save a user with the given username, email, and
        password."""
        if not email:
            raise ValueError('The given email must be set')
        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_user(self, email, password=None, **extra_fields):
        extra_fields.setdefault('is_staff', False)
        extra_fields.setdefault('is_superuser', False)
        return self._create_user(email, password, **extra_fields)

    def create_superuser(self, email, password, **extra_fields):
        extra_fields.setdefault('is_staff', True)
        extra_fields.setdefault('is_superuser', True)

        if extra_fields.get('is_staff') is not True:
            raise ValueError('Superuser must have is_staff=True.')
        if extra_fields.get('is_superuser') is not True:
            raise ValueError('Superuser must have is_superuser=True.')

        return self._create_user(email, password, **extra_fields)


class User(AbstractBaseUser, PermissionsMixin):
    """カスタムユーザーモデル."""

    email = models.EmailField(_('email address'), unique=True)
    first_name = models.CharField(_('first name'), max_length=30, blank=True)
    last_name = models.CharField(_('last name'), max_length=150, blank=True)

    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.'
        ),
    )
    date_joined = models.DateTimeField(_('date joined'), default=timezone.now)

    objects = UserManager()

    EMAIL_FIELD = 'email'
    USERNAME_FIELD = 'email'
    REQUIRED_FIELDS = []

    class Meta:
        verbose_name = _('user')
        verbose_name_plural = _('users')

    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)
```

モデルを追加したら、adminを修正する。

```python:users/admin.py
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin
from django.contrib.auth.forms import UserChangeForm, UserCreationForm
from django.utils.translation import ugettext_lazy as _
from .models import User


class MyUserChangeForm(UserChangeForm):
    class Meta:
        model = User
        fields = '__all__'


class MyUserCreationForm(UserCreationForm):
    class Meta:
        model = User
        fields = ('email',)


class MyUserAdmin(UserAdmin):
    fieldsets = (
        (None, {'fields': ('email', 'password')}),
        (_('Personal info'), {'fields': ('first_name', 'last_name')}),
        (_('Permissions'), {'fields': ('is_active', 'is_staff', 'is_superuser',
                                       'groups', 'user_permissions')}),
        (_('Important dates'), {'fields': ('last_login', 'date_joined')}),
    )
    add_fieldsets = (
        (None, {
            'classes': ('wide',),
            'fields': ('email', 'password1', 'password2'),
        }),
    )
    form = MyUserChangeForm
    add_form = MyUserCreationForm
    list_display = ('email', 'first_name', 'last_name', 'is_staff')
    list_filter = ('is_staff', 'is_superuser', 'is_active', 'groups')
    search_fields = ('email', 'first_name', 'last_name')
    ordering = ('email',)


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

モデルを作ったら、migrationする。

```
docker-compose run web python ./manage.py makemigrations
```

```
docker-compose run web python ./manage.py migrate
```

# superユーザー作成

```
docker-compose run web python ./manage.py createsuperuser
```

createsuperuserを入れると、ID (今回だとメールアドレス)とPasswordを求められる。
管理者権限のユーザーなので、秘密にする。

```
メールアドレス: admin@example.com
Password:
Password (again):
Superuser created successfully.
```


# Dockerで立ち上げよう①

Dockerを起動

```
docker-compose up
```

localhost:8000/adminへアクセスする。
表示されるけど、CSSが効いてない…
<img width="1440" alt="スクリーンショット 2020-02-11 14.39.45.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547363/cbbc0636-1f53-8cd6-23cf-156ce680c651.png">


# 静的ファイルの設定
Djangoで参照するSTATIC_ROOTを指定する。

```python:setting.py

STATIC_ROOT = '/static'
```

DockerでStaticフォルダを指定する。

```yml: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:
      - web

  db-postgres:
    image: postgres

  web:
    build: ./web
    volumes:
      - ./src:/code
      - ./static:/static #ここを追加
    expose:
     - "8000"
    depends_on:
      - db-postgres
    command: uwsgi --socket :8000 --module mysite.wsgi
```

collectstaticでプロジェクト内に入っているSTATICファイルの中身を
最上位のSTATICに集約する。

```
docker-compose run web python ./manage.py collectstatic
```

# Dockerで立ち上げよう②
localhost:8000/adminへアクセスする。
ちゃんとCSSが効いた！

<img width="1440" alt="スクリーンショット 2020-02-11 15.01.09.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547363/019a2f2b-aad7-8bf2-c4b4-5490e80461e3.png">

最後はこんな感じ。

```
.
├── docker-compose.yml
├── nginx
│   ├── conf
│   │   └── mysite_nginx.conf
│   └── uwsgi_params
├── src
│   ├── manage.py
│   ├── mysite
│   │   ├── __init__.py
│   │   ├── __pycache__
│   │   │   ├── __init__.cpython-37.pyc
│   │   │   ├── settings.cpython-37.pyc
│   │   │   ├── urls.cpython-37.pyc
│   │   │   └── wsgi.cpython-37.pyc
│   │   ├── settings.py
│   │   ├── urls.py
│   │   └── wsgi.py
│   ├── static
│   └── users
│       ├── __init__.py
│       ├── __pycache__
│       │   ├── __init__.cpython-37.pyc
│       │   ├── admin.cpython-37.pyc
│       │   ├── apps.cpython-37.pyc
│       │   └── models.cpython-37.pyc
│       ├── admin.py
│       ├── apps.py
│       ├── migrations
│       │   ├── 0001_initial.py
│       │   ├── __init__.py
│       │   └── __pycache__
│       │       ├── 0001_initial.cpython-37.pyc
│       │       └── __init__.cpython-37.pyc
│       ├── models.py
│       ├── tests.py
│       └── views.py
├── static
│   └── admin
│       ├── css
│       │   ├── autocomplete.css
│       │   ├── base.css
│　　　〜〜〜この中にいっぱい入っている。省略。
└── web
    ├── Dockerfile
    └── requirements.txt
    
    
```

# 参考
https://narito.ninja/blog/detail/39/