DjangoのModelをソートするキーにプロパティを使いたいとき

2021年5月29日 2023年11月8日
カテゴリ: プログラミング
Python Django

あらすじ

とあるDjangoのプロジェクトで商品の登録日(DateField)が新しい順にソートされていたデータを、商品の発売日が新しい順にソートさせたかったのでModelの内容を確認したところ発売日のフィールドは設定されていないようでした。

そんな場合は大抵Modelに新しいフィールドを追加するところなのですが、商品の発売日が商品コード(CharField)の一部として登録されていることが分かりました。

このプロジェクトでは商品コードを次のような12桁のコードで設定していました。
カテゴリコード(2桁)、発売日(8桁)、枝番(2桁)

コードはこんな感じになっていました。

models.py
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()を使用します。
たとえば下記の様な感じで使用します。

views.py
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を付けてメソッドを作るとプロパティになります。

models.py
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 という風にします。

商品のリストを発売日が新しい順にソートするには以下のようにします。

views.py
products = sorted(Product.objects, key=lambda product : product.release_date, reverse=True)

新しい順なので発売日の降順ソートにするため reverse=True としています。

関連の記事

【Django】CSS や JavaScript の変更が反映されないときの対処法

ローカルで動かしているDjangoのサイトをスマホから見る方法

DjangoでAppRegistryNotReadyにハマった話

OpenPyXl でチェックボックスを押したい