Django - Understanding RelatedManager remove method
<p>So here I will be using classic Django Blog and Entry models from the documentation (<a href="https://docs.djangoproject.com/en/1.10/topics/db/queries/" rel="nofollow">link</a>). I added <code>null=True</code> to the Entry's blog attribute.</p>

<pre><code>&gt;&gt;&gt; cb = Blog.objects.get(name__startswith="Cheese")
&gt;&gt;&gt; gouda = Entry.objects.get(headline__startswith="Gouda")
&gt;&gt;&gt; cb.entry_set.all()
[&lt;Entry: Gouda&gt;, &lt;Entry: Emmentaler&gt;]
&gt;&gt;&gt; cb.entry_set.remove(gouda)
&gt;&gt;&gt; gouda.blog
&lt;Blog: Cheese blog&gt;
</code></pre>

<p>I know that everything is fine and updated in database and if I query the second line from my example again that <code>gouda.blog</code> is gonna return <code>None</code>, but my question is why is <code>gouda.blog</code> not <code>None</code> without another query? </p>

<p>EDIT:</p>

<p>So if I understand everything correctly, this is how it works:</p>

<pre><code>&gt;&gt;&gt; cb = Blog.objects.get(name__startswith="Cheese")
&gt;&gt;&gt; gouda = Entry.objects.get(headline__startswith="Gouda")
&gt;&gt;&gt; cb.entry_set.all()
[&lt;Entry: Gouda&gt;, &lt;Entry: Emmentaler&gt;]
&gt;&gt;&gt; cb.entry_set.remove(gouda)
&gt;&gt;&gt; gouda.blog
&lt;Blog: Cheese blog&gt;
</code></pre>

<p>So default value of the <code>bulk</code> argument for the <code>remove()</code> method is <code>True</code>. That means that <code>QuerySet.update()</code> will be used. Object <code>gouda</code> will not be altered at the Python level so the <code>blog</code> attribute will still hold primary key of the "Cheese blog" blog. When we query <code>gouda.blog</code>, we will still get the "Cheese blog" object. </p>

<p>But what happens when <code>bulk=False</code> is passed to <code>remove()</code>? From documentation: If bulk=False, the save() method of each individual model instance is called instead.</p>

<p>So then I override <code>save()</code> method of the <code>Entry</code> model like this:</p>

<pre><code>def save(self, *args, **kwargs):
    super().save(*args, **kwargs)
    if self.blog == None:
        print(id(self))
</code></pre>

<p>and then:</p>

<pre><code>&gt;&gt;&gt; cb = Blog.objects.get(name__startswith="Cheese")
&gt;&gt;&gt; gouda = Entry.objects.get(headline__startswith="Gouda")
&gt;&gt;&gt; cb.entry_set.all()
[&lt;Entry: Gouda&gt;, &lt;Entry: Emmentaler&gt;]
&gt;&gt;&gt; id(gouda)
139797518743592
&gt;&gt;&gt; cb.entry_set.remove(gouda, bulk=False)
139797518745552
&gt;&gt;&gt; gouda.blog
&lt;Blog: Cheese blog&gt;
</code></pre>

<p>Now we see that <code>gouda</code> object that <code>save()</code> method is called on is not the same as the one in our shell, so our object in the shell still holds primary key of "Cheese blog" in its <code>blog</code> attribute. And when we query for the blog with <code>gouda.blog</code> we still get "Cheese blog" object.</p>

<p>Is that correct and if it is, why is <code>save()</code> not called on the same object that we passed to <code>remove()</code>?</p>

<p>You've said it yourself, you need to go back to the database to get the new information. The <code>gouda</code> object doesn't automatically keep a link to its database row; it only queries it when told to do so.</p>

python django