<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Django Mastery</title>
    <description>The latest articles on DEV Community by Django Mastery (@djangomastery).</description>
    <link>https://dev.to/djangomastery</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4157736%2F9bd79969-a902-4495-8df3-65fbbc0e9948.jpg</url>
      <title>DEV Community: Django Mastery</title>
      <link>https://dev.to/djangomastery</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/djangomastery"/>
    <language>en</language>
    <item>
      <title>Your Celery task will run twice. Here's how to make that safe</title>
      <dc:creator>Django Mastery</dc:creator>
      <pubDate>Fri, 02 Oct 2026 19:58:01 +0000</pubDate>
      <link>https://dev.to/djangomastery/your-celery-task-will-run-twice-heres-how-to-make-that-safe-20mo</link>
      <guid>https://dev.to/djangomastery/your-celery-task-will-run-twice-heres-how-to-make-that-safe-20mo</guid>
      <description>&lt;p&gt;Celery can't promise a task runs exactly once. It can run twice, and one day it will. If that task sends an email or charges a card, your user gets two.&lt;/p&gt;

&lt;p&gt;The fix isn't to stop duplicates. It's to make the second run do nothing.&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/4OG__qxIZPU" width="710" height="399"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  How a task ends up running twice
&lt;/h2&gt;

&lt;p&gt;With &lt;code&gt;acks_late=True&lt;/code&gt;, the message stays unacknowledged while the task runs. Now picture this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A worker runs &lt;code&gt;send_order_confirmation&lt;/code&gt;. The email goes out.&lt;/li&gt;
&lt;li&gt;The worker is killed (out of memory, a pod eviction) &lt;strong&gt;before it acks&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;The broker redelivers: RabbitMQ when the connection drops, Redis after the visibility timeout.&lt;/li&gt;
&lt;li&gt;Another worker runs the task again. &lt;strong&gt;Second email.&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;And it's not only crashes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;an autoretry after a timeout where the other side actually succeeded,&lt;/li&gt;
&lt;li&gt;a user double-clicking a button,&lt;/li&gt;
&lt;li&gt;a scheduled task overlapping its previous run,&lt;/li&gt;
&lt;li&gt;a task that runs longer than the visibility timeout.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Pick your failure mode
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Setting&lt;/th&gt;
&lt;th&gt;Crash during the task&lt;/th&gt;
&lt;th&gt;Guarantee&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Default (early ack)&lt;/td&gt;
&lt;td&gt;The task is &lt;strong&gt;lost&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;At-most-once&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;acks_late=True&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The task &lt;strong&gt;runs again&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;At-least-once&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;For anything important, pick &lt;strong&gt;&lt;code&gt;acks_late&lt;/code&gt; plus idempotency&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;One catch: with &lt;code&gt;task_reject_on_worker_lost&lt;/code&gt;, a task that always runs out of memory gets redelivered forever. Cap it with a delivery counter or time limits.&lt;/p&gt;

&lt;p&gt;So exactly-once &lt;em&gt;execution&lt;/em&gt; isn't available. Aim for an exactly-once &lt;strong&gt;effect&lt;/strong&gt; instead.&lt;/p&gt;

&lt;h2&gt;
  
  
  The strongest guard: a unique constraint
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;EmailLog&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;kind&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;CharField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max_length&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;object_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;BigIntegerField&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;sent_at&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;DateTimeField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;auto_now_add&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Meta&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;constraints&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;UniqueConstraint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;fields&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;kind&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;object_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;uniq_email_per_object&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;]&lt;/span&gt;


&lt;span class="nd"&gt;@shared_task&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;send_order_confirmation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;transaction&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;atomic&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
            &lt;span class="n"&gt;EmailLog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;kind&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;order_confirmation&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;object_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;order_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="nf"&gt;send_confirmation_email&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;IntegrityError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;How it works:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Claim the work first&lt;/strong&gt; by inserting the log row. A duplicate run raises &lt;code&gt;IntegrityError&lt;/code&gt; right there, so it exits quietly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;If sending fails&lt;/strong&gt;, the exception rolls back the log row too, so a retry can still run.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The database enforces it&lt;/strong&gt;, even when two workers hit the same order at the same moment.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Use this for anything that should happen once per object: emails, notifications, certificates, payouts.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bonus: task states and their gotchas
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;State&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;th&gt;Gotcha&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;PENDING&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Waiting, or unknown id&lt;/td&gt;
&lt;td&gt;A typo'd id is PENDING forever&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;STARTED&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;A worker began&lt;/td&gt;
&lt;td&gt;Only with &lt;code&gt;task_track_started = True&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;RETRY&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Failed, retry scheduled&lt;/td&gt;
&lt;td&gt;A new message, same task id&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;SUCCESS&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Returned normally&lt;/td&gt;
&lt;td&gt;Needs a result backend to be visible&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;FAILURE&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Raised, no retries left&lt;/td&gt;
&lt;td&gt;Traceback stored if enabled&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;REVOKED&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Cancelled with &lt;code&gt;revoke()&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;A running task goes on unless &lt;code&gt;terminate=True&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Quick check
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;acks_late=True&lt;/code&gt; is on. A worker sends an email, then is killed before acking. What happens?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It runs again and sends a second email&lt;/strong&gt;, unless the task is idempotent. Celery has no idea the email already went out.&lt;/p&gt;

&lt;h2&gt;
  
  
  Remember
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Exactly-once execution doesn't exist; aim for an exactly-once effect.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;acks_late&lt;/code&gt; plus idempotency for important work.&lt;/li&gt;
&lt;li&gt;A unique constraint is the strongest duplicate guard.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;This article and the video are a free sample from &lt;strong&gt;Django Mastery&lt;/strong&gt;, a video course with 306 short animated lessons that takes you from your first model to production deployment (Django 6.1 and 5.2 LTS), including a full level on Celery and background jobs.&lt;/p&gt;

&lt;p&gt;👉 Full course: &lt;a href="https://djangomastery.gumroad.com/l/django-mastery" rel="noopener noreferrer"&gt;https://djangomastery.gumroad.com/l/django-mastery&lt;/a&gt;&lt;br&gt;
📺 More free lessons: &lt;a href="https://www.youtube.com/@Django-Mastery" rel="noopener noreferrer"&gt;https://www.youtube.com/@Django-Mastery&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The course and this article were produced with AI assistance, and the video uses AI narration.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>django</category>
      <category>python</category>
      <category>celery</category>
      <category>webdev</category>
    </item>
    <item>
      <title>The N+1 query problem in Django: how 3 lines send 101 queries</title>
      <dc:creator>Django Mastery</dc:creator>
      <pubDate>Fri, 02 Oct 2026 14:41:17 +0000</pubDate>
      <link>https://dev.to/djangomastery/the-n1-query-problem-in-django-how-3-lines-send-101-queries-34c9</link>
      <guid>https://dev.to/djangomastery/the-n1-query-problem-in-django-how-3-lines-send-101-queries-34c9</guid>
      <description>&lt;p&gt;Here's a loop that looks completely harmless:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;orders&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;order&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three lines. Show each order with its customer's name. With 100 orders, this sends &lt;strong&gt;101 queries&lt;/strong&gt; to your database.&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/JwPQi2z4M1k" width="710" height="399"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  What actually happens
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;Order.objects.all()&lt;/code&gt; builds a query but sends nothing yet. QuerySets are lazy.&lt;/li&gt;
&lt;li&gt;The loop starts, so Django fetches the orders. &lt;strong&gt;That's 1 query.&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;order.customer&lt;/code&gt; is a foreign key that isn't loaded yet. Every time the loop touches it, Django asks the database again. &lt;strong&gt;That's 1 query per order.&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;100 orders = 1 + 100 = &lt;strong&gt;101 queries&lt;/strong&gt;. That's the N+1 problem: one query for the list, plus one for every row.&lt;/p&gt;

&lt;p&gt;At around 2 ms per query, that's 200 ms added to a single page, and it grows with your data. It looks fine on your laptop with 5 rows and melts in production.&lt;/p&gt;

&lt;h2&gt;
  
  
  The fix is one method call
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;orders&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;select_related&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;customer&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;order&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;select_related&lt;/code&gt; joins the customer into the same query. The loop then reads it from memory. Same loop, same output: &lt;strong&gt;101 queries become 1&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  select_related or prefetch_related?
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;The relation you follow&lt;/th&gt;
&lt;th&gt;Use&lt;/th&gt;
&lt;th&gt;Queries&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;One object: ForeignKey, OneToOne (&lt;code&gt;order.customer&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;select_related()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;1 (JOIN)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Many objects: reverse FK, ManyToMany (&lt;code&gt;order.items.all()&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;prefetch_related()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;2 (second query with &lt;code&gt;IN&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Catch it before production
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;django-debug-toolbar&lt;/strong&gt; shows how many queries each page ran.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;assertNumQueries&lt;/code&gt;&lt;/strong&gt; in a test fails the day someone reintroduces the loop:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_order_list_queries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertNumQueries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/orders/&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Django 6.1&lt;/strong&gt;: &lt;code&gt;fetch_mode(models.FETCH_RAISE)&lt;/code&gt; turns a hidden lazy query into an error, which is great in tests.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Quick check
&lt;/h2&gt;

&lt;p&gt;A view loops over 50 blog posts and prints &lt;code&gt;post.author.name&lt;/code&gt;. How many queries without any fix?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;51.&lt;/strong&gt; One for the posts, plus one per post for its author. &lt;code&gt;select_related("author")&lt;/code&gt; brings it down to 1.&lt;/p&gt;

&lt;h2&gt;
  
  
  Remember
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Looping over rows and touching a related field? Check the query count.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;select_related&lt;/code&gt; for one related object, &lt;code&gt;prefetch_related&lt;/code&gt; for many.&lt;/li&gt;
&lt;li&gt;Measure with the debug toolbar; lock it in with &lt;code&gt;assertNumQueries&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;This article and the video are a free sample from &lt;strong&gt;Django Mastery&lt;/strong&gt;, a video course with 306 short animated lessons that takes you from your first model to production deployment (Django 6.1 and 5.2 LTS).&lt;/p&gt;

&lt;p&gt;👉 Full course: &lt;a href="https://djangomastery.gumroad.com/l/django-mastery" rel="noopener noreferrer"&gt;https://djangomastery.gumroad.com/l/django-mastery&lt;/a&gt;&lt;br&gt;
📺 More free lessons: &lt;a href="https://www.youtube.com/@Django-Mastery" rel="noopener noreferrer"&gt;https://www.youtube.com/@Django-Mastery&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The course and this article were produced with AI assistance, and the video uses AI narration.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>django</category>
      <category>webdev</category>
      <category>database</category>
    </item>
  </channel>
</rss>
