#34195: Duplicate Records created when specifying None as a target in a custom
ManyToManyField with sqlite3
-------------------------------------+-------------------------------------
Reporter: | Owner: nobody
Credentive |
Type: Bug | Status: new
Component: Database | Version: 4.1
layer (models, ORM) | Keywords: ManyToManyField,
Severity: Normal | null
Triage Stage: | Has patch: 0
Unreviewed |
Needs documentation: 0 | Needs tests: 0
Patch needs improvement: 0 | Easy pickings: 0
UI/UX: 0 |
-------------------------------------+-------------------------------------
Reading through the documents, the only reference to null in
ManyToManyFields is the following:
**null has no effect since there is no way to require a relationship at
the database level.**
When allowing null values as a target in an M2M field, you are allowed to
assign "None" as the target of a model. However, if you assign "None"
multiple times, you will get multiple DB records.
Understanding that M2M fields are implemented as join tables, I can see
why this may be happening, but I think this behavior should be documented
at least. Note from the example that adding non-null targets multiple
times produces the expected result (it works, but no extra rows are
created)
**Model code (<ProjectRoot>/policypublisher/models.py):**
{{{
class Section(models.Model):
uuid = models.UUIDField(
primary_key=True,
unique=True,
editable=False,
default=uuid.uuid4,
help_text="A unique identifier for the Section",
)
<...>
version = models.ManyToManyField(Version,
related_name="sections_in_version")
<...>
under = models.ManyToManyField("self", through="SectionHierarchy",
symmetrical=False, related_name="over")
class SectionHierarchy(models.Model):
under_id = models.ForeignKey(Section, on_delete=models.CASCADE,
related_name="+")
over_id = models.ForeignKey(Section, null=True,
on_delete=models.CASCADE, related_name="+")
version = models.ForeignKey(Version, on_delete=models.CASCADE)
class Meta:
constraints = [
models.UniqueConstraint(fields=["under_id", "over_id",
"version"], name="unique_sec_under_per_version")
]
}}}
**$ python manage.py shell**
{{{
Python 3.9.15 (main, Nov 15 2022, 09:54:34)
[GCC 10.2.1 20210110] on linux
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>> from policypublisher.models import *
>>> section = Section.objects.first()
>>> version = section.version.first()
>>> SectionHierarchy.objects.count()
0
>>> section.under.add(None, through_defaults={"version": version})
>>> section.under.add(None, through_defaults={"version": version})
>>> SectionHierarchy.objects.count()
2
>>> section2 = Section.objects.last()
>>> section.under.add(section2, through_defaults={"version":version})
>>> SectionHierarchy.objects.count()
3
>>> section.under.add(section2, through_defaults={"version":version})
>>> SectionHierarchy.objects.count()
3
>>>
}}}
**$ python manage.py dbshell**
{{{
SQLite version 3.34.1 2021-01-20 14:10:07
Enter ".help" for usage hints.
sqlite> select * from policypublisher_sectionhierarchy;
55||055744fc79e24f89a9384d79894eee7a|be8225c85fab4a0ba8ea879a3d992abe
56||055744fc79e24f89a9384d79894eee7a|be8225c85fab4a0ba8ea879a3d992abe
}}}
--
Ticket URL: <https://code.djangoproject.com/ticket/34195>
Django <https://code.djangoproject.com/>
The Web framework for perfectionists with deadlines.
--
You received this message because you are subscribed to the Google Groups
"Django updates" group.
To unsubscribe from this group and stop receiving emails from it, send an email
to [email protected].
To view this discussion on the web visit
https://groups.google.com/d/msgid/django-updates/01070184cd31fa29-ff4e10fb-b55c-44d1-ad64-3dd0792a4507-000000%40eu-central-1.amazonses.com.