Como adicionar vários objetos ao relacionamento ManyToMany de uma só vez no Django?

185

Com base no documento do Django, eu devo passar vários objetos de uma só vez para ser adicionado a um relacionamento com muitos, mas recebo uma

* TypeError: unhashable type: 'list'

quando tento passar um conjunto de consultas django fundido em uma lista. Passar um Queryset ou um ValuesListQueryset também parece falhar. Existe uma maneira melhor do que usar um loop for?

philgo20
fonte

Respostas:

320

Use: object.m2mfield.add(*items)conforme descrito na documentação :

add() aceita um número arbitrário de argumentos, não uma lista deles.

add(obj1, obj2, obj3, ...)

Para expandir essa lista em argumentos, use *

add(*[obj1, obj2, obj3])

Termo aditivo:

O Django não chama obj.save()para cada item, mas usa bulk_create().

Tomita, Yuji Tomita
fonte
Se você olhar para o gerente, ele fará um loop for sobre os objetos e as chamadas serão salvas neles.
Sam Dolan
3
Uma breve experiência do shell mostra que @sdolan está de fato (atualmente) incorreto. Ou seja, quando olho para o SQL gerado, vejo apenas uma única instrução de inserção: INSERT INTO app_one_twos (one_id, two_id) VALUES (1, 1), (1, 2), (1, 3), (1, 4); Isso está no Django 1.4.
Klaas van Schelven 26/03
@KlaasvanSchelven: Não me lembro de testar isso através do sql gerado, mas com base no meu comentário, tenho certeza de que apenas dei uma olhada no código-fonte. Lembre-se de que isso aconteceu há mais de 2 anos, então eu espero que as coisas tenham sido otimizadas um pouco.
Sam Dolan
1
@sdolan Não, eles não o melhoraram. Eu estava apenas testando.
Saransh Mohapatra
1
Alguma maneira de fazer isso por valor (por exemplo, id)? Às vezes você não tem uma lista de objetos, mas uma lista de valores de objetos (ou seja, IDs)? Em vez de loop através e pegar todos os objetos em outra lista ...
DannyMoshe
48

Para adicionar, se você quiser adicioná-los de um conjunto de consultas

Exemplo

# Returns a queryset
permissions = Permission.objects.all()

# Add the results to the many to many field (notice the *)

group = MyGroup.objects.get(name='test')

group.permissions.add(*permissions)

De: inserir resultados do conjunto de consultas no ManytoManyfield

Dr. Manhattan
fonte
3
Isso realiza duas consultas em vez de um :(
DylanYoung
2
Observe que você não precisa transformá-lo em uma lista. Basta adicionar (* permissões) diretamente.
BjornW 26/02
34

O Django 1.9 adiciona maneiras adicionais de adicionar a um relacionamento muitos-para-muitos.

Documentação: https://docs.djangoproject.com/en/dev/ref/models/relations/#django.db.models.fields.related.RelatedManager.set

set é um novo detalhe:

>>> new_list = [obj1, obj2, obj3]
>>> e.related_set.set(new_list)
aero
fonte
2
setsempre esteve lá, eu acho. É só que ele costumava trabalhar com a atribuição e.related_set = new_listem Djangos mais antigos e.related_set.set(new_list). Eles apenas perceberam que "explícito é melhor que implícito".
precisa saber é o seguinte
1
Cuidado: Para ser conciso, o conjunto exclui todos os modelos associados existentes. Então se você quer lista de novos objetos adicionar e quer manter o intacto, add existente utilização (* newlist)
Tasawar Hussain