본문 바로가기
웹 개발/Back End

[Python Flask] - Todo 메모장 만들기

by L3m0n S0ju 2021. 11. 14.

 

 

 

 

모든 항목, 해야할 항목, 완료된 항목 세 가지로 분류하여 메모장을 만들었습니다. 삭제와 업데이트 기능을 구현하였고 MongoDB와 연동하여 데이터가 삭제 및 업데이트가 가능합니다.

 

 

 

 

 

 

 

 

 

 

 

app.py


#Call Lib
from flask import Flask
from flask import render_template
from flask import request
from flask import redirect, url_for

from flask_wtf import FlaskForm
from wtforms import StringField
from wtforms.validators import DataRequired    # 데이터 유효성 검사

from pymongo import MongoClient
from bson import ObjectId    # MongoDB 데이터 출력할 때 필요
from datetime import datetime

class TextForm(FlaskForm):    # 텍스트 폼 생성
    content = StringField('내용', validators=[DataRequired()])    # 메모가 존재하면 content에 저장

#Config setting
app = Flask(__name__)
app.config['SECRET_KEY'] = "secret"
connection = MongoClient('localhost', 27017) #ip, port 데이터베이스 연결
db = connection.project
todos = db.todo    # todo 컬렉션 생성

#Error Process
@app.errorhandler(404)
def page_not_found(error):
    return render_template('page_not.html'), 404

#home
@app.route("/")
@app.route("/about")
def home_page():
    return render_template('about.html')

#All list page
@app.route("/all")    # 전체 항목
def all_page():
    stat = "All list"
    todolist = todos.find().sort('date',-1)    # todos 컬렉션에서 모든 데이터 내림차순으로 가져옴
    form = TextForm()
    return render_template('index.html', todos=todolist, stat=stat, form=form)

#Active item list
@app.route("/active")    # 해야 할 항목
def active_page():
    stat = "Active list"
    todolist = todos.find({"done":"no"}).sort('date',-1)    # 수행되지 않은 항목 추출
    form = TextForm()
    return render_template('index.html', todos=todolist, stat=stat, form=form)

#Completed item list
@app.route("/completed")    # 완료된 항목
def complete_page():
    stat = "Completed list"
    todolist = todos.find({"done":"yes"}).sort('date',-1)
    form = TextForm()
    return render_template('index.html', todos=todolist, stat=stat, form=form)

#Update memo
@app.route("/update")
def update_page():
    id=request.values.get("_id")
    task=todos.find({"_id":ObjectId(id)})[0]
    form = TextForm()
    return render_template('update.html', task=task, form=form)

#New memo
@app.route("/action", methods=['GET','POST'])
def action_add():
    form = TextForm()
    if form.validate_on_submit():
        contents = request.form['content']    # index.html에서 넘겨준 content 저장
        date = datetime.today()
        primary = request.values.get('primary')    # 중요도 저장
        todos.insert_one({"contents":contents, "date":date, "primary":primary, "done":"no"})    # 데이터베이스에 삽입
        return """<script>
            window.location = document.referrer;    # 이전 페이지로 돌아감
            </script>"""
    else:
        return render_template('page_not.html')

#Done memo change
@app.route("/done")
def done_add():
    id=request.values.get("_id")
    task=todos.find({"_id":ObjectId(id)})
    if(task[0]["done"]=="yes"):    # 체크 해제 할 때
        todos.update_one({"_id":ObjectId(id)}, {"$set": {"done":"no"}})
    else:    # 체크 할 때
        todos.update_one({"_id":ObjectId(id)}, {"$set": {"done":"yes"}})
    return """<script>
        window.location = document.referrer;
        </script>"""

#Delete memo
@app.route("/delete")
def action_delete():
    key=request.values.get("_id")
    todos.delete_one({"_id":ObjectId(key)})    # 삭제
    return """<script>
        window.location = document.referrer;
        </script>"""

#Done memo update
@app.route("/action2", methods=['GET','POST'])
def done_update():
    if request.method == 'POST':
        key = request.values.get("_id")
        contents = request.form['content']
        primary = request.values.get('primary')
        todos.update_one({"_id":ObjectId(key)}, {'$set':{"contents":contents, "primary":primary}})    # 수정
        return redirect(url_for('all_page'))
    else:
        return render_template("page_not.html")

if __name__ == "__main__":
    app.run()

 

 

 

 

 

 

 

 

base.html


<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Server</title>
    <link rel="icon" href="/static/images/favicon.png">

    <!-- Latest compiled and minified CSS -->
    <!-- jQuery library -->
    <!-- Popper JS -->
    <!-- Latest compiled JavaScript -->
    <!-- fontawesome -->
    <link rel="stylesheet" type="text/css"
</head>
<body>
    <div class="text-left mx-3">
        <h2>
            <i class="fa fa-sticky-note"></i>
            <strong>할 일을 메모하자!</strong>
        </h2>
    </div>

    {% include "nav.html" %}    <!-- nav.html 삽입 -->

    {% block content %}{% endblock %}    <!-- 카드 컴포넌트 -->

    {% include "footer.html" %}    <!-- footer.html 삽입 -->
</body>

<style>
    html, body {
      height: 100%;
    }
  </style>
</html>
 
 
 
 
 
 
 
 
 
 
 
nav.html
<nav class="navbar navbar-expand-md navbar-dark bg-dark">    <!-- 중간 화면 이상이면 가로로 나열 아니면 강조된 세 메뉴를 버튼을 통해 세로로 나열 -->
    <a class="navbar-brand mb-0 h1" href="/">About</a>

    <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#myNavbar">
        <span class="navbar-toggler-icon"></span>    <!-- 햄버거 모양 아이콘 -->
    </button>

    <div class="navbar-collapse collapse" id="myNavbar">
        <ul class="nav navbar-nav ml-auto">
            <li class="nav-item active">
                <a class="nav-link" href="/all">모든 항목</a>
            </li>
            <li class="nav-item active">
                <a class="nav-link" href="/active">해야 할 항목</a>
            </li>
            <li class="nav-item active">
                <a class="nav-link" href="/completed">완료된 항목</a>
            </li>
        </ul>
    </div>
</nav>

 

 

 

 

 

about.html


{% extends "base.html" %}
{% block content %}
<body class="text-center">    
  <div class="card shadow-sm mx-2 mt-2">
    <div class="card-header font-weight-bold">
      About
    </div>

    <div class="card-body">
      <div class="card-text">
          파이썬 플라스크 웹 프레임워크를 사용해 만들어진 웹 페이지입니다.<br>
          <img class="img-fluid" src="{{ url_for('static', filename='images/flask.png') }}">    <!-- 반응형 이미지 -->
      </div>
    </div>
  </div>
</body>
{% endblock %}

 

 

 

 

 

 

index.html


{% extends "base.html" %}
{% block content %}
<div style="height: 80%;">
  <div class="card shadow-lg mx-2 mt-2">    <!--  좌우, top 마진 설정 -->
    <div class="card-header font-weight-bold text-center">
      Input memo
    </div>

    <div class="card-body">
      <form action="/action" method="POST" class="mx-2">    <!-- 데이터 입력하면 /action으로 이동 -->
        {{ form.csrf_token }}
        <div class="input mb-2">
          {{ form.content(class="form-control", placeholder="메모를 입력하세요") }}
        </div>

        <div class="input-group">
          <select name="primary" class="input-group-append form-control">
            <option selected disabled>중요도 선택</option>
            <option>Low</option>
            <option>Medium</option>
            <option>High</option>
          </select>

          <div class="input-group-append">
            <button type="submit" class="btn btn-outline-secondary float-right">
              추가 <i class="fa fa-plus-circle fa-lg"></i>
            </button>
          </div>
        </div>
      </form>
    </div>
  </div>

  <div class="card shadow-sm mx-2 mt-2">
    <div class="card-header font-weight-bold text-center">
      {{ stat }}    <!-- 항목 종류 출력 -->
    </div>

    <div class="card-body">
      {% include "list.html" %}
    </div>
  </div>
</div>
{% endblock %}

 

 

 

 

 

 

list.html


<table class="table table-sm table-striped">
    <thead class="text-center">
        <tr>
            <th>상태</th>
            <th class="text-left">내용</th>
            <th></th>
            <th>삭제</th>
            <th>변경</th>
        </tr>
    </thead>

    <tbody>
        {% for todo in todos %}
        <tr>
            <td class="text-center"><a href="./done?_id={{ todo['_id'] }}"><input type="image"   <!-- done page로 id값 전달
                        src="static/images/{{todo['done']}}.png" width="30" alt="Submit ME"></a></td>    <!-- 상태에 따른 이미지 출력 -->
            <td>
                <strong>{{ todo["contents"] }}</strong><br>    <!-- 메모 출력 -->
                - {{ todo["date"].strftime("%Y-%m-%d") }}
            </td>

            {% if todo["primary"] == "Low" %}
            <td class="badge badge-pill badge-success align-middle">{{ todo["primary"] }}</td>
            {% elif todo["primary"] == "Medium" %}
            <td class="badge badge-pill badge-warning align-middle">{{ todo["primary"] }}</td>
            {% elif todo["primary"] == "High" %}
            <td class="badge badge-pill badge-danger align-middle">{{ todo["primary"] }}</td>
            {% else %}
            <td class="badge badge-pill badge-light align-middle">{{ todo["primary"] }}</td>
            {% endif %}

            <td class="text-center">
                <img src="{{ url_for('static',filename='images/trash.png') }}" , width="30" data-toggle="modal"
                    data-target="#exampleModal" ) /></td>    <!-- 삭제 문구 출력 -->

            <td class="text-center"><a href="./update?_id={{ todo['_id'] }}">    <!-- 수정 -->
                    <img src="{{ url_for('static',filename='images/edit.png') }}" , width="30" ) /></a></td>
        </tr>

        <div class="modal fade" id="exampleModal" tabindex="-1" role="dialog" aria-labelledby="exampleModalLabel"
            aria-hidden="true">
            <div class="modal-dialog" role="document">
                <div class="modal-content">
                    <div class="modal-header">
                        <h5 class="modal-title" id="exampleModalLabel">삭제 경고</h5>
                        <button type="button" class="close" data-dismiss="modal" aria-label="Close">
                            <span aria-hidden="true">&times;</span>    <!-- x 아이콘 -->
                        </button>
                    </div>
                    <div class="modal-body">
                        정말로 삭제하시겠습니까?
                    </div>
                    <div class="modal-footer">
                        <a href="./delete?_id={{ todo['_id'] }}"><button type="button"    <!-- 삭제 -->
                                class="btn btn-danger">삭제</button></a>
                        <button type="button" class="btn btn-secondary" data-dismiss="modal">취소</button>
                    </div>
                </div>
            </div>
        </div>
        {% endfor %}
    </tbody>
</table>

 

 

 

 

 

 

 

update.html


{% extends "base.html" %}
{% block content %}

<body class="text-center">
    <div class="card shadow-sm mx-2 mt-2">
        <div class="card-header font-weight-bold">
            Update memo
        </div>

        <div class="card-body">
            <form action="/action2" method="POST" class="mx-2">
                {{ form.csrf_token }}
                <input type="hidden" name="_id" value="{{ task['_id'] }}">
                <div class="input mb-2">
                    {{ form.content(class="form-control", placeholder=task['contents'], value=task['contents']) }}    <!-- 수정할 메모 출력 -->
                </div>

                <div class="input-group">
                    <select name="primary" class="input-group-append form-control">
                        <option value="{{ task['primary'] }}" selected>{{ task['primary'] }}</option>
                        <option>None</option>
                        <option>Low</option>
                        <option>Medium</option>
                        <option>High</option>
                    </select>
                    <div class="input-group-append">
                        <button type="submit" class="btn btn-outline-secondary float-right">
                            변경 <i class="fa fa-exchange fa-lg"></i>
                        </button>
                    </div>
                </div>
            </form>
        </div>
    </div>
</body>
{% endblock %}
 
 
 
 
 
 
 
footer.html

<footer class="fixed-bottom bg-dark text-light align-items-center px-3">
    <div class="container">
        <div class="d-flex justify-content-end">
            <div>
                <i class="fa fa-copyright"></i> dsz08082@naver.com
            </div>
        </div>
    </div>
</footer>
 
 
 
 
 
 
 
page_not.html

{% extends "base.html" %}
{% block content %}
<body>
  <div class="bg-danger text-center" style="height: 87%;">
      <h3>잘못 접근하셨어요 :(</h3><br>
      <h1>페이지를 다른데 두고 오셨네요</h1>
  </div>
</body>
{% endblock %}

댓글