19

我对构建 Django 查询的最佳方法感到困惑,该查询检查字段(或列表)的所有元素ManyToMany是否存在于另一个ManyToMany字段中。

举个例子,我有几个Persons,他们可以拥有多个Specialty。也有Job人们可以启动的s,但他们需要一个或多个Specialtys才有资格启动。

class Person(models.Model):
    name = models.CharField()
    specialties = models.ManyToManyField('Specialty')

class Specialty(models.Model):
    name = models.CharField()

class Job(models.Model):
    required_specialties = models.ManyToManyField('Specialty')

一个人只有具备工作所需的所有专业才能开始工作。因此,再次举个例子,我们有三个专业:

  • 编码
  • 唱歌
  • 跳舞

我有一个Job需要唱歌和跳舞的专业。一个有唱歌和跳舞特长的人可以开始,但另一个有编码和唱歌特长的人不能——因为工作需要一个既能唱歌又能跳舞的人。

所以,现在我需要一种方法来找到一个人可以从事的所有工作。这是我解决它的方法,但我确信有一种更优雅的方法:

def jobs_that_person_can_start(person):
    # we start with all jobs
    jobs = Job.objects.all()
    # find all specialties that this person does not have
    specialties_not_in_person = Specialty.objects.exclude(name__in=[s.name for s in person.specialties])
    # and exclude jobs that require them
    for s in specialties_not_in_person:
        jobs = jobs.exclude(specialty=s)
    # the ones left should fill the criteria
    return jobs.distinct()

这是因为 using将返回与该人的任何Job.objects.filter(specialty__in=person.specialties.all())专长相匹配的工作,而不是所有专长。使用此查询,需要 Singing and Dancing 的工作将出现在唱歌的编码员中,这不是所需的输出。

I'm hoping this example is not too convoluted. The reason I'm concerned about this is that the Specialties in the system will probably be a lot more, and looping over them doesn't seem like the best way to achieve this. I'm wondering if anyone could lend a scratch to this itch!

4

2 回答 2

16

Another Idea

Ok I guess I should have added this to the other answer, but when I started on it, it seemed like it was going to be a different direction haha

No need to iterate:

person_specialties = person.specialties.values_list('pk', flat=True)

non_specialties = Specialties.objects.exclude(pk__in=person_specialties)

jobs = Job.objects.exclude(required_specialties__in=non_specialties)

note: I don't know exactly how fast this is. You may be better off with my other suggestions.
Also: This code is untested

于 2009-12-03T19:54:11.220 回答
6

I think you should look at using values_list to get the person's specialties

Replace:

[s.name for s in person.specialties]

with:

person.specialties.values_list('name', flat=True)

That will give you a plain list (ie. ['spec1', 'spec2', ...]) which you can use again. And the sql query used in the bg will also be faster because it will only select 'name' instead of doing a select * to populate the ORM objects

You might also get a speed improvement by filtering jobs that the person definately can NOT perform:

so replace:

jobs = Job.objects.all()

with (2 queries - works for django 1.0+)

person_specialties = person.specialties.values_list('id', flat=True)
jobs = Job.objects.filter(required_specialties__id__in=person_specialties)

or with (1 query? - works for django1.1+)

jobs = Job.objects.filter(required_specialties__in=person.specialties.all())

You may also get an improvement by using select_related() on your jobs/person queries (since they have a foreign key that you're using)

于 2009-12-03T19:10:05.553 回答