DjangoのModelをソートするキーにプロパティを使いたいとき
あらすじ
とあるDjangoのプロジェクトで商品の登録日(DateField)が新しい順にソートされていたデータを、商品の発売日が新しい順にソートさせたかったのでModelの内容を確認したところ発売日のフィールドは設定されていないようでした。
そんな場合は大抵Modelに新しいフィールドを追加するところなのですが、商品の発売日が商品コード(CharField)の一部として登録されていることが分かりました。
このプロジェクトでは商品コードを次のような12桁のコードで設定していました。
カテゴリコード(2桁)、発売日(8桁)、枝番(2桁)
コードはこんな感じになっていました。
class Product(models.Model):
item_code = models.CharField(
primary_key=True,
max_length=12,
blank=False,
null=False,
default="999999999999"
)
add_date = models.DateField(
blank=True,
null=True,
default=timezone.now()
)
...
発売日を含む情報は既にDBに登録されているので、新たにフィールドを追加することなく発売日でソートする方法を模索していきました。
DjangoのModelをソートするには
DjangoのModelをソートする場合はModel.objects.order_by()を使用します。
たとえば下記の様な感じで使用します。
products = Product.objects.order_by('add_date').reverse()
今回のような場合、商品コード(item_code)をキーにソートすればよさそうな感じですが、先頭にカテゴリコードがあるのがちょっとくせ者でした。
たとえば、下記のような感じです。
商品A 012020060100
商品B 012021050101
商品C 022019060102
商品コードから発売日を読み解くと次のようになります。
カテゴリコード | 発売日 | |
---|---|---|
商品A | 01 | 2020年6月1日 |
商品B | 01 | 2021年5月1日 |
商品C | 02 | 2019年6月1日 |
これを発売日が新しい順にソートすると商品B、商品A、商品Cの順になるはずです。
ところが、商品コードで降順ソートすると商品C、商品B、商品Aの順番になってしまうのです。
どうにかして商品コードから日付情報のみを抜き出す必要がありました。
Modelのデータフィールドの情報を変換して使用したい
Modelからデータフィールドの情報を一部抜き出したり変換したりして使用したい場合、プロパティを作るという方法があります。
Modelにプロパティを追加すると、データフィールド同様にデータを取得することができます。
商品の登録日がDateFieldだったので、発売日もdate型で取得できるようにしました。
クラス内に@propertyを付けてメソッドを作るとプロパティになります。
from datetime import date
class Product(models.Model):
...
@property
release_date(self):
return date(
year=int(self.item_code[2:6]),
month=int(self.item_code[6:8]),
day=int(self.item_code[8:10])
)
こうすることで発売日をProduct.release_dateという風に取得できるようになります。
order_byにプロパティは使用できない
せっかくプロパティを追加したのですが、Model.objects.order_by()で使用できるのはDBに保存されているデータ(データベースフィールド)のみで、プロパティは使用できないようでした。
その事実を知らずに少しハマってしまいました。
それでもプロパティでソートしたかったので調べたところ、組込み関数のsortedを使えばいいことが分かりました。
sortedの使い方は次の通りです。
sorted(イテラブルオブジェクト, ソート条件) # 昇順ソート
sorted(イテラブルオブジェクト, ソート条件, reverse=True) # 降順ソート
ソート条件は key=lambda x : x.data という風にします。
商品のリストを発売日が新しい順にソートするには以下のようにします。
products = sorted(Product.objects, key=lambda product : product.release_date, reverse=True)
新しい順なので発売日の降順ソートにするため reverse=True としています。