【问题标题】:Django filter where every item in an object's many to many list is included in provided querysetDjango 过滤器,其中对象的多对多列表中的每个项目都包含在提供的查询集中
【发布时间】:2022-12-21 03:38:23
【问题描述】:

我有以下型号。

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 可以访问的所有文档,条件是他们必须是所有允许组的成员。

我要这个: 案件

  1. 文档(allowed_groups=1,2,7)与人员(permission_groups=1,2,6,7,11,15)-> 匹配
  2. 文档(allowed_groups=1,2,7)与人员(permission_groups=1,7)-> NO_MATCH
  3. 文档 (allowed_groups=1,2,7) 与人员 (permission_groups=1,2) -> NO_MATCH
  4. 文档 (allowed_groups=1,2,7) with Person(permission_groups=2) -> NO_MATCH
  5. 文档 (allowed_groups=1,2,7) with Person(permission_groups=8) -> NO_MATCH
  6. 文档(allowed_groups=1,2,7)与人员(permission_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 对象变成了一个复杂的表达式,但除了这一点之外,我已经解决了所有问题。

    另外:我在表述问题标题时遇到了一些麻烦,这可能就是我找不到答案的原因。它不需要是查询集,它可以只是 pks 列表或其他一些方法。

【问题讨论】:

    标签: django many-to-many django-queryset django-orm django-filter


    【解决方案1】:

    使用了 numpy 库中的 np.isin 函数。比较两个数组时返回布尔数组。说明here.

    我将 values_list 和 flat=True 应用于对象以将它们提取到列表中以便在 numpy 中进行比较。我做了一个list generator aaa(list generator is many times faster than a loop),其中document与被选中的人比较,如果所有document值匹配,则all()返回True,然后i.pk被写入aaa列表.接下来,文档数据被这个列表过滤并传递给一个字典以显示在模板中。

    将 bboard 替换为放置模板的文件夹的名称。我有这个:应用程序文件夹中的模板/bboard。

    视图.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})
    

    【讨论】:

    • 谢谢!我想有很多方法可以做到这一点,如果它涉及首先将所有内容从数据库中拉出然后遍历每一行。我想知道是否可以在过滤器/sql 级别完成。这只会对数据库造成一次打击吗?还是 Document.objects.get(pk=x).allowed_groups.all().values_list('id', flat=True) 会进行另一个查询?
    • 我认为 Document.objects.prefetch_related('allowed_groups').all() 会解决这个问题
    • 据我所知,在访问特定记录时访问数据库。?也就是说,在 my_func 函数中,这发生在收到文档时。稍微改变了看法。很高兴看到如何只使用 django 来获得同样的东西。
    【解决方案2】:

    基于this answer

    解决方案:

    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
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-11-07
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-04-30
      • 2017-04-12
      • 2017-11-07
      • 2014-04-04
      相关资源
      最近更新 更多