Python 初心者 Webアプリ開発

# 目次
1.アプリ開発のきっかけ
2.開発方法
3.修正・改良時の工夫
4.ソースコード


# 1.アプリ開発のきっかけ
転職活動のためです。
私は新卒から5年間営業として続けてきましたが、技術系の職種に興味を持ち、独学でプログラミングを始めました。
実際にWebアプリを作成することで、実践力を身に付けたいと考えています。


# 2.開発方法
Youtubeにて、以下の動画を参考にしました。
https://youtu.be/EQIAzH0HvzQ?si=jUDIGKnPLmBChcXj

PythonプログラミングYoutuberのサプーさんです。
動画で環境構築の方法も含めて、書店の新刊情報を記録・表示するアプリの作成方法を解説しています。

動画で解説している内容をもとに、こちらの通りアプリを作成しました。
こちらはホーム画面です。「サプー書店」という書店を想定し、今月の新刊一覧を表示しています。
![（サプー版）ホーム.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3827197/8c072b28-d8a4-b01b-5c12-39314f3ab446.png)

ホーム画面下部の「編集」リンクをクリックすると、新規登録画面に遷移します。こちらの画面で入荷日・タイトル・金額のデータを入力できます。
![（サプー版）編集　新規登録.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3827197/ae9fe98a-ba25-cf27-5025-1b564d9f74c8.png)


作成したうえで、以下のように改善したいと思いました。
①実用的に考えると、書店ではなく「読んだ本を記録するアプリ」として作成したい
②登録済みのレコードに対し、データを編集・削除する機能を実装

そこで、作成済みのコードに対して以下の通り改良を加えました。

①「読書記録」アプリとしての作成
・タイトルを「読書記録」に変更
・データベースのカラムを、「入荷日」「タイトル」「金額」から、「購入日」「タイトル」「感想」に変更（それに伴う変数も変更）

②編集・削除機能の追加
・HTMLにて、各レコードに対する編集ボタン・削除ボタンの追加
・新しくedit.htmlというファイルを作成し、編集の機能を追加　それに紐づく処理をmain.pyに追加
・main.pyに削除の処理を追加

その他微修正
・required属性を追加し、タイトルが空の状態で登録できないように改良
・新規登録するためのリンクの表示名を「編集」から「新規登録」に変更

▼ホーム画面
![ホーム画面.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3827197/981e37e9-4ee2-167c-0132-e6d588cbca8a.png)

▼新規登録画面
![新規登録.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3827197/0a056230-4fc7-13cc-986f-ee72ed63828a.png)

▼編集画面
![編集.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3827197/b93a36ae-1b24-bd56-a1d8-a49ba9a58683.png)


# 3.修正・改良時の工夫
できるだけ動画の解説を参考に、「どこに何が、どういう意味合いで記載されているのか」を考えながら紐解きました。
自力で解決できない部分はChatGPTに質問し、コードを記述しました。
修正する内容は、システムのユーザになりきり、見た目や操作性で必要なものを考えました。


# 4.ソースコード
以下、7種類のファイルを作成しました。
・index.html
・form.html
・edit.html
・main.py
・init__.py
・db.py
・style.css

### index.html
```
<!DOCTYPE html>  

<html lang="ja"> 
    <head>
        <meta charset="UTF-8">
        <title>Webアプリ</title>
        <link rel="stylesheet" href="{{url_for('static',filename='style.css')}}">
    </head>
    <body>
        <h1>読書記録</h1>
            {% if books == [] %}
                <p>読書記録はありません</p>
            {% else %}
                <table border="1">
                    <tr>
                        <th>購入日</th>
                        <th>タイトル</th>
                        <th>感想</th>
                    </tr>
            {% for book in books %}
                <tr>
                    <td><p>{{ book.purchase_day }}</p></td>
                    <td><p>{{ book.title }}</p></td>
                    <td><p>{{ book.report }}</p></td>
                    <td><a href="{{ url_for('edit', title=book['title']) }}">編集</a></td> 
                    <td>
                    <form method="post" action="{{ url_for('delete', title=book.title) }}"> 
                    <input type="submit" value="削除">
                </tr>   
            {% endfor %}
        </table>
    {% endif %}
    <a href="{{ url_for('form') }}">新規登録</a> <!--ボタンを押したときに、formを表示する formはmain.pyで指定-->    
</body>
</html>
```

### form.html
```
<!DOCTYPE html>  

<html lang="ja"> 
    <head>
        <meta charset="UTF-8">
        <title>読書記録</title>
        <link rel="stylesheet" href="{{url_for('static',filename='style.css')}}">
    </head>
    <body>
        <h1>読書記録</h1>
        <form method="post" action="{{url_for('register')}}">  <!--post：リクエストの種類　action：登録ボタンを押した後に実行する関数（registerはmain.pyで記述）-->
            <table border="1">
            <tr>
                <th>購入日</th>
                <th>タイトル</th>
                <th>感想</th>
            </tr>

<!--テキストボックスの作成-->
            <tr>
                <td><input type="text" name="purchase_day"></td> 
                <td><input type="text" name="title" required></td> <!-- required：空の状態で登録しようとするとエラーを表示 -->
                <td><input type="text" name="report"></td>
            </tr>

        </table>
        </br>
        <input type="submit" value="登録">  <!--登録ボタンの作成-->
        </form>
    </body>
</html>
```

### edit.html
```
<!DOCTYPE html>  
<html lang="ja"> 
<head>
    <meta charset="UTF-8">
    <title>読書記録 - 編集</title>
    <link rel="stylesheet" href="{{url_for('static',filename='style.css')}}">
</head>
<body>
    <h1>本の情報を編集</h1>
    <form method="post" action="{{ url_for('update', title=book['title']) }}">
        <label for="purchase_day">購入日:</label>
        <input type="text" name="purchase_day" value="{{ book['purchase_day'] }}"><br>
        
        <label for="title">タイトル:</label>
        <input type="text" name="title" value="{{ book['title'] }}"><br>
        
        <label for="report">感想:</label>
        <input type="text" name="report" value="{{ book['report'] }}"><br>
        
        <input type="submit" value="更新">
    </form>
    <a href="{{ url_for('index') }}">戻る</a>
</body>
</html>
```

### main.py
```
from flaskr import app  #flaskアプリのオブジェクトをインストール
from flask import render_template, request, redirect, url_for
import sqlite3
DATABASE='database.db'

@app.route('/')
def index():
    con = sqlite3.connect(DATABASE)
    db_books=con.execute('SELECT*FROM books').fetchall()
    con.close()

    books=[]
    for row in db_books:
       books.append({'title':row[0], 'report':row[1], 'purchase_day':row[2]})
    return render_template(
        'index.html',
        books=books
    ) 

@app.route('/form')
def form():
  return render_template(
     'form.html' #新規登録ボタンが押された時の表示の仕方を記述（ボタンはindex.htmlのAタグ）
  )

#登録処理
@app.route('/register', methods=['POST'])  #/register:register関数の内容がURLとして紐づく postのリクエストの場合にregisterが呼び出される
def register():
   title=request.form['title']
   report=request.form['report']
   purchase_day=request.form['purchase_day']

   con=sqlite3.connect(DATABASE)
   con.execute('INSERT INTO books VALUES(?,?,?)',
              [title,report,purchase_day]) #1つ目の？にtitle、2つ目の？にreport、3つ目の？にpurchase_dayの値に当てはまり、booksのテーブルに登録される
   con.commit()
   con.close()
   return redirect(url_for('index')) #main.pyのindexのURLにリダイレクトされるようになる


# 編集処理（登録済みのレコード）
@app.route('/edit/<string:title>', methods=['GET'])
def edit(title):
    con = sqlite3.connect(DATABASE)
    book = con.execute('SELECT * FROM books WHERE title = ?', (title,)).fetchone()
    con.close()
    
    if book:
        book_data = {'title': book[0], 'report': book[1], 'purchase_day': book[2]}
        return render_template('edit.html', book=book_data)
    else:
        return redirect(url_for('index'))

# 更新処理
@app.route('/update/<string:title>', methods=['POST'])
def update(title):
    new_title = request.form['title']
    new_report = request.form['report']
    new_purchase_day = request.form['purchase_day']

    con = sqlite3.connect(DATABASE)
    con.execute('UPDATE books SET title = ?, report = ?, purchase_day = ? WHERE title = ?',
                (new_title, new_report, new_purchase_day, title))
    con.commit()
    con.close()
    
    return redirect(url_for('index'))

# 本の削除処理
@app.route('/delete/<string:title>', methods=['POST'])
def delete(title):
    con = sqlite3.connect(DATABASE)
    con.execute('DELETE FROM books WHERE title = ?', (title,))
    con.commit()
    con.close()
    
    return redirect(url_for('index'))
```

### init__.py
```
from flask import Flask 
app = Flask(__name__)
import flaskr.main

from flaskr import db
db.create_books_table()
```

### db.py
```
import sqlite3 

DATABASE='database.db'

def create_books_table():
    con = sqlite3.connect(DATABASE) #DATABASEへの接続用オブジェクトを作る
    con.execute("""
        CREATE TABLE IF NOT EXISTS books(
            title TEXT, 
            report TEXT, 
            purchase_day TEXT)
        """) 

    con.close()

#SQLite：flaskをインストールすると初めから入っている　表形式のデータをためておくことができる　小規模で、１ファイルだけに貯めておくもの
```

### style.css
```
h1{
    position: relative;
    background:#dfefff;
    box-shadow: 0px 0px 0px 5px #dfefff;
    border: dashed 2px white;
    padding: 0.2em 0.5em;
    color: #454545;
}

h1:after {
    position: absolute;
    content: '';
    left: -7px;
    top: -7px;
    border-width: 0 0 15px 15px;
    border-style: solid;
    border-color: #fff #fff #a8d4ff;
    box-shadow: 1px 1px 1px rgba(0,0,0,0.15);
}
```