5

私は単純なブログシステムに取り組んでいます。これが私のmodels.pyファイルです:

from django.contrib.auth.models import User
from django.db import models


class Comment(models.Model):
    user = models.ForeignKey(User)
    post = models.ForeignKey('Post')
    content = models.TextField()
    approved = models.NullBooleanField()

    class Meta:
        ordering = ('-id',)

    def __unicode__(self):
        return u'Comment by %s' % self.user


class Post(models.Model):
    user = models.ForeignKey(User)
    title = models.CharField(max_length=200)
    slug = models.CharField(max_length=50)
    content = models.TextField()

    class Meta:
        ordering = ('title',)

    def __unicode__(self):
        return self.title

これは、私が名前を付けたフィクスチャのテストデータですtestdata.json(「some_author」ユーザーはスーパーユーザーで、パスワードは「Stack Overflow」です):

[
{
  "pk": 1, 
  "model": "auth.user", 
  "fields": {
    "username": "some_author", 
    "first_name": "Some", 
    "last_name": "Author", 
    "is_active": true, 
    "is_superuser": true, 
    "is_staff": true, 
    "last_login": "2014-07-02T20:18:49Z", 
    "groups": [], 
    "user_permissions": [], 
    "password": "pbkdf2_sha256$12000$PTl1hfgcIGZy$/0w1jNMBuKi9zk11JXhoS5WrbMBUgMDkZAhEvNEelbs=", 
    "email": "some_author@example.com", 
    "date_joined": "2014-07-02T20:18:29Z"
  }
},
{
  "pk": 2, 
  "model": "auth.user", 
  "fields": {
    "username": "some_reader", 
    "first_name": "Some", 
    "last_name": "Reader", 
    "is_active": true, 
    "is_superuser": false, 
    "is_staff": false, 
    "last_login": "2014-07-02T20:21:10Z", 
    "groups": [], 
    "user_permissions": [], 
    "password": "pbkdf2_sha256$12000$CtTGfFeOaRhd$oVR6zFSpK2qg1AZ4fgdBG/wt6Sr56dHsEIxFO99mHC8=", 
    "email": "some_reader@example.com", 
    "date_joined": "2014-07-02T20:21:10Z"
  }
},
{
  "pk": 3, 
  "model": "auth.user", 
  "fields": {
    "username": "another_reader", 
    "first_name": "Another", 
    "last_name": "Reader", 
    "is_active": true, 
    "is_superuser": false, 
    "is_staff": false, 
    "last_login": "2014-07-02T20:21:34Z", 
    "groups": [], 
    "user_permissions": [], 
    "password": "pbkdf2_sha256$12000$ZPnmV7fVeie3$08H2vv3A8Py4E92+uVAIiEaeg8CAL5deTyNAZj1YJMs=", 
    "email": "another_reader@example.com", 
    "date_joined": "2014-07-02T20:21:34Z"
  }
},
{
  "pk": 1, 
  "model": "blog.comment", 
  "fields": {
    "content": "Comment 1 of 1 on post 1: approved", 
    "post": 1, 
    "user": 2, 
    "approved": true
  }
},
{
  "pk": 2, 
  "model": "blog.comment", 
  "fields": {
    "content": "Comment 1 of 1 on post 2: not approved", 
    "post": 2, 
    "user": 2, 
    "approved": false
  }
},
{
  "pk": 3, 
  "model": "blog.comment", 
  "fields": {
    "content": "Comment 1 of 2 on post 3: approved", 
    "post": 3, 
    "user": 2, 
    "approved": true
  }
},
{
  "pk": 4, 
  "model": "blog.comment", 
  "fields": {
    "content": "Comment 2 of 2 on post 3: not approved", 
    "post": 3, 
    "user": 2, 
    "approved": false
  }
},
{
  "pk": 5, 
  "model": "blog.comment", 
  "fields": {
    "content": "Comment 1 of 2 on post 4: not approved", 
    "post": 4, 
    "user": 2, 
    "approved": false
  }
},
{
  "pk": 6, 
  "model": "blog.comment", 
  "fields": {
    "content": "Comment 2 of 2 on post 4: approved", 
    "post": 4, 
    "user": 2, 
    "approved": true
  }
},
{
  "pk": 7, 
  "model": "blog.comment", 
  "fields": {
    "content": "Comment 1 of 2 on post 5: approved", 
    "post": 5, 
    "user": 2, 
    "approved": true
  }
},
{
  "pk": 8, 
  "model": "blog.comment", 
  "fields": {
    "content": "Comment 2 of 2 on post 5: approved", 
    "post": 5, 
    "user": 2, 
    "approved": true
  }
},
{
  "pk": 9, 
  "model": "blog.comment", 
  "fields": {
    "content": "Comment 1 of 2 on post 6: not approved", 
    "post": 6, 
    "user": 2, 
    "approved": false
  }
},
{
  "pk": 10, 
  "model": "blog.comment", 
  "fields": {
    "content": "Comment 2 of 2 on post 6: not approved", 
    "post": 6, 
    "user": 2, 
    "approved": false
  }
},
{
  "pk": 11, 
  "model": "blog.comment", 
  "fields": {
    "content": "Comment 1 of 1 on post 7: approved", 
    "post": 7, 
    "user": 3, 
    "approved": true
  }
},
{
  "pk": 1, 
  "model": "blog.post", 
  "fields": {
    "content": "First post", 
    "slug": "post-1", 
    "user": 1, 
    "title": "Post 1"
  }
},
{
  "pk": 2, 
  "model": "blog.post", 
  "fields": {
    "content": "Second post", 
    "slug": "post-2", 
    "user": 1, 
    "title": "Post 2"
  }
},
{
  "pk": 3, 
  "model": "blog.post", 
  "fields": {
    "content": "Third post", 
    "slug": "post-3", 
    "user": 1, 
    "title": "Post 3"
  }
},
{
  "pk": 4, 
  "model": "blog.post", 
  "fields": {
    "content": "Fourth post", 
    "slug": "post-4", 
    "user": 1, 
    "title": "Post 4"
  }
},
{
  "pk": 5, 
  "model": "blog.post", 
  "fields": {
    "content": "Fifth post", 
    "slug": "post-5", 
    "user": 1, 
    "title": "Post 5"
  }
},
{
  "pk": 6, 
  "model": "blog.post", 
  "fields": {
    "content": "Sixth post", 
    "slug": "post-6", 
    "user": 1, 
    "title": "Post 6"
  }
},
{
  "pk": 7, 
  "model": "blog.post", 
  "fields": {
    "content": "Seventh post", 
    "slug": "post-7", 
    "user": 1, 
    "title": "Post 7"
  }
},
{
  "pk": 8, 
  "model": "blog.post", 
  "fields": {
    "content": "Eighth post", 
    "slug": "post-8", 
    "user": 1, 
    "title": "Post 8"
  }
}
]

次の 2 つの条件の両方を満たすすべてのブログ投稿と、各ブログ投稿の最新のコメントをデータベースにクエリしようとしています。

  1. コメントは「一部の読者」によって作成されました (user_id = 2)
  2. コメントは承認されました

上記の 2 つの条件を満たすコメントがない場合でも、すべてのブログ投稿を返すクエリが必要です。上記の 2 つの条件を満たすコメントがないブログ投稿の場合、返されるコメント列はNULL. 私はこれを生のSQLで動作させています:

for p in Post.objects.raw(
    '''
    SELECT blog_post.id,
           blog_post.title,
           blog_comment.content
    FROM   blog_post
           LEFT OUTER JOIN (SELECT post_id,
                                   MAX(id) AS latest
                            FROM   blog_comment
                            WHERE  user_id = 2
                                   AND approved = 1
                            GROUP  BY post_id) AS x
                        ON x.post_id = blog_post.id
           LEFT OUTER JOIN blog_comment
                        ON blog_comment.post_id = x.post_id
                           AND blog_comment.id = x.latest
    ORDER  BY blog_post.id;
    '''
):
    print '%s: %s' % (
        p.title,
        p.content,
    )

上記のコードはこれを出力します(これが私が欲しいものです):

Post 1: Comment 1 of 1 on post 1: approved
Post 2: None
Post 3: Comment 1 of 2 on post 3: approved
Post 4: Comment 2 of 2 on post 4: approved
Post 5: Comment 2 of 2 on post 5: approved
Post 6: None
Post 7: None
Post 8: None

私の質問は次のとおりです。これと同じことを (効率的に) 実行できますが、生の SQL に頼ることはありませんか? 私は可能な限り生のクエリを避けたいと思っています。

4

2 に答える 2

3

django orm パラダイム内の生の SQL なしでは実行できません。しかし、db への 2 つのクエリでそれを行うことができます。

from django.db.models import Max
posts = Post.objects.annotate(Max('comment_set__id'))
comments_cache = Comment.objects.filter(id__in= posts.values('id', flat=True))
comments_dict = dict([(item.id, item) for item in comments_cache])
for item in posts:
     print post, comments_dict[item.id]

私はしばしば複雑なクエリを作成しますが、キャッシュオブジェクト内のいくつかのクエリで必要なすべてのデータを取得し、必要に応じてグループ化するよりも良い方法を見つけることができません。

次のようなコードは使用しないでください。

#get_comment: return self.comment_set.filter(user=user, approved=True).latest('id')
for post in Post.objects.all():
    print post.get_comment(request.user)

データベースへの len(posts) SQL クエリを生成します。それは悪い習慣です。

于 2014-07-07T08:21:51.017 に答える
2

すべての投稿が必要ですが、コメントは指定された基準に一致するものでなければなりNone. For that you can add a method/property inません。

ORMでこれを行う良い方法があるとは思わないでください。

モデルに次のメソッドを追加します。

class Post(models.Model):
    ...
    #your code

    def get_comment(self, user):
        comment = None
        try:
            comment = self.comment_set.filter(user=user, approved=True).latest('id')
        except Comment.DoesNotExist:
            comment = None #no comment matching criteria

        return comment

ビューまたは他のコードから、次のことができます

for post in Post.objects.all():
    print post.get_comment(request.user)
于 2014-07-03T04:40:46.887 に答える