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

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

Flaskでウェブアプリケーション作成①

今回はFlaskのアプリケーションになります。

以前にlineの自動応答を作る際にFlaskのコードで書いてGit Actionで動かすということをやったことがあるのですが、Flaskでのウェブアプリケーション作成はこれが一つ目です。やりたいことやプロジェクトなどを簡単に管理できるものを作りたいと思い作ってみました。もう少し凝ったものにしたいのですが、一旦最低限の機能は完成したので公開します。

Flask でウェブサービス① やりたいこと管理アプリ

 

例えばこんな状況で

今回は、単純に自分のゴールを管理したり、日々やっていることをプロジェクト単位で管理したり、簡単な備忘録を登録したり、あとは業務以外でやりたいことを一覧にしたりするというウェブサービスになります。

イメージはこんな感じで出来上がってます。

youtu.be

 

このガジェットでできることはこちら

  • 自分のプロジェクトを管理できます。
  • 備忘録程度のナレッジを管理できます。
  • やりたいことを管理できます。
  • プロジェクトは新規登録から編集、そして終了した項目などをチェックできます。
  • 備忘録はタグを1つつけることができるので、後からタグで記事をソートできます。
  • herokuでウェブに公開しスマホからでもチェック、作成、編集できます。

※未実装の機能(今後実装します)

  • 新規記事記入欄にフォントなどのツールボックスの設置
  • タグを複数設置できるようにする
  • ハンバーガーメニューの設置(現在どうもうまく動かない)

評価(自己)

役立ち度  ★★★★(良し)
効率化   ★★★(平均)
ミス防止  ★★★(平均)
楽しさ   ★★★★(良し)
操作    ★★★★(良し)

※基本的に自分の使いたい機能は実装できましたが、ハンバーガーメニューなど実装できていない部分があるのとデザイン的にイマイチかなと。。

 

実行環境

使用環境    heroku
使用言語    python
使用ライブラリ    flask、SQLAlchemyなど

その他     bootstrap

 

コード

フォルダの構成は下記のようになっています。

やりたいこと管理
> static
  > css
    + styles.css
  > img
> template
  + base.html
  + index.html
  + allprojects.html
  + allknowledge.html
  + alldesire.html
  + createproject.html
  + createknowledge.html
  + createdesire.html
  + showknowledge.html
  + showproject.html
  + knowledgetags.html
  + updateproject.html
  + updateknowledge.html
  + updatedesire.hmtl
 > app.py
 > blog.db
 > Procfile
 > requirement.txt

代表的なファイルは下記です。

app.py

from flask import Flask
from flask import render_template, request, redirect
# from flask_bootstrap import Bootstrap
from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate

from datetime import datetime
import pytz

app = Flask(__name__)

# ローカルでDBを使う際はこちら
# app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///blog.db'
# heroku用DB
app.config['SQLALCHEMY_DATABASE_URI'] = 'postgresql://ヘロクではpostgresqlを使用。ターミナルでheroku config --app アプリ名 で取得したもの'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db = SQLAlchemy(app)
migrate = Migrate(app, db)


class Project(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    projecttitle = db.Column(db.String(100), nullable=False)
    overview = db.Column(db.Text(), nullable=False)
    todo1 = db.Column(db.String(100))
    todo2 = db.Column(db.String(100))
    todo3 = db.Column(db.String(100))
    todo4 = db.Column(db.String(100))
    todo5 = db.Column(db.String(100))
    todo6 = db.Column(db.String(100))
    todo7 = db.Column(db.String(100))
    todo8 = db.Column(db.String(100))
    todo9 = db.Column(db.String(100))
    todo10 = db.Column(db.String(100))
    todo11 = db.Column(db.String(100))
    todo12 = db.Column(db.String(100))
    todo13 = db.Column(db.String(100))
    todo14 = db.Column(db.String(100))
    todo15 = db.Column(db.String(100))
    status1 = db.Column(db.Integer)
    status2 = db.Column(db.Integer)
    status3 = db.Column(db.Integer)
    status4 = db.Column(db.Integer)
    status5 = db.Column(db.Integer)
    status6 = db.Column(db.Integer)
    status7 = db.Column(db.Integer)
    status8 = db.Column(db.Integer)
    status9 = db.Column(db.Integer)
    status10 = db.Column(db.Integer)
    status11 = db.Column(db.Integer)
    status12 = db.Column(db.Integer)
    status13 = db.Column(db.Integer)
    status14 = db.Column(db.Integer)
    status15 = db.Column(db.Integer)
    completedrate = db.Column(db.Integer)
    created_at = db.Column(db.DateTime, nullable=False, default=datetime.now(pytz.timezone('Asia/Tokyo')))


class Knowledge(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    knowledgetitle = db.Column(db.String(100), nullable=False)
    article = db.Column(db.Text(), nullable=False)
    tags = db.Column(db.String(100))
    created_at = db.Column(db.DateTime, nullable=False, default=datetime.now(pytz.timezone('Asia/Tokyo')))

class Desire(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    desiretitle = db.Column(db.String(100), nullable=False)
    desirecontent = db.Column(db.Text(), nullable=False)
    status = db.Column(db.Integer)
    created_at = db.Column(db.DateTime, nullable=False, default=datetime.now(pytz.timezone('Asia/Tokyo')))


@app.route('/', methods=['GET', 'POST'])
def index():

    projects = Project.query.all()
    progress_project = [project.projecttitle for project in projects if project.completedrate < 100][:3]
    # 下記データベースをソートして取り出す方法
    knowledges = db.session.query(Knowledge).order_by(Knowledge.created_at.desc()).all()
    latest_knowledge = [knowledge.knowledgetitle for knowledge in knowledges][:3]

    desires = Desire.query.all()
    not_completeds = [desire.desiretitle for desire in desires if desire.status==0][:10]
    return render_template('index.html', progress_project=progress_project, latest_knowledge=latest_knowledge, not_completeds=not_completeds)

# 以下プロジェクトページ
@app.route('/allprojects', methods=['GET', 'POST'])
def allprojects():
    if request.method == 'GET':
        projects = db.session.query(Project).order_by(Project.created_at.desc()).all()


        return render_template('allprojects.html', projects=projects)


@app.route('/createproject', methods=['GET', 'POST'])
def createproject():
    if request.method == 'POST':
        projecttitle = request.form.get('projecttitle')
        overview = request.form.get('overview')
        todo1 = request.form.get('todo1')
        todo2 = request.form.get('todo2')
        todo3 = request.form.get('todo3')
        todo4 = request.form.get('todo4')
        todo5 = request.form.get('todo5')
        todo6 = request.form.get('todo6')
        todo7 = request.form.get('todo7')
        todo8 = request.form.get('todo8')
        todo9 = request.form.get('todo9')
        todo10 = request.form.get('todo10')
        todo11 = request.form.get('todo11')
        todo12 = request.form.get('todo12')
        todo13 = request.form.get('todo13')
        todo14 = request.form.get('todo14')
        todo15 = request.form.get('todo15')
        status1 = 0
        status2 = 0
        status3 = 0
        status4 = 0
        status5 = 0
        status6 = 0
        status7 = 0
        status8 = 0
        status9 = 0
        status10 = 0
        status11 = 0
        status12 = 0
        status13 = 0
        status14 = 0
        status15 = 0
        completedrate = 0



        project = Project(projecttitle=projecttitle, overview=overview, todo1=todo1, todo2=todo2, todo3=todo3, todo4=todo4, todo5=todo5,
                        todo6=todo6, todo7=todo7, todo8=todo8, todo9=todo9, todo10=todo10, todo11=todo11, todo12=todo12, todo13=todo13,
                        todo14=todo14, todo15=todo15, status1=status1, status2=status2, status3=status3, status4=status4, status5=status5,
                        status6=status6, status7=status7, status8=status8, status9=status9, status10=status10, status11=status11,
                        status12=status12, status13=status13, status14=status14, status15=status15, completedrate=completedrate)

        db.session.add(project)
        db.session.commit()
        return redirect('/')
    else:
        return render_template('createproject.html')

@app.route('/<int:id>/showproject', methods=['GET'])
def showproject(id):
    project = Project.query.get(id)
    if request.method == 'GET':
        # リストをあらかじめ作って渡すこと可能
        li =[project.todo1, project.todo2, project.todo3, project.todo4, project.todo5, project.todo6, project.todo7, project.todo8,
                 project.todo9, project.todo10, project.todo11, project.todo12, project.todo13, project.todo14, project.todo15]
        li2 = [project.status1, project.status2, project.status3, project.status4, project.status5, project.status6, project.status7,
               project.status8, project.status9, project.status10, project.status11, project.status12, project.status13, project.status14, project.status15]
        # htmlでzipを使用したいときは先にzip化して渡すこと
        return render_template('showproject.html', project=project, l=zip(li, li2))

@app.route('/<int:id>/updateproject', methods=['GET', 'POST'])
def updateproject(id):
    project = Project.query.get(id)
    if request.method == 'GET':
        return render_template('updateproject.html', project=project)
    else:
        project.projecttitle = request.form.get('projecttitle')
        project.overview = request.form.get('overview')
        project.todo1 = request.form.get('todo1')
        project.todo2 = request.form.get('todo2')
        project.todo3 = request.form.get('todo3')
        project.todo4 = request.form.get('todo4')
        project.todo5 = request.form.get('todo5')
        project.todo6 = request.form.get('todo6')
        project.todo7 = request.form.get('todo7')
        project.todo8 = request.form.get('todo8')
        project.todo9 = request.form.get('todo9')
        project.todo10 = request.form.get('todo10')
        project.tpdo11 = request.form.get('tpdo11')
        project.todo12 = request.form.get('todo12')
        project.todo13 = request.form.get('todo13')
        project.todo14 = request.form.get('todo14')
        project.todo15 = request.form.get('todo15')
        checks = request.form.getlist('check')
        if '0' in checks:
            project.status1 = 1
        if '1' in checks:
            project.status2 = 1
        if '2' in checks:
            project.status3 = 1
        if '3' in checks:
            project.status4 = 1
        if '4' in checks:
            project.status5 = 1
        if '5' in checks:
            project.status6 = 1
        if '6' in checks:
            project.status7 = 1
        if '7' in checks:
            project.status8 = 1
        if '8' in checks:
            project.status9 = 1
        if '9' in checks:
            project.status10 = 1
        if '10' in checks:
            project.status11 = 1
        if '11' in checks:
            project.status12 = 1
        if '12' in checks:
            project.status13 = 1
        if '13' in checks:
            project.status14 = 1
        if '14' in checks:
            project.status15 = 1
        li =[project.todo1, project.todo2, project.todo3, project.todo4, project.todo5, project.todo6, project.todo7, project.todo8,
                 project.todo9, project.todo10, project.todo11, project.todo12, project.todo13, project.todo14, project.todo15]
        li2 = [project.status1, project.status2, project.status3, project.status4, project.status5, project.status6, project.status7,
               project.status8, project.status9, project.status10, project.status11, project.status12, project.status13, project.status14, project.status15]
        completed = [elem for elem in li2 if elem == 1]
        todos = [elem for elem in li if elem]
        project.completedrate = round(len(completed)/len(todos)*100)

        db.session.commit()
        return redirect('/')
        # return render_template('showproject.html', project=project)

@app.route('/<int:id>/deleteproject', methods=['GET'])
def deleteproject(id):
    project = Project.query.get(id)

    db.session.delete(project)
    db.session.commit()
    return redirect('/')

# 以下知識用ページ
@app.route('/allknowledge', methods=['GET', 'POST'])
def allknowledge():
    if request.method == 'GET':
        knowledges = db.session.query(Knowledge).order_by(Knowledge.created_at.desc()).all()
        tag_list = list(set(knowledge.tags for knowledge in knowledges))
        return render_template('allknowledge.html', knowledges=knowledges, tag_list=tag_list)

@app.route('/createknowledge', methods=['GET', 'POST'])
def createknowledge():
    if request.method == 'POST':
        knowledgetitle = request.form.get('knowledgetitle')
        article = request.form.get('article')
        tags = request.form.get('tags')

        knowledge = Knowledge(knowledgetitle=knowledgetitle, article=article, tags=tags)

        db.session.add(knowledge)
        db.session.commit()
        return redirect('/')
    else:
        return render_template('createknowledge.html')

@app.route('/<int:id>/showknowledge', methods=['GET'])
def showknowledge(id):
    knowledge = Knowledge.query.get(id)
    if request.method == 'GET':
        return render_template('showknowledge.html', knowledge=knowledge)

@app.route('/<int:id>/updateknowledge', methods=['GET', 'POST'])
def updateknowledge(id):
    knowledge = Knowledge.query.get(id)
    if request.method == 'GET':
        return render_template('updateknowledge.html', knowledge=knowledge)
    else:
        knowledge.knowledgetitle = request.form.get('knowledgetitle')
        knowledge.article = request.form.get('article')

        db.session.commit()
        return render_template('showknowledge.html', knowledge=knowledge)

@app.route('/<int:id>/deleteknowledge', methods=['GET'])
def deletekowledge(id):
    knowledge = Knowledge.query.get(id)

    db.session.delete(knowledge)
    db.session.commit()
    return redirect('/')

@app.route('/<tags>/knowledgetags', methods=['GET'])
def sort_knowledge(tags):
    if request.method == 'GET':
        # フィルターでソートする方法
        knowledges = Knowledge.query.filter(Knowledge.tags == tags).all()
        return render_template('knowledgetags.html', knowledges=knowledges, tag=tags)

@app.route('/alldesire', methods=['GET', 'POST'])
def alldesire():
    if request.method == 'GET':
        desires = Desire.query.all()
        return render_template('alldesire.html', desires=desires)

@app.route('/createdesire', methods=['GET', 'POST'])
def createdesire():
    if request.method == 'POST':
        desiretitle = request.form.get('desiretitle')
        desirecontent = request.form.get('desirecontent')
        status = 0

        desire = Desire(desiretitle=desiretitle, desirecontent=desirecontent, status=status)

        db.session.add(desire)
        db.session.commit()
        return redirect('/')
    else:
        return render_template('createdesire.html')

@app.route('/<int:id>/updatedesire', methods=['GET', 'POST'])
def updatedesire(id):
    desire = Desire.query.get(id)
    if request.method == 'GET':
        return render_template('updatedesire.html', desire=desire)
    else:
        desire.desiretitle = request.form.get('desiretitle')
        desire.desirecontent = request.form.get('desirecontent')
        check = request.form.get('check')
        print(check)
        if check is not None:
            desire.status = 1
        db.session.commit()
        return redirect('/')



@app.route('/<int:id>/deletedesire', methods=['GET'])
def deletedesire(id):
    desire = Desire.query.get(id)

    db.session.delete(desire)
    db.session.commit()
    return redirect('/')

base.html

<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <!-- <link rel="stylesheet" href="{{url_for('static', filename='css/bootstrap.min.css')}}"> -->
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
    <link rel="stylesheet" href="{{url_for('static', filename='css/styles.css')}}">
    <title>やりたいこと管理アプリ</title>
  </head>
  <body>

      <nav class="navbar navbar-expand-lg navbar-dark bg-dark mb-5">
        <div class="container">
        <a class="navbar-brand" href="/">やりたいこと管理アプリ</a>
        <button class="navbar-toggler" type="button" data-toggle="collapse" data-target=".navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
          <span class="navbar-toggler-icon"></span>
        </button>
        <div class="collapse navbar-collapse navbarSupportedContent" id="navbarSupportedContent">
          <ul class="navbar-nav">
            <li class="nav-item"><a class="nav-link" href="/">ホーム</a></li>
            <li class="nav-item"><a class="nav-link" href="/allprojects">プロジェクト一覧</a></li>
            <li class="nav-item"><a class="nav-link" href="/createproject">プロジェクト作成</a></li>
            <li class="nav-item"><a class="nav-link" href="/allknowledge">ナレッジ一覧</a></li>
            <li class="nav-item"><a class="nav-link" href="/createknowledge">ナレッジ作成</a></li>
            <li class="nav-item"><a class="nav-link" href="/alldesire">やりたい事一覧</a></li>
            <li class="nav-item"><a class="nav-link" href="/createdesire">やりたい事登録</a></li>
          </ul>
        </div>
      </div>
      </nav>

    {% block content %}
    {% endblock %}
    <div class="container mt-5">
      <ul class="footer_ul">
        <li class="footer_li"><a href="/">ホーム</a></li>
        <li class="footer_li"><a href="/allprojects">プロジェクト一覧</a></li>
        <li class="footer_li"><a href="/createproject">プロジェクト作成</a></li>
        <li class="footer_li"><a href="/allknowledge">ナレッジ一覧</a></li>
        <li class="footer_li"><a href="/createknowledge">ナレッジ作成</a></li>
        <li class="footer_li"><a href="/alldesire">やりたい事一覧</a></li>
        <li class="footer_li"><a href="/createdesire">やりたい事登録</a></li>

      </ul>
    </div>
    </body>
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM" crossorigin="anonymous"></script>
    <!-- <script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.9.2/dist/umd/popper.min.js" integrity="sha384-IQsoLXl5PILFhosVNubq5LC7Qb9DXgDA9i+tQ8Zj3iwWAwPtgFTxbJ8NT4GN1R8p" crossorigin="anonymous"></script>
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.min.js" integrity="sha384-cVKIPhGWiC2Al4u+LWgxfKTRIcfu0JTxR+EQDz/bgldoEyl4H0zUF0QKbrJ0EcQF" crossorigin="anonymous"></script> -->

</html>

index.html

{% extends 'base.html' %}


{% block content %}
<div class="container">
  <h1 class="title"><em>Wanna</em></h1>

      {% for not_completed in not_completeds%}
        <ul class="top_list">
          <li class="list_item">{{not_completed}}</li>
        </ul>
      {% endfor %}

</div>
<div class="container mt-5">
  <h1><em>Doing</em></h1>
  <div class="row">
    <div class="col-12 col-md-6 mt-3">
      <div class="card me-10" style="height: 43rem;" >
        <img src="static/img/project.jpg" alt="プロジェクト画像" class="card-img-top">
        <div class="card-body">
          <h4 class="card-title border-bottom">プロジェクト</h4>
          <div>
            <p class="card-text text-success">進行中</p>
              {% for progress in progress_project %}
              <ul class="list-group list-group-flush">
                <li class="list-group-item">{{progress}}</li>
              </ul>
              {% endfor %}
          </div>
          <div class="text-center">
            <a href="/createproject" class="btn btn-outline-secondary position-absolute bottom-0 start-50 translate-middle-x mb-3">新規作成</a>
          </div>
        </div>
      </div>
    </div>
    <div class="col-12 col-md-6 mt-3">
      <div class="card ms-10" style="height: 43rem;" >
        <img src="static/img/konwledge.jpg" alt="知識画像" class="card-img-top">
        <div class="card-body">
          <h4 class="card-title border-bottom"">ナレッジ</h4>
          <div>
            <p class="card-text text-success" >最新</p>
            {% for latest in latest_knowledge %}
            <ul class="list-group list-group-flush">
              <li class="list-group-item">{{latest}}</li>
            </ul>
            {% endfor %}
          </div>
          <div class="text-center">
            <a href="/createknowledge" class="btn btn-outline-secondary position-absolute bottom-0 start-50 translate-middle-x mb-3">新規投稿</a>
          </div>
        </div>
      </div>
    </div>
  </div>
</div>
{% endblock %}

その他のhtmlファイルは同様にして作っています。