In commit 7ba6ebe9, to address #19580, accessing a RelatedManager on an unsaved model instance now raises a ValueError.
The initial ticket comment said, “These are slight backwards compatibility changes, but to me it seems that almost always the changes will be visible only in code that isn’t working as intended.”
Right after the change was merged, people started noticing problems from this. See comment:56 on ticket 19580. A developer reply was, “this is undocumented behavior and as far as I’m aware it should be quite rare.”
Later, once this hit the world, Alex Matos said in comment:58, “while upgrading it from version 3.2 to 4.2 a lot of code broke due to this change, where code that was once returning an empty queryset, now raises a ValueError. In my opinion, this change should be fully reverted.” See also comment:10 on #33952 and the next comment.
I had exactly the same experience. I agree with those commenters that this should be reverted.
At this point, you might be tempted to say (as was somewhat said already): It’s been multiple years now, people will have adjusted their code by now. And sure, that’s probably generally true. But the problem is, this change is so fundamentally wrong that, at my job, we keep making this mistake, even in new code.
For us, a common case for this issue is in a Model.clean(). We will need to check some condition on related objects. For example:
if not self.is_active:
if self.related_thing_one.filter(is_active=True).exists():
raise ValidationError(…)
Other examples might be acting upon those related objects, in an overridden save() or a signal handler.
The old approach was simple and ergonomic. Things Just Worked whether the object was saved or not. The new approach requires me to 1) remember this is a problem, and 2) add if self.pk guards every time. Despite this change being a pet peeve of mine, I am still forgetting to add those guards, and I’m not the only one. That’s how intuitive the old behavior is and how unintuitive the new behavior is.
I believe the correct behavior is that accessing a RelatedManager on an unsaved object should be functionally equivalent to .none(). That is, it should return no results, successfully, without hitting the database.
Why shouldn’t it work that way? When you are trying to access foo.related_thing, you are fundamentally asking to perform an operation on the set of Foo’s related things. If that Foo has not been saved, then it can’t possibly have any related things. So the answer is known: it is the empty set. The answer is not “You asked an impossible/nonsensical question.”, which is what ValueError means.
P.S. I had more things linked for the reader’s convienence, but I’m a “new user”, so I can only put two links in my post, despite them all going to Django GitHub or the Django ticket tracker.