我有以下模型。
class Document(models.Model):
allowed_groups = models.ManyToManyField(Group, related_name='allowed_documents')
class Person(models.Model):
permission_groups = models.ManyToManyField(Group, related_name='people')
class Group(models.Model):
id = models.BigIntegerField()
我想找到Person可以访问的所有文档,条件是他们必须是所有允许组的成员。
我想要这个:案例
- 带人员的文档(allowed_groups=1,2,7((permission_groups=1,2,6,7,11,15(->匹配
- 具有人员(permission_groups=1,7(的文档(allowed_groups=1,2,7(->NO_MATCH
- 具有人员(permission_groups=1,2(的文档(allowed_groups=1,2,7(->NO_MATCH
- 具有人员(permission_groups=2(的文档(allowed_groups=1,2,7(->NO_MATCH
- 具有人员(permission_groups=8(的文档(allowed_groups=1,2,7(->NO_MATCH
- 具有人员(permission_groups=1,2,7(的文档(allowed_groups=1,2,7(->匹配
如果我这样做:
person = Person.objects.get(pk=1)
Document.objects.filter(allowed_groups__in=person.permission_groups.all())
除了8(不是我想要的(之外,我会匹配上面所有的情况
关于堆栈溢出,有许多问题是关于精确匹配的,即只在情况6下匹配,而在情况1下不匹配。(也不是我想要的(
所以我的问题是如何使用django来实现这一点?我已经考虑过使用SQL,但肯定有一种方法可以使用Django ORM。这似乎不是什么疯狂的要求。
注意:我还有一些其他条件(其他类型的组和文档访问级别(,我已经用链式过滤器/Q对象将其转化为一个复杂的表达式,但除了这一点,我已经解决了所有问题。
此外:我在表述问题标题时遇到了一些困难,这可能是我找不到答案的原因。它不需要是查询集,它可以是pk列表或其他方法。
基于此答案
解决方案:
from django.db.models import Count, Q
person = Person.objects.get(pk=1)
permission_groups = set(person.permission_groups.all())
Document.objects.annotate(
allowed_groups_count=Count('allowed_groups', filter=Q(allowed_groups__in=permission_groups))
).filter(
allowed_groups_count__gt=0
)
然后它的查询将是这样的:
SELECT
document.id,
COUNT(document_allowed_groups.group_id) FILTER (
WHERE
document_allowed_groups.group_id IN (1, 2, 6, 7, 11, 15)
) AS allowed_groups_count
FROM
document
LEFT OUTER JOIN document_allowed_groups ON (
document.id = document_allowed_groups.document_id
)
GROUP BY
document.id
HAVING
COUNT(document_allowed_groups.group_id) FILTER (
WHERE
(
document_allowed_groups.group_id IN (1, 2, 6, 7, 11, 15)
)
) > 0
使用了numpy库中的np.isin函数。当比较两个数组时,它返回布尔数组。此处描述。
我对对象应用了values_list和flat=True,将它们提取到一个列表中,以便在numpy中进行比较。我做了一个列表生成器aaa(列表生成器比循环快很多倍(,在该生成器中,将文档与所选人员进行比较,如果所有文档值都匹配,则all((返回True,然后将I.pk写入aaa列表。接下来,文档数据通过该列表进行过滤,并传递给要在模板中显示的词典。
将bboard替换为放置模板的文件夹的名称我有这个:模板/bboard,它在应用程序文件夹中。
views.py
import numpy as np
def info(request):
person = Person.objects.get(pk=1).permission_groups.all().values_list('id', flat=True)
print(person)
def my_func(x):
document = Document.objects.get(pk=x).allowed_groups.all().values_list('id', flat=True)
return document
aaa = [i.pk for i in Document.objects.all() if np.isin(my_func(i.pk), person).all()]
document = Document.objects.filter(pk__in=aaa)
return render(request, 'bboard/templ.html', {'context': document})
模板
{% for a in context %}
<p>{{ a.id }}</p>
{% endfor %}
更新26.10.2022
import numpy as np
def info(request):
person = Person.objects.get(pk=1).permission_groups.all().values_list('id', flat=True)
def my_func(i):
document = i.allowed_groups.all().values_list('id', flat=True)
return document
aaa = [i.pk for i in Document.objects.prefetch_related('allowed_groups').all() if np.isin(my_func(i), person).all()]
document = Document.objects.filter(pk__in=aaa)
return render(request, 'bboard/templ.html', {'context': document})