对集合使用优化的 exists()

Using optimized exists() for a set

我想添加一个 属性 到我的 Person class 那个 returns 一个布尔值是否这个人是否有某个 AntecedentTag .

有关信息,我的简化模型如下:

class Person(models.Model):
    antecedent_tags = models.ManyToManyField(AntecedentTag, verbose_name=u"Tags", through='AntecedentInfo')

class AntecedentInfo(models.Model):
    antecedent_tag = models.ForeignKey(AntecedentTag)
    person = models.ForeignKey(Person)

class AntecedentTag(models.Model):
    name = models.CharField(max_length="64")

在我的个人 class 中,我正在考虑添加一个 @property,如下所示。它可以工作,但是如果我必须在比方说 250 个人的列表中使用它,恐怕在性能方面这不会是最好的。

@property
def is_diabetic(self):
    try:
        if self.antecedentinfo_set.get(antecedent_tag__name="Diabetes"):
            return True
    except:
        return False

问题:有什么方法可以在set上使用优化的exists()

我尝试了以下但没有成功:

>>> p.antecedentinfo_set.get(antecedent_tag__name="Diabetes")
<AntecedentInfo: Diabetes: >
>>> p.antecedentinfo_set.get(antecedent_tag__name="Diabetes").exists()
Traceback (most recent call last):
  File "<input>", line 1, in <module>
AttributeError: 'AntecedentInfo' object has no attribute 'exists'

我想到了count(),但如果这个人没有得病,那是行不通的:

>>> p.antecedentinfo_set.get(antecedent_tag__name="Diabetes")
Traceback (most recent call last):
  File "<input>", line 1, in <module>
  File "xx\lib\site-packages\django\db\models\manager.py", line 127, in manager_method
    return getattr(self.get_queryset(), name)(*args, **kwargs)
  File "xx\lib\site-packages\django\db\models\query.py", line 334, in get
    self.model._meta.object_name
DoesNotExist: AntecedentInfo matching query does not exist.

exists() 是查询集的方法,而不是模型实例的方法。所以你需要调用 filter() 而不是 get().

为什么要使用antecedentinfo_set中间关系?使用 M2M 字段:

@property
def is_diabetic(self):
    return self.antecedent_tags.filter(name="Diabetes").exists()

In my Person class, I was thinking of adding a @property like the below. It works, but if I have to use this on a list of let's say 250 individual persons, I'm afraid performance wise this is not going to be the best.

我可以想到两种方法,可以让您免于为一组 250 人数 250 次。

1。使用 .extra

我假设您还没有任何自定义 Managers/QuerySets,为了代码的最大 DRY-ness,最好将下一行代码放入自定义 Manager/QuerySet (将它写到视图中仍然可以,但是如果你想在其他地方重用这个功能,你将不得不重复它。)

因此我们将创建一个自定义查询集 class。如果您不熟悉该主题,可以在 Django Managers

阅读更多内容
class PersonQuerySet(models.QuerySet):

    #this adds a new field "has_"+disease 
    #for every object in our queryset
    def annotate_disease_existence(self, disease):
        has_disease = 'has_'+disease
        return self.extra(select={
            has_disease: "SELECT count(*) \
                FROM someapp_antecedentinfo \
                LEFT JOIN someapp_antecedenttag \
                    ON someapp_antecedenttag.id = someapp_antecedentinfo.antecedent_tag_id
                WHERE someapp_antecedentinfo.person_id = someapp_person.id \
                    AND someapp_antecedenttag.name = '%s" % disease
        })

#now lets use it
class Person(models.Model):
    #...    
    objects = PersonQuerySet.as_manager()

如何使用?

我们编写的这个自定义方法使我们能够针对每个 Person QuerySet 调用它。所以我们可以这样做:

def my_special_view(request):
    my250persons = Person.objects.annotate_disease_existence("Diabetes")[:250]

    for person in my250persons:
        print "{} has Diabetes? {}".format(
            person, 
            'Yes' if person.has_diabetes else 'No'
        )

2。使用 .prefetch_related

我希望您已经看到我们在第一个示例中所做的事情 - 我们使用 subqueryPerson 中添加了一些额外的信息,如果他有病的话。但是当我们真正想要使用整个 Disease (在我们的例子中是 Tag 或 Info) 对象时,问题就来了。然后我们可以将 prefetch_related 与自定义 Prefetch 对象一起使用。

class PersonQuerySet(models.QuerySet):

    def prefetch_disease(self, disease):
        disease_set = disease.lower() + '_set'
        return self.prefetch_related(Prefetch(
            #depending on our needs
            'antecedent_tags', # OR 'antecedentinfo_set'
            queryset = AntecedentTag.objects.filter(name=disease),
            #OR queryset = AntecedentInfo.objects.filter(antecedent_tag__name=disease)
            to_attr = disease_set
        ))

如何使用?

使用这种方法时,我们会向 queryset 的每个 Person 添加一个新的对象列表。所以我们可以像这样访问它们:

def my_very_special_view(request):
    my250persons = Person.objects.prefetch_disease("Diabetes")[:250]

    for person in my250persons:
        print "{} has Diabetes? {}! More Info: {}".format(
            person, 
            'Yes' if person.diabetes_set else 'No',
            person.diabetes_set[0]
        )

好的,但它如何与单个对象一起使用?

def my_single_person_view(request, id):
    person = get_object_or_404(Person.objects.prefetch_disease("Diabetes"),pk=id)
    #or
    person = get_object_or_404(Person.objects.annotate_disease_existence("Diabetes"),pk=id)