代码之家  ›  专栏  ›  技术社区  ›  odigity

在ManyToManyField中一起获取相关模型和通过模型的最佳方式

  •  0
  • odigity  · 技术社区  · 4 年前

    给定的架构:

    class Investor(models.Model):
        name = models.CharField(max_length=30)
        advisors = models.ManyToManyField("Advisor", related_name="investors", through="Connection")
    
    class Advisor(models.Model):
        name = models.CharField(max_length=30)
    
    class Connection(models.Model):
        investor = models.ForeignKey("Investor", related_name="connections", on_delete=models.CASCADE)
        advisor  = models.ForeignKey("Advisor",  related_name="connections", on_delete=models.CASCADE)
        blocked  = models.BooleanField(default=False)
    

    给定一个顾问“a”,当存在“a”时,获取所有投资者及其与“a”的联系的最佳列表是什么?

    到目前为止,我发现最好的是:

    from django.db.models import Prefetch
    
    for investor in Investor.objects.prefetch_related(
        Prefetch(
            'connections',
            queryset=Connection.objects.filter(advisor=a),
            to_attr='connection',
        )
    ):
        name = investor.name
        blocked = None
        if investor.connection:
            blocked = investor.connection[0].blocked
        print(f"{name} (blocked={blocked})")
    

    使现代化

    我找到了一种让使用上述策略更愉快的方法:

    class WithConnection:
        def __init__(self, queryset):
            self.queryset = queryset
    
        def __iter__(self):
            for model in self.queryset:
                connection = model.my_connections[0] if model.my_connections else None
                yield model, connection
    
    class InvestorQuerySet(models.QuerySet):
        def pov(self, advisor):
            return WithConnection(
                self.prefetch_related(models.Prefetch(
                    'connections',
                    queryset=Connection.objects.filter(advisor=advisor),
                    to_attr='my_connections',
                ))
            )
    

    使用方式如下:

    advisor = Advisor.objects.first()
    for investor, connection in Investor.objects.pov(advisor):
        # do stuff with investor
        if connection:
            # do stuff with connection
    

    希望在这方面有所改进。Django的ORM非常强大,似乎应该有一种更简单的方法来实现这一点。

    1 回复  |  直到 4 年前
        1
  •  0
  •   Jordan Hyatt    4 年前

    我相信你只是想要

    a.investor_set.all()
    
        2
  •  0
  •   odigity    4 年前

    经过多次研究和迭代,我得出了这个解决方案:

    class WithConnectionQuerySet(models.QuerySet):
        def __iter__(self):
            return map(
                lambda m: (m, m.my_connections[0]) if m.my_connections else (m, None),
                super().__iter__(),
            )
    
    class Investor(models.Model):
        ...
        @classmethod
        def viewed_by(cls, advisor):
            manager = cls._default_manager.__class__.from_queryset(WithConnectionQuerySet)()
            manager.model = cls
            return manager.prefetch_related(models.Prefetch(
                'connections',
                queryset=Connection.objects.filter(advisor=advisor),
                to_attr='my_connections',
            ))
    

    使用方式如下:

    advisor = Advisor.objects.first()
    for investor, connection in Investor.viewed_by(advisor).filter(age__gt=40):
        # do stuff with investor
        if connection:
            # do stuff with connection
    

    我对它还不是百分之百的兴奋,但我现在已经足够喜欢它了,可以开始使用了。如果有人有更好的答案,请插话。