Djangoのモデルクラスでsave()メソッドをオーバーライドする際、引数の受け渡し方法には主に2つの設計アプローチがあり、それぞれ用途と保守性に応じた選択が求められます。
アプローチ1:可変長引数による汎用的実装
以下のように*argsと**kwargsを用いることで、将来追加される可能性のある引数にも柔軟に対応できます:
def save(self, *args, **kwargs):
# 前処理(例:タイムスタンプ更新)
self.updated_at = timezone.now()
if not self.pk:
self.created_at = self.updated_at
super().save(*args, **kwargs)
このスタイルは、Django内部の引数仕様変更への耐性が高く、サードパーティライブラリとの連携時にも安定して動作します。
アプローチ2:明示的パラメータによる厳密な定義
一方、DjangoのModel.save()が実際に受け付ける引数を明示的に列挙する方式もあります:
def save(
self,
force_insert=False,
force_update=False,
using=None,
update_fields=None
):
self.updated_at = timezone.now()
if not self.pk:
self.created_at = self.updated_at
super().save(
force_insert=force_insert,
force_update=force_update,
using=using,
update_fields=update_fields
)
この記述は型ヒントやIDEの補完支援に優れ、呼び出し側が引数の意味を直感的に理解できるため、チーム開発や長期保守プロジェクトでは推奨されます。
実践的な拡張パターン
1. タイムスタンプの自動管理
from django.db import models
from django.utils import timezone
class Article(models.Model):
title = models.CharField(max_length=200)
slug = models.SlugField(unique=True, blank=True)
published_at = models.DateTimeField(null=True, blank=True)
updated_at = models.DateTimeField(auto_now=True)
def save(self, *args, **kwargs):
if not self.slug:
self.slug = slugify(self.title)
if not self.pk and not self.published_at:
self.published_at = timezone.now()
super().save(*args, **kwargs)
2. データ整合性の強制チェック
class InventoryItem(models.Model):
name = models.CharField(max_length=150)
stock_quantity = models.PositiveIntegerField()
min_stock_level = models.PositiveIntegerField(default=0)
def save(self, *args, **kwargs):
if self.stock_quantity < self.min_stock_level:
raise ValidationError(
f"在庫数({self.stock_quantity})は最低水準({self.min_stock_level})未満です"
)
super().save(*args, **kwargs)
3. 関連レコードの同期生成
class Order(models.Model):
customer = models.ForeignKey('Customer', on_delete=models.CASCADE)
status = models.CharField(max_length=20, default='draft')
def save(self, *args, **kwargs):
is_new = self._state.adding
super().save(*args, **kwargs)
if is_new:
# 注文作成時に関連する配送情報も初期化
ShippingInfo.objects.create(order=self)
4. 条件付き保存制御
class DraftPost(models.Model):
content = models.TextField()
is_published = models.BooleanField(default=False)
def save(self, *args, **kwargs):
if self.is_published and not self.pk:
# 公開済み投稿は新規作成不可(下書きから遷移必須)
raise PermissionError("公開済み記事は既存レコードからの更新のみ許可されます")
super().save(*args, **kwargs)
5. 外部サービスとの連携
import requests
class Notification(models.Model):
recipient = models.EmailField()
message = models.TextField()
sent_at = models.DateTimeField(null=True, blank=True)
def save(self, *args, **kwargs):
super().save(*args, **kwargs)
if not self.sent_at:
try:
response = requests.post(
"https://api.example.com/notify",
json={"to": self.recipient, "body": self.message},
timeout=5
)
if response.status_code == 200:
self.sent_at = timezone.now()
super().save(update_fields=['sent_at'])
except requests.RequestException:
pass # 通信失敗時はログ出力など別途処理