見出し画像

AIと生きる~Cursorでアプリ開発 #6~

開発の続き

OpenAIのAPIに課金した$10をほぼ使い果たした(残高$0.26)ので、少し手間ですが、「リートンのGPT-4Turbo」や、「Microsoft Copilot」の無料生成AIで開発を進めていきます。APIの残高は、ここぞというときに「GPT-3.5」で活用していきます。

データベースに保存されない原因を探る

メンテナンス管理ページの新規登録機能を実装進めています。最後のデータベースへの保存のところで、フォームで取得した値と、テーブルのカラムに保存する値の型が合わないようで、保存されないという現象に悩まされています。
そのときのコードが下記の部分です。

@app.route('/register_maintenance', methods=['POST'])
def register_maintenance():
    maintenance_form = MaintenanceForm()
    employees = Employee.query.all()
    maintenance_form.recipient_id.choices = [(employee.id, employee.name) for employee in employees]
    maintenance_form.maintenance_person_id.choices = [(employee.id, employee.name) for employee in employees]


    if maintenance_form.validate_on_submit():
        recipient_id = request.form['recipient_id']
        maintenance_person_id=request.form['maintenance_person_id']
        maintenance = Maintenance(
                customer_id=maintenance_form.customer_id,
                date_of_receipt=maintenance_form.date_of_receipt,
                recipient_id = recipient_id,
                model_number=maintenance_form.model_number,
                maintenance_person_id=maintenance_person_id,
                paid_free=maintenance_form.paid_free,
                number_of_days_deposited=maintenance_form.number_of_days_deposited,
                scheduled_return_date=maintenance_form.scheduled_return_date,
                return_processing_date=maintenance_form.return_processing_date,
                returned_unreturned=maintenance_form.returned_unreturned
        )
        db.session.add(maintenance)
        db.session.commit()
        return redirect(url_for('index')) 
    
    return render_template('maintenance_management.html', maintenance_form=maintenance_form, employees=employees)

if文の箇所で、ページから値を受け取って、データベースに保存するコードを書いています。これが成功すると、indexページが表示されるようになっていますが、ページで送信ボタンを押すと、メンテナンス管理ページのTOPに戻ってしまいます。つまり、データベースへの保存ができていないということです。
ただ、コードとしては成立しているので、Cursorでエラーが出ません。
これだとユーザーも何が起こったのか理解できないので、エラーメッセージを表示するように、最後の行を変更しました。

#変更前
return render_template('maintenance_management.html', maintenance_form=maintenance_form, employees=employees)

#変更後
return f'登録できませんでした。最初からやり直してください。'

このコードをコピーして、リートンに貼り付け、「下記のコードにエラーログが出力されるようにして」とプロンプト(質問)を入力しました。
返ってきたコードが下記です。

@app.route('/register_maintenance', methods=['POST'])
def register_maintenance():
    maintenance_form = MaintenanceForm()
    try:
        employees = Employee.query.all()
        maintenance_form.recipient_id.choices = [(employee.id, employee.name) for employee in employees]
        maintenance_form.maintenance_person_id.choices = [(employee.id, employee.name) for employee in employees]

        if maintenance_form.validate_on_submit():
            recipient_id = request.form['recipient_id']
            maintenance_person_id = request.form['maintenance_person_id']
            maintenance = Maintenance(
                customer_id=maintenance_form.customer_id.data,
                date_of_receipt=maintenance_form.date_of_receipt.data,
                recipient_id=recipient_id,
                model_number=maintenance_form.model_number.data,
                maintenance_person_id=maintenance_person_id,
                paid_free=maintenance_form.paid_free.data,
                number_of_days_deposited=maintenance_form.number_of_days_deposited.data,
                scheduled_return_date=maintenance_form.scheduled_return_date.data,
                return_processing_date=maintenance_form.return_processing_date.data,
                returned_unreturned=maintenance_form.returned_unreturned.data
            )
            db.session.add(maintenance)
            db.session.commit()
            return redirect(url_for('index'))
        else:
            for fieldName, errorMessages in maintenance_form.errors.items():
                for err in errorMessages:
                    app.logger.error(f'Error in {fieldName}: {err}')
    except Exception as e:
        app.logger.exception('Failed to register maintenance.')
        return f'登録できませんでした。エラー: {e}'

    return f'登録できませんでした。最初からやり直してください。'

これで実行すると、下記のとおりエラーログが表示されたので、原因がはっきりとしました(エラー部分の和訳は、「アプリでエラーが発生しました:date_of_receipt でエラーが発生しました:有効な日付値ではありません。」)。

[2024-03-07 10:03:50,704] ERROR in app: Error in date_of_receipt: Not a valid date value.

確認すると、下記のコードで、maintenance_form.date_of_receiptが重複していました。単純なコーディングミスです。
返却予定日をmaintenance_form.scheduled_return_date
返却(処理)日をmaintenance_form.return_processing_date
と、適切なフォームの変数に変更します。

<div class="form-group"
    <label for="date_of_receipt">受付日:</label>
    {{ maintenance_form.date_of_receipt(class="form-control", id="date_of_receipt", type="date") }}
</div>
<div class="form-group">
     <label for="scheduled_return_date">返却予定日:</label>
     {{ maintenance_form.date_of_receipt(class="form-control", id="scheduled_return_date") }}
</div>
<div class="form-group">
     <label for="return_processing_date">返却 (処理)日:</label>
     {{ maintenance_form.date_of_receipt(class="form-control", id="returnProcessingDate", type="date", required=false) }}
</div>

実行すると、新たなエラー

ERROR in app: Error in return_processing_date: Not a valid date value.
(ERROR in app:return_processing_date のエラー: 有効な日付値ではありません。)

これは、返却(処理)日が未入力なので発生しています。
この項目は、未入力でもOKにしたいので、そのように修正します。
以下、引用部分はリートンのテキストによる返答、コード部分はリートンが示した修正案

日付が選択されていなくてもデータベースに保存できるようにするには、いくつかの変更を行います。
1. HTML フォームの `required` 属性を削除または `required="false"` に設定します(ただし、HTML5では `required="false"` は不要で、単に `required` 属性を省略すればよいです)。
2. WTForms のフィールドバリデータから `DataRequired()` を削除するか、`Optional()` バリデータを使用して、フィールドが空でもバリデーションを通過できるようにします。
3. モデル定義が `nullable=True` であることを確認します(すでになっている場合はそのままで大丈夫です)。
以下に、変更点を示します。
HTMLフォームの変更点(`required` 属性を削除):

リートンの返答部分
<div class="form-group">
    <label for="return_processing_date">返却 (処理)日:</label>
    {{ maintenance_form.return_processing_date(class="form-control", id="returnProcessingDate") }} 
</div> 

WTFormsのフィールド定義の変更点(`Optional()` バリデータを使用):

リートンの返答部分
from wtforms.validators import Optional 

class MaintenanceForm(FlaskForm):
    # ... 他のフィールド ... 

  return_processing_date = DateField('返却 (処理)日', validators=[Optional()])
    
  # ... 他のフィールド ... 

モデル定義は既に `nullable=True` となっているので、これに変更は必要ありません。 これらの変更を行うことで、`return_processing_date` フィールドが空の状態でもフォームのバリデーションを通過し、データベースに保存することが可能になります。 また、`return_processing_date` フィールドのデータをデータベースに保存する際に、日付が入力されていない場合は `None` が保存されるようになります。これは Flask と SQLAlchemy が自動的に処理してくれます。

リートンの返答部分

該当箇所を修正して、実行しても、下記のようなエラーが出ます

エラー: (builtins.TypeError) SQLite Date type only accepts Python date objects as input. [SQL: INSERT INTO maintenance (customer_id, date_of_receipt, recipient_id, model_number, maintenance_person_id, paid_free, number_of_days_deposited, scheduled_return_date, return_processing_date, returned_unreturned) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)] [parameters: [{'number_of_days_deposited': '1', 'model_number': '0000', 'recipient_id': '1', 'returned_unreturned': 'unreturned', 'maintenance_person_id': '2', 'sch ... (3 characters truncated) ... led_return_date': '2024-03-08', 'paid_free': 'free', 'date_of_receipt': datetime.date(2024, 3, 7), 'customer_id': '8', 'return_processing_date': None}]]

アプリの画面

リートンに説明を求めると

このエラーは、SQLiteが return_processing_date フィールドに対して None 値を受け取ったときに発生しています。SQLiteのDate型はPythonの date オブジェクトのみを受け入れるため、None が渡されると TypeError が発生します。

リートンの返答

とのことだったので、このあと、return_processing_dateの値で試行錯誤、四苦八苦します。
ノートンで確認しても

return_processing_date: None と出力されたということは、return_processing_date フィールドに対して None 値が正しくフォームから受け取られていることを示しています。SQLiteが None を日付型のカラムに対して受け入れないという問題は通常は発生しないため、問題は別の場所にある可能性があります。
エラーメッセージ「SQLite Date type only accepts Python date objects as input.」が示す通り、SQLiteでは Date 型のカラムには None またはPythonの date オブジェクトのみを受け入れます。None が適切に受け入れられている場合、このエラーは発生しません。

リートンの返答

と、エラーにならないはずだと言います。
そこで、該当箇所に日付を選択して送信してみました。
適切な値が送信されているはずなのに、また同じ下記のエラー

エラー: (builtins.TypeError) SQLite Date type only accepts Python date objects as input. [SQL: INSERT INTO maintenance (customer_id, date_of_receipt, recipient_id, model_number, maintenance_person_id, paid_free, number_of_days_deposited, scheduled_return_date, return_processing_date, returned_unreturned) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)] [parameters: [{'number_of_days_deposited': '1', 'model_number': '0000', 'recipient_id': '1', 'returned_unreturned': 'unreturned', 'maintenance_person_id': '2', 'sch ... (3 characters truncated) ... led_return_date': '2024-03-08', 'paid_free': 'free', 'date_of_receipt': datetime.date(2024, 3, 7), 'customer_id': '8', 'return_processing_date': None}]]

アプリの画面

これは、違う場所だということに、ようやく気づきました。


エラーの原因特定に成功!!

今回のコードにDateで設定している箇所は、下記の3箇所

  • 受付日

  • 返却予定日

  • 返却(処理)日

ここで気づきました!!
返却予定日は、自動計算して表示する「JavaScript」を設定しています。
ここだ!
と思い、forms.pyとmodels.pyを確認すると、

#forms.py
scheduled_return_date = StringField('Scheduled Return Date')

#models.py
scheduled_return_date = db.Column(db.Date, nullable=False)

と、forms.pyでは「StringField(文字列)」に設定して、models.pyでは「db.Date(日付)」で設定していました。
整合性が取れないので、エラーになっていたようです。
forms.pyを「DateField」に修正しました。
これで実行して、入力フォームに入力後に送信すると、無事に保存されました!!
これで、メンテナンス管理ページの新規登録ボタンがクリックされたあとの機能は実装されました。
次は、同ページの下記を実装していきます。

  • メンテナンス情報の更新

    • 顧客検索

    • 顧客検索結果の一覧表示

    • 顧客一覧表示から任意のレコードを選択

    • 選択した顧客idに紐づくメンテナンス情報の一覧表示

    • メンテナンス情報の一覧から任意のレコードを選択

    • メンテナンス情報の編集・更新

    • データベースへの保存

今日の体調

今日は、いつもどおりの倦怠感だけで、体調不良で引きこもりになって以降の体調でいうと、「マシ」な状態です。
こういう日は、作業をしていても、過集中になりがちです。
案の定、昼休憩をとらずにエラーの修正に躍起になってしまいました。

この記事が気に入ったらサポートをしてみませんか?