DjangoでWebsocketしたい 01 ~まずはhttp通信~

# 01. きっかけ
とある業務改善アプリを作ったんです。
でもね、サーバーとある機器との間にシリアル通信が入るのですが、http通信では通信が終わるまで画面更新できないという壁。
調べてみるとhttpではなくてWebsocketがいいらしい。
とChatGPTが言ってました。

そこで、Wevsocketで通信させてテストしてみたらまぁいい感じなのよ。

はい。完全に備忘録として書きますデス。
「をいっ！それ如きの内容でいちいちQiitaかくなよっ！」ってツッコミをしようと思ったらここでやめといたほうが良いと思います。w

<br>

ということでやってみることにしました。
えっと、またシリーズものになりそうです。すみません。
それにしてもwebアプリ作ったり、マクロ書いたり、生成AIのファインチューニングしたり、スクラッチでDeepLearningモデル作ったり。俺、何者？
というか事務系の部署にいると理解のない上司のおかげで自由に色々やれる反面、何でもやらされるという・・・。笑

<br>
<br>


# 02. Websocketってなに？
こういうのこそ、Qiitaでしょ。

https://qiita.com/maooz4426/items/f7b2946bf1defd75729f

こんなページとか

https://qiita.com/kenta1760/items/89fc514a96344789edb5

こんなページ
もうみんな神だよね


ということで、ひとまずhttpで動くサーバーを作っていきます。

# 03. Djangoをインストール
まず、作業フォルダを作りましょう。
どこでもいいので、`django-websocket-qiita`というフォルダを作ります。ぶっちゃけ、わかりやすければ何でもいいっす。

このフォルダの中で環境を作ります。
```bash
# mac/linux
python3 -m venv ws_env
source ws_env/bin/activate

# windows
# python -m venv ws_env
# ws_env/Script/activate
```
これで環境の中に入りました。ターミナルは以下のようになっているはず。

<br>

```bash
(ws_env) <folder path>:django-websocket-qiita$
```

<br>

```bash
pip install django
```
Djangoをインストールしましょう
今回は5.2がインストールされました。バージョン指定するときは`pip install django==5.2`みたいに書くのはご存知だと思います。

<br>


# 04. プロジェクトを作る
そう、プロジェクトを作るんです。その中にアプリを作っていきます。
これがDjangoのルールです。多分

<br>


```bash
django-admin startproject websocket_proj
```
今回のプロジェクト名は`websocket_porj`としてみました。

<br>


![スクリーンショット 2025-04-14 23.29.54.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3655320/4cd98c68-c2e9-4062-bc7b-80f8cfac266d.png)

そうすると、プロジェクト名「websocket_proj」というフォルダができました。
この中にDjangoで使うファイルやフォルダが自動で生成されます。

<br>


![スクリーンショット 2025-04-14 23.31.22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3655320/0e572f19-7d91-4609-92f6-4027fa460c81.png)

websocket_proの中身はこんな感じ。websocket_projの中にまたwebsocket_projが存在します。（わかりにくいやん）

<br>


次にアプリを作ります。
```bash
python3 manage.py startapp strange_watch
```

<br>

実行後のフォルダ、ファイル構成はこちら

![スクリーンショット 2025-04-14 23.44.54.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3655320/72505d2a-5591-4e5e-b9bf-7cf13be2fd6f.png)

プロジェクトの下にstrange_watchというアプリができました。
ここでwebアプリを作っていきます。

ちなみに、プロジェクト名と同じ名前のwebsocket_projフォルダはいろんな設定をするためのフォルダなので後で触りますね。

# 05.setting.pyを設定する
ひとまず、httpでつながることを目指します。
ファイルは`websocket_proj/websoketproj/setting.py`にあります。

<br>


- INSTALLED_APPSへの追記
setting.pyの中の`INSTALLED_APPS`を編集します。先ほど作ったstrange_watch.apps.StrangeWatchConfigを追記します。

```python:setting.py
# Application definition
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'strange_watch.apps.StrangeWatchConfig', # 追記
]
```
<br>


ついでなので、このファイルを見てみましょう。
設定に記載した通り、`strange_watch/apps.py`を開いてみます。
```python:strange_watch/apps.py
from django.apps import AppConfig


class StrangeWatchConfig(AppConfig):
    default_auto_field = 'django.db.models.BigAutoField'
    name = 'strange_watch'
```
なにやらクラスが設定されていますね。strange_watch（アプリ名=フォルダ名）にはこのようなファイルがいくつか自動で生成されてます。

<br>


```python
DATABASES = {
    # 'default': {
    #     'ENGINE': 'django.db.backends.sqlite3',
    #     'NAME': BASE_DIR / 'db.sqlite3',
    # }
}
```
今回はデータベースを使わないので、DATABASEの中はすべてコメントアウトしておきます。

<br>


```python
LANGUAGE_CODE = 'ja-JP' # 'en-us'

TIME_ZONE = 'Asia/Tokyo' # 'UTC'
```
念のため、言語コードとタイムゾーンを日本にしておきます。

<br>

# 06. 疎通確認
ターミナルで以下を打ち込みます。

```bash
cd websoket_proj # project名のフォルダ
```
ここで、ls（mac/Linux）、dir（Windows）とターミナルに入力すると、現在いるフォルダのファイルの一覧が表示されます。ここに`manage.py`があることを確認してから以下を実行します。

```bash
python manage.py runserver

# Watching for file changes with StatReloader
# Performing system checks...
# 
# System check identified no issues (0 silenced).
# April 19, 2025 - 19:04:25
# Django version 5.2, using settings 'websocket_proj.settings'
# Starting development server at http://127.0.0.1:8000/
# Quit the server with CTRL-BREAK.
# 
# WARNING: This is a development server. Do not use it in a production setting. # Use a production WSGI or ASGI server instead.
# For more information on production servers see: https://docs.djangoproject.com/en/5.2/howto/deployment/
```

ここで`http://127.0.0.1:8000`にアクセスすると以下のような画面がでればOKです。

![画像3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3655320/40906974-de26-4769-88a9-24bd684314c5.png)

<br>
<br>

# 07. routing/html/処理を作成
Django独特なのかどうかわかりませんが、ルーティングをしなければいけません。
要はこのURLの要求があれば、Djangoの中のどのファイルを参照しろ～みたいなルーティングです。
おきまりの作法みたいなもんですが、ちゃんと読めば何やっているのかは理解できるはず

<br>

## 07-1. websokcet_proj/strange_watch/views.pyを作る
最終的にここからhtmlを返す処理をここに書きます。そして、ここにリクエストが来て、htmlをブラウザに返すようにルーティングします。

<br>


```python: websokcet_proj/strange_watch/views.py
from django.shortcuts import render

# Create your views here. ここまでは自動で作られています。以下追加

from django.http import HttpResponse

# add
def index(request):
    return HttpResponse('''<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Test Page</title>
</head>
<body>
    <h1>Test page dayo!</h1>
    
</body>
</html>''')
```
HttpResponseクラスを使うと、文字列をhtmlにてをブラウザに送り付けてくれます。
テスト用？？？

<br>



## 07-2 websokcet_proj/strange_watch/urls.pyの作成と編集
strange_watchアプリフォルダの中にはurls.pyがありません。アプリを作った時に自動生成してくれれば嬉しいんですけど、他にもルーティングの方法があるんでしょうか？
ファイルを新規作成し、ルーティングを記載します。

```python:websokcet_proj/strange_watch/urls.py
from django.urls import path
from . import views

urlpatterns = [
    path('', views.index),
]
```
ポートの後ろに何もないurlが来た時には、先ほど書いたviews.pyのindex関数に紐づけられます。
そう、さっき作ったやつです。
使うためには`from . import views`でインポートしておく必要があります。

<br>


## 07-3. websokcet_proj/websocket_proj/urls.pyの編集
さらにこのDjangoサーバーのおおもとに`websokcet_proj/strange_watch/urls.py`を紐づける必要があります。

```python:websokcet_proj/websocket_proj/urls.py
from django.contrib import admin
from django.urls import path, include
import strange_watch
import strange_watch.urls # 追記

urlpatterns = [
    # path('admin/', admin.site.urls), # コメントアウト
    path('', include(strange_watch.urls)),  # 追記,
]
```
設定したポートに接続されると、ここに来ます。
この時に`strange_watch`アプリの`urls.py`ファイルも参照しろよ！というおまじないです。

最初の`views.py`を書くと、vscodeなどではどんどん保管されてくるのでタイポが減るのでこの順序で書くのがおすすめです。


ではサーバーを起動して、ブラウザから127.0.0.1:8000にアクセスしてみましょう。
![タイトルなし.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3655320/7844a153-1426-4635-855d-b17120a9661f.png)

h1タグでこれが表示できたらOKデス。

<br>

# 08. htmlをテンプレートフォルダにまとめる
Djangoはアプリフォルダ内にテンプレートフォルダを作ってそこにhtmlやcssなどを置くのが基本のようです。
先ほどはviews.pyのindex関数からhtmlを送っていましたが、そもままではvscodeなどの高機能エディタで編集する時にいろんな機能が使いにくいです。はい、補完機能全力で使ってます！GitHub Copilotも最近使い始めました。
そのため、htmlを一つのフォルダの中に保存し、管理しやすくするのが普通みたいです。まずは`strange_watch`フォルダの中に`templates`フォルダを作ります。
さらにここに`index.html`ファイルを作ります。

![タイトルなし.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3655320/e1bc78ee-7949-479a-938d-ea00d9f82285.png)


## 08-1. htmlファイルに書き込む
ファイルには適当にhtmlを書き込みます。テストなんで。

```html:index.html
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Test Page</title>
</head>
<body>
    <h1>Test page Yheaaaaaa!!</h1>
    <h2>WebSocket Test</h2>
    <div id="correct_watch">時計表示部分</div>
</body>
</html>
```
まぁ、内容は適当に・・・

<br>


## 08-2. setting.pyを修正

```python:setting.pyの修正箇所
TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': ['./strange_watch/templates'], # 修正
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]
```
テンプレートのディレクトリを指定します。要はhtmlをファイルを保存する場所です。
え？ここで指定するの？って感じがしますね。w

<br>


## 08-3. アプリ（strange_watch）のviews.pyの修正
```python:websokcet_proj/strange_watch/views/py
from django.shortcuts import render

# Create your views here.

# add
def index(request):
    return render(request, 'index.html')
```
render関数は、08-1で指定したフォルダの中から、templetesフォルダ内にある第二引数の名前のhtmlファイルをブラウザに送信してくれるみたいです。

<br>



## 08-4. ルーティングを修正
またしてもwebsoxket_proj内とstrange_watch内のルーティングを修正します。

```python:websokcet_proj/strange_watch/urls.py
from django.urls import path
from . import views

urlpatterns = [
    path('', views.index),
    path('index.html', views.index)),
]
```
ポート番号の後ろに何もなくても、index.htmlがあってもいいよにしとこ
ま、テスト何でなんでもいい。ちょっと試したかっただけ。笑

ブラウザの検索ウインドウに
http://127.0.0.1:8000
http://127.0.0.1:8000/index.html
いずれでも同じページが表示されるようにルーティングしておきます


<br>


```python:websokcet_proj/websocket_proj/urls.py
from django.contrib import admin
from django.urls import path, include
import strange_watch.urls # 追記

urlpatterns = [
    # path('admin/', admin.site.urls), # コメントアウト
    path('', include(strange_watch.urls)),  # 追記,
    path('index', include(strange_watch.urls)),  # 追記,
]
```
こっちも同様の目的で追記します。


<br>

## 08-5. サーバー起動して確認
127.0.0.1:8000/index.html（/index.html無しも同じ。）にアクセスしてこのページがでたらOKです。

![タイトルなし.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3655320/51ce0120-8bfd-4ac3-b946-3a11ce232b26.png)

あ、よく考えたらhttpやってるのに「WebSocket Test」って意味わからんっすね。すまん

<br>
<br>


# 09. サーバー時刻をブラウザで受け取る機能を実装
http通信でhtmlを送ってブラウザで表示することができました。
次hブラウザでボタンが押されたら、http通信でサーバーに要求を出して、サーバーから時刻を返すことをやりたいと思います。

<br>


## 09-1. views.pyに関数を作る
```python:views.pyに追記
import datetime
from django.http import JsonResponse

def get_server_time(request):
    current_time = datetime.datetime.now()
    current_time = current_time.strftime("%H:%M:%S")
    data = {
        'current_time': current_time,
    }
    print(data)
    return JsonResponse(data)
```
views.pyにこれを書き加えます。書き換えじゃないです。**追記**です。この関数からサーバー時間をブラウザに返します。

<br>

## 09-2. ルーティングの修正
面倒ですが、ルーティングを直します。ポート番号の後に、`get_current_time`という文字がついていた時のルーティングを作ります。

```python:websokcet_proj/strange_watch/urls.py
from django.urls import path
from . import views

urlpatterns = [
    path('', views.index),
    path('index.html', views.index)),
    path('get_current_time/', views.get_current_time),
]
```
`get_current_time`がポート番号の後についていたら、vews.pyのget_current_time関数に飛ばします。


```python:websokcet_proj/websocket_proj/urls.py
from django.contrib import admin
from django.urls import path, include
import strange_watch.urls # 追記

urlpatterns = [
    # path('admin/', admin.site.urls), # コメントアウト
    path('', include(strange_watch.urls)),  # 追記,
    path('index', include(strange_watch.urls)),  # 追記,
    path('get_current_time/', include(strange_watch.urls)),  # 追記
]
```
websocket_projのurls.pyも追記します。
`get_current_time`の文字があってもstrange_watch.urls.pyにルーティングさせるよう追記です。
ここまでやってくると、二つのurls.pyの意味が分かってきますね。
全体のルーティングはwebsocket_proj/urls.pyが担い、各アプリのルーティングはアプリ内のurls.pyが担うって感じですかね。

<br>


## 09-3. htmlを修正する
大幅に修正するので（といっても追記ですが・・）、全体を載せておきます。
```html:index.html
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Test Page</title>
</head>
<body>
    <h1>Test page Yheaaaaaa!!</h1>
    <h2>WebSocket Test</h2>
    <!-- 追記部分・・・ここから -->
    <span>  <!-- 時計表示部分 -->
        <p id="correct_watch">時計表示部分</p>
        <button id="correct_time">現在時刻取得</button>
    </span>

    <script> // ここからJavaScript
        let current_watch = document.getElementById("correct_watch");
        let get_correct_watch_button = document.getElementById("correct_time");

        // ボタンが押されたときに動く関数
        get_correct_watch_button.addEventListener("click", function(e) {
            get_correct_time(e);
        });

        // ボタンが押されたときの処理関数
        const get_correct_time = function(e) {
            console.log("ボタンが押されました");
            let url = 'get_current_time';
            fetch(url)
                .then(response => response.json())
                .then(data => {
                    console.log(data);
                    current_watch.textContent = data.current_time;
                })
                .catch(error => {
                    console.error('Error:', error);
                });
        };
    </script>
    <!-- 追記部分・・・ここまで -->
</body>
</html>
```

<br>


## 09-4. 試してみる
サーバーを再起動してアクセスしてみます。

![タイトルなし.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3655320/49aa769f-2850-4833-9a80-5f8ab3a7d3af.png)

時計表示部分とボタンが出現しました。
ではボタンをポチリます。

![タイトルなし2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3655320/04e328fd-4473-4659-aa09-c46683bc7357.png)

いゃぁぁぁぁぁぁぁ！

サーバーの時間が戻ってきてます。
サーバーを立ち上げたコマンドプロンプトを見てみまます。

![タイトルなし.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3655320/2f20d9fa-70da-499b-ad68-84da993207e2.png)

301が帰ってきているのはなんでだろ？
わかんないので、これはまた調べよう。というか、どなたか、教えてください。
Webや通信は苦手・・・


# 10. 終わりに・・・
ひとまずhttp通信ができました。目的はwebsocket通信なので、今はまだ道半ばです。
いきなりwebsocket通信をやってもイマイチ良さが理解できなかったので一旦http通信をおさらいしました。
次はいよいよWebSocket化していきます。

つづく