286👍
You cannot create m2m relations from unsaved objects. If you have the pk
s, try this:
sample_object = Sample()
sample_object.save()
sample_object.users.add(1,2)
Update: After reading the saverio’s answer, I decided to investigate the issue a bit more in depth. Here are my findings.
This was my original suggestion. It works, but isn’t optimal. (Note: I’m using Bar
s and a Foo
instead of User
s and a Sample
, but you get the idea).
bar1 = Bar.objects.get(pk=1)
bar2 = Bar.objects.get(pk=2)
foo = Foo()
foo.save()
foo.bars.add(bar1)
foo.bars.add(bar2)
It generates a whopping total of 7 queries:
SELECT "app_bar"."id", "app_bar"."name" FROM "app_bar" WHERE "app_bar"."id" = 1
SELECT "app_bar"."id", "app_bar"."name" FROM "app_bar" WHERE "app_bar"."id" = 2
INSERT INTO "app_foo" ("name") VALUES ()
SELECT "app_foo_bars"."bar_id" FROM "app_foo_bars" WHERE ("app_foo_bars"."foo_id" = 1 AND "app_foo_bars"."bar_id" IN (1))
INSERT INTO "app_foo_bars" ("foo_id", "bar_id") VALUES (1, 1)
SELECT "app_foo_bars"."bar_id" FROM "app_foo_bars" WHERE ("app_foo_bars"."foo_id" = 1 AND "app_foo_bars"."bar_id" IN (2))
INSERT INTO "app_foo_bars" ("foo_id", "bar_id") VALUES (1, 2)
I’m sure we can do better. You can pass multiple objects to the add()
method:
bar1 = Bar.objects.get(pk=1)
bar2 = Bar.objects.get(pk=2)
foo = Foo()
foo.save()
foo.bars.add(bar1, bar2)
As we can see, passing multiple objects saves one SELECT
:
SELECT "app_bar"."id", "app_bar"."name" FROM "app_bar" WHERE "app_bar"."id" = 1
SELECT "app_bar"."id", "app_bar"."name" FROM "app_bar" WHERE "app_bar"."id" = 2
INSERT INTO "app_foo" ("name") VALUES ()
SELECT "app_foo_bars"."bar_id" FROM "app_foo_bars" WHERE ("app_foo_bars"."foo_id" = 1 AND "app_foo_bars"."bar_id" IN (1, 2))
INSERT INTO "app_foo_bars" ("foo_id", "bar_id") VALUES (1, 1)
INSERT INTO "app_foo_bars" ("foo_id", "bar_id") VALUES (1, 2)
I wasn’t aware that you can also assign a list of objects:
bar1 = Bar.objects.get(pk=1)
bar2 = Bar.objects.get(pk=2)
foo = Foo()
foo.save()
foo.bars = [bar1, bar2]
Unfortunately, that creates one additional SELECT
:
SELECT "app_bar"."id", "app_bar"."name" FROM "app_bar" WHERE "app_bar"."id" = 1
SELECT "app_bar"."id", "app_bar"."name" FROM "app_bar" WHERE "app_bar"."id" = 2
INSERT INTO "app_foo" ("name") VALUES ()
SELECT "app_foo_bars"."id", "app_foo_bars"."foo_id", "app_foo_bars"."bar_id" FROM "app_foo_bars" WHERE "app_foo_bars"."foo_id" = 1
SELECT "app_foo_bars"."bar_id" FROM "app_foo_bars" WHERE ("app_foo_bars"."foo_id" = 1 AND "app_foo_bars"."bar_id" IN (1, 2))
INSERT INTO "app_foo_bars" ("foo_id", "bar_id") VALUES (1, 1)
INSERT INTO "app_foo_bars" ("foo_id", "bar_id") VALUES (1, 2)
Let’s try to assign a list of pk
s, as saverio suggested:
foo = Foo()
foo.save()
foo.bars = [1,2]
As we don’t fetch the two Bar
s, we save two SELECT
statements, resulting in a total of 5:
INSERT INTO "app_foo" ("name") VALUES ()
SELECT "app_foo_bars"."id", "app_foo_bars"."foo_id", "app_foo_bars"."bar_id" FROM "app_foo_bars" WHERE "app_foo_bars"."foo_id" = 1
SELECT "app_foo_bars"."bar_id" FROM "app_foo_bars" WHERE ("app_foo_bars"."foo_id" = 1 AND "app_foo_bars"."bar_id" IN (1, 2))
INSERT INTO "app_foo_bars" ("foo_id", "bar_id") VALUES (1, 1)
INSERT INTO "app_foo_bars" ("foo_id", "bar_id") VALUES (1, 2)
And the winner is:
foo = Foo()
foo.save()
foo.bars.add(1,2)
Passing pk
s to add()
gives us a total of 4 queries:
INSERT INTO "app_foo" ("name") VALUES ()
SELECT "app_foo_bars"."bar_id" FROM "app_foo_bars" WHERE ("app_foo_bars"."foo_id" = 1 AND "app_foo_bars"."bar_id" IN (1, 2))
INSERT INTO "app_foo_bars" ("foo_id", "bar_id") VALUES (1, 1)
INSERT INTO "app_foo_bars" ("foo_id", "bar_id") VALUES (1, 2)
134👍
For future visitors, you can create an object and all of its m2m objects in 2 queries using the new bulk_create in django 1.4. Note that this is only usable if you don’t require any pre or post-processing on the data with save() methods or signals. What you insert is exactly what will be in the DB
You can do this without specifying a “through” model on the field. For completeness, the example below creates a blank Users model to mimic what the original poster was asking.
from django.db import models
class Users(models.Model):
pass
class Sample(models.Model):
users = models.ManyToManyField(Users)
Now, in a shell or other code, create 2 users, create a sample object, and bulk add the users to that sample object.
Users().save()
Users().save()
# Access the through model directly
ThroughModel = Sample.users.through
users = Users.objects.filter(pk__in=[1,2])
sample_object = Sample()
sample_object.save()
ThroughModel.objects.bulk_create([
ThroughModel(users_id=users[0].pk, sample_id=sample_object.pk),
ThroughModel(users_id=users[1].pk, sample_id=sample_object.pk)
])
- [Django]-Does django with mongodb make migrations a thing of the past?
- [Django]-OneToOneField() vs ForeignKey() in Django
- [Django]-Django REST framework: non-model serializer
20👍
Django 1.9
A quick example:
sample_object = Sample()
sample_object.save()
list_of_users = DestinationRate.objects.all()
sample_object.users.set(list_of_users)
- [Django]-Where to put business logic in django
- [Django]-Django QuerySet order
- [Django]-Using JSON in django template
9👍
RelatedObjectManagers are different “attributes” than fields in a Model. The simplest way to achieve what you are looking for is
sample_object = Sample.objects.create()
sample_object.users = [1, 2]
That’s the same as assigning a User list, without the additional queries and the model building.
If the number of queries is what bothers you (instead of simplicity), then the optimal solution requires three queries:
sample_object = Sample.objects.create()
sample_id = sample_object.id
sample_object.users.through.objects.create(user_id=1, sample_id=sample_id)
sample_object.users.through.objects.create(user_id=2, sample_id=sample_id)
This will work because we already know that the ‘users’ list is empty, so we can create mindlessly.
- [Django]-Django substr / substring in templates
- [Django]-Can't compare naive and aware datetime.now() <= challenge.datetime_end
- [Django]-Using Django time/date widgets in custom form
3👍
You could replace the set of related objects in this way (new in Django 1.9):
new_list = [user1, user2, user3]
sample_object.related_set.set(new_list)
- [Django]-Storing an Integer Array in a Django Database
- [Django]-Visual Editor for Django Templates?
- [Django]-Django TypeError: get() got multiple values for keyword argument 'invoice_id'
-1👍
If someone is looking to do David Marbles answer on a self referring ManyToMany field. The ids of the through model are called:
"to_’model_name_id" and
"from_’model_name’_id".
If that doesn’t work you can check the django connection.
- [Django]-Are there any plans to officially support Django with IIS?
- [Django]-Celery : Execute task after a specific time gap
- [Django]-In a Django form, how do I make a field readonly (or disabled) so that it cannot be edited?