Opened 16 years ago
Closed 5 years ago
#8467 closed New feature (fixed)
For ManyToMany manager, we should convert objects being added or removed to the pk type if they are not.
Reported by: | Wonlay | Owned by: | nobody |
---|---|---|---|
Component: | Database layer (models, ORM) | Version: | dev |
Severity: | Normal | Keywords: | Duplicate entry, add, remove, ManyToManyField |
Cc: | wonlay@… | Triage Stage: | Accepted |
Has patch: | yes | Needs documentation: | no |
Needs tests: | no | Patch needs improvement: | no |
Easy pickings: | no | UI/UX: | no |
Description
For example:
class A(models.Model): name = models.CharField(blank=True) class B(models.Model): a = models.ManyToManyField(A) a = A(name='aaa') a.save() b = B() b.save() # This line works fine: b.a.add('1') # But this one will raise 'Duplicate entry' error: b.a.add('1')
This is caused by '1'
is not an integer, the duplication checking code in add()
fails when checking the string type '1'
.
The same problem is applied to the remove()
method.
If we convert the objects to its pk type of the model, code will run correctly.
And my patch is attached.
Attachments (1)
Change History (20)
by , 16 years ago
Attachment: | smart_many2many_add_remove.patch added |
---|
comment:1 by , 16 years ago
milestone: | 1.0 → post-1.0 |
---|
comment:2 by , 16 years ago
milestone: | post-1.0 |
---|
comment:3 by , 16 years ago
Triage Stage: | Unreviewed → Design decision needed |
---|
comment:4 by , 14 years ago
Severity: | → Normal |
---|---|
Type: | → New feature |
comment:7 by , 12 years ago
Component: | Core (Other) → Database layer (models, ORM) |
---|
comment:8 by , 12 years ago
Triage Stage: | Design decision needed → Accepted |
---|
I think this fix makes sense. The patch should be made aware of from_field/to_field if possible, even it is not possible to create such ManyToManys currently this feature will very likely appear some day.
comment:9 by , 11 years ago
Needs tests: | set |
---|---|
Patch needs improvement: | set |
Patch needs improvement per @akaariai's comment as well as tests.
comment:10 by , 9 years ago
What's the use case for this functionality? Do we do similar type coercion elsewhere?
comment:11 by , 9 years ago
I think we should fix this on the basis that using b.a.add('1') works partly, but the duplication check doesn't work. Either we should reject strings where ints are required, or handle type coercions fully.
To fix this, we should coerce the input values to right type right at the beginning of add() and remove(). The same applies likely also to reverse foreign key sets. Still, we need to convert to the primary key type of the target model, that is, the code should work also when A had a custom primary key (for example, A.name was primary key).
Tests for m2m.add can be found from https://github.com/akaariai/django/commit/b1308c114f601db2f3f0c8d76c55acd966a14672. Similar tests should be added for .remove(), and for reverse foreign key .add() and .remove().
comment:12 by , 8 years ago
As noted in duplicate #27249, calling Field.to_python()
on the input might work.
comment:13 by , 5 years ago
I was unable to reproduce this on a recent version (tag 3.0rc1
for example).
I'm using the following test case (same models as in the original ticket):
from django.db import IntegrityError from django.test import TestCase from .models import A, B class Ticket8467TestCase(TestCase): def test_integrityerror(self): pk = A.objects.create().pk b = B.objects.create() b.a.add(str(pk)) with self.assertRaises(IntegrityError): b.a.add(str(pk))
This test fails (meaning that an IntegrityError
is not raised) on 3.0rc1
but passes for 2.2
.
Using git bisect
, I tracked it down to this commit: 28712d8acfffa9cdabb88cb610bae14913fa185d.
However it's not immediately clear to me why that commit would fix this ticket so I'm unsure whether I should close this ticket.
comment:14 by , 5 years ago
I'm pretty sure we can consider this fixed by 28712d8acfffa9cdabb88cb610bae14913fa185d.
Even if we don't perform the conversion to the pk type in the branch handling non-model instances of _get_target_ids
we now ignore conflicts on bulk insertions so the collision on insertion is now ignored.
I guess we could adjust the branch as an optimization if we wanted to by calling target_field.to_python(obj)
by re-purposing this ticket as an optimization.
comment:15 by , 5 years ago
A regression test for the above optimization could be to assertNumQueries(1)
on the b.a.add(str(pk))
call when a m2m_changed
signal is registered for the relationship. That is to disable the fast insertion mechanism that is enabled on all backends except for Oracle.
comment:16 by , 5 years ago
Actually, this is more than an optimization because it will still crash after 28712d8acfffa9cdabb88cb610bae14913fa185d on backends that don't have the supports_ignore_conflicts
feature flag enabled (only Oracle).
I'll submit a PR for it.
comment:17 by , 5 years ago
Needs tests: | unset |
---|---|
Patch needs improvement: | unset |
Milestone post-1.0 deleted