MPTTモデルの潜在的なカウントの注釈を注釈を付ける -- django フィールド と django-models フィールド と orm フィールド と django-mptt フィールド 関連 問題

Annotate Total Count of Descents of Mptt Model












2
vote

問題

日本語

質問

以下のモデルを考えると、ページに関連付けられているコメントスレッドツリー内のすべてのコメントを含めて、ページに関連付けられているスレッド内のコメントの総数で注釈を付けて、すべてのページのクエリセットを付けたいと考えています。

django-mptt コメントツリーを保存しています。

comment.get_descendant_count() を使用してPythonでこれを取得できますが、これはすべてのページを照会するときには非常に不定です。

モデル

 <コード> class CommentThread(models.Model):     ...   class Page(models.Model):     ...     thread = models.ForeignKey("CommentThread", ...)       class Comment(MPTTModel):     body = models.TextField()     author = models.ForeignKey("User", ...)      # Validation ensures a comment has     # a thread or parent comment but not both     thread = models.ForeignKey("CommentThread", related_name="comments", ...)     parent = TreeForeignKey(                 'self',                 on_delete=models.CASCADE,                 null=True,                 blank=True,                 related_name='children'     )      class MPTTMeta:         level_attr = 'mptt_level'         order_insertion_by=['name']    

このモデルでは、ページに複数の「ルートコメント」を追加することができますが、各コメントの下で各コメントの下にコメントを再帰的にネストします。

 <コード>  # Model Use Examples  thread = CommentThread() page = Page(thread=thread)  # add page level root comments comment1 = Comment(thread=thread, ...) comment2 = Comment(thread=thread, ...) comment3 = Comment(thread=thread, ...)  # add Comment Replies to comment #1 comment_reply1 = Comment(parent=comment1, ...) comment_reply2 = Comment(parent=comment1, ...) comment_reply3 = Comment(parent=comment1, ...)   
現在のアプローチ - Python

作品だが非常に無力:

<事前> <コード> page = Page.objects.first() total_comments = [c.get_descendant_count() for c in page.thread.comments.all()]

私が試したこと

QuerySetと注釈を使用してこれを実現する方法はわかりません。 私は各MPTTモデルも<コード> treed_id を取得することを知っているので、私はより複雑な副照会を構築する必要があると思います。

ルートコメントの数だけを取得する(ネストされていない)、このようにすることができます:

 <コード> pages = Page.objects.all().annotate(num_comments=models.Count("thread__comments")) num_root_comments = pages[0].num_comments   

もう一度、目標は入れ子になったすべてのコメントを入手することです:

 <コード> # Non working code - this kind of  pseudo queryset code of what I am trying:  all_page_comments = Comment.objects.filter(tree_id__in= (Page.thread__comments__tree_id)) Page.objects.all().annotate(num_comments=Count(Subquery(all_page_comments))   

提供されたあらゆる助けのために事前にありがとう。

溶液

@ Andreyの答えのおかげで作業解決策を得ました。 それが最適であるわけではありませんが、単一のクエリで正しい値を返すようです。

<事前> <コード> threads = CommentThread.objects.filter( id=models.OuterRef("thread") ).annotate( comment_count=models.Sum( Floor((models.F("comments__rght") - models.F("comments__lft") - 1) / 2) ) ) qs_pages_with_comment_count = ( Page.objects .annotate( comment_count=models.Subquery( threads.values("comment_count")[:1], output_field=models.IntegerField() ) ) # Count from subquery included count of descendents of # each "root" comment but not the root comment itself # so we add number of root comments per thread on top .annotate( comment_count=models.F("comment_count") + models.Count("thread__comments", distinct=True) ) )
英語

Question

Given the models below, I want to get a queryset of all pages, annotated with the total number of comments in the thread associated to the page, including all comments in a comment thread tree associated with pages.

I am using django-mptt to store comment tree.

I can get this in python using comment.get_descendant_count(), but this is very ineficient when querying all pages

Models

class CommentThread(models.Model):     ...   class Page(models.Model):     ...     thread = models.ForeignKey("CommentThread", ...)       class Comment(MPTTModel):     body = models.TextField()     author = models.ForeignKey("User", ...)      # Validation ensures a comment has     # a thread or parent comment but not both     thread = models.ForeignKey("CommentThread", related_name="comments", ...)     parent = TreeForeignKey(                 'self',                 on_delete=models.CASCADE,                 null=True,                 blank=True,                 related_name='children'     )      class MPTTMeta:         level_attr = 'mptt_level'         order_insertion_by=['name']  

This model allows me to add multiple "root comments" to page, but also nest comments under each comment as replies, recursively.

 # Model Use Examples  thread = CommentThread() page = Page(thread=thread)  # add page level root comments comment1 = Comment(thread=thread, ...) comment2 = Comment(thread=thread, ...) comment3 = Comment(thread=thread, ...)  # add Comment Replies to comment #1 comment_reply1 = Comment(parent=comment1, ...) comment_reply2 = Comment(parent=comment1, ...) comment_reply3 = Comment(parent=comment1, ...) 
Current approach - in python

Works but very inneficient:

page = Page.objects.first() total_comments = [c.get_descendant_count() for c in page.thread.comments.all()] 

What I have tried

I am not sure how to achieve this with querysets and annotations. I know each mptt model also get a treed_id, so I am guessing I would need to build a more complex subquery.

To get the number of root comments only (not including nested), I could do it like this:

pages = Page.objects.all().annotate(num_comments=models.Count("thread__comments")) num_root_comments = pages[0].num_comments 

Once again, the goal is to get all comments, including nested:

# Non working code - this kind of  pseudo queryset code of what I am trying:  all_page_comments = Comment.objects.filter(tree_id__in= (Page.thread__comments__tree_id)) Page.objects.all().annotate(num_comments=Count(Subquery(all_page_comments)) 

Thanks in advance for any help provided.

Solution

Got a working solution thanks to @andrey's answer below. Not sure it's optimal but seems to return the correct values in a single query.

threads = CommentThread.objects.filter(         id=models.OuterRef("thread")     ).annotate(         comment_count=models.Sum(             Floor((models.F("comments__rght") - models.F("comments__lft") - 1) / 2)         )     )  qs_pages_with_comment_count = (     Page.objects     .annotate(         comment_count=models.Subquery(             threads.values("comment_count")[:1], output_field=models.IntegerField()         )     )     # Count from subquery included count of descendents of      # each "root" comment but not the root comment itself     # so we add  number of root comments per thread on top     .annotate(         comment_count=models.F("comment_count")         + models.Count("thread__comments", distinct=True)     ) ) 
</div
           

回答リスト

2
 
vote
vote
ベストアンサー
 
<事前> <コード> queryset.annotate( descendants_count=Floor((F('rght') - F('lft') - 1) / 2) ).values( 'descendants_count' ).aggregate( total_count=Count('descendants_count') )

教えてください

最初に、<コード> get_descendant_count の現在の方法は既存のデータを操作するだけなので、QuerySetで使用できます。

<事前> <コード> def get_descendant_count(self): """ Returns the number of descendants this model instance has. """ if self._mpttfield('right') is None: # node not saved yet return 0 else: return (self._mpttfield('right') - self._mpttfield('left') - 1) // 2

現在のMPTTモデルの方法です。 QuerySetでは、すべてのインスタンスがすでに保存されていることが確実ですので、それをスキップします。

次のステップは、数学の操作をDB式に変換することです。 Django 3.0が登場<コード> Floor 式。しかし、私たちは1.7でもそれを使うことができます(私のように)

<事前> <コード> from django.db.models.lookups import Transform class Floor(Transform): function = 'FLOOR' lookup_name = 'floor'

ハードコード rght, lft の代わりに self._mpttfield('right') Analogを使用したい場合は、これを Manager メソッド

として使用することができます。

テストしましょう。私は子孫

のトップ要素を持っています <事前> <コード> $("table.rctable"); 0
 
queryset.annotate(     descendants_count=Floor((F('rght') - F('lft') - 1) / 2) ).values(     'descendants_count' ).aggregate(     total_count=Count('descendants_count') ) 

Let me explain

First, current method of get_descendant_count just operates existing data, so we can use it in Queryset.

def get_descendant_count(self):     """     Returns the number of descendants this model instance has.     """     if self._mpttfield('right') is None:         # node not saved yet         return 0     else:         return (self._mpttfield('right') - self._mpttfield('left') - 1) // 2 

This is the current mptt models' method. In queryset we are sure that all of instances is already saved so we'll skip that.

Next step is to transform math operations into db expressions. In Django 3.0 appeared Floor expression. But we can use it even in 1.7 (as I do)

from django.db.models.lookups import Transform  class Floor(Transform):      function = 'FLOOR'      lookup_name = 'floor' 

If you want you can refactor this to use self._mpttfield('right') analog instead of hardcoded rght, lftand make this as Manager method

Let's test. I have top element with descendants

In [1]: m = MenuItem.objects.get(id=settings.TOP_MENU_ID)  In [2]: m.get_descendant_count() Out[2]: 226  In [3]: n = m.get_descendants()  In [4]: n.annotate(descendants_count=Floor((F('rght') - F('lft') - 1) / 2)).values('descendants_count').aggregate(total_count=Count('descendants_count')) Out[4]: {'total_count': 226} 
</div
 
 
     
     

関連する質問

36  Django - 新しいオブジェクトを保存するときにself.idを取得する方法は?  ( Django how to get self id when saving a new object ) 
私は私のモデルの1つに問題があります。イメージをアップロードしています.ID(データベーステーブルにPK)を保存したいが、どのPoint Djangoが self.id にアクセスできるかを知る必要があります。 model.py <事前> <コード> ...

4  TastypieのDjango-MPTTの木の直列化  ( Serializing django mptt trees in tastypie ) 
popState2 ?の popState1 のツリーをシリアル化する方法 popState3 の<コード> popState4 を使用します。私はさまざまなテイスピーフックで申請しようとしましたが、それはエラーをスローしました。 ...

9  Djangoの管理ページ内の階層データ  ( Hierarchical data in admin pages in django ) 
Djangoプロジェクトでは、Models.pyのように定義されたMPTTを使用して階層モデルを持っています。 <事前> <コード> class Structure(MPTTModel): name = models.CharField(max_le...

3  Django-MPTTエラーadmin / mptt_change_list.htmlテンプレートが見つかりません  ( Django mptt error cant find admin mptt change list html template ) 
i VEこのトピックと似た問題を得ました django-mptt、 < / P> installation_apps に 'MPTT'を追加しました <事前> <コード> INSTALLED_APPS = ( 'django.contrib.ad...

0  Django、複数のフォームフィールドを単一のモデル属性に保存する方法  ( Django how to save multiple form fields into a single model attribute ) 
私はユーザーと多数の多数関連するジャンル(3レベル階層)と呼ばれるツリー構造を持っています。ユーザーはジャンルリーフノードを選択します。 以下はMy Model.py です <事前> <コード> class Genre(MPTTModel): name...

0  ユーザーが折りたたまれたときにネストしたセット全体を取得しないようにするためのSQLクエリ  ( Sql query to avoid fetching the entire nested set when parts of it are collapsed ) 
私はDjango-MPTTとcontrib.adminをadminのフラットリストよりも友好的なリストを提供することによって、一緒に貢献しています。木は大きくなるはずだ(そうでなければ私はネストセットを使用しないで)、ユーザーはその一部を拡大し折りたたることが...

1  Django-MPTTで次のようにしての間違いは何ですか?  ( Whats my mistake in doing the following in django mptt ) 
カテゴリツリーを持つ、カテゴリに関連する項目エントリがあります。だから私のモデルファイルです。 <事前> <コード> from django.db import models import mptt class Category(models.Model):...

15  MPTT QuerySetの先祖のクエリセットを取得するための効率的な機能  ( Efficient function to retrieve a queryset of ancestors of an mptt queryset ) 
MPTT QuerySetのすべての先祖を取得するための効率的なアルゴリズムを持っていますか?私がこれまでのと考えることができる最高のものはこのようなものです: <事前> <コード> def qs_ancestors(queryset): if is...

0  Django-MPTTを使用してDjango Adminで多対多のオブジェクトを作成できません  ( Cant create many to many object in django admin using django mptt ) 
Django Adminでオブジェクトを作成できません。それはエラーを発生させる: <事前> <コード> if (args.length > 0) { try { patience = Long.par...

0  Django-MPTT:オンラインエントリを持っているすべてのカテゴリをフィルタリングする  ( Django mptt filter all categories having an online entry ) 
これを渡しています Django Blog App ダジャンゴMPTTへのカテゴリシステム。 問題がある問題は、 _get_online_category メソッドについてです。 この方法では、<コード> Entry を持つカテゴリのみを取得できます。 <事前...




© 2022 cndgn.com All Rights Reserved. Q&Aハウス 全著作権所有