Djangoでデータベースを使わず掲示板を作ってみた!【Render+シンクラウド for Free】

※ 当サイトではアフィリエイト広告を利用しています。リンクは広告リンクも含みます。

この記事は約16分で読めます。
広告

Djangoが使えるRenderの無料だと
PostgreSQL(数か月は使える)やMySQLのデータベースが使えない。
さらにファイルに保存するデータベースsqlite3は使えるが保存ができない。
(スリープ状態になるとデフォルトのdb.sqlite3ファイルに内容が戻ってしまう)
例えば、こちらDjango+Vuetifyで作った掲示板(立ち上がるまで数分かかることがあります)
月末になるとRenderの750時間の無料インスタンス枠を使い切って
This service has been suspended.になっていることがあります
。)
詳細はこちら↓

数分は普通に保存できるが15分何もしないとスリープ状態になる。
そうすると新しい書き込みが消えてデフォルトに戻ってしまう。

そこで無料のサーバー、シンクラウド for Freeにcsvファイルを保存することで
掲示板のデータを保存することにしました。
シンクラウド for Freeへのcsvの読み込みはrequests、
書き込みはFTP、掲示板のデータはpandasで処理
csvファイルだけでなく同じようにAPIで取ってきた画像も保存するようにしました。
完成品 イチゲブログ掲示板(csvをシンクラウド for Free保存)(立ち上がるまで数分かかることがあります。)
実際、この方法はセキュリティや、たくさんのアクセスなど考慮されておらず実用的ではありません。
データベースを使ってください。
現在、関連性は分かりませんが、以下記事に書いたことがあったので、ご注意ください

シンクラウド for FreeでPythonで掲示板を作ってみた!
シンクラウド for FreeはPythonが使えます。CGIというものによって、クライアントからのリクエストをPythonで処理しレスポンスを返します。設定も簡単ですが、ちょっとハマるところもあるので、そういったところを紹介できればと思い
広告

プログラム

Djangoの基本的なプログラムは、こちらを参考にしてください。

settings.pyについては省略します。
urls.pyはユーザーが、そこへアクセスしたときに、どの処理に行くかを決めています。
path(‘空欄’なのでローカルの場合はhttp://127.0.0.1:8000/ にアクセスしたら、
どの処理をするかというとviews.pyのKeijiftpViewです。

プロジェクトの方のurls.py

from django.contrib import admin
from django.urls import path,include

urlpatterns = [
    path("admin/", admin.site.urls),
    path('',include("keijiftp_app.urls")),
]

アプリの方のurls.py(新規追加)

from django.urls import path
from . import views

app_name    = "keijiftp_app"
urlpatterns = [ 
    path('', views.KeijiftpView.as_view(), name="index"),
]

view.pyではget(ただアクセスしたときの動作)とpost(入力データが送られてきたときの処理)を書きます。

from django.shortcuts import render
from django.views import View
import requests
from keijiftp_app.freeftp import freeftp
import pandas as pd
from datetime import date
from datetime import datetime, timedelta
from dateutil.relativedelta import relativedelta
from django.shortcuts import redirect
import os
class KeijiftpView(View):
#Get処理(ユーザーが、ただアクセスしたとき実行される)
    def get(self, request, *args, **kwargs):
#ここは自分のシン・クラウド for Freeのurlに変えてください。
        url = 'https://cf193110.cloudfree.jp/html/keijiftp/toukouftp.csv'
#シン・クラウド for Freeのurlにアクセスしたときのレスポンスをresponseに代入
        response = requests.get(url)
#正常受信の場合
        if response.status_code == 200:
#受信したデータの中身をバイナリデータモードでcsvファイルとして保存(後述)
            with open('toukouftp.csv', 'wb') as f:
                f.write(response.content)
#csvファイルをpandasのデータフレームにする
            df=pd.read_csv("toukouftp.csv",index_col=0)
# postsに変換、to_dictはデータフレームを辞書型に変換してくれてhtmlに送ったとき扱いやすい
            posts = df.to_dict('records')
#インデックスがto_dictではつかないので追加した
            for i in range(len(posts)):
                posts[i]['id'] = str(df.index[i])
#htmlに送るデータ作成
            context = {'posts': posts}
#ローカル時は"keijiftp_app/index.html"を'http://127.0.0.1:8000/index.html'にしてください。
            return render(request,"keijiftp_app/index.html",context)
        else:            
            return render(request,"keijiftp_app/index.html")
#POST処理(ユーザーがデータを送信したときに実行される)
    def post(self, request, *args, **kwargs):
#削除する場合の処理
        delid=request.POST.get("user_id")
        if request.POST.get("user_id")!=None:
            df=pd.read_csv("toukouftp.csv",index_col=0)
            # 有効なidだったら削除
            if delid.isdigit():
                delid=int(delid)
                max_id = int(df.index.max())
                if max_id>=delid:
                # 削除したい行のidをリストで取得する
                    ids = [delid]
                # リストで取得したidを元に、行を削除する
                    df = df.drop(ids)
#投稿の場合の処理
        else:
            name=request.POST.get("user_name")
            message=request.POST.get("user_message")
            columns = [ 'toukoubi','name','message']
#csvファイルが存在している場合
            if os.path.exists('toukouftp.csv'):
                df=pd.read_csv("toukouftp.csv",index_col=0)
                dt_now = datetime.now()
                toukoubi=dt_now.strftime('%Y年%m月%d日 %H:%M:%S')
                list = [[toukoubi,name,message]]
                df_append = pd.DataFrame(data=list, columns=columns)
                df1 = pd.concat([df, df_append], ignore_index=True, axis=0)
#csvファイルがない場合、新しく作る
            else:
                df1 = pd.DataFrame(columns=columns)
            df=df1
        df.to_csv("toukouftp.csv")
    # postsに変換
        posts = df.to_dict('records')
        context = {'posts': posts}
#csvファイルをシン・クラウド for FreeにFTPで保存
        freeftp("toukouftp.csv")
#ワンちゃんの写真を取得
        response = requests.get('https://dog.ceo/api/breeds/image/random')
        # url = response.json()['image']
        url = response.json()['message']
        image = requests.get(url).content

        with open('dog.jpg', 'wb') as f:
            f.write(image)
            filename='dog.jpg'
#ワンちゃんの写真をシン・クラウド for FreeにFTPで保存
        freeftp(filename)
#ここでgetのときと同じreturn処理をすると、F5を押したとき「フォームを再送信しますか」というのがいちいち出てしまう。PRG「Post/Redirect/Get」というらしい。その対策でgetにいくようにリダイレクトしている。
        return redirect('/')

index.htmlはhtmlの最小部分だけ載せます。
view.pyでcontext = {‘posts’: posts}とreturn render(request,”keijiftp_app/index.html”,context)によりindex.htmlに変数postsを渡しています。

<div>
    <p>名前を投稿内容を記入し送信ボタンを押してください!投稿後、削除もできます。</p>
	  <!--<form action="http://127.0.0.1:8000/" method="post">-->
    <form action="https://keijibanftp.onrender.com/" method="post">
    {% csrf_token %}
    <label for="user_name">名前:</label>
    <input type="text" id="user_name" name="user_name" class="border"><br><br>
    <label for="user_message">投稿内容:</label>
    <input type="text" id="user_message" name="user_message" class="border"><br><br>
    <input type="submit" class="btn btn--orange"  value="送信">
  </form>  	
	</div>

	<p>このサイトの詳細は<a href="https://kikuichige.com">イチゲブログ</a></p>
  <p>ランダムに犬の写真を表示します。リロード(F5)ではなくスーパーリロード(CTRL+F5)で更新されます。</p>
  <img src="https://cf193110.cloudfree.jp/html/keijiftp/dog.jpg" style="width: 25%; height: auto;"></a>
  <h2>掲示板</h2> 
  <table>
  <thead>
    <tr>
      <th>Id</th>
      <th>Date</th>
      <th>Name</th>
      <th>Post</th>
    </tr>
  </thead>
  <tbody>
    {% for post in posts %}
    <tr>
      <td>{{ post.id}}</td>
      <td>{{ post.toukoubi}}</td>
      <td>{{ post.name }}</td>
      <td>{{ post.message }}</td>
    </tr>
    {% endfor %}
  </tbody>
</table>
<p>削除したいIDを半角英数で入力語削除ボタンを押すと削除されます。</p>
<!-- <form action="http://127.0.0.1:8000/" method="post"> -->
  <form action="https://keijibanftp.onrender.com/" method="post">
  {% csrf_token %}
  <label for="user_id">削除id:</label>
    <input type="text" id="user_id" name="user_id" class="border"><br><br>
    <input type="submit" class="btn btn--red"  value="削除">
</form>

</div> 

目次へ

受信ファイルの処理

受信したデータの中身はresponse.contentに入っています。これは保存しないと使えません。
バイナリデータ(テキスト以外)としてファイル名.拡張子を指定して
response.contentの中身を保存する処理です。
with open(file, mode) as 変数:はpythonでファイルを保存するときの定型的な書き方です。
別途お調べください。
openのモード’wb’は
’w’:ファイルを書き込み用として開く
’b’:ファイルをバイナリモードで開く r,w,a,xと合わせて使う
withを先頭に付けておくと処理が終わるとファイルを閉じてくれます。
バイナリモードとテキストモードで何が違うのかは、
文字以外はバイナリモードにしないとダメだと思います。
csvはメモ帳で見れて文字なのでテキストモードにしても、
もしかしたら問題ないかもしれません。(やってないのでわかりません)

CSVファイルを受信して保存するところ

        url = 'https://cf193110.cloudfree.jp/html/keijiftp/toukouftp.csv'
#urlにアクセスしたときのレスポンスをresponseに代入
        response = requests.get(url)
#正常受信の場合
        if response.status_code == 200:
#受信したデータの中身をバイナリデータモードでcsvファイルとして保存
            with open('toukouftp.csv', 'wb') as f:
                f.write(response.content)

ただ画像はバイナリモードでないとうまくいかないはずです。
こちらDOG APIというところでAPIにアクセスすると
ランダムに犬の画像が返ってくるものを使わせてもらいました。
https://dog.ceo/api/breeds/image/randomにアクセスすると
画像の場所がmessage変数の中に入っているので、{ “message”: “https://images.dog.ceo/breeds/saluki/n02091831_2288.jpg”, “status”: “success” }
そこへrequests.getでアクセスします。
image = requests.get(url).contentは
先ほどと同じよう書くと
response = requests.get(url)
image = response.content
これはモードをテキストモードのwtでは確実にダメだと思います。(確認はしてない)
また今回、Djangoでダウンロードした画像の保存先は、特に気にしないでやってますが
処理によってはstaticディレクトリーとの関係を気にする必要が出てくるかもしれません。

具体的には、index.htmlで使う画像をDjangoからダウンロードした画像にする場合、
ファイルはstaticに保存しないと配信できないし動的に
ダウンロードして配信するのも簡単ではないかも。
今回はindex.htmlの画像はシンクラウド for Freeにアップロードしたものを
imgタグでダウンロードしています。<img src=”https://cf193110.cloudfree.jp/html/keijiftp/dog.jpg”

画像を受信して保存するところ

        response = requests.get('https://dog.ceo/api/breeds/image/random')
        url = response.json()['message']
        image = requests.get(url).content

        with open('dog.jpg', 'wb') as f:
            f.write(image)
            filename='dog.jpg'

目次へ

ファイル送信処理

Djangoが動いているサーバー(Render)から
別のサーバー(シンクラウド for Free)にファイルをFTPアップロードする方法は
PythonでFTPアップロードを行う方法を関数にして、そのまま使わしていただきました。
もちろん接続先ホストの設定は違います。
jupitornotebookなどで単独で、この部分を実施確認してからの方が楽です。
シンクラウド for FreeのFTPに関しては、こちらを参考にしてください。
シンクラウド for FreeにHtml、CSS、Javascriptをアップロードしてみた。

まとめ

Renderは無料で使えるのは、いいのですが無料で使うには工夫が必要で
そのために思いついた内容でした。
Renderの起動が遅いのは、無料版では、どうすることもできないです。
Herokuやrailway(期間限定で無料で使えるかも?)は起動速くて無料で使えたのですが
使えなくなりました。悪用されるからか理由は分かりませんが無料の流れがなくってる中で
Renderは起動を遅くして無料で使わしてくれていると思います。

目次へ

イチゲをOFUSEで応援する(御質問でもOKです)Vプリカでのお支払いがおすすめです。
MENTAやってます(ichige)

コメント

タイトルとURLをコピーしました