Overview
You want to build a web application.
You need users.
A database.
Permissions.
Forms.
Sessions.
An administration area.
Emails.
Pages.
URLs.
And protection against a few creative ways of setting the Internet on fire.
At this point, two philosophies appear.
The first consists of choosing a small library and gradually assembling every piece yourself.
The second says:
“These problems appear in almost every application. Why not start with a box that is already full?”
Django clearly belongs to the second school.
A framework that would rather provide too much than too little
Django is an open-source web framework written in Python.
It directly provides a large part of the infrastructure required by a traditional web application:
ORM;
database migrations;
URL routing;
views;
templates;
forms;
authentication;
users, groups, and permissions;
sessions;
administration interface;
internationalization;
static file management;
email;
middleware;
cache;
testing tools;
security;
management commands.
The official website summarizes this philosophy with an expression that has become almost inseparable from the project:
“batteries included.”
The goal is not to solve every possible problem.
It is to prevent every new project from rebuilding the same foundations from scratch.
Django does not only give you the bricks. It already suggests a reasonable way to build the house.
Born in a newsroom, not in an abstract laboratory
Django emerged in the early 2000s at the Lawrence Journal-World newspaper in the United States.
Developers such as Adrian Holovaty and Simon Willison were working on several news websites and needed to quickly produce web applications powered by large amounts of data.
The same needs kept returning.
Models.
Administration.
Publishing.
URLs.
Forms.
Databases.
Instead of solving the same problems again and again, their internal tools gradually formed a coherent framework.
Django was released as open source in 2005.
Its name pays tribute to guitarist Django Reinhardt.
That history explains many of its choices.
Django was not designed as a demonstration of architectural minimalism.
It was built for a much more concrete situation:
you need to ship a serious application, with real data, real people using it, and a deadline that has absolutely no intention of negotiating.
That context still appears in the official motto:
“The web framework for perfectionists with deadlines.”
A complete Python framework, not a website generator
Django does not automatically build an application for you.
It does not replace HTML.
It does not replace CSS.
It does not replace JavaScript.
It does not replace PostgreSQL.
And it does not magically turn an idea into a profitable SaaS before lunch.
It provides a server-side structure.
A Django application receives HTTP requests, executes the required Python logic, potentially communicates with a database, then returns a response:
HTML;
JSON;
a file;
a redirect;
a stream;
or another HTTP response.
The frontend can be rendered directly with Django templates.
Or it can be completely separate, using React, Vue, Svelte, or a mobile application consuming an API.
Django can therefore be used as a traditional full-stack framework.
But also as a backend.
MTV rather than MVC
Django is often compared with the MVC — Model View Controller pattern.
Its historical terminology is slightly different.
Instead, it usually refers to:
Model: the data and its representation;
Template: the presentation layer;
View: the logic that receives the request and decides on the response.
The role that other frameworks sometimes call the “controller” is partly distributed between URL routing and Django views.
You will therefore often encounter the term MTV — Model Template View.
The vocabulary difference matters less than the idea behind it:
separate responsibilities enough that the application can grow without immediately becoming a single 9,000-line app.py file.
A project made of applications
Django generally distinguishes the project from the apps.
The project contains the global configuration.
Apps group more targeted functionality.
For example:
myproject/
├── accounts/
├── articles/
├── billing/
├── dashboard/
└── myproject/
articles can manage content.
accounts the users.
billing the payments.
dashboard the internal views.
This structure encourages modular organization.
A well-designed Django app can even be reused across several projects.
That does not mean every function should be turned into an independent app.
As always, architecture becomes strange when treated as a cutting competition.
But the mechanism provides a useful boundary.
Django 6.1 today
As of August 8, 2026, the latest official release is Django 6.1, published on August 5, 2026.
It officially supports:
Python 3.12;
Python 3.13;
Python 3.14.
Django 6.1 notably continues recent work around ORM performance, administration, Content Security Policy, tasks, email, and several internal components.
But “latest version” does not necessarily mean “best version for every project.”
The Django 5.2 branch is an LTS — Long-Term Support release.
It receives extended support until April 2028.
Django 6.1, meanwhile, is a newer feature release with extended support planned until December 2027.
For a new project, the decision can therefore be:
6.1 for the newest features
or
5.2 LTS for a longer maintenance window.
In April 2027, Django 6.2 is expected to become the next LTS branch.
Features
The ORM: working with data as Python objects
One of Django’s central components is its ORM — Object-Relational Mapper.
You can define a model:
from django.db import models
class Article(models.Model):
title = models.CharField(max_length=200)
published = models.BooleanField(default=False)
created_at = models.DateTimeField(auto_now_add=True)
Django then uses that definition to work with the database.
Create:
Article.objects.create(
title="Understanding Django",
published=True,
)
Search:
articles = Article.objects.filter(published=True)
Sort:
articles = Article.objects.order_by("-created_at")
The idea is to manipulate data through a Python API instead of writing SQL directly for every operation.
That speeds development up enormously.
But the ORM does not erase the database.
Under the Python objects there are still:
tables;
indexes;
joins;
constraints;
transactions;
queries;
execution plans.
The convenience of the ORM is enormous.
It becomes even better once you understand what it generates.
Migrations: evolving the schema alongside the code
A model changes.
A field appears.
Another becomes nullable.
A relationship is added.
Django can generate a migration:
python manage.py makemigrations
Then apply it:
python manage.py migrate
Migrations describe the evolution of the database schema.
They can be versioned with Git.
A team can therefore share not only its code, but also the structural history of its data.
That almost feels ordinary today.
Until you imagine manually coordinating the SQL changes of fifteen developers across several environments.
Migrations suddenly become very friendly.
Administration: probably the feature that sells Django in five minutes
You define a model.
Register it in the admin.
And Django can generate a management interface.
List.
Search.
Filters.
Creation.
Editing.
Deletion.
Relationships.
Permissions.
All of that from the models.
from django.contrib import admin
from .models import Article
admin.site.register(Article)
The administration interface can then be heavily customized.
Columns.
Filters.
Actions.
Forms.
Inline relationships.
Search.
Permissions.
Templates.
It is an extraordinary feature for:
back offices;
internal CMSs;
catalogs;
editorial teams;
business tools;
administrable databases.
But its purpose needs to be understood.
Django Admin is an interface for people who administer data. It is not automatically the frontend your users should see.
Confusing the two quickly leads to trying to turn an excellent internal interface into a consumer product with enough CSS to begin negotiating directly with fate.
Authentication, users, and permissions
Django has its own authentication system.
It notably provides:
users;
passwords;
hashing;
groups;
permissions;
sessions;
login;
logout;
password reset;
customizable authentication backends.
Models can automatically receive the traditional permissions:
add
change
delete
view
These permissions can then be assigned to a user or group.
That provides a solid foundation for many business applications.
Django can also be extended to work with external authentication systems or custom user models.
And one piece of advice appears regularly throughout the ecosystem:
if the project will probably need a custom user model, define it early.
Changing the user model once fifteen applications and three years of migrations refer to it is technically possible.
Like moving the kitchen after finishing the building.
Forms and validation
Web forms have the remarkable ability to look simple until you actually need to manage:
validation;
error messages;
types;
required values;
files;
security;
redisplaying submitted data;
relationships with models.
Django provides a Forms and ModelForms system.
from django import forms
from .models import Article
class ArticleForm(forms.ModelForm):
class Meta:
model = Article
fields = ["title", "published"]
A form can:
validate input;
convert types;
produce structured errors;
generate HTML fields;
connect directly to a model.
It is less spectacular than a new JavaScript framework with an animated homepage.
But extremely useful when you need to build the seventy-second form in a business application.
URL routing
Django has an explicit router.
from django.urls import path
from . import views
urlpatterns = [
path("articles/", views.article_list, name="article-list"),
]
A URL is associated with a view.
Patterns can be grouped by app.
Routes can be named.
Templates and code can then generate URLs without hardcoding paths everywhere.
It is a small abstraction.
But it prevents a lot of broken links as the application evolves.
Function-based or class-based views
A Django view can be a simple function:
def home(request):
return render(request, "home.html")
Or a class-based view.
Django provides many generic views for common operations:
list;
detail;
creation;
editing;
deletion;
redirect;
templates.
Class-based views can remove a lot of repetition.
They can also create that special moment when you are looking through five levels of inheritance trying to understand why a method is being called.
Both styles are therefore useful.
The best view is generally the one whose behavior the team can still understand six months later.
Templates: producing HTML on the server
Django has its own template engine.
{% for article in articles %}
<article>
<h2>{{ article.title }}</h2>
</article>
{% endfor %}
It supports:
variables;
loops;
conditions;
inheritance;
includes;
filters;
custom tags.
Since Django 6.0, the template language also includes built-in template partials, making it easier to define and reuse small fragments.
This evolution accompanies renewed interest in modern server-side architectures where Django is combined with HTMX, Alpine.js, or small JavaScript layers rather than a complete SPA.
Maybe old-school server rendering was not dead.
It was just taking a long marketing nap.
An administration interface that follows the models
The admin does more than display tables.
It understands model relationships.
Foreign keys.
Many-to-many.
Permissions.
Filters.
Search.
Actions.
Widgets.
In a few minutes, it can provide an internal tool that would otherwise have required several days of custom development.
It is probably one of the purest expressions of Django’s philosophy:
write the domain structure once, then let several parts of the framework make use of it.
The model feeds the ORM.
The migrations.
The forms.
The admin.
The permissions.
That consistency dramatically reduces duplication.
Built-in security
Django has long had a serious reputation for web security.
The framework provides protection against several common classes of attacks, including:
SQL injection through parameterized ORM queries;
Cross-Site Request Forgery — CSRF;
Cross-Site Scripting — XSS in the template engine when escaping is used normally;
clickjacking;
password storage with appropriate hashing algorithms;
secure sessions;
HTTPS headers and settings.
Django 6.0 added built-in support for Content Security Policy — CSP.
An application can now configure CSP policies and their headers directly through the framework.
That obviously does not mean:
“Django is secure, so I can stop thinking about security.”
A developer can still:
disable CSRF;
inject HTML marked as safe;
write incorrect raw SQL;
expose a SECRET_KEY;
leave DEBUG = True in production;
misconfigure HTTPS.
The framework provides guardrails.
It cannot stop someone from removing them.
Middleware: intervening in the request-response cycle
Middleware lets you insert a layer around request processing.
It can be used for:
authentication;
sessions;
security;
compression;
logging;
headers;
localization;
performance measurement;
project-specific logic.
A request passes through several middleware components before reaching the view.
The response then passes back through the chain.
It is a very practical architecture for cross-cutting behavior.
As long as middleware does not become the drawer where all the logic that had nowhere else to go gets hidden.
Sessions, messages, and cookies
Django includes a session infrastructure.
An application can associate temporary data with a user across several requests.
The framework also has a messages system that can display, for example:
“Article created”
“Payment failed”
“Profile updated”
after a redirect.
These are small features.
But the “batteries included” philosophy is measured precisely through details like these.
A real application is made of hundreds of small, unspectacular needs.
Django tries to have already solved many of them.
Django provides an abstraction for sending email.
SMTP.
Development backends.
Custom backends.
HTML messages.
Attachments.
Since Django 6.1, the system is evolving toward a new MAILERS configuration capable of managing several email backends.
You could imagine:
a transactional backend;
a marketing backend;
a specific backend for certain notifications.
Another feature that you could build yourself.
And another reason you may not need to.
Background tasks
Django 6.0 introduced a Tasks framework.
It provides an abstraction for:
defining a task;
validating it;
placing it in a queue;
managing its result.
For example, sending an email or processing data can leave the main HTTP cycle.
But there is an important nuance:
Django does not itself provide the production worker that executes these tasks.
The built-in backends are primarily intended for development and testing.
Actual execution requires suitable external infrastructure.
The framework therefore now provides the contract.
Not the entire factory.
Celery, Dramatiq, or other systems can still have a place depending on the project.
Synchronous and asynchronous
Django was historically a synchronous framework built around WSGI.
It now has growing support for ASGI and asynchronous code.
Views can be async.
Some operations have asynchronous versions.
The ecosystem continues to move in that direction.
But Django did not suddenly become a fully async framework at every layer.
Some parts remain synchronous.
Some third-party libraries do too.
And calling blocking code inside an async view remains an excellent way to build a very modern architecture that waits very efficiently.
Several officially supported databases
Django officially supports:
PostgreSQL
MariaDB
MySQL
Oracle
SQLite
SQLite is particularly convenient for getting started.
It requires no separate server.
For a substantial application, Django’s documentation generally recommends developing with the same database engine planned for production.
PostgreSQL is now an extremely common choice throughout the Django ecosystem.
The framework also provides additional PostgreSQL-specific functionality through django.contrib.postgres.
Cache
Django has a cache abstraction.
Depending on the infrastructure, the cache can store:
expensive results;
pages;
fragments;
sessions;
frequently accessed data.
It can work with several backends.
An application can therefore start simply and introduce more caching when the load actually requires it.
Which is often preferable to building a distributed caching architecture on Monday morning for the twelve users expected by Friday.
Internationalization and localization
Django has long provided tools for:
translating strings;
handling several languages;
adapting date formats;
working with time zones;
generating translation catalogs;
detecting or selecting a language.
For a multilingual project, this infrastructure prevents translations from becoming a homemade collection of conditions scattered throughout the templates.
Management commands
The manage.py file provides access to many commands:
python manage.py runserver
python manage.py migrate
python manage.py createsuperuser
python manage.py collectstatic
python manage.py test
python manage.py check
Projects can also create their own commands.
This is extremely useful for:
imports;
maintenance;
data migration;
automation;
administrative tasks;
internal scripts.
Instead of creating twenty isolated scripts that nobody remembers whether they should still be used, these operations can be integrated directly into the Django environment.
Use cases
Building a data-rich business application
Users.
Customers.
Orders.
Documents.
Permissions.
Statuses.
History.
Administration.
Search.
Relationships.
Django is particularly comfortable when the heart of an application looks like this.
You define the models.
Build the business rules.
The ORM manages the data.
The admin quickly provides a back office.
Authentication controls access.
Forms manage input.
This combination makes Django extremely effective for management applications.
Building a CMS or editorial platform
Django’s own history comes from web publishing.
The framework therefore remains a natural fit for:
articles;
categories;
authors;
media;
translations;
editorial permissions;
publishing workflows;
SEO;
back office.
Django itself is not a ready-made CMS.
But it is an excellent foundation for building one.
Projects such as Wagtail show just how far this architecture can be pushed.
Creating a SaaS
A SaaS application often has:
accounts;
teams;
permissions;
subscriptions;
dashboards;
emails;
database;
back office;
API;
background tasks.
Django already provides a large part of this plumbing.
What remains is building what actually differentiates the product.
Which, in theory, was the point all along.
Quickly building an internal tool
A company needs to manage:
inventory;
projects;
requests;
customers;
documents;
catalogs;
approvals.
The application does not necessarily need a spectacular frontend.
It needs to work.
Django Admin becomes particularly formidable here.
A few models.
A few permissions.
A few customizations.
And the team already has a usable internal interface.
Not necessarily the one that will win a design award.
But the one that might stop twenty people from continuing to run the business with final_spreadsheet_2026_v12_fixed.xlsx.
Creating an API
Django can absolutely serve as an API backend.
It can return JSON directly.
But Django core alone does not provide the entire experience of a specialized REST framework.
For that, the ecosystem widely uses:
Django REST Framework
or solutions such as:
Django Ninja.
Django REST Framework notably adds:
serializers;
viewsets;
API permissions;
authentication;
pagination;
browsable API;
REST tooling.
Django then becomes the layer for:
models;
ORM;
users;
business logic;
admin;
infrastructure.
And DRF exposes that logic to the frontend or mobile applications.
Backend for a separate frontend application
A React, Vue, or Svelte application can completely ignore Django templates.
The frontend requests:
/api/articles/
Django checks the user.
Queries PostgreSQL.
Applies permissions.
Returns JSON.
In this scenario, Django plays the role of a structured backend.
Its admin can continue serving the internal team while the public uses a completely different interface.
That is one of its most practical advantages.
The frontend and back office do not need to be the same visual application.
Building with very little JavaScript
Django can also take the opposite approach.
Server-side templates.
Django forms.
HTMX for a few interactions.
Alpine.js for certain local behaviors.
It is therefore possible to create highly interactive applications without necessarily building a complete SPA.
This approach is experiencing renewed interest.
It can reduce:
frontend build complexity;
duplicated validation;
state management;
unnecessary internal APIs;
number of dependencies.
Not everything requires two separate applications communicating in JSON just to display an “Edit” button.
Building a prototype that needs to survive
There are excellent frameworks for creating a demonstration in a few minutes.
The problem begins when the demonstration becomes the product.
Django has a particular quality:
you can start fairly quickly while keeping foundations capable of supporting a much larger project.
Models.
Migrations.
Tests.
Apps.
Permissions.
Settings.
Admin.
The structure already exists.
The prototype can grow without immediately requiring a complete rewrite.
Provided, obviously, that the prototype has not turned views.py into a 7,000-line diary.
PANACHES review
Django has a quality that has become almost unusual in modern development:
it openly admits that it has opinions.
An application has models.
A settings system.
Apps.
Migrations.
URLs.
Templates.
Conventions.
An admin.
Django does not look at you on first launch and say:
“Here is an HTTP function. Good luck with the rest.”
It provides a framework.
Its real strength is the consistency between the pieces
Taken individually, almost every Django component has an alternative.
ORM?
SQLAlchemy.
Templates?
Jinja.
Forms?
Other libraries exist.
Auth?
More alternatives.
Admin?
Specialized packages.
Routing?
Easy to find.
Django’s power therefore does not come from the fact that no other technology can do these things.
It comes from the fact that all these pieces were designed to work together.
The model feeds the migrations.
The ORM uses the models.
ModelForms use the models.
The admin uses the models.
Permissions use the models.
That continuity dramatically reduces the number of decisions and manual connections.
Batteries included or imposed dependencies?
It depends entirely on how you look at it.
For some developers:
“Great, everything is here.”
For others:
“Why is everything already here?”
That is exactly the boundary between Django and microframeworks.
With Flask, you choose more of the components yourself.
With FastAPI, you get an excellent modern base for building APIs, then assemble the rest according to your needs.
With Django, many decisions have already been made.
That constraint can dramatically accelerate a project.
It can also feel excessive if the application only needs three JSON endpoints.
Django vs FastAPI
The comparison comes up often.
But the two tools do not start from exactly the same place.
FastAPI excels at building modern APIs, notably through Python type annotations, OpenAPI, and its async orientation.
Django aims at a broader scope:
data;
auth;
admin;
templates;
sessions;
forms;
migrations;
complete business applications.
If the goal is:
“I want eight extremely clean API endpoints”
FastAPI may feel natural.
If the goal becomes:
“users, roles, admin, thirty models, forms, emails, CMS, back office, and an API”
Django starts to show why it carries more luggage.
Django vs Flask
Flask takes almost the opposite philosophy.
A small core.
Lots of freedom.
Add what the project needs.
That can be excellent for:
small services;
prototypes;
special-purpose applications;
teams wanting to choose each component precisely.
Django prefers to provide a more complete architecture.
The question is therefore not:
“Which one is better?”
But:
“How many decisions do we want to make ourselves?”
Django vs Laravel or Ruby on Rails
Laravel and Ruby on Rails are probably philosophically closer to Django.
Full-stack frameworks.
ORM.
Migrations.
Conventions.
Large ecosystems.
Rapid development.
Rails has historically pushed convention over configuration very far.
Laravel benefits from an enormous PHP ecosystem and a highly polished developer experience.
Django, on the other hand, has the advantage of living inside the Python universe.
And that universe has become enormous.
Data.
Scripts.
AI.
Automation.
Image processing.
Research.
Django can therefore connect naturally to many Python libraries without creating a second technology stack.
The admin remains a superpower
It is easy to underestimate Django Admin because it is not very spectacular.
Then a project arrives with forty models.
Non-technical people need to modify the data.
Permissions are needed.
Filters.
Search.
Actions.
Relationships.
And suddenly the team realizes that it had a largely functional back office from day one.
It is probably one of the features with the greatest ratio of:
lines of code written / actual work avoided.
Django is not old. It is mature.
Django has existed for more than twenty years.
In technology, that can trigger a strange reflex:
“If it has existed for a long time, there must be something more modern.”
Sometimes yes.
But “newer” and “better” are not synonyms.
Django has:
years of fixes;
enormous documentation;
stabilized conventions;
a mature ecosystem;
solid security practices;
battle-tested migrations;
a worldwide community;
companies that have used it in production for years.
Maturity produces fewer exciting screenshots than a new framework released on Thursday.
It often produces more peaceful nights.
But Django still needs to evolve
The web of 2026 is no longer the web of 2005.
Async.
APIs.
SPAs.
HTMX.
WebSockets.
Background tasks.
CSP.
Type hints.
AI.
Distributed architectures.
Django has had to gradually integrate these changes.
Versions 6.0 and 6.1 show that movement clearly:
built-in CSP;
template partials;
tasks framework;
async improvements;
email evolution;
new ORM optimizations.
The balance is delicate.
Moving too fast would break an enormous ecosystem.
Moving too slowly would turn Django into a museum.
For now, the project continues to walk that line rather well.
Django does not try to be the smallest framework. It tries to make enough ordinary problems disappear so you can spend your time on the problems specific to your application.
Points to consider
Django 6.1 is not an LTS
As of August 8, 2026, Django 6.1 is the latest official version.
Its mainstream support is expected to end in April 2027.
Its extended support in December 2027.
The 5.2 LTS branch remains supported until April 2028.
That creates two perfectly valid strategies.
Project wanting the newest features:
Django 6.1
Project prioritizing a long and very stable support window:
Django 5.2 LTS
The next planned LTS is Django 6.2, announced for April 2027.
A newer version is therefore not automatically a better operational choice.
Django 6.1 requires a recent Python
Django 6.1 officially supports:
Python 3.12;
3.13;
3.14.
An old project still running Python 3.10 or 3.11 therefore cannot simply install Django 6.1 and hope everyone negotiates politely.
Django 5.2 is the last series to retain support for Python 3.10 and 3.11.
A Django upgrade can therefore involve a Python upgrade.
Then a dependency review.
Then tests.
Then, possibly, a sufficiently large cup of coffee.
The ORM does not remove the need to understand SQL
Writing:
Article.objects.all()
is easy.
Writing an ORM query that triggers 4,000 SQL queries without realizing it is unfortunately easy too.
The classic N+1 problem remains possible.
Django provides:
select_related()
prefetch_related()
and, since Django 6.1, new fetch modes capable of improving certain lazy-loading scenarios.
But no abstraction completely removes the need to understand:
joins;
indexes;
cardinality;
transactions;
query cost.
An ORM makes SQL more comfortable.
Not intellectually optional.
Migrations can conflict
Two developers modify the same model on two branches.
Each generates a migration.
The branches are merged.
Django now discovers two different futures for the same history.
Sometimes the migrations need to be merged or reorganized.
This is not a weakness specific to Django.
It is the logical consequence of trying to version the evolution of a database within a distributed project.
But the more developers and migrations an application has, the more important that discipline becomes.
The admin is not a universal user interface
Django Admin is designed for trusted users who manage data.
It can be customized.
A lot.
But trying to turn it into a public-facing frontend quickly reveals limits.
UX.
Navigation.
Design.
Highly specific workflows.
Mobile.
Advanced interactions.
The admin is excellent when it stays close to its purpose:
administering the system’s data.
For the final product, dedicated views are generally preferable.
runserver is not a production server
The command:
python manage.py runserver
is fantastic for development.
It is not intended to serve an application in production.
Django must then be deployed through suitable infrastructure using WSGI or ASGI.
For example:
Gunicorn;
uWSGI;
Uvicorn;
Daphne;
Hypercorn;
depending on the chosen architecture.
You also need to manage:
reverse proxy;
HTTPS;
processes;
logs;
monitoring;
static files;
media;
timeouts;
restarts;
security.
The “deploy” button is not included in the framework.
DEBUG = True must never end up in production
During development, DEBUG = True provides extremely detailed error pages.
Tracebacks.
Variables.
Settings.
Information about the code.
That is wonderful while developing.
And exactly the kind of information you do not want to display to a stranger on the Internet.
In production:
DEBUG = False
is not an aesthetic suggestion.
It is a basic security measure.
The SECRET_KEY is actually secret
Django uses SECRET_KEY for several cryptographic signing mechanisms.
It should not be:
publicly committed;
reused everywhere;
hardcoded inside a public repository;
copied from a tutorial.
In production, it should come from a secure source:
environment variable;
secret manager;
private configuration.
Django even provides manage.py check --deploy to flag several dangerous configurations.
The framework tries to warn you.
But it cannot physically pull the .env file out of the developer’s hands before git add ..
Static files and media require a real strategy
CSS.
JavaScript.
Interface images.
User uploads.
These elements should not be handled exactly like Python code.
Django provides:
STATIC_URL
STATIC_ROOT
MEDIA_URL
MEDIA_ROOT
and the command:
python manage.py collectstatic
But in production, you need to decide where these files actually live.
Nginx.
CDN.
S3 or compatible storage.
Persistent volume.
External service.
User uploads are particularly important.
The application container can be disposable.
User photos, considerably less so.
The tasks system does not yet provide the entire infrastructure
Django 6.0 added a tasks framework.
That is an important evolution.
But Django does not provide an integrated production worker that executes these tasks by itself.
External infrastructure is still required.
So you should not read:
“Django now has tasks”
as:
“Celery, Redis, and workers no longer exist.”
The new framework standardizes part of the API.
Execution still depends on the selected backend and architecture.
Async does not mean all of Django has become async
Django supports WSGI and ASGI.
It has async views and a growing number of asynchronous APIs.
But some parts of the framework or ecosystem remain synchronous.
A project therefore needs to understand where the boundaries are.
Otherwise, you can create a view:
async def my_view(request):
...
and immediately call three blocking libraries inside it.
The word async is definitely there.
The benefit, slightly less so.
Django REST Framework is not Django
The confusion is common.
Django REST Framework — DRF is a third-party project built on top of Django.
It has its own version.
Its own documentation.
Its own maintenance cycle.
Django itself can produce JSON and build an API.
But serializers, viewsets, and many REST features commonly associated with Django often come from DRF.
That distinction matters when evaluating a project’s dependencies.
A modern frontend can add an entire second architecture
Django + React.
Django + Vue.
Django + Next.js.
That is perfectly valid.
But it can mean:
backend routing;
frontend routing;
backend validation;
frontend validation;
backend auth;
frontend state;
Node build;
Python build;
API;
CORS;
two dependency ecosystems.
A SPA is therefore not automatically “more modern.”
It is primarily a different architecture.
For some applications, it is essential.
For others, Django templates combined with HTMX or a little JavaScript can be much simpler.
Django applications can become very comfortable monoliths
Django makes adding functionality easy.
Sometimes too easy.
You add an app.
Then another.
Then a model.
Then some logic in signals.py.
Then a function in utils.py.
Then a service.
Then a model method that sends an email and modifies three other tables because “it was convenient.”
Ten years later, the monolith is still running.
Nobody dares look directly at it.
Django does not create this problem.
But its productivity can allow a project to become large before the team is forced to think seriously about architectural boundaries.
Signals are powerful and easy to make invisible
Django has a signals system.
An object is saved.
A function triggers somewhere else.
That is useful for decoupling certain operations.
But too many signals can turn the program flow into a treasure hunt.
You save a user.
Why did three emails get sent?
Why was an invoice just created?
Why did the cache change?
Ah.
post_save.
In a large business application, explicit logic is often easier to follow than a forest of invisible side effects.
Choosing SQLite at the beginning can hide certain behaviors
SQLite is perfect for:
learning;
testing;
prototyping;
small projects.
It is built into Python and requires very little configuration.
But PostgreSQL, MySQL, MariaDB, and Oracle do not behave in exactly the same way.
Types.
Constraints.
Concurrency.
SQL functions.
Transactions.
If an important application is going to use PostgreSQL in production, developing with PostgreSQL early enough reduces the risk of discovering those differences the day before deployment.
Django’s documentation also recommends this consistency for substantial projects.
A new Django version deserves a read through the release notes
Django has a relatively disciplined deprecation system.
But an upgrade can still bring:
incompatible changes;
removals;
new minimum database versions;
dependency changes;
new behavior.
Django 6.1, for example, now requires at least:
PostgreSQL 15;
MySQL 8.4;
MariaDB 10.11;
SQLite 3.37.
Updating the framework without looking at the infrastructure underneath it can therefore produce a few surprises.
Open source does not mean “maintenance-free”
Django is free.
BSD.
Open source.
No commercial per-user license.
No subscription to activate the premium ORM.
But a real Django application still has costs.
Server.
Database.
Storage.
Backups.
Emails.
CDN.
Monitoring.
Maintenance.
Security updates.
Development.
The framework is free.
The application using the framework still lives in the physical world of bills and machines.
When is Django too big?
If the project is essentially:
three endpoints;
no complex database;
no back office;
no users;
no forms;
no sessions;
no significant business logic;
then Django can indeed feel heavy.
FastAPI or Flask may be more direct.
Conversely, if three months later the small service has:
users;
permissions;
emails;
twenty models;
admin;
dashboard;
back office;
payments;
then Django’s amount of functionality suddenly starts to look less like weight and more like work that has already been done.
That is probably the right way to choose.
Not:
“Is Django modern?”
But:
“How many of the problems Django solves am I actually going to encounter?”
Django is most worthwhile when your application has enough business logic, data, and users that rebuilding the foundations starts becoming a distraction.