Django管理者:データベースフィールドがないカスタムlist_displayフィールドの1つで並べ替える方法


122
# admin.py
class CustomerAdmin(admin.ModelAdmin):  
    list_display = ('foo', 'number_of_orders')

# models.py
class Order(models.Model):
    bar = models.CharField[...]
    customer = models.ForeignKey(Customer)

class Customer(models.Model):
    foo = models.CharField[...]
    def number_of_orders(self):
        return u'%s' % Order.objects.filter(customer=self).count()  

顧客に応じてnumber_of_orders、顧客をどのように分類できますか?

admin_order_fieldプロパティは、並べ替えにデータベースフィールドを必要とするため、ここでは使用できません。Djangoがソートを実行するために基礎となるDBに依存しているので、それはまったく可能ですか?注文の数を含む集約フィールドを作成することは、ここではやり過ぎのようです。

おもしろいことに、ブラウザで手動でURLを変更してこの列をソートすると、期待どおりに動作します。


「おもしろいことに、ブラウザで手動でURLを変更してこの列をソートすると、期待どおりに動作します。」/ admin / myapp / customer /?ot = asc&o = 2本当にいいですか?
アンディベイカー

そう、ascとdscの両方です。多分それは小数で動作します。
mike_k 2010年

複数のページで動作するとは思いません。
Chase Seibert、2010

回答:


159

この問題に対するGregの解決策は気に入りましたが、管理者が直接同じことを実行できることを指摘しておきます。

from django.db import models

class CustomerAdmin(admin.ModelAdmin):
    list_display = ('number_of_orders',)

    def get_queryset(self, request):
    # def queryset(self, request): # For Django <1.6
        qs = super(CustomerAdmin, self).get_queryset(request)
        # qs = super(CustomerAdmin, self).queryset(request) # For Django <1.6
        qs = qs.annotate(models.Count('order'))
        return qs

    def number_of_orders(self, obj):
        return obj.order__count
    number_of_orders.admin_order_field = 'order__count'

この方法では、管理インターフェース内でのみ注釈を付けます。あなたが行うすべてのクエリではありません。


5
はい、これははるかに良い方法です。:)
Greg

2
この回答には編集提案があります。削除したテキストが多すぎるため、私はそれを拒否することにしました。私はDjangoを知りません、提案されたコード変更が言及する価値があるかどうかわかりません。
Gilles「SO-悪をやめなさい」

1
@Gilles提案された編集は、より単純なnumber_of_orders定義については正しいです。これは機能します: def number_of_orders(self, obj): return obj.order__count
Nils

1
get_queryset()代わりにすべきではありqueryset()ませんか?
Mariusz Jamro 2016

2
get_queryset(self、request):... Django 1.6以降の場合
マイケル

50

私はこれをテストしていませんが(動作するかどうかを知りたいのですが)、Customer注文の数を集計するカスタムマネージャーを定義し、admin_order_fieldその集計に設定する方法についてはどうでしょうか。

from django.db import models 


class CustomerManager(models.Manager):
    def get_query_set(self):
        return super(CustomerManager, self).get_query_set().annotate(models.Count('order'))

class Customer(models.Model):
    foo = models.CharField[...]

    objects = CustomerManager()

    def number_of_orders(self):
        return u'%s' % Order.objects.filter(customer=self).count()
    number_of_orders.admin_order_field = 'order__count'

編集:私はこのアイデアをテストしましたが、それは完全に機能します-django管理サブクラス化は必要ありません!


1
これは、受け入れられたものよりも良い答えです。承認されたものを適用する際に遭遇した問題は、管理者レベルでその更新されたクエリセットと一緒に何かを検索すると、時間がかかりすぎ、検出された結果のカウントが間違っていることです。
ミュータント

0

私が考えることができる唯一の方法は、フィールドを非正規化することです。つまり、派生元のフィールドと常に同期するように更新される実際のフィールドを作成します。通常、これを行うには、非正規化フィールドを持つモデルまたはそれから派生するモデルのいずれかでsaveをオーバーライドします。

# models.py
class Order(models.Model):
    bar = models.CharField[...]
    customer = models.ForeignKey(Customer)
    def save(self):
        super(Order, self).save()
        self.customer.number_of_orders = Order.objects.filter(customer=self.customer).count()
        self.customer.save()

class Customer(models.Model):
    foo = models.CharField[...]
    number_of_orders = models.IntegerField[...]

1
これは確かに機能するはずですが、余分なDBフィールドが含まれているため、受け入れ済みとしてマークできません。また、クエリ設定行の最後に.count()がないことに注意してください。
mike_k

count()を修正しました。他の唯一の解決策(contrib.adminの大きなチャンクをサブクラス化しない場合)は、Jquery / Ajaxyハックです。
アンディベイカー
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.