<?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: Degisew Mengist</title>
    <description>The latest articles on DEV Community by Degisew Mengist (@py_dagi).</description>
    <link>https://dev.to/py_dagi</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F995744%2F79af15a4-267a-4ff3-964c-ea9bc0d28963.jpg</url>
      <title>DEV Community: Degisew Mengist</title>
      <link>https://dev.to/py_dagi</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/py_dagi"/>
    <language>en</language>
    <item>
      <title>Caching in Django</title>
      <dc:creator>Degisew Mengist</dc:creator>
      <pubDate>Mon, 07 Jul 2025 11:09:53 +0000</pubDate>
      <link>https://dev.to/py_dagi/caching-in-django-5gcc</link>
      <guid>https://dev.to/py_dagi/caching-in-django-5gcc</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Caching&lt;/strong&gt; = &lt;em&gt;&lt;strong&gt;Where to store&lt;/strong&gt; + &lt;strong&gt;What to cache&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  What is Caching?
&lt;/h2&gt;

&lt;p&gt;Caching is the process of storing copies of files or data in a temporary storage location so that future requests for that data can be served much faster than accessing the data’s primary storage location. It can also mean saving the result of an expensive calculation so you don’t have to perform the calculation again.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cache&lt;/strong&gt; is a high-speed data storage layer that stores either the result of an earlier computation or a copy of data stored elsewhere.&lt;/p&gt;

&lt;p&gt;There are different types of caching like &lt;strong&gt;browser&lt;/strong&gt; caching, &lt;strong&gt;CDN&lt;/strong&gt; caching, &lt;strong&gt;DNS&lt;/strong&gt; caching, &lt;strong&gt;in-memory&lt;/strong&gt; caching but here, I’ll focus on &lt;strong&gt;server-side&lt;/strong&gt; caching and how to implement it in Django.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Do We Need Caching?
&lt;/h2&gt;

&lt;p&gt;Every time a user requests a page in your application, the web server performs numerous calculations: database queries, business logic processing, and template rendering (if you’re building a full-stack app). This creates the requested page from scratch each time. This approach becomes expensive, especially for medium- to high-traffic sites, and can lead to poor user experience or even server crashes when traffic exceeds server limits. Caching helps solve this by storing the result of these expensive computations and reuse precomputed responses, which significantly boosting performance and reducing load.&lt;/p&gt;

&lt;p&gt;Now that you understand what caching is and why we need it, let’s explore the steps and techniques for implementing caching in &lt;strong&gt;Django&lt;/strong&gt; — Python’s mature full-stack web framework.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Where to Store the Cache (Cache Backends)
&lt;/h2&gt;

&lt;p&gt;First, you need to decide where to store your cache data. This choice significantly affects your cache’s performance, since some cache types are faster than others.&lt;/p&gt;

&lt;p&gt;Django provides a robust cache system that lets you save dynamic pages so they don’t need to be recalculated for each request. You can choose from several storage options:&lt;/p&gt;

&lt;h3&gt;
  
  
  In-Memory Caching
&lt;/h3&gt;

&lt;p&gt;Stores data in your computer’s RAM, making it extremely fast to access compared to reading from disk or a database. Django supports several in-memory caching options:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Local memory caching&lt;/strong&gt; (Django’s default)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Redis&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Memcached&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  File system Caching
&lt;/h3&gt;

&lt;p&gt;This method stores cache data as separate files in your file system. File system caching is the simplest to configure but becomes the slowest option when storing numerous files.&lt;/p&gt;

&lt;h3&gt;
  
  
  Database Caching
&lt;/h3&gt;

&lt;p&gt;You can use your database to store caches in Django. You might wonder: &lt;em&gt;&lt;strong&gt;"Isn’t the database slow? Isn’t avoiding database hits the whole point of caching?"&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Here’s the key difference: During normal requests, the server performs heavy calculations including database queries, external API calls, and other complex operations. With database caching, you perform these calculations once and store the result in the database. Future requests only need a single, fast query to retrieve the pre-calculated result, instead of repeating all those expensive operations.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: &lt;em&gt;This works best with a fast, well-indexed database server.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Custom Cache Backend
&lt;/h3&gt;

&lt;p&gt;While Django includes several cache backends out-of-the-box, you might occasionally need a customized solution. However, unless you have a compelling reason (such as host limitations), stick to Django’s included cache backends. They’ve been well-tested and are well-documented.&lt;/p&gt;

&lt;p&gt;Note: You can find the cache backends configuration setting in the official &lt;a href="https://docs.djangoproject.com/en/5.2/topics/cache/#setting-up-the-cache" rel="noopener noreferrer"&gt;documentation&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. What to Cache (Caching Strategy)
&lt;/h2&gt;

&lt;p&gt;Once your cache is configured, you can choose what data to cache for fast lookup. Django offers the following main caching strategies:&lt;/p&gt;

&lt;h3&gt;
  
  
  Per-Site Cache
&lt;/h3&gt;

&lt;p&gt;Per-site caching means Django caches the entire response for every request. This works best for mostly static websites.&lt;/p&gt;

&lt;p&gt;To implement per-site caching, add these middleware configurations to your settings:&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;MIDDLEWARE&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;django.middleware.cache.UpdateCacheMiddleware&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="c1"&gt;# ... other middleware ...
&lt;/span&gt;    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;django.middleware.cache.FetchFromCacheMiddleware&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;UpdateCacheMiddleware&lt;/code&gt; saves responses to cache after views run (place first)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;FetchFromCacheMiddleware&lt;/code&gt; checks if URLs are cached before views run (place last)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And add these required settings to your Django settings file:&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;CACHE_MIDDLEWARE_ALIAS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;default&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;  &lt;span class="c1"&gt;# The cache alias for storage
&lt;/span&gt;&lt;span class="n"&gt;CACHE_MIDDLEWARE_SECONDS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;600&lt;/span&gt;      &lt;span class="c1"&gt;# Seconds each page should be cached
&lt;/span&gt;&lt;span class="n"&gt;CACHE_MIDDLEWARE_KEY_PREFIX&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;''&lt;/span&gt;    &lt;span class="c1"&gt;# Site name if using multiple sites
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; &lt;em&gt;Per-site caching only caches &lt;strong&gt;GET&lt;/strong&gt; and &lt;strong&gt;HEAD&lt;/strong&gt; requests.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Per-View Caching
&lt;/h3&gt;

&lt;p&gt;This caches the full response for specific views based on URL and request parameters. If multiple URLs point to the same view, each URL is cached separately. Per-view caching can be implemented using the &lt;code&gt;cache_page&lt;/code&gt; decorator:&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="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django.views.decorators.cache&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;cache_page&lt;/span&gt;

&lt;span class="nd"&gt;@cache_page&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;60&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Cache for 5 minutes
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;my_view&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# view logic here
&lt;/span&gt;    &lt;span class="k"&gt;pass&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For class-based views, use the method decorator:&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="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django.views.decorators.cache&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;cache_page&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django.utils.decorators&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;method_decorator&lt;/span&gt;

&lt;span class="nd"&gt;@method_decorator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;cache_page&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;60&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;5&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;get&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# set the name of the method like get, post
&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MyView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;View&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get&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;request&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="c1"&gt;# view logic here
&lt;/span&gt;        &lt;span class="k"&gt;pass&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But the above implementation has downsides because we’re hard-coding caching into the view itself. That means that the view is always cached, and we can’t easily reuse that view in a non-cached environment. It’s also not flexible since it’s tightly coupled to caching logic.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Better approach:&lt;/strong&gt; Instead of hard-coding caching into views, apply caching in your URL configuration for more flexibility:&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="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django.views.decorators.cache&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;cache_page&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;myapp.views&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;my_view&lt;/span&gt;

&lt;span class="n"&gt;urlpatterns&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nf"&gt;path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;my-url/&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;cache_page&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;60&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;)(&lt;/span&gt;&lt;span class="n"&gt;my_view&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This approach keeps your views reusable and decouples caching logic from view implementation.&lt;/p&gt;

&lt;h3&gt;
  
  
  Template Fragment Caching
&lt;/h3&gt;

&lt;p&gt;For full-stack Django applications, you can cache specific template fragments using the &lt;code&gt;{% cache %}&lt;/code&gt; template tag. This is perfect for expensive-to-render or static page sections like sidebars or dropdowns.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;{% load cache %}
{% cache 300 product_sidebar %}
    &lt;span class="c"&gt;&amp;lt;!-- expensive rendering logic --&amp;gt;&lt;/span&gt;
{% endcache %}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Low-Level Cache API
&lt;/h3&gt;

&lt;p&gt;Use low-level caching when you need to cache any python data that isn’t views or templates, such as &lt;strong&gt;querysets&lt;/strong&gt;, &lt;strong&gt;calculated values&lt;/strong&gt;, or &lt;strong&gt;API responses&lt;/strong&gt;. This API gives you full control over cache keys, timeouts, and invalidation.&lt;/p&gt;

&lt;p&gt;To use low level cache API, you use Django’s built-in default &lt;code&gt;cache&lt;/code&gt; object from &lt;code&gt;django.core.cache&lt;/code&gt;. The default cache object has methods like &lt;code&gt;cache.set()&lt;/code&gt;, &lt;code&gt;cache.get()&lt;/code&gt;, &lt;code&gt;cache.delete()&lt;/code&gt; to manage the cache.&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="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django.core.cache&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;cache&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_expensive_data&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cache_unique_key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cache&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="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# get the data from the cache
&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;expensive_function&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;  &lt;span class="c1"&gt;# Database query or expensive operation
&lt;/span&gt;        &lt;span class="n"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;600&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Cache for 10 minutes
&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, you can use this function wherever you need. The function performs the expensive calculation only once, then serves cached results for subsequent requests. I personally use low-level caching to store data lookups (like dropdown values), because they rarely change. You can also handle cache invalidation on creation or update using Django signals.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Caching is essential for building performant Django applications. Start by choosing the right cache backend for your needs, then implement the appropriate caching strategy based on your application’s requirements. Remember: the best caching strategy often combines multiple approaches. You might use per-view caching for dynamic pages, while employing low-level caching for expensive data lookups.&lt;/p&gt;

&lt;p&gt;The key to successful caching is understanding your application’s bottlenecks and choosing the right caching strategy for each use case. So before doing any caching or performance improvement, track the bottlenecks using tools like &lt;a href="https://github.com/django-commons/django-debug-toolbar" rel="noopener noreferrer"&gt;django debug toolbar&lt;/a&gt;, &lt;a href="https://github.com/jazzband/django-silk" rel="noopener noreferrer"&gt;django silk&lt;/a&gt;. You can also use &lt;a href="https://github.com/locustio/locust" rel="noopener noreferrer"&gt;locust&lt;/a&gt; for performance/load testing.&lt;/p&gt;

</description>
      <category>django</category>
      <category>caching</category>
      <category>backenddevelopment</category>
      <category>webperf</category>
    </item>
  </channel>
</rss>
