見出し画像

非エンジニアのスタートアップ 0 年生 ( 5. Django で開発 - その 3 )

こんにちは、株式会社ピケでサーバサイドエンジニアをしている古内です。

こちらの記事は【 5. Django で開発 - その 3 】になります。

1. サービス開発に必要なこと
2. HTML、CSS、JavaScript の基本
3. Python の基本
4. Docker の基本
5. Django で開発 ( 本記事 )
6. GCP にデプロイ


更新

2018 / 09 / 23
本記事内の 3. モデルの反映と管理画面追加 にミスがあったので修正しました。修正内容はmyfacebook_users/admin.py ファイルの MyFacebookUserAdmin クラス内の fieldsets 変数に 'thumbnail' を追加しました。


目次

1. この記事について
2. モデルの作成
3. モデルの反映と管理画面追加
4. まとめ


1. この記事について

今回の記事では簡易版 Facebook に必要なデータを取り扱うためのコードを実装します。

Facebook では写真や投稿、友達の機能があるのでそれを実装します。

出来るだけ複雑にならないよう実装するため、最適な実装ではないかもしれませんがよろしくおねがいします。


2. モデルの作成

モデルとはデータベースに登録するデータの雛形のようなものです。

本シリーズ記事の Python の記事で紹介したクラスの概念に近いです。

クラスは人間の名前や性別など人間の共通する特徴を定義するものでした。

モデルも似ていて、保存しておきたいデータの特徴を定義します。

例えば簡易版 Facebook のユーザーであれば

・名前
・年齢
・性別
・友達
・投稿

などが挙げられます。

これらのそれではこれらの特徴を定義していきます。

まず、Django が動いている Docker を操作するために以下のコマンドを実行します。
( Django の Docker を操作できる状態でしたら以下コマンドはいりません )

docker-compose exec web bash

myfacebook ディレクトリに移動します。

cd myfacebook/

続いて、以下コマンドを実行します。

python manage.py startapp myfacebook_users

このコマンドはアプリケーションに機能を実装するために必要なファイルの雛形を作ってくれます。

Ruby on Rails の scaffold の機能と似ていますね。

今回は簡易版 Facebook のユーザー機能に必要なファイルを作っています。

ls コマンドを実行すると myfacebook_users ディレクトリが作られていることが確認できます。

簡易版 Facebook のユーザーに保存したい情報を定義します。

まず、myfacebook_users ディレクトリ内の models.py を以下の内容に編集します。

from django.db import models
from django.contrib.auth.models import AbstractUser


class User(AbstractUser):
    """
    簡易版 Facebook ユーザー
    """

    GENDER_CHOICES = (
        ('male', '男'),
        ('female', '女')
    )

    thumbnail = models.ImageField(
        verbose_name='サムネイル',
        upload_to='uploads/%Y/%m/%d/',
        null=True,
        blank=True
    )
    age = models.PositiveIntegerField(verbose_name='年齢', default=0)
    gender = models.CharField(
        verbose_name='性別',
        max_length=10,
        choices=GENDER_CHOICES,
        default='male'
    )
    friends = models.ManyToManyField(
        'self',
        verbose_name='友人',
        related_name='myfacebook_users',
        blank=True
    )

このモデルでは名前、年齢、性別、友達を定義しました。
投稿についていは投稿モデルを作成したときに追加します。

また、このモデルは少し特別です。
Django が最初から定義しているユーザーを拡張しているからです。

それでは恒例のコードの解説をします。

from django.db import models はパッケージのインポートです。

パッケージのインポートは from 〇〇 import ☓☓ と書きます。
他にも import △△ のように from 文がないケースもあります。

インポートの役割はライブラリ ( 便利機能 ) の読み込みです。

class User(AbstractUser): は AbstractUser クラスを継承した User クラスの定義です。

継承とは定義したクラスを更に拡張できることです。

そのため、この class 定義文は「Django の最初からあるユーザー機能を拡張します」と解釈できます。

GENDER_CHOICES = ( ... ) は男性と女性の情報を入れた配列の定義です。

配列とは一つの変数に複数の値を入れられるものを指します。

この画像の例を Python のコードにしてみると…

A = ['a', 'b', 'c']

になります。

余談ですが Python の配列の種類にはタプル、リスト、辞書があります。
必要なときにググりましょう。

thumbnail = models.ImageField( ... ) はサムネイルの定義です。

サムネイルはなくても許容され、後述する MEDIA_ROOT で設定されているディレクトリ内の uploads/年/月/日/ ディレクトリに保存されます。

age = models.PositiveIntegerField(verbose_name='年齢', default=0) は年齢の定義です。

年齢には正の整数だけ許容して、で初期値で 0 が代入されるという意味です。

初期値で 0 が代入されるというのは特に年齢が指定されないときに 0 に設定されることを指します。

gender = models.CharField( ... ) は性別の定義です。

性別には GENDER_CHOICES で定義されているものだけ許容して、初期値に 'male' 文字列が代入されます。

許容される文字列は 'male' と 'female' だけです。

friends = models.ManyToManyField( ... ) は友達の定義です。

友達には User クラス ( 今回だと自身のクラス ) のインスタンスだけを許容して、空っぽの状態も許容されます。


続いて投稿モデルを定義します。

ユーザー作成時に実行したコマンドを投稿用に変更して実行します。

python manage.py startapp posts

ユーザー同様 models.py を編集します。
( posts/models.py ファイル )

from django.db import models


class Post(models.Model):
    """
    投稿
    """

    title = models.CharField(verbose_name='タイトル', max_length=30)
    body = models.TextField(verbose_name='文章', null=True, blank=True)

    created_at = models.DateTimeField(verbose_name='作成日', auto_now_add=True)

    def __str__(self):
        return self.title

こちらも解説します。

title = models.CharField(verbose_name='タイトル', max_length=30) はタイトルの定義です。

タイトルは 30 文字までです。

User クラスにも models.CharField( ... ) がありましたが投稿機能では自由に 30 文字までなら許容されます。

逆に User クラスにはあった choices の指定がある時は自由な入力ではないと思いましょう。

body = models.TextField(verbose_name='文章', null=True, blank=True) は投稿内容の定義です。

models.TextField 長い文章を保存する事ができます。

なんとなく気づいているかもしれませんが models.CharField と models.TextField は文字列を保存するためのものです。

違いとしては以下の認識で大丈夫です。

・models.CharField 短い文
・models.TextField 長い文

created_at = models.DateTimeField(verbose_name='作成日', auto_now_add=True) は投稿の作成日時の定義です。

投稿すると自動的に日時を保存します。

def __str__(self): は管理画面の一覧画面で表示される文字列になります。

投稿モデルを作成したので先程作ったユーザーモデルに組み込みます。
( myfacebook_users/models.py )

from django.db import models
from django.contrib.auth.models import AbstractUser
from posts.models import Post


class User(AbstractUser):
    """
    簡易版 Facebook ユーザー
    """

    GENDER_CHOICES = (
        ('male', '男'),
        ('female', '女')
    )

    thumbnail = models.ImageField(
        verbose_name='サムネイル',
        upload_to='uploads/%Y/%m/%d/',
        null=True,
        blank=True
    )
    age = models.PositiveIntegerField(verbose_name='年齢', default=0)
    gender = models.CharField(
        verbose_name='性別',
        max_length=10,
        choices=GENDER_CHOICES,
        default='male'
    )
    friends = models.ManyToManyField(
        'self',
        verbose_name='友人',
        related_name='myfacebook_users',
        blank=True
    )
    posts = models.ManyToManyField(
        Post,
        verbose_name='投稿',
        related_name='myfacebook_users',
        blank=True
    )

追加した部分は from posts.models import Post と posts = models.ManyToManyField( ... ) です。

models.ManyToManyField については友達と同じ様な仕組みです。

ここまででモデルの作成ができました。

最後に先程出てきた MEDIA_ROOT と STATIC_ROOT、STATIC_URL などの設定をしてモデル作成を完了します。

myfacebook/settings.py を修正します。

修正部分は以下の部分です。

STATIC_URL = '/static/'

このコードを以下にします。

PROJECT_ROOT = os.path.dirname(os.path.abspath(__file__))
STATIC_ROOT = os.path.join(PROJECT_ROOT, 'staticfiles')

# 本番環境
if DEBUG is False:
    STATICFILES_STORAGE = ''
    STATIC_URL = ''

# 開発環境
else:
    STATIC_URL = '/static/'
    MEDIA_ROOT = os.path.join(PROJECT_ROOT, 'staticfiles', 'media')
    MEDIA_URL = '/media/'

この設定も本番と開発の環境で分けます。

本番環境はまだ用意していないので特に設定していません。
あとで大丈夫です。

PROJECT_ROOT は今回の簡易版 Facebook だと myfacebook/myfacebookディレクトリになります。

STATIC_ROOT は myfacebook/myfacebook/staticfiles ディレクトリになります。

STATIC_URL は実際の URL にすると http://localhost/static になります。

MEDIA_ROOT は myfacebook/myfacebook/staticfiles/media ディレクトリになります。

MEDIA_URL は実際の URL にすると http://localhost/media になります。

STATIC_〇〇系の設定は管理画面とかの CSS、JavaScript などのファイルや公開される URL の設定です。

MEDIA_〇〇系の設定はアップロードする画像等のファイルを保存や公開される URL の設定です。

まとめ

・モデルは基本的に models.py ファイル名のものに書く
・models.Model クラスを継承してモデルを定義していく


3. モデルの反映と管理画面追加

この作業は前回記事のマイグレーションと同じです。

ですが、マイグレーション前に管理画面の追加と修正を行います。

まず、ユーザーの管理画面から行います。

ユーザーの管理画面は myfacebook_users/admin.py です。

from django.contrib import admin

# Register your models here.

これを以下に変更します。

from django.contrib import admin
from django.contrib.auth.admin import UserAdmin
from django.contrib.auth.forms import UserChangeForm
from myfacebook_users.models import User


class MyFacebookUserChangeForm(UserChangeForm):
    class Meta(UserChangeForm.Meta):
        model = User


class MyFacebookUserAdmin(UserAdmin):
    form = MyFacebookUserChangeForm

    fieldsets = UserAdmin.fieldsets + (
            (None, {'fields': (
                'age',
                'gender',
                'friends',
                'thumbnail'
            )}),
    )


admin.site.register(User, MyFacebookUserAdmin)

解説していきます。

class MyFacebookUserChangeForm(UserChangeForm):
    class Meta(UserChangeForm.Meta):
        model = User

このコードは、先程定義した myfacebook_users/models.py ファイルの User クラスに定義したものを読み取っていると思ってください。

class MyFacebookUserAdmin(UserAdmin):
    form = MyFacebookUserChangeForm

    fieldsets = UserAdmin.fieldsets + (
            (None, {'fields': (
                'age',
                'gender',
                'friends'
            )}),
    )

このコードは、MyFacebookUserChangeForm と管理画面を接続しているものと思ってください。

一番下のコードの

admin.site.register(User, MyFacebookUserAdmin)

これは管理画面に上記コードで設定したものを登録しています。


続いて投稿用の管理画面です。

投稿用の管理画面の設定は posts/admin.py ファイルです。

このファイルの内容を以下にします。

from django.contrib import admin
from posts.models import Post

@admin.register(Post)
class PostAdmin(admin.ModelAdmin):
    pass

このコードでは投稿の管理画面を追加しています。

@admin.register(Post) の様な @ で始まるものを Python ではデコレータと呼びます。

pass は何もしないという意味です。


最後にユーザー ( myfacebook_users ) と投稿 ( posts ) を使えるように settings.py に設定を追加します。

設定を追加する箇所は以下です。

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

この設定を以下のように追加します。

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'myfacebook_users',
    'posts',
]

更に、settings.py の一番下の行に以下のコードを追加します。

# 認証に使うユーザー

AUTH_USER_MODEL = 'myfacebook_users.User'

このコードは認証に使うユーザーモデルを指定しています。

ここまでやってモデルは完成です。

これからマイグレーション作業 ( データベースにモデルを反映 ) するのですが実はこのままやると失敗してしまいます。

Django の仕様で AUTH_USER_MODEL を変更した際は一度 DB を削除する必要があります。

https://docs.djangoproject.com/ja/2.0/topics/auth/customizing/#s-changing-to-a-custom-user-model-mid-project

今回はわざとちょっとめんどくさいことをしました。

理由としては、これから読者の方がアプリケーションを作る際に認証に使うユーザーを後でカスタマイズすることが困難であることを知っていてほしかったからです。( 初心者がハマりやすいポイント )

それでは今までのデータベースを破棄して、初期化します。

まずは Docker を止めます。
止めるには docker-compose up を実行した画面で「control + c」キーを同時押しで止めれます。

続いて、初期化には myfacebook/db/datadir を削除します。
( ここの myfacebook ディレクトリは一番上の階層のものです )

以下のコマンドで削除できます。
( もしわからなかったら Finder や exproler で削除してしまっても大丈夫です )

rm -rf db/datadir/*

削除したら、以下コマンドを実行してデータベースを初期化します。

docker-compose up db

こんな文字が出たら大丈夫です。

db_1   | 2018-08-21T12:32:17.514255Z 0 [Note] Beginning of list of non-natively partitioned tables
db_1   | 2018-08-21T12:32:17.863018Z 0 [Note] End of list of non-natively partitioned tables

そうしたら「control + c」キーで止めて、以下のコマンドで起動し直します。

docker-compose up

これらの作業が完了したらマイグレーションの処理をします。

まず、マイグレーションを行うためにマイグレーションファイルを生成します。

生成するためにまず、Django の Docker を操作するようにします。

docker-compose exec web bash
cd myfacebook/

以下コマンドでマイグレーションファイルを作成します。

python manage.py makemigrations

あらら、エラーが起こりましたね。

SystemCheckError: System check identified some issues:

ERRORS:
myfacebook_users.User.thumbnail: (fields.E210) Cannot use ImageField because Pillow is not installed.
        HINT: Get Pillow at https://pypi.org/project/Pillow/ or run command "pip install Pillow".

エラーが起こった時はエラー文を読みましょう。

run command "pip install Pillow" と書いてありますね。
エラー文通り実行しましょう。

pip install Pillow

インストールが完了したら前々回の記事に書いてあるようにインストールしたライブラリは requirements.txt ファイルに残しましょう。

cd ~
pip freeze > requirements.txt

完了したら、 myfacebook ディレクトリに移動して先程のコマンドを再実行してみましょう

cd ~/myfacebook/
python manage.py makemigrations

こんな感じの画面が出たら成功です。

Migrations for 'posts':                                                                                                │
  posts/migrations/0001_initial.py                                                                                     │
    - Create model Post                                                                                                │
Migrations for 'myfacebook_users':                                                                                     │
  myfacebook_users/migrations/0001_initial.py                                                                          │
    - Create model User

それではデータベースにモデルを反映します。

python manage.py migrate

データベースを削除したので前回記事でも行った管理画面用ユーザーも作成します。

python manage.py createsuperuser

ここまでで出来たら完了です。

一度 Docker を再起動するために「control + c」で止めて docker-compose up しなおしましょう。

起動したら http://localhost:8000/admin/ を開いてみてください。

以前のような画面が表示されていたら成功です。

まとめ

・「モデル作成 → makemigrations → migrate」 でデータベースを構築
・AUTH_USER_MODEL を変更する際、データベースの反映が面倒


4. まとめ

今回はわざとエラーをしたり、面倒な作業をしてみました。

実際にサービスを作るともっと思いもよらぬエラーが待っていると思います。

そういうとき、エラー文を読んで解決できる必要があります。

今回のエラーはエラー文に実行すべきコマンドが示されていたので簡単でしたが、ほとんどのエラーは読んだだけでは解決できないものが多いと思います。

そういうときにエラー文を Google で検索して自分と同じエラーの人の解決策を見つける必要があります。

おそらくこのトラブルシューティングがサービス開発の大半の時間を占めると思います。

しかし、勉強ではないのに時間をかけてしまうのはもったいないです。

そういった方のために、普段私がやっているトラブルシューティングを残しておきます。

・Python のエラーの場合は一番下方にあるエラー文でググる
・Python 対応のエディター ( atom ) を使って予め構文のミスを無くす
・pdb でエラー箇所っぽいところで止めてみて変数の状態等を調べる
・どうにもならない時は他の人にコードを見てもらう

正直このトラブルシューティングにも時間がかかります。

辛いことも多いですが乗り越えたときの達成感がたまらないので頑張ってください!

次回は UI とか作ると思います。お楽しみに〜


「どうしてもエラーが解決できない」そんな方のために…

=======================================
PR
古内開発教室始めてみました!
この記事読んだけどもうちょっと知りたい!
自分ひとりだと心細い!
そういった方がいましたらぜひこちらを見てください!
古内開発教室
=======================================


次の記事 5. Django で開発 - その 4 ←開発中
前の記事 5. Django で開発 - その 2

この記事が気に入ったらサポートをしてみませんか?