272

Based on the Django doc, I should be able to pass multiple objects at once to be added to a manytomany relationship but I get a

* TypeError: unhashable type: 'list'

when I try to pass a django queryset casted in a list. Passing a Queryset or a ValuesListQueryset seems to fail also. Is there a better way than use a for loop ?

Alireza Savand
  • 3,462
  • 3
  • 26
  • 36
philgo20
  • 6,337
  • 6
  • 34
  • 43

3 Answers3

448

Use: object.m2mfield.add(*items) as described in the documentation:

add() accepts an arbitrary number of arguments, not a list of them.

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

To expand that list into arguments, use *

add(*[obj1, obj2, obj3])

Addendum:

Django does not call obj.save() for each item but uses bulk_create(), instead.

jnns
  • 5,148
  • 4
  • 47
  • 74
Yuji 'Tomita' Tomita
  • 115,817
  • 29
  • 282
  • 245
  • 1
    If you look at the manager it just does a for loop over the objects and calls save on them. – Sam Dolan Jul 09 '11 at 05:33
  • 5
    A short experiment from the shell shows that @sdolan is in fact (currently) incorrect. I.e. when I look at the generated SQL I see only a single insert statement: INSERT INTO app_one_twos (one_id, two_id) VALUES (1, 1), (1, 2), (1, 3), (1, 4); This is in Django 1.4. – Klaas van Schelven Mar 26 '13 at 16:54
  • @KlaasvanSchelven: I don't remember testing this through the generated sql, but based on my comment I'm pretty sure I just took a glance at the source code. Keep in mind that this was over 2 years ago, so I would hope that things had been optimized a bit. – Sam Dolan Mar 26 '13 at 18:56
  • 1
    @sdolan No they have not improved it. I was just testing it. – Saransh Mohapatra May 15 '13 at 16:33
  • @sdolan @SaranshMohapatra Please have a close look at the source of `ManyRelatedManager` -- not `RelatedManager`! Django does, in fact, use `bulk_create()` to add the relationships. – jnns Mar 27 '14 at 11:16
  • @jnns Yeah it does now :) In 2011 bulk_create didn't even exist yet :) My mistake was saying save instead of create. See the source at the time of my comment (I was curious!) https://github.com/django/django/blob/fcf8312ade4375faf6027afe610fb76bcdead084/django/db/models/fields/related.py#L583 – Sam Dolan Mar 27 '14 at 16:55
  • Django 1.3 doesn't have bulk_create, unfortunately – TankorSmash Dec 17 '14 at 00:17
  • `object.m2mfield.add(*AnotherModel.objects.all()[:3])` will work for Django 1.7 – jerinisready Jul 02 '18 at 09:59
  • 2
    Any way to do this by value (ex. id)? Sometimes you don't have a list of objects but a list of object values (i.e. id's)? Rather than looping through and grabbing all objects into another list... – DannyMoshe Feb 13 '19 at 19:42
  • What if the table that `add()` will be saving to has custom fields when the user uses `through='tablename'`? How can using `add()` insert values for any custom fields there might be? – enchance Mar 10 '20 at 17:09
94

To add on, If you want to add them from a queryset

Example

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

From: Insert queryset results into ManytoManyfield

Dr Manhattan
  • 13,537
  • 6
  • 45
  • 41
58

Django 1.9 adds additional ways for adding to a many-to-many relationship.

Documentation: https://docs.djangoproject.com/en/dev/ref/models/relations/#django.db.models.fields.related.RelatedManager.set

set is a new nicety:

>>> new_list = [obj1, obj2, obj3]
>>> e.related_set.set(new_list)
Cory Madden
  • 5,026
  • 24
  • 37
aero
  • 1,654
  • 1
  • 21
  • 31
  • 2
    `set` was always there I think. It's just that it used to work through assignment `e.related_set = new_list` in older Djangos is equivalent to `e.related_set.set(new_list)`. They just realized that "explicit is better than implicit". – DylanYoung Mar 07 '19 at 20:37
  • 18
    Caution: To be concise, set deletes all the existing associated models. So if you want add list of new objects and want to keep the existing one intact, use add(*newlist) – Tasawar Hussain Nov 11 '19 at 06:58
  • 2
    This solution is great when you want to update record to new set, and if object have already in set "obj1" for example, than record in database not recreating. – MegaJoe Aug 06 '20 at 18:26
  • 2
    Works the same way in current Django version (3.2.6) – Igor Atsberger Nov 03 '21 at 08:57
  • 2
    The set method defaults to `clear=True` but you can explicitly change that by passing `clear=False` – cnk Feb 16 '23 at 23:58
  • @TasawarHussain to clarify, I believe that only the entries in the through table are deleted from the database, not the actual other end objects themselves right? That would be terrible since those end objects are probably referenced elsewhere. – kevlar Aug 24 '23 at 05:00