Djangoアプリの機能追加編!商品追加スクリプト (addition.py) の使い方

コマンドラインから商品情報を入力してカタログデータベースに追加するためのaddition.pyスクリプトの作成について解説いたします。

前回作成したアプリでは、Djangoの管理画面から商品を登録してWEB画面に表示する仕様でした。

しかし、私の調査不足で恐縮なのですが、GCP cloud shellでは、管理画面を表示させることができず。。。

もう少し調べたらいいのですが、調べる時間があるなら追加機能として実装してしまった方が早いのでは?!と思い直し、今回の記事をさくせいしました。

CloudShell以外のローカル環境を利用されていて、すでに管理画面から商品を追加できるようであれば今回の記事は不要かもしれませんが、こう言ったアイディアがあるんだ。と言うことをご紹介させていただきます。

準備

  1. addition.pyをプロジェクトのルートフォルダ(manage.pyと同じ場所)に配置してください。
  2. スクリプトに実行権限を付与します
1. ファイル作成
touch addition.py

2. 権限変更
chmod +x addition.py

サンプルコード

#!/usr/bin/env python
"""
商品追加スクリプト (addition.py)
商品情報をコマンドラインから入力してデータベースに追加するスクリプト
"""
import os
import sys
import django
from decimal import Decimal
from pathlib import Path

# Djangoの設定を読み込むために必要なセットアップ
# このスクリプトはプロジェクトのルートディレクトリに配置してください
sys.path.append(os.path.dirname(os.path.abspath(__file__)))
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'catalog_project.settings')
django.setup()

# モデルのインポートは django.setup() の後に行う必要があります
from products.models import Product

def clear_screen():
    """画面をクリアする"""
    os.system('cls' if os.name == 'nt' else 'clear')

def print_header():
    """ヘッダー表示"""
    print("="*50)
    print("商品カタログ管理システム - 商品追加")
    print("="*50)
    print()

def get_validated_input(prompt, validation_func=None, error_message=None):
    """
    ユーザー入力を取得し、バリデーションを行う
    
    Args:
        prompt: 表示するプロンプト
        validation_func: 入力値を検証する関数(Noneの場合は検証しない)
        error_message: バリデーションエラー時のメッセージ
    
    Returns:
        検証済みの入力値
    """
    while True:
        value = input(prompt)
        if validation_func is None or validation_func(value):
            return value
        print(error_message or "入力が無効です。もう一度入力してください。")

def validate_price(value):
    """価格が正の数値であることを確認"""
    try:
        price = Decimal(value)
        return price > 0
    except:
        return False

def validate_image_path(value):
    """画像パスが存在するファイルであることを確認(空も可)"""
    if not value:
        return True  # 空のパスは許可
    
    path = Path(value)
    return path.is_file() and path.suffix.lower() in ['.jpg', '.jpeg', '.png', '.gif', '.webp']

def add_product():
    """商品情報を入力してデータベースに追加する"""
    clear_screen()
    print_header()
    
    print("新しい商品の情報を入力してください:")
    print("(入力をキャンセルするには、名前の入力で 'q' を入力してください)")
    print()
    
    # 商品名の入力
    name = get_validated_input("商品名: ", 
                              lambda x: len(x) > 0 and x.lower() != 'q', 
                              "商品名は必須です。")
    
    if name.lower() == 'q':
        print("\n商品追加をキャンセルしました。")
        return
    
    # 商品説明の入力
    description = input("商品説明: ")
    
    # 価格の入力
    price = get_validated_input("価格(数字のみ): ", 
                               validate_price, 
                               "有効な価格を入力してください(正の数値)。")
    
    # 画像パスの入力(オプション)
    image_path = get_validated_input("画像ファイルパス(オプション): ", 
                                    validate_image_path,
                                    "有効な画像ファイルパスを入力してください(.jpg, .png, .gif, .webp)。")
    
    # 商品の登録確認
    print("\n=== 登録内容確認 ===")
    print(f"商品名: {name}")
    print(f"説明: {description}")
    print(f"価格: {price}")
    print(f"画像: {'なし' if not image_path else image_path}")
    print("="*20)
    
    confirm = input("\nこの内容で登録しますか? (y/n): ").lower()
    if confirm != 'y':
        print("\n商品登録をキャンセルしました。")
        return
    
    # 商品をデータベースに追加
    try:
        product = Product(
            name=name,
            description=description,
            price=Decimal(price)
        )
        
        # 画像ファイルがある場合は設定
        if image_path:
            # 画像ファイルをmediaディレクトリにコピーする
            from django.core.files import File
            with open(image_path, 'rb') as f:
                image_name = os.path.basename(image_path)
                product.image.save(image_name, File(f), save=False)
        
        # データベースに保存
        product.save()
        print(f"\n商品「{name}」を登録しました!(ID: {product.id})")
    
    except Exception as e:
        print(f"\nエラーが発生しました: {e}")

def list_products():
    """登録済みの商品一覧を表示"""
    clear_screen()
    print_header()
    
    products = Product.objects.all().order_by('-created_at')
    
    if not products:
        print("登録されている商品はありません。")
        return
    
    print(f"商品一覧(全{products.count()}件):")
    print("-" * 70)
    print(f"{'ID':4} | {'商品名':30} | {'価格':10} | {'登録日時'}")
    print("-" * 70)
    
    for product in products:
        print(f"{product.id:4} | {product.name[:28]:30} | {product.price:10} | {product.created_at.strftime('%Y-%m-%d %H:%M')}")
    
    print("-" * 70)

def main_menu():
    """メインメニュー"""
    while True:
        clear_screen()
        print_header()
        
        print("1. 商品を追加する")
        print("2. 商品一覧を表示する")
        print("0. 終了")
        print()
        
        choice = input("選択してください (0-2): ")
        
        if choice == "1":
            add_product()
            input("\nEnterキーを押してメニューに戻ります...")
        elif choice == "2":
            list_products()
            input("\nEnterキーを押してメニューに戻ります...")
        elif choice == "0":
            print("\nプログラムを終了します。")
            break
        else:
            print("\n無効な選択です。もう一度入力してください。")
            input("Enterキーを押して続行...")

if __name__ == "__main__":
    try:
        main_menu()
    except KeyboardInterrupt:
        print("\n\nプログラムを終了します。")
        sys.exit(0)

使用方法

1. コマンドラインからスクリプトを実行します。

python addition.py

2. メインメニューが表示されます。

  • 「1」を選択して商品追加モードに入ります。
  • 商品情報を順番に入力していきます。
  • 商品名(必須): 商品の名前を入力します
  • 商品説明: 商品の説明文を入力します
  • 価格(数字のみ): 数値のみを入力します(例: 1500.05)
  • 画像ファイルパス(オプション): 画像ファイルの絶対パスまたは相対パスを入力します

この内容で登録しますか? (y/n):

「y」を入力すると商品が登録されます。

商品一覧の表示

メインメニューで「2」を選択すると、登録されている商品の一覧が表示されます。

画像のアップロード

画像パスを指定すると、自動的にDjangoのメディアディレクトリ(MEDIA_ROOTで指定されたディレクトリ)に画像がコピーされます。

対応している画像形式は以下の通りです。

  • JPEG (.jpg, .jpeg)
  • PNG (.png)
  • GIF (.gif)
  • WebP (.webp)

注意点

  1. スクリプトはDjangoプロジェクトの環境設定を使用するため、必ずプロジェクトのルートディレクトリから実行してください。
  2. 画像パスはオプションです。空のままでも商品は登録できます。
  3. 価格は正の数値のみ有効です。小数点を含めることも可能です(例: 1999.99)。
  4. スクリプト実行中にキャンセルしたい場合は、Ctrl+Cを押すことでいつでも終了できます。

トラブルシューティング

エラー: No module named ‘catalog_project’

このエラーは、スクリプトがDjangoプロジェクトの設定を見つけられない場合に発生します。

スクリプトをプロジェクトのルートディレクトリ(manage.pyと同じ場所)で実行していることを確認してください。

# 試しに、manage.pyファイルがないフォルダに、<addition.py>を移動させて、スクリプトを実行してみます。

Rokuta@Terminal %1~ %# ls
addition.py  asgi.py  __init__.py  __pycache__  settings.py  urls.py  wsgi.py

# 実行
Rokuta@Terminal %1~ %# python addition.py

## 以下、エラーのメッセージ ##
Traceback (most recent call last):
  File "/home/<プロジェクトのフォルダ>/catalog_project/catalog_project/addition.py", line 16, in <module>
    django.setup()
  File "/usr/local/lib/python3.12/dist-packages/django/__init__.py", line 19, in setup
    configure_logging(settings.LOGGING_CONFIG, settings.LOGGING)
                      ^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/dist-packages/django/conf/__init__.py", line 81, in __getattr__
    self._setup(name)
  File "/usr/local/lib/python3.12/dist-packages/django/conf/__init__.py", line 68, in _setup
    self._wrapped = Settings(settings_module)
                    ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/dist-packages/django/conf/__init__.py", line 166, in __init__
    mod = importlib.import_module(self.SETTINGS_MODULE)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.12/importlib/__init__.py", line 90, in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "<frozen importlib._bootstrap>", line 1387, in _gcd_import
  File "<frozen importlib._bootstrap>", line 1360, in _find_and_load
  File "<frozen importlib._bootstrap>", line 1310, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 488, in _call_with_frames_removed
  File "<frozen importlib._bootstrap>", line 1387, in _gcd_import
  File "<frozen importlib._bootstrap>", line 1360, in _find_and_load
  File "<frozen importlib._bootstrap>", line 1324, in _find_and_load_unlocked
ModuleNotFoundError: No module named 'catalog_project'

エラー: 画像パスが無効です

指定した画像ファイルが存在しないか、サポートされていない形式の場合に発生します。ファイルパスが正しいこと、および対応形式(.jpg, .png, .gif, .webp)であることを確認してください。

エラー: データベースに接続できません

データベースの設定に問題がある場合に発生します。

settings.pyDATABASES設定を確認し、データベースが正しく設定されていることを確認してください。

まとめ

Djangoの管理画面も非常に便利ではありますが、本番環境でWEB上から入力するなどの手間を考えると、こちらの方が拡張性(自分で好きな機能を追加できるなど)もあり、便利かと思います。

ぜひご利用ください。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

CAPTCHA