【Django】DjangoによるWeb開発プロジェクトPart4 - コードスタイル統一とUserモデルとのRelationShipを設定とindex、detailページの作成  -

## はじめに

年内に0.0.1バージョンくらいまでは完成させてサービス告知の記事を執筆しようと思うのでペースあげます。

## コーディングスタイルを統一させる

そろそろ本格的にコーディングする事になるので、ここいらでPythonのコーディング規約に基づくコーディングができるようにしておきます。
PythonではPEPというドキュメント群があり、Pythonコミュニティに対して情報を提供するドキュメントがあります。その中でPythonのコードスタイルについて書かれているのが`PEP8`となります。

[https://pep8-ja.readthedocs.io/ja/latest/](https://pep8-ja.readthedocs.io/ja/latest/)

さて、このドキュメントを一読した後はソースコードをチェックできるツールを探してみましょう。

### pycodestyleでソースコードチェック

[https://pypi.org/project/pep8/](https://pypi.org/project/pep8/)

PythonにはPEP8パッケージが存在しますが、パッケージの方は後方互換のために残されている模様でこちらを実行するとWARNINGが発生しました。

```bash
% pip install pep8
% pep8
pep8 has been renamed to pycodestyle (GitHub issue #466)
Use of the pep8 tool will be removed in a future release.
Please install and use `pycodestyle` instead.
```

[https://pypi.org/project/pycodestyle/](https://pypi.org/project/pycodestyle/)

新しく、pycodestyleという名前のパッケージがリリースされているので、こちらでソースコードのチェックを行なってみます。

```bash
% pip install pycodestyle
% find ./ -name "*.py" -print | xargs pycodestyle
./manage.py:6:80: E501 line too long (83 > 79 characters)
```

警告が出たらその都度直していきましょう。

この他にも、`pyflakes`というそもそもPythonの文法自体のエラーを検出してくれるツールも便利ですのでこちらも導入します。

[【Django】DjangoによるWeb開発プロジェクトPart2 - ログインページを作成する所まで -](https://qiita.com/himrock922/items/bd020a0d313233556c97)

```bash
% pip install pyflakes
% find ./ -name "*.py" -print | xargs pyflakes
```

## UserモデルとのRelationShipを設定する

コーディングスタイルを設定したので、いよいよ本格的にコードを書いていきます。
Djangoでは、アプリケーションを構築してコードを書いていきます。アプリケーションの考え方についてはこちら→[https://qiita.com/himrock922/items/bd020a0d313233556c97](https://qiita.com/himrock922/items/bd020a0d313233556c97)

今回、プライベートリポジトリで開発を進めているので、アプリケーション名は多少ぼかしています。ご了承下さい。

```python:application/urls.py
from django.contrib import admin
from django.urls import include, path
from django.conf.urls import url
from django.views.generic.base import TemplateView

urlpatterns = [
    path('django_admins/', admin.site.urls),
    path('accounts/', include('django.contrib.auth.urls')),
    path('', TemplateView.as_view(template_name='index.html')),
    url(r'^polls/', include('polls.urls')),
]
```

```python:polls/urls.py
from django.conf.urls import url

from . import views

urlpatterns = [
    url(r'^$', views.index, name='index'),
]
```

ここで作ったpollsアプリケーションは、デフォルトのauthアプリケーションで作成されたUserモデルと関連付けをさせる予定です。
当初、Userをカスタマイズする予定がなかったため、デフォルトの設定をそのまま使用していましたが、例えauthアプリケーションをそのまま使う予定だとしても、カスタマイズしたUserモデルを使うことが推奨されています。
[https://docs.djangoproject.com/ja/2.1/topics/auth/customizing/#using-a-custom-user-model-when-starting-a-project](https://docs.djangoproject.com/ja/2.1/topics/auth/customizing/#using-a-custom-user-model-when-starting-a-project)
という事で、一旦migrationをリセットして、Userモデルをオーバーライドします。

```bash
% python manage.py showmigrations
admin
 [X] 0001_initial
 [X] 0002_logentry_remove_auto_add
 [X] 0003_logentry_add_action_flag_choices
auth
 [X] 0001_initial
 [X] 0002_alter_permission_name_max_length
 [X] 0003_alter_user_email_max_length
 [X] 0004_alter_user_username_opts
 [X] 0005_alter_user_last_login_null
 [X] 0006_require_contenttypes_0002
 [X] 0007_alter_validators_add_error_messages
 [X] 0008_alter_user_username_max_length
 [X] 0009_alter_user_last_name_max_length

%  python manage.py migrate --fake auth zero
Operations to perform:
  Unapply all migrations: auth
Running migrations:
  Rendering model states... DONE
  Unapplying auth.0009_alter_user_last_name_max_length... FAKED
  Unapplying auth.0008_alter_user_username_max_length... FAKED
  Unapplying auth.0007_alter_validators_add_error_messages... FAKED
  Unapplying auth.0006_require_contenttypes_0002... FAKED
  Unapplying auth.0005_alter_user_last_login_null... FAKED
  Unapplying auth.0004_alter_user_username_opts... FAKED
  Unapplying auth.0003_alter_user_email_max_length... FAKED
  Unapplying auth.0002_alter_permission_name_max_length... FAKED
  Unapplying admin.0003_logentry_add_action_flag_choices... FAKED
  Unapplying admin.0002_logentry_remove_auto_add... FAKED
  Unapplying admin.0001_initial... FAKED
  Unapplying auth.0001_initial... FAKED

% python manage.py showmigrations
admin
 [ ] 0001_initial
 [ ] 0002_logentry_remove_auto_add
 [ ] 0003_logentry_add_action_flag_choices
auth
 [ ] 0001_initial
 [ ] 0002_alter_permission_name_max_length
 [ ] 0003_alter_user_email_max_length
 [ ] 0004_alter_user_username_opts
 [ ] 0005_alter_user_last_login_null
 [ ] 0006_require_contenttypes_0002
 [ ] 0007_alter_validators_add_error_messages
 [ ] 0008_alter_user_username_max_length
 [ ] 0009_alter_user_last_name_max_length
contenttypes
 [X] 0001_initial
 [X] 0002_remove_content_type_name
sessions
 [X] 0001_initial
```

usersアプリケーションを作成して、authのクラスをオーバーライドします。

```bash
% python manage.py startapp users
```

```python:users/models.py
from django.db import models
from django.contrib.auth.models import AbstractUser

class User(AbstractUser):
    pass
```

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

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

```python:application/settings.py
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'webpack_loader',
    'users',
]
AUTH_USER_MODEL = 'users.User'
```

### DB再migration

```bash
% sudo su - postgres
% psql
postgres=# DROP DATABASE application_development;
DROP DATABASE
postgres=# CREATE DATABASE application_development;
CREATE DATABASE
postgres=# GRANT ALL PRIVILEGES ON DATABASE application_development TO application_development;
```

前述で特定の箇所のmigrationをROLLBACKしていましたが、一度DBをDropしました。
再度migrationすることにします。なお、この時にusersアプリケーションのinitalizeのmigrationファイルを作成したいので、usersアプリケーションのmigrationファイルを作成しています。

```bash
% python manage.py makemigration users
% python manage.py migrate 
Operations to perform:
  Apply all migrations: admin, auth, contenttypes, sessions, users
Running migrations:
  Applying contenttypes.0002_remove_content_type_name... OK
  Applying auth.0001_initial... OK
  Applying auth.0002_alter_permission_name_max_length... OK
  Applying auth.0003_alter_user_email_max_length... OK
  Applying auth.0004_alter_user_username_opts... OK
  Applying auth.0005_alter_user_last_login_null... OK
  Applying auth.0006_require_contenttypes_0002... OK
  Applying auth.0007_alter_validators_add_error_messages... OK
  Applying auth.0008_alter_user_username_max_length... OK
  Applying auth.0009_alter_user_last_name_max_length... OK
  Applying users.0001_initial... OK
  Applying admin.0001_initial... OK
  Applying admin.0002_logentry_remove_auto_add... OK
  Applying admin.0003_logentry_add_action_flag_choices... OK
  Applying sessions.0001_initial... OK
```

なお、Userモデルのオーバーライドについてこんな記事もありました。
[Djangoでは常にカスタムUserを使用すべき](https://qiita.com/NAKKA-K/items/7627b6a22f364941b989)

今後、どうなるか分かりませんが、とりあえずカスタムUserモデルを作って柔軟に対応できるようにしようという問題は解決したので、僕の方では一旦AbstractUserモデルをオーバーライドするようにしています。

と、ここまで設定したのですが、現在開発しているアプリケーションで利用しているライブラリがデータを標準出力で返してくれているので、一先ずRelationShipは置いておいて、index,detailページへの展開出力を行なっていきます。

## views.pyのclassでtemplateに変数を持たせる

まずは、indexとdetailページを作ってあげます。

```python:****/urls.py
from django.urls import path

from . import views

urlpatterns = [
    path('', views.index, name='index'),
    path('<int:****_id>/', views.detail, name='detail'),
]
```

また、各views.pyの役割としてHTTPレスポンスを正常に返す場合の処理と例外処理を行う場合の2通りの役割があります。この事も考慮してviews.pyの中をコーディングしていきます。
実際にtemplateに変数を渡す際には

```python
    template = loader.get_template('polls/index.html')
    context = {
        'latest_question_list': latest_question_list,
    }
    return HttpResponse(template.render(context, request))
```

のように、`polls/index.html`templateをロードし、コンテキストを渡してあげます。コンテキストは、テンプレート変数名をPythonオブジェクトに渡す辞書のようなものです。

Djangoには上記の処理をもう少し、モダンにかけるように`render()`メソッドが用意されています。

```python:***/views.py
from django.shortcuts import render
from django.http import Http404
from ***** import *****

def index(request):
    try:
        **** = *****
    except (Exception, SystemExit):
        raise Http404("**** does not exist")
    return render(request, '****/index.html', {'****': ****.****})

def detail(request, ***_name)
    try:
        test = *****.****(****_name)
    except (Exception, SystemExit):
        raise Http404("**** does not exist")
    return  render(request, '****/detail.html', {'****': *****})
```

色々とぼかしてすみません。例外処理ももう少し丁寧に後々、書いていきます。
ひとまず、`Http404`という例外をDjangoで提供しているため、これでキャッチしておきます。

index.htmlの方でcontextを展開してリスト表示してみます。

```template/****/index.html
{% extends 'application.html' %}

{% block title %}**** List{% endblock %}

{% block content %}
  <main class="dashboard">
    <nav class="navbar navbar-dark sticky-top bg-dark flex-md-nowrap p-0">
      <a href="{% url '****:index' %}" class="navbar-brand col-sm-3 col-md-2 mr-0">Jails</a>
      <ul class="navbar-nav px-3">
        <li class="nav-item text-nowrap">
          <a class="nav-link" href="{% url 'logout' %}">log out</a>
        </li>
      </ul>
    </nav>
    <div class="container-fluid">
      <h2>****s</h2>
      <div class="table-responsive">
        <table class="table table-striped table-sm">
          <thead>
            <tr>
              <th>***</th>
              <th>***</th>
              <th>***</th>
              <th>***</th>
              <th>***</th>
            </tr>
          </thead>
          <tbody>
            {% for *** in **** %}
              <tr>
                <td>{{ ***.0 }}</td>
                <td>{{ ***.1 }}</td>
                <td>{{ ***.2 }}</td>
                <td>{{ ***.3 }}</td>
                <td>{{ ***.4 }}</td>
              </tr>
            {%  endfor %}
          </tbody>
       </table>
      </div>
    </div>
  </main>
{% endblock %}
```

ちなみに、Djangoの記述で配列を展開していく場合は`[0]`ではなく、`.0`と書くのが正しい模様です。
[https://stackoverflow.com/questions/19895894/could-not-parse-the-remainder-0-from-item0-django](https://stackoverflow.com/questions/19895894/could-not-parse-the-remainder-0-from-item0-django)

次にdetailページを作るわけですが、今回使っているライブラリがidではなく、名前で設定を表示するようになっていましたので、urls.pyで文字列をパラメータとして渡せるように書き換えます。

```python:****/urls.py
from django.urls import path

from . import views

urlpatterns = [
    path('', views.index, name='index'),
    path('<str:****_name>/', views.detail, name='detail'),
]
```

また、detailの詳細情報は配列ではなく、ハッシュ形式なため、ハッシュを展開できるようにします。ややこしいですね。

```python:****/details.py
(中略)
          <tbody>
            {% for k, v in ****.items %}
              <tr>
                <td>{{ k }}</td>
                <td>{{ v }}</td>
              </tr>
            {%  endfor %}
          </tbody>
```

[https://stackoverflow.com/questions/8018973/how-to-iterate-through-dictionary-in-a-dictionary-in-django-template](https://stackoverflow.com/questions/8018973/how-to-iterate-through-dictionary-in-a-dictionary-in-django-template)

## まとめ

色々とぼかしまくってすみません汗
冒頭にも述べた通り、年内に0.0.1バージョンくらいまでは完成させてサービス告知の記事を執筆しようと思うのでペースあげます。

## シリーズ

[Part3](https://qiita.com/himrock922/items/facef344ad20f307720e) ← [Part4](https://qiita.com/himrock922/items/563d52a5a9c74ee4e25e) → [Part5](https://qiita.com/himrock922/items/001d1a58d7ed61270bf7)
## 参考文献

* [Python のコーディング規約 PEP8 に準拠する](https://qiita.com/ynakayama/items/8616f4c0c6e372de9a42)
* [はじめての Django アプリ作成、その 1](https://docs.djangoproject.com/ja/1.11/intro/tutorial01/)
* [Django の認証方法のカスタマイズ](https://docs.djangoproject.com/ja/2.1/topics/auth/customizing/)
* [はじめての Django アプリ作成、その 3](https://docs.djangoproject.com/ja/2.1/intro/tutorial03/)