ビジネスパーソン・ガジェット置場 empty lot for business

営業や仕事、それに伴う生活を便利に楽にするツール、ガジェットを作ります。既にあるツールも自分用にカスタマイズ。

python: Flaskアプリの作成11 DBの関連付け

備忘録です。

今回は2つのDBのテーブルを関連付ける備忘録です。通常はユーザーと投稿記事を関連付けるなどで使用すると思うのですが、今回は2種類の投稿記事用のテーブルを関連づけます。

関連付け

2つのテーブルを関連づける

今回はプロジェクトを管理するアプリケーションという設定で、

  • プロジェクト
  • プロジェクトを達成するための要件

という2つのテーブルを作成して関連づけます。

 

プロジェクト

プロジェクトを運用するとき、そのプロジェクトを成功させるための要件というものが必要ですよね。

 

例えばWebアプリケーションを作るというプロジェクトであれば、

  1. 開発環境を準備する
  2. 必要なライブラリをインストールする
  3. 必要なライブラリをインポートする
  4. DBを準備する

等々が要件になるかと思います。

 

これらを管理するアプリケーションを作る場合、

  • プロジェクトという投稿
  • プロジェクトを達成するための要件という投稿

が出てきます。

 

こんな感じ。

 

つまりあるプロジェクトとそれを運用するための要件は密接に関わってい流ので、それらを関連付けするようにテーブルに設定を行います。

 

今回のようなケースでは、特に関連づける必要もないのかもですが、今後、ユーザーと記事を関連づけたいというようなことがあっても同じように使えるかと思い備忘録とします。

 

※ユーザーと記事を関連づける場合、今回のプロジェクトがユーザー、要件が記事と考えれば大丈夫。

 

その1 関連づける2つのテーブルを作成

# プロジェクト用テーブル
class Project(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    p_title = db.Column(db.String(255))
    p_content = db.Column(db.Text)
    p_status = db.Column(db.Integer)
    p_crated_at = db.Column(db.DateTime, default=datetime.now(pytz.timezone('Asia/Tokyo')))
    p_completed_at = db.Column(db.DateTime)
        # 関連付けに必要 elements = db.relationship('Element', backref='projecter') # 要件用テーブル class Element(db.Model): id = db.Column(db.Integer, primary_key=True) e_title = db.Column(db.String(255)) e_content = db.Column(db.Text) e_status = db.Column(db.Integer) e_created_at = db.Column(db.DateTime, default=datetime.now(pytz.timezone('Asia/Tokyo'))) e_completed_at = db.Column(db.DateTime)
        # 関連付けに必要 projecter_id = db.Column(db.Integer, db.ForeignKey('project.id'))

このプロジェクトと要件を関連づけますが、関連付けで必要なコードはそれぞれのモデルの最後の行です。

 

ちなみにプロジェクトは複数の要件を持ち、要件は一つのプロジェクトを持つ場合の設定になっています。

 

一つのプロジェクトを持つ要件の方にはそのプロジェクトのidをForeinKeyの引数に渡します。また、複数の要件を持つプロジェクトにはbackref=に要件でつけた変数名(projecter)を渡します。

 

その2 プロジェクトの要件追加ボタンにプロジェクトのidをセットする

# project.html(一部)※その4で全文を掲載します

<h1 class="mt-4">プロジェクト</h1>
<h2 class="mt-4 border-bottom border-secondary border-3">メインプロジェクト</h2><br/>
<div class="{% if project.p_status == 0 %}shadow p-3 mb-5 bg-body rounded{% else %}shadow p-3 mb-5 bg-secondary rounded text-white{% endif %}">
  <h2>{{ project.p_title }}</h2>
  <small>作成日:{{ project.p_crated_at.strftime('%Y-%m-%d %H:%M') }}</small><br/>
  {% if project.p_completed_at %}
    <small>完了日:{{ project.p_completed_at.strftime('%Y-%m-%d')}}</small><br/>
  {% endif %}
  {{ project.p_content }} <br/><br/>
  <a href="{{ url_for('edit_project', id=project.id )}}" class="btn btn-outline-secondary btn-sm">編集</a>
  <a href="{{ url_for('delete_project', id=project.id)}}" class="btn btn-outline-danger btn-sm">削除</a>
  # ボタンをクリックした際にprojectのidを同時に渡し、add_element/p_idというurlを作る
  <a href="{{ url_for('add_element', p_id=project.id)}}" class='btn btn-outline-success btn-sm' >要件追加</a>
</div>

これはプロジェクトを表示するページですが、そのページに要件を作成するボタンをセットしています。(下記緑のボタン)

コードの最後の</div>の前のaタグ(要件を作成するボタン)の中でurl_forに引数としてこのプロジェクトのidを渡しています。つまり、要件を作成するためにボタンを押す段階で、どのプロジェクトの要件を作るのかを示すことになります。

 

その3 要件を作成する際にプロジェクトのidをDBに登録する

#app.py

# 要件を追加するページ(<>の中でプロジェクトのid(p_id)が渡されてきています)
@app.route('/add-element/<int:p_id>', methods=['GET', 'POST'])
def add_element(p_id):
    form = ElementForm()
    if form.validate_on_submit():
        # プロジェクトのcreateボタンから遷移させることでprojectのIDを渡すことができ、それをDBに保存する
        # 同じprojectのidをもった要件を複数作ることができる
        projecter = p_id
        e_status = 0
        element = Element(e_title=form.e_title.data, e_content=form.e_content.data, e_status=e_status, projecter_id=projecter)
        form.e_title.data = ''
        form.e_content.data = ''
        db.session.add(element)
        db.session.commit()
        # 属しているプロジェクトのページに戻す
        project = Project.query.get_or_404(projecter)
        return render_template('project.html', project=project)
    return render_template('add_element.html', form=form)

これで、要件が関連づいたプロジェクトのidを持つことができます。

 

その4 プロジェクトを表示するページで関連する要件を全て呼び出す。

# app.py

# 各プロジェクトのページ
@app.route('/project/<int:id>')
def project(id):
    project =Project.query.get_or_404(id)
    return render_template('project.html', project=project)

プロジェクトのページのルーティングは上記のように単純です。

 

プロジェクト用のテーブルで、要件に関連づいたelementsという項目を追加しているのでこの変数を呼び出せば、このプロジェクトに関連付いている全ての要件を呼び出すことができます。

 

例えば、その2で掲載しているプロジェクトを表示するコードの下に

{{ project.element }}

このようにコードを書けば関連づいている要件がリスト形式で返ってきます。

 

では要件を表示します。

 

今回は要件の表示もプロジェクトと同じようにbootstrapのカード形式で表示したいのでコードは下記のようになります。

# project.html(全文)

{% extends "base.html" %}

{% block content %}
<h1 class="mt-4">プロジェクト</h1>
<h2 class="mt-4 border-bottom border-secondary border-3">メインプロジェクト</h2><br/>
<div class="{% if project.p_status == 0 %}shadow p-3 mb-5 bg-body rounded{% else %}shadow p-3 mb-5 bg-secondary rounded text-white{% endif %}">
  <h2>{{ project.p_title }}</h2>
  <small>作成日:{{ project.p_crated_at.strftime('%Y-%m-%d %H:%M') }}</small><br/>
  {% if project.p_completed_at %}
    <small>完了日:{{ project.p_completed_at.strftime('%Y-%m-%d')}}</small><br/>
  {% endif %}
  {{ project.p_content }} <br/><br/>
  <a href="{{ url_for('edit_project', id=project.id )}}" class="btn btn-outline-secondary btn-sm">編集</a>
  <a href="{{ url_for('delete_project', id=project.id)}}" class="btn btn-outline-danger btn-sm">削除</a>
  <!-- ボタンをクリックした際にprojectのidを同時に渡し、add_element/p_idというurlを作る -->
  <a href="{{ url_for('add_element', p_id=project.id)}}" class='btn btn-outline-success btn-sm' >要件追加</a>
</div>
<h2 class="mt-4 border-bottom border-secondary border-3">要件</h2>
<br/>
# 以下からproject.elementsを繰り返しで呼び出し、関連づいている要件を一つづつ取り出して表示します
 {% for element in project.elements %}
 <div class="{% if element.e_status == 0 %}shadow p-3 mb-5 bg-body rounded{% else %}shadow p-3 mb-5 bg-secondary rounded text-white{% endif %}">
   <h2>{{ element.e_title }}</h2>
   <small>作成日:{{ element.e_created_at.strftime('%Y-%m-%d %H:%M') }}</small><br/>
   {% if element.e_completed_at %}
     <small>完了日:{{ element.e_completed_at.strftime('%Y-%m-%d')}}</small><br/>
   {% endif %}
   {{ element.e_content }} <br/><br/>
 <a href="{{ url_for('edit_element', id=element.id)}}" class='btn btn-outline-secondary btn-sm'>編集</a>
 <a href="{{ url_for('delete_element', id=element.id)}}" class='btn btn-outline-danger btn-sm'>削除</a>

 </div>
 {% endfor %}
{% endblock %}

 

その5 要件を編集した後、関連づいたプロジェクトのページに戻す

 

# app.py

# 要件を編集するページ
@app.route('/elements/edit/<int:id>', methods=['GET', 'POST'])
def edit_element(id):
    element = Element.query.get_or_404(id)
    form = ElementForm()
    if form.validate_on_submit():
        element.e_title = form.e_title.data
        element.e_content = form.e_content.data
        element.e_status = form.e_status.data
        element.e_completed_at = form.e_completed_at.data
        db.session.add(element)
        db.session.commit()
        # 属しているプロジェクトのページに戻す(既にDBにプロジェクトのIDを取得している)
        project = Project.query.get_or_404(element.projecter_id)
        return render_template('project.html', project=project)
    form.e_title.data = element.e_title
    form.e_content.data = element.e_content
    form.e_status.data = element.e_status
    form.e_completed_at.data = element.e_completed_at
    return render_template('edit_element.html', form=form)

要件を編集した後も、ちゃんとその関連づいているプロジェクトのページに戻す必要があります。これは要件テーブルの中に保存しているプロジェクトのidを使ってそのプロジェクトのデータを呼び出せば出大丈夫です。(中断コメント部分)