Skip to content

Commit 2537104

Browse files
authored
Exclude ManyToManyFields when fetching excluded fields. (#707)
* Exclude ManyToManyFields when fetching excluded fields. Includes a test that fails on master but passes with the change. * Formatting fix. * Fix existing failing test. * Small naming fix. * Add to CHANGES.rst * Add a comment explaining the fix to #706.
1 parent dded689 commit 2537104

File tree

5 files changed

+34
-4
lines changed

5 files changed

+34
-4
lines changed

AUTHORS.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ Authors
5959
- Jonathan Loo (`alpha1d3d <https://github.com/alpha1d3d>`_)
6060
- Jonathan Sanchez
6161
- Jonathan Zvesper (`zvesp <https://github.com/zvesp>`_)
62+
- Jordon Wing (`jordonwii <https://github.com/jordonwii`_)
6263
- Josh Fyne
6364
- Keith Hackbarth
6465
- Kevin Foster

CHANGES.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ Unreleased
66
- Add default date to ``bulk_create_with_history`` and ``bulk_update_with_history`` (gh-687)
77
- Exclude ManyToManyFields when using ``bulk_create_with_history`` (gh-699)
88
- Added ``--excluded_fields`` argument to ``clean_duplicate_history`` command (gh-674)
9+
- Exclude ManyToManyFields when fetching excluded fields (gh-707)
910

1011
2.11.0 (2020-06-20)
1112
-------------------

simple_history/models.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
from django.contrib.auth import get_user_model
1515
from django.core.exceptions import ObjectDoesNotExist
1616
from django.db import models
17-
from django.db.models import Q
17+
from django.db.models import ManyToManyField, Q
1818
from django.db.models.fields.proxy import OrderWrt
1919
from django.forms.models import model_to_dict
2020
from django.urls import reverse
@@ -384,9 +384,12 @@ def get_instance(self):
384384
field.attname: getattr(self, field.attname) for field in fields.values()
385385
}
386386
if self._history_excluded_fields:
387+
# We don't add ManyToManyFields to this list because they may cause
388+
# the subsequent `.get()` call to fail. See #706 for context.
387389
excluded_attnames = [
388390
model._meta.get_field(field).attname
389391
for field in self._history_excluded_fields
392+
if not isinstance(model._meta.get_field(field), ManyToManyField)
390393
]
391394
try:
392395
values = (

simple_history/tests/models.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -695,11 +695,17 @@ class Street(models.Model):
695695
log = HistoricalRecords(related_name="history")
696696

697697

698-
class BulkCreateManyToManyModelOther(models.Model):
698+
class ManyToManyModelOther(models.Model):
699699
name = models.CharField(max_length=15, unique=True)
700700

701701

702702
class BulkCreateManyToManyModel(models.Model):
703703
name = models.CharField(max_length=15, unique=True)
704-
other = models.ManyToManyField(BulkCreateManyToManyModelOther)
704+
other = models.ManyToManyField(ManyToManyModelOther)
705705
history = HistoricalRecords()
706+
707+
708+
class ModelWithExcludedManyToMany(models.Model):
709+
name = models.CharField(max_length=15, unique=True)
710+
other = models.ManyToManyField(ManyToManyModelOther)
711+
history = HistoricalRecords(excluded_fields=["other"])

simple_history/tests/tests/test_models.py

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,8 @@
6969
OverrideModelNameUsingBaseModel1,
7070
MyOverrideModelNameRegisterMethod1,
7171
Library,
72+
ManyToManyModelOther,
73+
ModelWithExcludedManyToMany,
7274
ModelWithFkToModelWithHistoryUsingBaseModelDb,
7375
ModelWithHistoryInDifferentDb,
7476
ModelWithHistoryUsingBaseModelDb,
@@ -1004,6 +1006,16 @@ def test_as_of_nonexistant(self):
10041006
poll.delete()
10051007
self.assertRaises(Poll.DoesNotExist, poll.history.as_of, time)
10061008

1009+
def test_as_of_excluded_many_to_many_succeeds(self):
1010+
other1 = ManyToManyModelOther.objects.create(name="test1")
1011+
other2 = ManyToManyModelOther.objects.create(name="test2")
1012+
1013+
m = ModelWithExcludedManyToMany.objects.create(name="test")
1014+
m.other.add(other1, other2)
1015+
1016+
# This will fail if the ManyToMany field is not excluded.
1017+
self.assertEqual(m.history.as_of(datetime.now()), m)
1018+
10071019
def test_foreignkey_field(self):
10081020
why_poll = Poll.objects.create(question="why?", pub_date=today)
10091021
how_poll = Poll.objects.create(question="how?", pub_date=today)
@@ -1163,7 +1175,14 @@ def test_migrations_include_order(self):
11631175

11641176
model_state = state.ModelState.from_model(SeriesWork.history.model)
11651177
found = False
1166-
for name, field in model_state.fields:
1178+
# `fields` is a dict in Django 3.1
1179+
fields = None
1180+
if isinstance(model_state.fields, dict):
1181+
fields = model_state.fields.items()
1182+
else:
1183+
fields = model_state.fields
1184+
1185+
for name, field in fields:
11671186
if name == "_order":
11681187
found = True
11691188
self.assertEqual(type(field), models.IntegerField)

0 commit comments

Comments
 (0)