Como consultar corretamente um ManyToManyField para todos os objetos em uma lista (ou outra ManyToManyField)?

StackOverflow https://stackoverflow.com/questions/1841931

Pergunta

Estou bastante perplexo sobre a melhor maneira de construir uma consulta de Django que verifica se todas os elementos de um campo ManyToMany (ou uma lista) estão presentes em outro campo ManyToMany.

Como um exemplo, eu tenho várias Persons, que podem ter mais de uma especialidade. Há também Jobs que as pessoas podem começar, mas eles exigem um ou mais Specialtys para ser elegível para ser iniciado.

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')

Uma pessoa pode iniciar um trabalho de única , se eles têm todas as especialidades que o trabalho exige. Então, novamente por causa do exemplo, temos três especialidades:

  • Codificação
  • Singing
  • Dancing

E eu tenho um Job que requer as especialidades de canto e dança. Uma pessoa com canto e dança especialidades pode iniciá-lo, mas outra com Codificação e cantando especialidades não pode -. Como o trabalho exige uma pessoa que pode tanto cantar e dançar

Então, agora eu preciso de uma maneira de encontrar todos os trabalhos que uma pessoa pode assumir. Esta foi a minha maneira de lidar com ele, mas eu tenho certeza que há uma abordagem mais elegante:

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()

Este é porque usar Job.objects.filter(specialty__in=person.specialties.all()) voltará empregos que correspondem qualquer de especialidades da pessoa, não todos eles. Usando esta consulta, o trabalho que exige cantando e dançando apareceria para o codificador de cantar, o que não é a saída desejada.

Eu estou esperando que este exemplo não é muito complicado. A razão que eu estou preocupado com isso é que as especialidades no sistema provavelmente será muito mais, e looping sobre eles não parece ser a melhor maneira de conseguir isso. Eu estou querendo saber se alguém poderia emprestar um arranhão a esta coceira!

Foi útil?

Solução

Outra idéia

Ok, eu acho que deveria ter adicionado este para a outra resposta, mas quando eu comecei sobre ele, parecia que ia ser uma direção diferente haha ??

Não há necessidade de 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)

Nota : eu não sei exatamente o quão rápido é isso. Você pode ser melhor fora com as minhas outras sugestões.
Também: Este código não foi testado

Outras dicas

Eu acho que você deve olhar para usando values_list para obter especialidades da pessoa

Substituir:

[s.name for s in person.specialties]

com:

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

Isso vai lhe dar uma lista simples (ie. [ 'Spec1', 'spec2', ...]) que você pode usar novamente. E a consulta SQL usada no bg também será mais rápido porque ele só irá selecionar 'nome' em vez de fazer uma select * para preencher os objetos ORM

Você também pode obter uma melhoria de velocidade por empregos de filtragem que a pessoa definitivamente não pode executar:

para substituir:

jobs = Job.objects.all()

com (2 consultas - obras para Django 1.0 +)

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

ou com (1 query -? Trabalha para django1.1 +)

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

Você também pode obter uma melhoria usando select_related () em seu emprego pesquisas / pessoa (uma vez que têm uma chave estrangeira que você está usando)

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top