Python
MongoDB
bootstrap
bottle

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

0.注意

試行錯誤しながら開発しつつ、それと並行して記事を書いているため、人に読ませる文章ではなくなってしまいました。完全に個人用のメモです。

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

Instagramのようなwebアプリケーションを作るために、第一弾ではまずはモックを作成した。
現状ではまだ、連番に名前付けされた画像とテキストを読み込むことしかしておらず、使い物になりません。
そこで第二弾ではmongoDBを使用してデータベースの導入を行います。

2. 目標完成図

データベースに「ユーザー情報」コレクションと「投稿内容」コレクションを作ることにします。それぞれ中身は次のようになっています。
「ユーザー情報コレクション」 = {userdbID , userID}
「投稿内容コレクション」 = {entryID , userdbID , imgfilename, comment}
userdbIDでユーザーを管理し、userdbIDをもつデータを「投稿内容コレクション」から引っ張ってきて表示させるイメージです。

3. mongoDBの用意

bottleにはプラグインとしてbottle-mongoがあリます。これをインストールしました。
mongoDBのデータサービスをしてくれるmLabに登録し、無料で500MBを使わせてもらいます。
mLabに「chenstagram」という名前でデータベースを作成し、このデータベースのuser登録をします(mLabのユーザー登録とは別)。この時登録したuser名とデータベース用のパスワードはデータベースの読み書きで使用するため、セキュリティー上安全に保管しておく必要があります。

さてこのデータベースに「userinfo」という名前のコレクションを作り、ここにuserIDとを保存しようと思います。

4. データベースの呼び出し

database.pyをプロジェクト内に作成し、MongoPluginをimportします。
ここに先ほどデータベースのユーザー登録をした時のuser名とパスワードを書き、以下の要領でdb_pluginを作成します。
ユーザー名とパスワードは先ほど言ったようにこれを知っているとデータベースにアクセスできてしまいます。このように野ざらしで書いていてセキュリティー上問題がある気がしてならないが、どう対処するのかわからないため、今回はこのようにファイルを分けることに留めます。セキュリティー対策については後ほど詳しい人から意見を伺うことにします・・・

database.py
from bottle.ext.mongo import MongoPlugin

def createDatabase():
    username = "??????"
    password = "??????"
    database_name = "chenstagram"

    database_uri = "mongodb://" + username + ":" + password + "@ds117878.mlab.com:17878/" + database_name
    db_plugin = MongoPlugin(uri =database_uri , db = database_name)
    return db_plugin

5. Bottleにデータベースをインストール!

chenstagram.pyに戻り(もうdatabase.pyには触れない)、データベースをbottleにインストールします。

chenstagram.py
import database

db_plugin = database.createDatabase()
app.install(db_plugin)

これにより不思議な魔法がかかり、mongodbという変数を呼ぶと、データベースにアクセスできるようになります!

6. ユーザー登録機能の実装

まずは簡単な形でユーザー登録機能を実装します。
"/register/<username>"にアクセスすると、<username>を登録する形にします。
ただしすでにその<username>が存在していた場合は何もしません。

登録するのはusernameだけにします。
登録が終わったらユーザーページに飛ぶようにしましょう。
この辺はどうせ後で大幅にアップグレードすると思うので適当に書きます。

chenstagram.py
@app.route("/register/<userID>")
def database(mongodb , userID) :
    #データベースを検索してすでに存在しないかを検証
    query = mongodb["userinfo"].find({"userID" : userID})
    if query.count() == 0 :
        #登録する
        userdbID = mongodb["userinfo"].insert_one( {"userID" : userID   } ).inserted_id

        return redirect("/" + userID)
    else :
        #すでに存在している
        return redirect("/" + userID)

7. データベースにユーザー情報を登録してみる

とりあえず、デバッグというかお試しでusername:yuno_miyakoに投稿内容コレクションを作成してみましょう。ついでにimgのファイル名についても考えます。今はimgフォルダの中に画像を突っ込んでいますが、これはユーザーごとに仕分けないといけませんし、画像のファイル名も一意に定まらないと検索ができません。
そこでファイル名は「エントリーのid_ファイル名」とすることにします。これで一意には定まると思います・・・

chenstagram.py
#デバッグ用にyuno_miyakoには画像とコメントを加えておく
@app.route("/debug/yuno_miyako")
def database(mongodb):
    #yuno_miyakoが存在するか確認
    query = mongodb["userinfo"].find({"userID": "yuno_miyako"})
    if query.count() == 0 : mongodb["userinfo"].insert_one( {"userID" : "yuno_miyako"  } )

    query = mongodb["userinfo"].find_one({"userID": "yuno_miyako"})
    userdbID = query["_id"]

    entry = mongodb["entry"].find({"userdbID" : userdbID})
    if entry.count() == 0 :
        #testimgから3枚画像を引っ張ってきてそれを登録する。testimgの名前に+entryIDを加えてimgに移動する
        texts = ["シャネルのバッグです" , "すっご〜〜い" , "そら 綺麗"]
        imgs = os.listdir("./testimg")

        for i in range(3) :
            entryID = mongodb["entry"].insert_one(
                {"userdbID" : userdbID , "imgfilename" : imgs[i] , "comment" : texts[i]}
            ).inserted_id

            shutil.copy("./testimg/"+imgs[i] , "./img/"+str(entryID)+ "_" + imgs[i])
        return "登録完了"
    else :
        return "すでに何かがcontentsに登録されています"

8. htmlに表示させる

userページを表示させるためのpythonコードを書き換えます。
今まではコメントをそのまま直で渡していましたが、「投稿内容」コレクションから情報を取り出し渡すことにします(entryをそのまま渡す方がいいのだろうか・・・後で検討)

chenstagram.py
#USER PAGE
@app.get("/<userID>")
def userpage(userID , mongodb) :
    userdbID = methods.userdbID_from_userID(userID, mongodb)
    entry = mongodb["entry"].find({"userdbID" : userdbID})

    contents = []
    for q in entry :
        entryID = q["_id"]
        imgfilename = q["imgfilename"]
        comment = q["comment"]
        contents.append({"entryID" : entryID , "imgfilename" : imgfilename , "comment" : comment})
    return template("userpage" , userID = userID , contents = contents)

html側も書き換えてあげることで表示がされました。

userpage.html
            %for i in range(0,3) :
            <div class="col-lg-6 mb-5">
              <div class="card lg-6 box-shadow" >
                <img class="card-img-top" src="../img/{{str(contents[i]['entryID'])+'_'+contents[i]['imgfilename']}}" alt="Card image cap">
                <div class="card-body">
                  <h5 class="card-title">@{{userID}}</h5>
                  <p class="card-text">{{contents[i]['comment']}}</p>
                  <div class="d-flex justify-content-between align-items-center">
                    <div class="btn-group">
                      <button type="button" class="btn btn-sm btn-outline-secondary">View</button>
                      <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

9. まとめ

見た目は前回と全く変わっていませんが、mongoDBと接続させて外部データベースから画像ファイルの名前やコメントを取り出せるようになりました。
開発の途中で変数名を変えたり(username -> userID)しており、人が読んでわかる内容ではなくなってしまったことが気がかりです。
次回は実際に画像をアップロードしてエントリーを投稿できるようにしたいと思います。