Django ORM filter SUM different related objects on a loop

ぃ、小莉子 提交于 2021-01-01 06:38:56

问题


I have the following models:

    class Developer(models.Model):
        name = models.CharField(max_length=100)

    class Skill(models.Model):
        code = models.CharField(max_length=30)

    class Experience(models.Model):
        date_from = models.DateField(blank=True, null=True)
        date_to = models.DateField(blank=True, null=True)
        developer = models.ForeignKey(Developer, related_name='experience', 
        on_delete=models.CASCADE)
    
    class SkillExperience(models.Model):
        skill = models.ForeignKey(Skill, on_delete=models.CASCADE, related_name='skill_experience')
        experience = models.ForeignKey(Experience, on_delete=models.CASCADE, related_name='skill_experience')
        years_experience = models.IntegerField()

I know that for a single query, to get the Devs with at least 5 years of experience in Python, I could do:

Developer.objects.filter(
    experience__skill_experience__skill__code='Python'
).annotate(
    total_years=Sum('experience__skill_experience__years_experience')
).filter(
    total_years__gte=5
)

But how to proceed if I'm on a loop of skills and I need to check an OR, so let's say looping this array: [{code: 'python', years_experience: 5}, {code: 'node', years_experience: 2}] and I need to return Devs who have either 5 years xp with python OR 2 years xp with node?


回答1:


I think the simplest way will be to determine the sums of all skills and then check if at least one of these satisfies the constraints, so:

# since Django-2.0

from django.db.models import Q, Sum

# sample data
data = [
    {code: 'python', years_experience: 5},
    {code: 'node', years_experience: 2}
]

annotates = {
    f'total_years_{i}': Sum(
        'experience__skill_experience__years_experience',
        filter=Q(experience__skill_experience__skill__code=di['code'])
    ) for i, di in enumerate(data)
}

qs = Q(
    *[(f'total_years_{i}__gte', di['years_experience']) for i, di in enumerate(data)],
    _connector=Q.OR
)

developers = Developer.objects.annotate(
    **annotates
).filter(
    qs    
)

As a bonus, the Developer objects in developers will have attributes like .total_years_0, .total_years_1, etc. for each skill in the list that contains the sum of the years of that skill.



来源:https://stackoverflow.com/questions/65312167/django-orm-filter-sum-different-related-objects-on-a-loop

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!