Python
MongoDB
bottle

PythonのBottleとBootstrapでInstagramみたいなのを作る(第三弾)

0.注意

この記事は完全に自分用のメモになっています

1. 前回のあらすじと今回の目的

第一弾では見た目を作りました。
第二弾ではデータベースを接続して使える一歩手前まで持っていきました。
画像の保存先をstaticなフォルダに指定していましたが、このままだとdeployするたびに全データが消失してしまうため、画像もmongoDBに保存することにします。
さらに今回は実際に投稿したり、削除したりできるようにしたいと思います。

2. 目標完成図

binaryImageというコレクションを新たに作り、「filepath」と「binaryImage」というドキュメントをもたせたいと思います。そして/user/<filepath>にアクセスした時にその画像が表示されるようにします。よってfilepathは前回と同じく<entryID>_<filename.jpg>のような名前にしたいと思います。

画像とコメントを入力できるページを作成し、アップロードをするとユーザーページに表示されるようにします。
エントリーについているボタンを押すとそのエントリーを消せるようにしたいと思います。

3. binaryImageコレクション

画像を専門で集めるコレクションbinaryImageを作ります。画像の保存はエントリーを作成する際に行われるので、そこに実装していきましょう。書き換えたのは最後の部分だけです。
ファイル名をsavepathという変数で指定し、画像自体のバイナリデータと一緒にbinaryImageコレクションに入れておきました。これで画像の保存ができたので、今度は画像を表示させることを考えます。

chenstagram.py
#エントリー投稿のロジック部分
@app.route("/<userID>/upload" , method = "post")
def addEntry(mongodb , userID) :
    #userが存在していなかったらなにもしない
    query = mongodb["userinfo"].find({"userID": userID})

    img = request.files.get("upload")
    comment = request.forms.get("comment")  #文字化けしない・・・?
    if query.count() == 0 :
        return "user not found"
    else :
        #userが存在するとき。コメントのみなのか画像もあるのかで処理内容区別
        userdbID = methods.userdbID_from_userID(userID, mongodb)
        if (img == None) & (comment == "") : return "なにもないよ"
        elif img == None :
            #commentのみ
            entryID = mongodb["entry"].insert_one(
                {"userdbID": userdbID, "imgfilename": "" , "comment": comment , "userID" : userID}
            ).inserted_id
        else :
            print(img.filename)  # safeな文字列のみで構成させるので、複数画像をアップロードするときにnameがかぶるかも
            entryID = mongodb["entry"].insert_one(
                {"userdbID": userdbID, "imgfilename": img.filename, "comment": comment , "userID" : userID}
            ).inserted_id

            #entryIDを付与してimgに格納
            savepath = str(entryID) + "_" + img.filename
            binaryImage = img.file.read()  #これでバイナリーデータになる模様

            mongodb["binaryImage"].insert_one({"filepath" : savepath , "binaryImage":binaryImage})

        return redirect("/" + userID)

4. 画像の表示

filepath(という名のファイル名)をデータベースに投げたら画像が表示されるようにしたいです。「/img/<filepath>」にアクセスした時に、filepathに基づいて画像を表示するプログラムが以下のように書きました。
今まで画像はimgフォルダに保存してそこから読み込んでいましたが、それはimg_staticというフォルダに変えて、基本的にこちらが用意した画像を呼び出す時にのみ使うようにします。

chenstagram.py
#IMGs : データベースから読みだしたい
@app.route('/img/<filepath>')
def sample_image(mongodb , filepath):
    binaryImage = mongodb["binaryImage"].find_one({"filepath":filepath})["binaryImage"]

    resp = HTTPResponse(status=200, body=binaryImage)  #200 means OK
    resp.content_type = 'image'  #'image/*' だとダウンロードになってしまった。
    resp.set_header('Content-Length', str(len(binaryImage)))
    return resp

@app.route('/img_static/<filepath>')
def static_image(filepath):
    return static_file(filepath, root="./img_static")

5. エントリーの削除を実装する

これは簡単です。entryIDを受けてentryコレクションからその記事を探し出し、削除します。
エントリーはデータベースから消えますが、画像はデータベース上に残り続けます。

chenstagram.py
#エントリー削除用のURL
@app.route("/deleteEntry/<userID>/<entryID>")
def deleteEntry(mongodb , userID , entryID) :
    #entryが存在していなかったらなにもしない
    query = mongodb["entry"].find({"_id": ObjectId(entryID)}) #IDをObjectIdに戻すの忘れがち
    if query.count() == 0 :
        return "The entry is not found"
    elif query.count() > 1 :
        return "記事がいくつもある・・・"
    else :
        #そのエントリーを削除する
        mongodb["entry"].delete_one({"_id": ObjectId(entryID)})  #IDをObjectIdに戻すの忘れがち
        return redirect("/" + userID)

6. HTMLの編集

userinfoコレクションとentryコレクションからデータを引っ張ってくるようにしました。
これでdeployするたびに画像が消えることはなくなりました。

userpage.html
          %for q in reversed(list(entry)):
            <div class="col-lg-6 mb-5">
              <div class="card lg-6 box-shadow" >
                <img class="card-img-top" src="../img/{{str(q['_id'])+'_'+ q['imgfilename']}}" alt = "">
                <div class="card-body">
                  <h5 class="card-title">@{{userinfo['userID']}}</h5>
                  <p class="card-text">{{q['comment']}}</p>
                  <div class="d-flex justify-content-between align-items-center">
                    <div class="btn-group">
                      <a href="deleteEntry/{{userinfo['userID']}}/{{str(q['_id'])}}" type="button" class="btn btn-sm btn-outline-secondary">Delete</a>
                      <button type="button" class="btn btn-sm btn-outline-secondary">Edit</button>
                    </div>
                    <small class="text-muted">9 mins</small>
                  </div>
                </div>
              </div>
            </div>
         %end

7. 今後

次はユーザーの登録や、ツイッターとの連携をやってみたいです。
見た目もこだわりたいので、javascriptの勉強とそれに付随するライブラリに触れていきたいと思います。