【Python】Fletで簡易的な在庫管理アプリを作成

Python

※この記事にはプロモーションが含まれています。

簡単な在庫管理のシステムを作りたいのですが、エクセルとかではちょっと…

それならPythonで使えるFletとというフレームワークならデスクトップアプリもWebアプリも比較的簡単に作ることが出来ますよ。

でもWebアプリとかにするとHTMLやCSSとか大変そう…

FletならPythonのみでモダンなデザインのアプリを作ることが出来ます。

Pythonだけで作ったのですか?

レンタルサーバー 高速・高機能・高安定性の【エックスサーバー】
高速かつ高い安定性を誇る高性能レンタルサーバー【エックスサーバー】稼働率99.99%以上の高い安定性で、業界トップクラスの高コストパフォーマンスを誇る高品質レンタルサーバーです。月額990円(税込)から利用可能。まずは無料お試し10日間。

Fletとは

 Fletは、Pythonだけでフロントエンドのユーザーインターフェース(UI)を簡単に作成できるオープンソースのフレームワークで、Googleがリリースした「Flutter」というアプリ開発フレームワークを搭載しているので、モダンなデザインのGUIであることが特徴です。
 また、クロスプラットフォーム対応でFletで作成したアプリケーションは、Webブラウザ、デスクトップ(Windows、macOS、Linux)、およびモバイル(iOS、Android)で動作します。

実行環境の準備

 まず、Fletは仮想環境にインストールすることを推奨していますので、使用するOSに従って仮想環境を作成して下さい(仮想環境の設定が不安な方はこちら)。
 仮想環境を立ち上げることができたら、下記のコマンドでFletをインストールします。

pip install flet

 カレントディレクトリ内にアプリのひな型を作成するには以下のコマンドを入力します。

flet create my_flet_app      # my_flet_appはプロジェクト名

 コマンドを入力すると新しいディレクトリmy_flet_appが作成されます。
 アプリを起動するにはmy_flet_app内でflet run コマンドを入力するか、下記のようにパスを指定することでデスクトップアプリが立ち上がります。

flet run my_flet_app

 また、下記のコマンドを入力することで、WebアプリとしてランダムなTCPポートを使用してブラウザで立ち上がらせることができます。

flet run --web my_flet_app

 my_flet_app内の main.py に下記のコードがデフォルトで記述されています。

import flet as ft

# 型ヒントで変数 page が ft.Pageオブジェクトであることが示されている
def main(page: ft.Page): 
    page.add(ft.SafeArea(ft.Text("Hello, Flet!")))

ft.app(main)

 まず、Fletをインストールして、エイリアス(別名)を ft とします。 Fletのappメソッドは引数にエントリーポイントであるmain関数を受け取ってアプリケーションを初期化してイベントループを開始します(ここではウィンドウに Hello,Flet! と表示させるだけ)。

 そしてmain関数は引数にappメソッドの実行時に自動的に設定されるFletのPageクラスのインスタンスであるpageオブジェクトを受け取ります。このオブジェクトはFletフレームワークにおいてアプリケーションのUIを構築するための主要なコンテナで、このオブジェクトを通じて、UIコンポーネントを追加したり、ページ全体の設定を行ったりします。
 

アプリの基本構造

 次に、コードの構造を整理してアプリケーションの拡張や保守を容易にするため、InventoryAppクラスを作成します。このクラスの中のpageオブジェクトを使ってUIコンポーネントを追加したり、イベント処理を設定してアプリケーションのロジックやUIを管理します。

import flet as ft

class InventoryApp:
    def __init__(self, page: ft.Page):
        # Flet Pageインスタンスを受け取る
        self.page = page
        self.page.add(ft.SafeArea(ft.Text("Hello, Flet!")))

# Fletアプリケーションを実行
def main(page: ft.Page):
    InventoryApp(page)

ft.app(target=main)

 デフォルトのコードと結果は同じです。次はデータベースを構築します。

👉 【最大10,000円割引!】 XServer レンタルサーバー招待リンク

データベースを構築する

なぜデータベースを構築するのですか?

データベースを構築しないと入力したデータが次にシステムを立ち上げたときに消えてしまいます。コードを書く前に用語について少しおさらいしましょう。

データベースとは

 データベース (Database) とは、データを効率的に蓄積、管理、検索、更新するためのシステムまたはソフトウェアです。データベースは、情報を整理し、容易にアクセスできるようにするために設計されていて、よく使われるデータベースの種類として、リレーショナルデータベース(RDBMS)があり、データをテーブルとして組織化し、SQLを使用してデータを操作します。
 

SQLとは

 SQL (Structured Query Language) は、データベースや情報システムに対してデータの検索、取得、操作を行うための専用言語(クエリ言語:Query Language)です。
 主にリレーショナルデータベース管理システム(RDBMS)で使用され、データの定義、操作、制御、およびトランザクションの管理を行います。トランザクションとはデータベース内の一連の操作を一つの単位として扱い、全てが成功するか、全てが失敗するかを管理することです。
 今回の構築には軽量なデータベースであるSQLiteを使用します。

SQLiteとは

 SQLiteは、軽量で自己完結型の高性能なデータベース管理システムで、ライブラリとしてアプリケーションに直接組み込まれるため、サーバーが必要なくその設定や管理が不要です。
 また、データベース全体が単一のファイルに格納されるため、データのバックアップや転送が簡単で、小規模から中規模のデータベースアプリケーションにおいて高いパフォーマンスを発揮します。

 このSQLiteをPythonのsqlite3を使って操作します。sqlite3は標準ライブラリなのでそのままインポートします。

import flet as ft
import sqlite3

class InventoryApp:
    def __init__(self, page: ft.Page):
        self.page = page
        # SQLiteデータベースへの接続を確立
        self.conn = sqlite3.connect('inventory.db')
        # データベーステーブルを作成(存在しない場合)
        self.create_table()
    # アプリ終了時にデータベース接続を閉じる
        self.page.on_close = self.on_close
        
    def create_table(self):
       # データベース操作を実行するためのオブジェクトの作成
       cursor = self.conn.cursor()
       # inventoryテーブルを作成するためのSQLを実行
       cursor.execute('''CREATE TABLE IF NOT EXISTS inventory
                       (id INTEGER PRIMARY KEY,
                        name TEXT,
                        price INTEGER,
                        quantity INTEGER)''')
       # データベースに対するすべての変更を確定
       self.conn.commit()

 アプリが立ち上がると、sqlite3.connect(‘inventory.db’)のコードで、inventory.dbファイルのデータベースに接続します。もしデータベースが存在しない場合は空のファイルを作成します。
 そして、データベースに接続、もしくは空ファイルを作成したあとにcreate_table()メソッドを呼び出します。

データベースのテーブルを作成する

 まず、cursor = self.conn.cursor() でデータベース操作を実行するためのオブジェクトであるcursorオブジェクトを作成します。
 そして、cursorオブジェクトができたらexecute()メソッドによってSQLを実行します。SQL文のCREATE TABLEは新しいテーブルを作成するためのもので、テーブル名はここではinventoryにしています。またIF NOT EXISTSのオプションは、テーブルが存在しないときにのみ処理を実行することよってデータの上書きを防ぎます。

テーブルの構成

 ここでのテーブル構成は下記のように設定しています。

テーブル名: inventory

  • 整数型のユニークなプライマリーキー
  • SQLiteにおけるTEST型 商品名
  • 整数型の商品価格
  • 整数型の商品数量

 テーブル作成が終わったら、self.conn.commit() によってトランザクションの完了を示し、すべての変更をデータベースに保存します。

データベースとの接続を閉じる

 データベースの接続を閉じるためのメソッドを設定します。このメソッドはpageオブジェクトのon_closeプロパティに設定されていて、ユーザーがアプリを終了したときに呼ばれ、これによってリソースを解放し、安全にデータベースを終了することができます。

def on_close(self, e):
        # データベース接続を閉じる
        self.conn.close()

UIを構築するメソッド

 アプリのタイトルや入力フィールド、登録ボタンや更新ボタン、入力したデータを表示するテーブルの設定と全体の配置、デザインを整えています。データが画面より大きくなるとスクロールできるようになります。

def build_ui(self):
        # ウィンドウのタイトルを設定
        self.page.title = "Inventory Management App"
        # 画面スクロール設定
        self.page.scroll = ft.ScrollMode.ADAPTIVE
        # アプリのタイトル
        self.title = ft.Text(
            value="在庫管理アプリ",
            theme_style=ft.TextThemeStyle.HEADLINE_MEDIUM
        )
        # 商品名入力フィールド
        self.item_name_input = ft.TextField(label="商品名")
        # 価格入力フィールド
        self.item_price_input = ft.TextField(label="価格")
        # 数量入力フィールド
        self.item_quantity_input = ft.TextField(label="数量")
        # 商品追加ボタン
        self.add_button = ft.ElevatedButton(
            text="登 録",
            on_click=self.add_item
        )
        # 更新ボタン(デフォルトで非表示)
        self.update_button = ft.ElevatedButton(
            text="更 新",
            on_click=self.update_item,
            visible=False
        )
        # 入力フィールド
        self.input_field = ft.Row(
            [
                self.item_name_input,
                self.item_price_input,
                self.item_quantity_input,
                self.update_button,
                self.add_button
            ],
            alignment=ft.MainAxisAlignment.CENTER
        )
        # データテーブル
        self.datatable = ft.DataTable(
            bgcolor="#E7D0A9",
            border_radius=5,
            border=ft.border.all(2, "#E7D0A9"),
            vertical_lines=ft.BorderSide(3, "#DFE7A9"),
            heading_row_height=30,
            columns=[
                ft.DataColumn(self.settings("ID", 20)),
                ft.DataColumn(self.settings("商品", 300)),
                ft.DataColumn(self.settings("価格", 100), numeric=True),
                ft.DataColumn(self.settings("数量", 50), numeric=True),
                ft.DataColumn(self.settings("金額", 150), numeric=True),
                ft.DataColumn(self.settings("編集", 40)),
                ft.DataColumn(self.settings("削除", 40)),
            ],
            rows=[]
        )
        # 全体の配置
        self.page.add(
            ft.Column(
                [self.title, self.input_field, self.datatable],
                horizontal_alignment=ft.CrossAxisAlignment.CENTER
            )
        )
    # セルタイトルの詳細設定
    def settings(self, cell_title, width_px):
        return ft.Container(
            ft.Text(cell_title),
            width=width_px,
            alignment=ft.alignment.center
        )

 機能としては、入力したデータの削除と編集ができます。

各機能を設定する

 ここまでで、アプリのUIを作成することはできましたが、これからアプリに必要なCRUD操作の機能を実際にプログラムしていきます。

 CRUDとはCreate(作成): データの新規作成、Read(読み取り): データの読み取りや表示、Update(更新): 既存データの更新、Delete(削除): データの削除です。

データを読み込んで表示する(古いデータを削除する)

 まず最初に、データを読み込んでアプリに表示するメソッドload_items()を作成します。

def load_items(self):
    cursor = self.conn.cursor()
    cursor.execute("SELECT * FROM inventory")
    rows_data = cursor.fetchall()
    
    # テーブルの行をクリア
    self.datatable.rows.clear()
  
    # データベースから取得した各行に対して
    for row in rows_data:
        # 各行を DataRow オブジェクトとして追加
        self.datatable.rows.append(
            ft.DataRow(
                cells=[
                    ft.DataCell(ft.Text(row[0])),  # 商品ID
                    ft.DataCell(ft.Text(row[1])),  # 商品名
                    ft.DataCell(ft.Text(row[2])),  # 単価
                    ft.DataCell(ft.Text(row[3])),  # 数量
                    ft.DataCell(ft.Text(row[2] * row[3])),  # 合計 (単価 × 数量)
                    ft.DataCell(
                        # 編集ボタン
                        ft.IconButton(
                            icon=ft.icons.EDIT,
                            on_click=lambda e, id=row[0]: self.edit_item(id)  
                        )
                    ),
                    ft.DataCell(
                        # 削除ボタン
                        ft.IconButton(
                            icon=ft.icons.DELETE,
                            on_click=lambda e, id=row[0]: self.delete_item(id)  
                        )
                    )
                ]
            )
        )
    # ページの更新
    self.page.update()

 最初のSELECT * FROM inventoryのSQL文からfetchall()メソッドを使ってデータベースにあるすべてのデータを取得して変数rows_dataに格納します。
 次のコードのself.datatablerows.clear()は先のメソッドbuild_ui()で作成したテーブルをrows=[](空)にするということで、アプリに表示されているテーブルのデータ行をすべてクリアにします。


 そして今度は、タプルで行ごとに格納されている変数rows_dataのデータをテーブルのセルデータとして、self.datatablerows=[]にfor文で順番に加えていきアプリに新しいデータとして表示させるという流れです。


 また、データを各行ごとに表示させた最後に編集アイコンと削除アイコンを設置して、それぞれにこの後に設定するメソッドをクリックイベントで発生するようにPythonのlambda(ラムダ)式で設定していきます。
 そしてこのメソッドload_items()初期化時に一度実行されるようにコンストラクタの中でこのプログラムの呼び出しを行うようにします。

def __init__(self, page: ft.Page):
        self.page = page
        self.conn = sqlite3.connect('inventory.db')
        self.create_table()
        self.build_ui()
        self.load_items() ここ!
        self.page.on_close = self.on_close

データを入力する

 登録ボタンを押したときに呼ばれ、入力したデータをデータベースに登録してアプリの表示データを更新するメソッドadd_item()を作成します。ちなみに今回は、フォームの入力に関する例外処理は省いています。ご了承下さい。

def add_item(self, e):
        # 入力フィールドから商品名、価格、数量を取得
        name = self.item_name_input.value
        price = self.item_price_input.value
        quantity = self.item_quantity_input.value
        # データベースに商品を追加
        cursor = self.conn.cursor()
        cursor.execute(
            "INSERT INTO inventory (name, price, quantity) VALUES (?, ?, ?)", 
            (name, price, quantity)
        )
        self.conn.commit()
        # 商品リストを更新
        self.load_items()
        # 入力フィールドをクリア
        self.item_name_input.value = ""
        self.item_price_input.value = ""
        self.item_quantity_input.value = ""
        self.page.update()

データを編集する

 編集アイコンを押したときに呼ばれるedit_item()メソッドを設定します。

def edit_item(self, item_id):
        # 編集する商品を取得し、入力フィールドに設定
        cursor = self.conn.cursor()
        cursor.execute("SELECT * FROM inventory WHERE id = ?", (item_id,))
        row = cursor.fetchone()
        if row:
            self.item_name_input.value = row[1]
            self.item_price_input.value = row[2]
            self.item_quantity_input.value = row[3]
            self.update_button.visible = True
            self.add_button.visible = False
            self.update_button.data = item_id  # 更新する商品IDを保存
            self.page.update()

 ここでは編集アイコンを押したボタンの行のIDを一度self.update_button.dataに保存しています。後でデータを更新する際にどのIDのデータを編集したかを確認するためです。

データを更新する

 更新ボタンを押したときに呼ばれ、編集したデータを更新するメソッドupdate_item()を作成します。ここではデータを編集するときに変数self.update_button.dataに記録しておいたIDを変数item_idに代入します。これによって、編集したデータと更新するデータベースのデータの紐づけをしています。

def update_item(self, e):
        # 記録していたidを変数item_idに格納
        item_id = self.update_button.data
        # 入力フィールドから商品名と価格と数量を取得
        name = self.item_name_input.value
        price = self.item_price_input.value
        quantity = self.item_quantity_input.value
        # データベースのデータを更新
        cursor = self.conn.cursor()
        cursor.execute(
            "UPDATE inventory SET name = ?, price = ?, quantity = ? WHERE id = ?",
            (name, price, quantity, item_id)
        )
        self.conn.commit()
        # 商品リストを更新
        self.load_items()
        # 入力フィールドをクリア
        self.item_name_input.value = ""
        self.item_price_input.value = ""
        self.item_quantity_input.value = ""
        # ボタンの可視性を更新
        self.update_button.visible = False
        self.add_button.visible = True
        self.page.update()

データを削除する

 削除アイコンを押したときに呼ばれるdelete_item()メソッドを設定します。SQL文で引数item_idから受け取ったIDと同じIDのデータをデータベースから削除してアプリデータ表示を更新します。

def delete_item(self, item_id):
        # データベースから商品を削除
        cursor = self.conn.cursor()
        cursor.execute("DELETE FROM inventory WHERE id = ?",
                       (item_id,))
        self.conn.commit()
        # 商品リストを更新
        self.load_items()

 これで一連のプログラムは完成しました。

おわりに

 Fletで簡易的な在庫管理アプリを作成することによって、Fletの基本的な使い方を掴むことが出来、かつデータベースとの連携も確認することができました。もちろん、Fletの機能についてはごく一部ですし、本格的な管理システムを作成するにはPostgreSQLやMySQLなどのデータベースを使用することが推奨されます。


 最後までご覧頂きありがとうございました。

タイトルとURLをコピーしました