質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
Django

DjangoはPythonで書かれた、オープンソースウェブアプリケーションのフレームワークです。複雑なデータベースを扱うウェブサイトを開発する際に必要な労力を減らす為にデザインされました。

WebSocket

WebSocketとは双方向・全二重コミュニケーションのためのAPIでありプロトコルのことを指します。WebSocketはHTML5に密接に結びついており、多くのウェブブラウザの最新版に導入されています。

Python

Pythonは、コードの読みやすさが特徴的なプログラミング言語の1つです。 強い型付け、動的型付けに対応しており、後方互換性がないバージョン2系とバージョン3系が使用されています。 商用製品の開発にも無料で使用でき、OSだけでなく仮想環境にも対応。Unicodeによる文字列操作をサポートしているため、日本語処理も標準で可能です。

Q&A

解決済

1回答

929閲覧

Djangoを用いたチャット作成時のwebsocketの書き方について

myy388

総合スコア16

Django

DjangoはPythonで書かれた、オープンソースウェブアプリケーションのフレームワークです。複雑なデータベースを扱うウェブサイトを開発する際に必要な労力を減らす為にデザインされました。

WebSocket

WebSocketとは双方向・全二重コミュニケーションのためのAPIでありプロトコルのことを指します。WebSocketはHTML5に密接に結びついており、多くのウェブブラウザの最新版に導入されています。

Python

Pythonは、コードの読みやすさが特徴的なプログラミング言語の1つです。 強い型付け、動的型付けに対応しており、後方互換性がないバージョン2系とバージョン3系が使用されています。 商用製品の開発にも無料で使用でき、OSだけでなく仮想環境にも対応。Unicodeによる文字列操作をサポートしているため、日本語処理も標準で可能です。

0グッド

0クリップ

投稿2022/03/24 23:18

前提

現在、独学でDjangoを用いてチャットアプリを作成しようと試みています。
まず、こちらの記事(https://www.hiramine.com/programming/chat_django_channels/index.html)を参考にチャットを作成し、これはうまく動かすことができました。これを作成するとき初めてWebSocketを扱いました。

実現したいこと

上記ページでは、チャットの参加画面とチャット画面を同じページで動作させているため、ページ分けたい(ルーム名の入力等をentrance.html、実際のチャットをchat.htmlで行うイメージ)と思い変更を加えているのですが、チャット画面で思うように結果が表示されず、質問いたしました。

発生している問題・エラーメッセージ

上記URLの内容から、entrance.htmlとchat.htmlに分け、またjsをmain.jsとし、static fileから読み込む形にしました。
しかし、Join Chat , Send等のボタンを押す度にwebsocketがリダイレクトされてしまい、うまく接続ができていないように思います。
(認識違いかもしれません。)

main.jsとして分けるものではないのかもしれませんが、entranceで入力されたユーザー名等をそのままチャット画面でも表示したいと思っているため、このようにしています。ほかに方法があればそちらで問題ありません。

該当のソースコード

https://github.com/myyyy38/chat_1
掲載すべきコードが大量になってしまうため、githubに上げました。申し訳ありません。

試したこと

自分なりにdjangoでチャットを作成されている方のページを拝見しましたが、解決には至りませんでした。

補足情報(FW/ツールのバージョンなど)

python: 3.9.5

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答1

0

ベストアンサー

【エラーの状況】
まず、質問のソースを実行してConsoleで確認してみました。
entranceページで「Join Chat」ボタンをクリックすると、一瞬下記のエラーが出てchatページに遷移しています。

Uncaught TypeError: Cannot read properties of null (reading 'value') at onsubmitButton_Send (main.js:63:48) at HTMLElement.onsubmit ((index):41:75)

遷移後のchatページの username や roomname のinputは空白の状態で、「Send」ボタンをクリックすると上記と同様なエラーが出ています。

ここで、エラー2行目のonsubmitButton_Send (main.js:63:48)は、main.jsの

js

1 function onsubmitButton_JoinChat(){ 2 console.log("joinが押されました") 3 //ユーザー名 4 let strInputUserName = g_elementInputUserName.value;  // <<<ここ

が該当していました。すなわち、g_elementInputUserNameがnullということです。

g_elementInputUserNameの定義をさかのぼると、main.jsの4行目で

const g_elementInputUserName = document.getElementById("input_username");

となっており、エラー状況から考えると document.getElementById("input_username") がnullを返してきていることになります。

【エラーの原因】
おそらく、main.jsの冒頭1行目以降が、chat.html内要素のレンダリング前に実行されているため、g_elementInputUserName などのグローバル変数は null を保持してしまっているのでしょう。

【修正の検討】
では、window.onload =~等に、グローバル変数(g_***)への値代入処理部分を入れてしまえばよいのでは、と思われるかもしれませんが、これでは解決しません。

なぜならば、たとえレンダリング後に document.getElementById を行い、entrance.html内の要素内の値や、webソケットオブジェクト(g_socket)を適切に取得できたとしても、chat.htmlにページが遷移したら、staticフォルダから同じ内容のmain.js が新規に読み込まれ、それまで保持していたグローバル変数にはアクセスできなくなるからです。


したがって、修正案として次のようなアプローチが考えられます。
・entrance.html で入力したusername と roomname は、フォームデータの形で サーバーに送信する。
・サーバーは、受け取った username と roomname を設定した chat.html を返す。
・クライアント側でのwebソケットの管理はchat.html内のjsだけで行う。(entranceとchatの2ベージでmain.jsを使い回さない)
注:下記は元コードからの変更を少なくするため、最適化されていません。文字数の都合上、不要なログ表示やコメントを削除していますが、修正に影響のない部分のコードはそのまま残しています。


entrance.html
-> フォームとして username と roomname を submit します。

html

1{% extends 'chat/base.html' %} 2{% load static %} 3{% block content %} 4<div id="div_container"> 5 <div id="div_header"> 6 <h1>My Chat</h1> 7 </div> 8 9 <div id="div_main"> 10 <div id="div_join_screen"> 11 <form action="{% url 'chat:chat'%}" method="post" style="text-align: center; width:100%;"> 12 {% csrf_token %} 13 User name<br /> 14 <input type="text" name="username" placeholder="Enter User name" autofocus><br /><br /> 15 Room name<br /> 16 <input type="text" name="room" placeholder="Enter Room name" autofocus><br /><br /> 17 Room name must be a string containing only ASCII alphanumerics, hyphens, or periods.<br /><br /> 18 <button type="submit">Join Chat</button> 19 </form> 20 </div> 21 </div> 22</div> 23{% endblock %}

chat.html
-> views.py から受け取った username、room を input に表示するとともに、
main.jsの内容の大半をこちらの中のscriptに移行。
onopen() の中でチャット開始処理を行なっています。

html

1{% extends 'chat/base.html' %} 2{% load static %} 3{% block content %} 4<div id="div_container"> 5 <div id="div_header"> 6 <h1>My Chat</h1> 7 </div> 8 9 <div id="div_main"> 10 <div id="div_chat_screen"> 11 <form action="{% url 'chat:entrance'%}" onclick="onclickButton_LeaveChat()"> 12 <button type="submit">Leave Chat</button> 13 </form><br /> 14 15 Room name : <input type="text" id="text_roomname" value="{{room}}" readonly="readonly"><br /> 16 User name : <input type="text" id="text_username" value="{{username}}" readonly="readonly"><br /> 17 18 <form action="" onsubmit="onsubmitButton_Send();return false;"> 19 Message : <input type="text" id="input_message" autocomplete="off" autofocus /><input type="submit" 20 value="Send" /> 21 </form> 22 <ul id="list_message"></ul> 23 </div> 24 </div> 25</div> 26<script> 27 28 let ws_scheme = window.location.protocol == "https:" ? "wss" : "ws"; 29 const g_socket = new WebSocket(ws_scheme + "://" + window.location.host + "/ws/chat/"); 30 31 function onclickButton_LeaveChat() { 32 //メッセージリストのクリア 33 while (document.getElementById("list_message").firstChild) { 34 document.getElementById("list_message").removeChild(document.getElementById("list_message").firstChild); 35 } 36 document.getElementById("text_username").value = ""; 37 g_socket.send(JSON.stringify({ "data_type": "leave" })); 38 } 39 40 function onsubmitButton_Send() { 41 let strMessage = document.getElementById("input_message").value; 42 if (!strMessage) { 43 return; 44 } 45 g_socket.send(JSON.stringify({ "message": strMessage })); 46 console.log("Sendが実行されました") 47 console.log(JSON.stringify({ "message": strMessage })) 48 document.getElementById("input_message").value = ""; 49 return false; 50 } 51 g_socket.onopen = (event) => { 52 console.log("websocket open") 53 g_socket.send(JSON.stringify({ "data_type": "join", "username": document.getElementById("text_username").value, "roomname": document.getElementById("text_roomname").value })); 54 } 55 56 g_socket.onmessage = (event) => { 57 if (!document.getElementById("text_username").value) { 58 return; 59 } 60 61 let data = JSON.parse(event.data); 62 let strMessage = data["datetime"] + "-[" + data["username"] + "]-" + data["message"]; 63 let elementLi = document.createElement("li"); 64 elementLi.textContent = strMessage; 65 document.getElementById("list_message").prepend(elementLi); 66 } 67 68 g_socket.onclose = (event) => { 69 console.error("Unexpected: Chat socket closed."); 70 } 71</script> 72 73{% endblock %}

base.html
main.jsの内容はchat.html内に移したので、static ディレクトリからの読み込み部分を削除。(main.js自体も不要なので削除)
(main.jsはstaticディレクトリ内に残しておき、上記chat.html内に書いたスクリプトに修正の上、下記main.jsの読み込み行をbase.htmlからchat.htmlに移してもよいと思います)

diff

1 <body> 2 <!-- Optional JavaScript --> 3- <script type="text/javascript" src="{% static 'js/main.js' %}"></script>  <===削除 4 <!-- jQuery first, then Popper.js, then Bootstrap JS --> 5 <script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script> 6 (略)

・consumers.py
strGroupName が空だとchannel側でエラーが出るので設定。

diff

1(略) 2 #websocket接続時の処理 3 async def connect(self): 4 #groupに参加 5+ self.strGroupName = 'chat' 6(略)

views.py
entranceからsubmitされたデータ(username, room)を、chat.htmlの各inputに設定します。

py

1() 2def entrance(request): 3 print("entrance実行") 4 return render(request,'chat/entrance.html') 5 6def chat(request): 7 print("chat実行") 8 params = { 9 "username" : request.POST.get("username"), 10 "room" : request.POST.get("room"), 11 } 12 return render(request,'chat/chat.html', params)

元の記事のコードだと、1ページの中でentranceとチャットページを管理するようになっていましたが、それを無理やり2つに分けるとなると苦労しますね。

django-channelsの公式チュートリアルには、URLでルームを分けるというアプローチでのコードが丁寧に解説されているので、そちらも参照されてはいかがでしょうか。
https://channels.readthedocs.io/en/stable/tutorial/index.html

検索すると、ログイン認証まで考慮した実践的なコードも見つかりました。
https://zenn.dev/y_k/articles/e8878460fff3d5aa1d1d

投稿2022/03/26 21:00

編集2022/03/26 23:43
退会済みユーザー

退会済みユーザー

総合スコア0

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

myy388

2022/03/30 04:57

ご丁寧なコメント本当にありがとうございます! 少し時間をかけて読んでみようと思います。読み終えたらまたコメントさせていただきます。
myy388

2022/04/07 04:08

遅くなり申し訳ございません。 拝見しました。jsファイルを別で管理するのが必ずしも適切でないということがわかりました。ありがとうございました!
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問