Django入門-簡単なアクセス履歴表示アプリ

- [はじめに](#はじめに)
  - [TL;DR](#tldr)
  - [記事の内容について](#記事の内容について)
  - [環境](#環境)
  - [参考ページ](#参考ページ)
  - [作成したコード](#作成したコード)
- [Djangoのインストール](#djangoのインストール)
- [今回のアプリを初期セットアップ](#今回のアプリを初期セットアップ)
- [簡単にファイルを追加して動作確認](#簡単にファイルを追加して動作確認)
- [作り込み](#作り込み)
- [動作確認](#動作確認)
- [まとめ](#まとめ)
- [2023/05/14 17:38追記](#20230514-1738追記)


## はじめに
### TL;DR
- Djangoは簡単なウェブアプリ作るのには便利で楽。
- Python触ったことなくてもある程度書ける。

### 記事の内容について
- JavaエンジニアがDjangoに入門してみた内容
- Djangoの入門にあたり、以下の様なの簡単なアプリを作成
    - ページにアクセスするたび、アクセス元IP、アクセス時間をページに表示する
    - データベースにはPostgreSQLを使用する
- Python歴1日(ソース読んだだけ)でのチャレンジなので、文法・記法的なところでお見苦しいところがあるかもしれませんが、お許しください(おすすめページとかあれば教えていただけると幸いです。)
    - ソースを読んだ時の記事(https://qiita.com/ramune2371/items/e7e8044f88124a545e95)

### 環境
- 端末：MacBookAir M1
- Python：3.10.6
- pip：23.1.1
- psql:psql (PostgreSQL) 14.8 (Homebrew)
- Docker:Docker Desktop 4.7.1 (77678)

### 参考ページ
https://docs.djangoproject.com/ja/4.2/intro/tutorial01/

### 作成したコード
https://github.com/ramune2371/access_history

## Djangoのインストール
- pipを使用してインストール
```terminal
$ pip install Django
```
## 今回のアプリを初期セットアップ
djang-admin startprojectを使用することで、簡単にDjangoの初期ファイルが作成できる
```terminal
$ django-admin startproject access_history 
$ ls -alh
total 24
drwxr-xr-x  21 username staff   672B  5 13 23:21 .
drwxr-xr-x+ 78 username staff   2.4K  5 13 23:22 ..
drwxr-xr-x   4 username staff   128B  5 13 23:21 access_history
$ cd access_history 
$ ls -alh
total 8
drwxr-xr-x   5 username staff   160B  5 13 23:24 .
drwxr-xr-x  21 username staff   672B  5 13 23:21 ..
drwxr-xr-x   8 username staff   256B  5 13 23:24 access_history
-rwxr-xr-x   1 username staff   670B  5 13 23:21 manage.py
```
作成されたフォルダの中を見ると`manage.py`というファイルが存在し、どうやらこいつをサーバ起動等に使用するらしい。
ということで起動してみた。
```terminal
$ python manage.py runserver
Watching for file changes with StatReloader
Performing system checks...

System check identified no issues (0 silenced).

You have 18 unapplied migration(s). Your project may not work properly until you apply the migrations for app(s): admin, auth, contenttypes, sessions.
Run 'python manage.py migrate' to apply them.
May 13, 2023 - 14:27:43
Django version 4.2.1, using settings 'access_history.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.
```


```terminal
[13/May/2023 14:27:49] "GET / HTTP/1.1" 200 10731
[13/May/2023 14:27:50] "GET /static/admin/css/fonts.css HTTP/1.1" 404 1816
```
無事に起動&アクセスできた。
![スクリーンショット 2023-05-13 23.27.54.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3274868/02865c7a-ab19-556b-0f76-6b907bd56ec4.png)


## 簡単にファイルを追加して動作確認
下記ファイルを追加。追加場所は`access_history/access_history`配下
```python:hello.py
from django.http import HttpResponse

def hello(request):
    return HttpResponse("Hello World")
```
`access_history/access_history/urls.py`を修正
```python:urls.py(変更前)
from django.contrib import admin
from django.urls import path

urlpatterns = [
    path('admin/', admin.site.urls),
]
```

```python:urls.py(変更後)
from django.contrib import admin
from django.urls import path
from . import hello

urlpatterns = [
    path('hello/', hello.hello, name='hello'),
    path('admin/', admin.site.urls),
]
```
`localhost:8080/hello/`へアクセス
![スクリーンショット 2023-05-13 23.38.52.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3274868/73f74a5a-7284-6843-bb20-c721aeb9b9c8.png)

無事アクセス(&世界にあいさつ)できた

## 作り込み
まず今回作成するアクセス履歴表示画面用にDjangoプロジェクトに機能追加
```terminal
$ python manage.py startapp history 
```
作成されたファイルを下記通りに修正
(*長いので折りたたみ*→上から順に変更orファイル追加をしている。ファイル名：（追加or変更）で書いている)
<details>
<summary>
修正内容
</summary>

```python:history/models.py(変更)
from django.db import models
from django.core.exceptions import ValidationError
import re

# ip アドレスのバリデーション用
pattern = "([0-9]{3}\.){3}\.[0-9]{3}"
matcher = re.compile(pattern)

def validate_ip(value):
    if not matcher.match(value):
        raise ValidationError(
            _("%{value} is not valid ip!"),
            params={"value",value}
        )

class AccessHistory(models.Model):
    # データの設定
    ip=models.CharField("ip",max_length=200,validators=[validate_ip])
    access_time=models.DateTimeField("date accessed")

    # __str__メソッドのオーバーライド
    # ページ表示する際に使用
    def __str__(self) -> str:
        return "ip : %s / access time : %s" % (self.ip,self.access_time)
```
```python:history/list_page.py(追加)
from django.http import HttpResponse
from django.utils import timezone as tz
from history.models import AccessHistory

class ListPage:
    def list(request):
        # requestからIP情報を取得
        ip = request.META.get('REMOTE_ADDR')

        # テーブルへ保管
        AccessHistory(ip=ip,access_time=tz.datetime.now()).save()

        # レスポンスの組み立て
        query_set = AccessHistory.objects.all()
        response_str=''
        for qs in query_set:
            response_str = response_str + str(qs) + "<br>"
        # レスポンス返却
        return HttpResponse(response_str)
        
```
```python:history/urls.py(変更)
from django.urls import path
from history.list_page import ListPage

urlpatterns = [
    # 作成したListPage.listを登録。history配下では特にパスを付けない。
    path('', ListPage.list , name="list"),
]
```
```python:access_history/urls.py(変更)
from django.contrib import admin
from django.urls import path,include
from . import hello

urlpatterns = [
    path('hello/', hello.hello, name='hello'),
    # ↓が追加した行。ここでパスを指定するため、history/urls.pyでは指定なしとした。
    path('list/', include("history.urls")),
    path('admin/', admin.site.urls),
]
```
上記が、機能作成。
↓でDjangoに機能を登録し、データベースとの紐付けをさせる
```python:access_history/settings.py
INSTALLED_APPS = [
    #↓の1行が追加した行。history.aaps.HistoryConfigがDjangoでの機能登録に使用するクラス
    'history.apps.HistoryConfig',
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
]
```
</details>

DjangoではModelとデータベースのを紐づけることができる。
今回作成したAccessHistoryの場合、そのフィールドである、ip,access_timeが、その型情報と一緒にデータベースと関連づけられる。
後ほど出てくるが、このModelをもとに、テーブルまで自動で作成することができる。

今回はデータベースにPostgreSQLを使いたいので追加でライブラリをインストール＆一部ファイル修正
```s
$ pip install psycopg
```
PostgreSQLをセットアップ
お試しなので、DockerでPostgreSQLを起動し、簡単に捨てられる様にしておく。
```terminal
$ docker run -d --name postgres -e POSTGRES_PASSWORD='p@ssw0rd' -p 5432:5432  postgres:14.7-bullseye
$ psql -h localhost -U postgres # 今回使用するデータベースを作成するため、一度接続
Password for user postgres: <p@ssw0rdを入力しEnter>
psql (11.20, server 11.3 (Debian 11.3-1.pgdg90+1))
Type "help" for help.

postgres=# create database access_history;
CREATE DATABASE
postgres=# \q
```
DjangoがPostgreSQLへ接続できる様設定ファイルの一部を修正
```python:settings.py(変更前)
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': BASE_DIR / 'db.sqlite3',
    }
}
```
```python:settings.py(変更後)
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql',
        'NAME': 'access_history',
        'HOST': 'localhost',
        'USER': 'postgres',
        'PASSWORD': 'p@ssw0rd',
        'PORT': '5432',
    }
}
```
manage.pyを使用してデータベースをセットアップ。
```terminal
$ python manage.py makemigrations history # こいつで、history/models.pyをデータベースと紐付け
$ python manage.py migrate # データベースの初期化(テーブル作成等)。エラーが出なければOK
```
どうやら、`models.py`から紐付けしたテーブル名は、`<createappした時の名前>_<クラス名を小文字変換したもの>`になるらしい。今回だと、`history_accesshistory`（少しださくなってしまったが…まぁよしとしよう…)

## 動作確認
```terminal
$ paython manage.py runserver # 起動
Watching for file changes with StatReloader
Performing system checks...

System check identified no issues (0 silenced).
May 14, 2023 - 14:09:55
Django version 4.2.1, using settings 'access_history.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.
```
`http://localhost:8000/list/`にブラウザでアクセスすると
![スクリーンショット 2023-05-14 14.11.02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3274868/0d9d1330-4dd2-fc21-0746-96ba9673ea4d.png)


更新を繰り返すと
![スクリーンショット 2023-05-14 14.11.24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3274868/dab30ce9-4d6a-3dbd-23c4-a9111a96bc02.png)
完成

今回はDjangoのバックエンド的な観点での勉強がしたかったので、フロントエンドの作り込みは気が向いたらやろうと思う ~~(多分)~~ →少しだけやった。ページ下部の追記を参照

## まとめ
- Django、すごく便利
    - sql書かずにPostgreSQLが使えてしまった…
    - 機能の拡張が簡単
    - 他にもたくさん便利機能があるというのだから驚き
- Pythonが思ったより書きやすかった

## 2023/05/14 17:38追記
どうしても気になったので下記変更を追加
- フロントエンドをDjangoのテンプレート機能＋Bootstrapを使用してもう少しまともに
- Djangoでは、DBに登録する際にUTCに変換されるため、DBから取得した値をフロントで使用する前にTZに合わせたものに変更するよう修正

修正後のアクセス画面は下記
![スクリーンショット 2023-05-14 17.33.26.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3274868/7399db41-5c0c-4319-2a29-ddb08d798184.png)

修正内容は下記コミットを参照
https://github.com/ramune2371/access_history/commit/2b89566d98e71dc5bd883b108420cf86696f1538#diff-c62fe861387b8d587fd54e896c8cf2069cb178d8f15fa78429a8a6df22dea1c6