Hello! In our previous lessons, we explored the MVT architecture, models, views, and forms. We learned how to build a data-driven application.
Now, we'll focus on the equally important concepts of serving and optimizing your application. This lesson covers how Django handles files (static vs. media), how it "remembers" users between requests (sessions), and the critical techniques for making your app faster (caching). These are essential topics for building professional, production-ready websites.
This is a two-part answer: how it works in development vs. production.
Static files are the "application files" that you, the developer, provide. This includes all your CSS, JavaScript, fonts, and images (like your site's logo). They are part of your project's code.
Analogy: Static files are like the uniforms, printed menus, and logos for a restaurant chain. They are designed, packaged, and shipped with each new restaurant (deployment).
In `settings.py`, you tell Django where to find your static files:
When `DEBUG = True`, Django's built-in server is smart enough to find and serve these files for you automatically.
In production, your Python app (Django) should not serve static files. This is very inefficient. You want your fast web server (like Nginx) to handle this.
To make this work, you must first run the `collectstatic` command:
$ python manage.py collectstatic
This command copies all static files from all your app folders (`STATICFILES_DIRS`) and puts them into one single folder defined by your `settings.py`:
You then configure Nginx to serve all requests to `/static/` directly from that `STATIC_ROOT` folder, bypassing Django entirely.
Sample Output of `collectstatic`:
131 static files copied to '/var/www/my-site/static'.
This is a critical distinction in Django. Both are "files," but they have completely different origins and purposes.
Analogy: Let's use the restaurant again.
| Feature | Static Files | Media Files |
|---|---|---|
| Source | Provided by the developer. | Uploaded by the user at runtime. |
| Trust Level | Trusted. Part of the application code. | Untrusted. Must be validated. |
| Example | `style.css`, `logo.png`, `main.js` | `user_avatar.jpg`, `uploaded_report.pdf` |
| `settings.py` (URL) | `STATIC_URL` (e.g., `/static/`) | `MEDIA_URL` (e.g., `/media/`) |
| `settings.py` (Path) | `STATIC_ROOT` (for `collectstatic`) | `MEDIA_ROOT` (where uploads are saved) |
The HTTP protocol is stateless. This means every request is independent, and the server forgets you the moment it sends a response.
The Session Framework is the mechanism Django uses to "remember" a user between requests. It allows you to store arbitrary data for each visitor.
Analogy: The Coat Check
This is the best way to think about it:
Django can store session data in different places, configured in `settings.py`:
# A simple view showing how sessions are used
def my_view(request):
# Get a 'visit_count' from the session,
# default to 0 if it doesn't exist
visit_count = request.session.get('visit_count', 0)
# Increment the count
visit_count += 1
# Save it back into the session
request.session['visit_count'] = visit_count
# This count will persist for this user
return HttpResponse(f"You have visited this page {visit_count} times.")
The caching framework is a system for saving the results of expensive operations so you don't have to compute them over and over.
Analogy: The Chef's Hot Plate
Imagine a chef (`View`) has to prepare a very complex sauce (an expensive `SELECT` query).
Django provides a robust, simple-to-use API for caching. You just need to configure your `CACHES` backend in `settings.py`.
# settings.py
# Example configuration for using Redis as the cache
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.redis.RedisCache',
'LOCATION': 'redis://127.0.0.1:6379/1',
'OPTIONS': {
'CLIENT_CLASS': 'django_redis.client.DefaultClient',
}
}
}
# my_app/views.py
from django.core.cache import cache
def my_expensive_view(request):
# 1. Try to get the data from the cache
report = cache.get('my_daily_report')
# 2. If it's not in the cache...
if report is None:
# 3. ...run the expensive query
report = run_very_slow_database_query()
# 4. Save the result in the cache for 15 mins
cache.set('my_daily_report', report, 60 * 15)
return render(request, 'my_report.html', {'report': report})
These are two common strategies for applying the caching framework.
This is the "all-or-nothing" approach. You cache the entire response from a view.
Analogy: The chef puts the entire, fully-plated dish (the whole HTML page) on the hot plate.
from django.views.decorators.cache import cache_page
# This entire view's response will be
# cached for 15 minutes.
@cache_page(60 * 15)
def homepage_view(request):
# ... logic to get blog posts ...
return render(request, 'index.html', context)
This is a more granular approach. You cache just one specific, heavy part of a template.
Analogy: The page is customized ("Welcome, Alice!"), but the "Top 10 Products" sidebar is the same for everyone. The chef makes the custom part of the meal but grabs the sidebar from a pre-made batch.
{% load cache %}
<h1>Welcome, {{ user.username }}!</h1>
<div class="sidebar">
{% cache 500 sidebar %}
<h3>Top 10 Products</h3>
<ul>
{% for product in top_products %}
<li>{{ product.name }}</li>
{% endfor %}
</ul>
{% endcache %}
</div>
3 questions · no sign-up, nothing stored
Progress is stored in this browser only - no sign-up, nothing sent anywhere.
Step through the algorithms behind these answers one operation at a time, forwards or backwards, with the code highlighted as it runs.
The same topic at architecture level, in the system design curriculum.