Skip to content

Commit

Permalink
Fixed #31046 -- Allowed callable values in through_defaults
Browse files Browse the repository at this point in the history
  • Loading branch information
bmispelon committed Nov 29, 2019
1 parent 67654c9 commit 95e5973
Show file tree
Hide file tree
Showing 4 changed files with 34 additions and 1 deletion.
3 changes: 2 additions & 1 deletion django/db/models/fields/related_descriptors.py
Expand Up @@ -68,6 +68,7 @@ class Child(Model):
from django.db.models import Q, signals
from django.db.models.query import QuerySet
from django.db.models.query_utils import DeferredAttribute
from django.db.models.utils import resolve_callables
from django.utils.functional import cached_property


Expand Down Expand Up @@ -1118,7 +1119,7 @@ def _add_items(self, source_field_name, target_field_name, *objs, through_defaul
if not objs:
return

through_defaults = through_defaults or {}
through_defaults = dict(resolve_callables(through_defaults or {}))

target_ids = self._get_target_ids(target_field_name, objs)
db = router.db_for_write(self.through, instance=self.instance)
Expand Down
22 changes: 22 additions & 0 deletions docs/ref/models/relations.txt
Expand Up @@ -73,6 +73,14 @@ Related objects reference
:ref:`intermediate model <intermediary-manytomany>` instance(s), if
needed.

.. versionchanged:: 3.1

You can use callables as values in the ``through_defaults`` dictionary
and they will be evaluated before creating any potential
intermediate instance(s). Note that the callables are only evaluated
once and will therefore have the exact same value for each created
intermediate instance.

.. method:: create(through_defaults=None, **kwargs)

Creates a new object, saves it and puts it in the related object set.
Expand Down Expand Up @@ -107,6 +115,12 @@ Related objects reference
:ref:`intermediate model <intermediary-manytomany>` instance, if
needed.

.. versionchanged:: 3.1

You can use callables as values in the ``through_defaults`` dictionary
and they will be evaluated just before creating the new
intermediate instance.

.. method:: remove(*objs, bulk=True)

Removes the specified model objects from the related object set::
Expand Down Expand Up @@ -195,6 +209,14 @@ Related objects reference
:ref:`intermediate model <intermediary-manytomany>` instance(s), if
needed.

.. versionchanged:: 3.1

You can use callables as values in the ``through_defaults`` dictionary
and they will be evaluated before creating any potential
intermediate instance(s). Note that the callables are only evaluated
once and will therefore have the exact same value for each created
intermediate instance.

.. note::

Note that ``add()``, ``create()``, ``remove()``, ``clear()``, and
Expand Down
5 changes: 5 additions & 0 deletions docs/releases/3.1.txt
Expand Up @@ -209,6 +209,11 @@ Models

* :attr:`.CheckConstraint.check` now supports boolean expressions.

* Callables can now be used with the ``through_defaults`` argument to
:meth:`RelatedManager.add() <django.db.models.fields.related.RelatedManager.add>`,
:meth:`~django.db.models.fields.related.RelatedManager.set`, and
:meth:`~django.db.models.fields.related.RelatedManager.create`.

Pagination
~~~~~~~~~~

Expand Down
5 changes: 5 additions & 0 deletions tests/m2m_through/tests.py
Expand Up @@ -62,6 +62,11 @@ def test_add_on_m2m_with_intermediate_model(self):
self.assertSequenceEqual(self.rock.members.all(), [self.bob])
self.assertEqual(self.rock.membership_set.get().invite_reason, 'He is good.')

def test_add_on_m2m_with_intermediate_model_callable_through_default(self):
self.rock.members.add(self.bob, through_defaults={'invite_reason': lambda: 'He is good.'})
self.assertSequenceEqual(self.rock.members.all(), [self.bob])
self.assertEqual(self.rock.membership_set.get().invite_reason, 'He is good.')

def test_add_on_m2m_with_intermediate_model_value_required(self):
self.rock.nodefaultsnonulls.add(self.jim, through_defaults={'nodefaultnonull': 1})
self.assertEqual(self.rock.testnodefaultsornulls_set.get().nodefaultnonull, 1)
Expand Down

0 comments on commit 95e5973

Please sign in to comment.