Django 用Count注释不起作用

Django 用Count注释不起作用,django,Django,我正在通过构建一个简单的RPG来尝试Django。它有装甲升级。装甲有不同的类别(例如头部和身体)。每个类别都有许多盔甲。例如,“头”类别可能有“龙盔”、“鸭盔”、“针盔”等 为了让用户看到a类别中可用的任何盔甲,必须首先授予他们访问该类别中至少一件盔甲的权限。在这一点上,他们可以查看该类别中的所有装甲,包括他们还不能购买的装甲 问题 我试图高效地查询数据库中某个类别的所有护甲,同时记录用户已被授予访问权限的护甲。我有点工作,但不是完全工作 相关代码 型号.py from django.cont

我正在通过构建一个简单的RPG来尝试Django。它有装甲升级。装甲有不同的类别(例如头部和身体)。每个类别都有许多盔甲。例如,“头”类别可能有“龙盔”、“鸭盔”、“针盔”等

为了让用户看到a类别中可用的任何盔甲,必须首先授予他们访问该类别中至少一件盔甲的权限。在这一点上,他们可以查看该类别中的所有装甲,包括他们还不能购买的装甲

问题 我试图高效地查询数据库中某个类别的所有护甲,同时记录用户已被授予访问权限的护甲。我有点工作,但不是完全工作

相关代码 型号.py

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


class Armor(models.Model):
    armor_category = models.ForeignKey('ArmorCategory')
    name = models.CharField(max_length=100)
    profile = models.ManyToManyField('Profile', through='ProfileArmor')

    def __unicode__(self):
        return self.name


class ArmorCategory(models.Model):
    name = models.CharField(max_length=100)
    slug = models.SlugField(blank=True)

    class Meta:
        verbose_name_plural = 'Armor categories'

    def __unicode__(self):
        return self.name


class Profile(models.Model):
    user = models.OneToOneField(User)
    dob = models.DateField('Date of Birth')

    def __unicode__(self):
        return self.user.get_full_name()


class ProfileArmor(models.Model):
    profile = models.ForeignKey(Profile)
    armor = models.ForeignKey(Armor)
    date_created = models.DateTimeField(auto_now_add=True)

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

    def __unicode__(self):
        return '%s: %s' % (self.profile.user.get_full_name(), self.armor.name)
from django.conf.urls import patterns, url
from core import views

urlpatterns = patterns('',
    url(r'^upgrades/(?P<armor_category_slug>[-\w]+)/$', views.Upgrades.as_view(), name='upgrades'),
)
url.py

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


class Armor(models.Model):
    armor_category = models.ForeignKey('ArmorCategory')
    name = models.CharField(max_length=100)
    profile = models.ManyToManyField('Profile', through='ProfileArmor')

    def __unicode__(self):
        return self.name


class ArmorCategory(models.Model):
    name = models.CharField(max_length=100)
    slug = models.SlugField(blank=True)

    class Meta:
        verbose_name_plural = 'Armor categories'

    def __unicode__(self):
        return self.name


class Profile(models.Model):
    user = models.OneToOneField(User)
    dob = models.DateField('Date of Birth')

    def __unicode__(self):
        return self.user.get_full_name()


class ProfileArmor(models.Model):
    profile = models.ForeignKey(Profile)
    armor = models.ForeignKey(Armor)
    date_created = models.DateTimeField(auto_now_add=True)

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

    def __unicode__(self):
        return '%s: %s' % (self.profile.user.get_full_name(), self.armor.name)
from django.conf.urls import patterns, url
from core import views

urlpatterns = patterns('',
    url(r'^upgrades/(?P<armor_category_slug>[-\w]+)/$', views.Upgrades.as_view(), name='upgrades'),
)
问题的更多细节 我创建了“头部”类别,并将问题第一段中指出的三件盔甲交给它。我按照上面列出的顺序创建了盔甲。然后我创建了两个用户配置文件

我给了第一个用户配置文件访问“鸭盔”装甲。然后,我使用第一个用户配置文件访问了/upgrades/head/,并从
for
循环中获得了以下输出:

Dragon Helm: 0
Duck Helm: 1
Needle Helm: 0
这是预期的产出。接下来,我让第二个用户配置文件访问“龙盔”装甲。当我使用第二个用户配置文件访问同一URL时,我得到以下输出:

Dragon Helm: 1
Needle Helm: 0
Duck Helm: 1
Needle Helm: 0
为什么没有列出“鸭盔”装甲?我决定再次使用第一个用户配置文件返回相同的URL,以确保它仍然有效。当我这样做时,我得到了以下输出:

Dragon Helm: 1
Needle Helm: 0
Duck Helm: 1
Needle Helm: 0
现在“龙盔”盔甲不见了

有什么想法吗?

1。快速解释 当您在Django查询集中针对
None
测试多对多关系时,就像您在这里所做的那样:

Q(profilearmor__profile=None)
匹配多对多关系中没有对应行的行。那么你的问题呢

self.armor_category.armor_set.filter(
    Q(profilearmor__profile=self.request.user.profile) |
    Q(profilearmor__profile=None))
匹配
self.request.user
有权访问或无人访问的装甲项目。这就是为什么您对第二个用户的查询未能为Duck Helm返回一行的原因:因为有人(即第一个用户)可以访问它

2.该怎么办 要运行的查询在SQL中如下所示:

选择'myapp\u armor`.'id`、'myapp\u armor`.'armor\u category\u id`、'myapp\u armor`.'name`,
将(`myapp\u profilearmor`.`id`)计数为`profile\u armor\u计数`
来自“myapp\u armor”`
左外连接'myapp_`
在'myapp\u armor'。'id`='myapp\u profilearmor`.'armor\u id`
和'myapp\u profilearmor`.'profile\u id`=%s
其中`myapp\u armor`.`armor\u category\u id`=%s
按“myapp\u armor”.“id”.“myapp\u armor”.“armor\u category\u id”.“myapp\u armor”.“名称”分组`
不幸的是,Django的对象关系映射系统似乎没有提供一种表达此查询的方法。但是,您总是可以绕过ORM并发出一个命令。像这样:

sql = '''
    SELECT `myapp_armor`.`id`, `myapp_armor`.`armor_category_id`, `myapp_armor`.`name`,
           COUNT(`myapp_profilearmor`.`id`) AS `profile_armor_count`
    FROM `myapp_armor`
    LEFT OUTER JOIN `myapp_profilearmor`
                 ON `myapp_armor`.`id` = `myapp_profilearmor`.`armor_id`
                    AND `myapp_profilearmor`.`profile_id` = %s
    WHERE `myapp_armor`.`armor_category_id` = %s
    GROUP BY `myapp_armor`.`id`, `myapp_armor`.`armor_category_id`, `myapp_armor`.`name`
'''
armor = Armor.objects.raw(sql, [self.request.user.profile.id, self.armor_category.id])
for armor_item in armor:
    print('{:14}{}'.format(armor_item.name, armor_item.profile_armor_count))
例如:

>>> helmets = ArmorCategory.objects.get(id=1)
>>> profile = Profile.objects.get(id=1)
>>> armor = Armor.objects.raw(sql, [profile.id, helmets.id])
>>> for armor_item in armor:
...     print('{:14}{}'.format(armor_item.name, armor_item.profile_armor_count))
... 
Dragon Helm   0
Duck Helm     1
Needle Helm   0
3.你怎么能自己解决这个问题 下面是令人不快的Django查询:

armor = self.armor_category.armor_set.filter(
    Q(profilearmor__profile=self.request.user.profile) |
    Q(profilearmor__profile=None)
).annotate(profile_armor_count=Count('profilearmor__id'))
当您不理解查询产生错误结果的原因时,总是值得查看实际的SQL,您可以通过获取queryset的
query
属性并将其转换为字符串来实现:

>>来自django.db.models import Q
>>>helmets=ArmorCategory.objects.get(name='helmets')
>>>profile=profile.objects.get(id=1)
>>>打印(头盔、盔甲套件、过滤器(Q(外形盔甲外形=外形)|
…Q(profilearmor\uuuu profile=None)
注释(profile\u armor\u count=count('profilearmor\u id'))。查询)
选择“myapp\u armor”。“id”。“myapp\u armor”。“armor\u category\u id”。“myapp\u armor”。“name”,
将(`myapp\u profilearmor`.`id`)计数为`profile\u armor\u计数`
来自“myapp\u armor”`
左外连接'myapp_`
ON(`myapp\u armor`.`id`=`myapp\u profilearmor`.`armor\u id`)
左外连接'myapp_配置文件`
ON(`myapp\u profilearmor`.`profile\u id`=`myapp\u profile`.`id`)
其中(`myapp\u armor`.`armor\u category\u id`=1
和(`myapp\u profilearmor`.`profile\u id`=1
或'myapp_profile'。'id'为空)
按“myapp\u armor”.“id”.“myapp\u armor”.“armor\u category\u id”.“myapp\u armor”.“名称”分组`
按空排序
这是什么意思?如果您完全了解SQL联接,可以跳到第5节。否则,请继续阅读

4.SQL联接简介 我相信您知道,a由这些表中的行的组合组成(取决于您在查询中指定的某些条件)。例如,如果您有两类护甲和六项护甲:

mysql>从myapp\u armorcategory中选择*;
+----+---------+---------+
|id | name | slug|
+----+---------+---------+
|1 |头盔|头盔|
|2套西装|
+----+---------+---------+
一组2行(0.00秒)
mysql>从myapp_armor中选择*;
+----+-------------------+-------------+
|id |装甲|类别| id |名称|
+----+-------------------+-------------+
|1 | 1 |龙舵|
|2 | 1 |鸭盔|
|3 | 1 |针形头盔|
|4 | 2 |尖头西装|
|5 | 2 |花套装|
|6 | 2 |战斗服|
+----+-------------------+-------------+
一组6行(0.00秒)
然后,这两个表的
JOIN
将包含第一个表的2行和第二个表的6行的所有12个组合:

mysql>从myapp\u armor类别中选择*加入myapp\u armor;
+----+---------+---------+----+-------------------+-------------+
|id |名称|蛞蝓| id |装甲|类别| id |名称|
+----+---------+---------+----+-------------------+-------------+
|1 |头盔|头盔| 1 | 1 |龙盔|
|2 |套装|套装| 1 | 1 |龙盔|
|1 |头盔|头盔