skip to content

In Django, how does manage.py shell differ from manage.py dbshell, and what does shell import automatically since Django 5.2?

level: middleimportance: nice to knowfreq 30%

answer

  1. Python with Django set up vs SQL client
  2. psql, mysql, sqlite3, sqlplus
  3. models from every installed app
  4. -v 2 lists the imports
  5. get_auto_imports() and --no-imports

basics

~20 s

shell opens a Python interpreter with Django configured, so you use the ORM; dbshell launches the database's own SQL client with DATABASES credentials. Since 5.2 shell auto-imports every installed app's models, and since 6.0 also settings, connection and timezone.

solid answer

~40 s

`manage.py shell` starts an interactive Python interpreter (IPython, then bpython, then plain Python, unless `-i` picks one) after Django is set up, so you work through models and the ORM, with `-c` to run a snippet and exit. `manage.py dbshell` instead launches the database's command-line client — `psql`, `mysql`, `sqlite3` or `sqlplus` — with the connection details from `DATABASES` (`--database` picks an alias, arguments after `--` go to the client), and you type SQL with no ORM involved. Since Django 5.2 `shell` imports the models of all installed apps automatically; Django 6.0 added common utilities such as `settings`, `connection`, `models`, `functions`, `reset_queries` and `timezone`. `-v 2` lists what was imported, `--no-imports` disables it, and overriding `get_auto_imports()` in your own `shell` command customises the list.

code

python · 10 lines
python
# shop/management/commands/shell.py
from django.core.management.commands import shell


class Command(shell.Command):
    def get_auto_imports(self):
        return super().get_auto_imports() + [
            "django.urls.reverse",
            "shop.services.expire_coupons",
        ]

go deeper

for a junior

Recall that shell is Python with Django configured and dbshell is the database's SQL client using the DATABASES settings.

for a middle

Explain the 5.2 model auto-imports, the 6.0 utilities, -v 2, --no-imports, and customising the list by overriding get_auto_imports().

for a senior

Show judgement about production data fixes: when the ORM's save() and signals must run, when raw SQL is justified, and how to make either reviewable.

for a principal

Set a policy for interactive production access: who may open shell or dbshell, with what audit trail, and when a reviewed management command replaces ad-hoc sessions.

## Two different tools Both are built-in **management commands**, and both give you an interactive prompt, but they live at different layers: | | `manage.py shell` | `manage.py dbshell` | |---|---|---| | What starts | A Python interpreter with Django set up | The database's own command-line client | | Language you type | Python: models, QuerySets, any project code | SQL in that database's dialect | | Goes through | The ORM, model methods, managers, signals | Nothing in Django; raw client to the server | | Connection details | Your settings, via Django | `DATABASES` values passed to the client | | Useful options | `-i ipython/bpython/python`, `-c`, `--no-startup`, `--no-imports` | `--database <alias>`, `-- <client args>` | ## manage.py shell `shell` configures settings and the app registry, then starts the first available interface in the order **IPython, bpython, plain Python** (or the one you name with `-i`). Anything on standard input, or a string passed with `-c`, runs as code and the command exits, which makes it handy for one-off scripted checks: ```bash python manage.py shell -c "print(Coupon.objects.filter(is_active=True).count())" ``` Because it runs your models and managers, writes go through the same `save()` overrides and signal receivers the application uses. That is usually what you want when inspecting or repairing data, and it is the main reason to prefer `shell` over raw SQL for writes. ## Automatic imports (5.2 and 6.0) For years every shell session began with a block of manual imports. Two releases changed that: - **Django 5.2**: `shell` imports the **models of every installed app** into the namespace. If two apps define a model with the same name, the one from the app listed **earlier** in `INSTALLED_APPS` wins. - **Django 6.0**: common utilities are added too — `settings`, `connection`, `models` (the `django.db.models` module), `functions`, `reset_queries` and `timezone`. Controls: 1. `-v 2` (or `--verbosity=2`) prints the generated import lines at startup; the default verbosity prints only a count. 2. `--no-imports` starts with an empty namespace. 3. To add or remove entries, write your own `shell` command in one of your apps, subclass `django.core.management.commands.shell.Command`, and override **`get_auto_imports()`** to return a list of dotted import paths; returning `None` disables auto-imports. There is no setting for this; the customisation point is the method. ## manage.py dbshell `dbshell` reads the engine and connection values (`NAME`, `USER`, `PASSWORD`, `HOST`, `PORT`) for a database alias and executes the matching client: `psql` for PostgreSQL, `mysql` for MySQL, `sqlite3` for SQLite, `sqlplus` for Oracle. Points to remember: - The client program must be on `PATH`; Django cannot be told where it lives. - `--database replica` opens the client against another alias. - Everything after `--` is passed through, for example `manage.py dbshell -- -c 'select count(*) from shop_coupon'` with PostgreSQL. - Nothing Django-side runs: no model validation, no `save()`, no signals. It is the right tool for inspecting the real schema, checking a query plan, or reading data the ORM cannot easily express. ## Common mistakes - Expecting `dbshell` to understand model names: it only knows table names, such as `shop_coupon`, as Django generated them. - Assuming the auto-imported namespace includes every module of the project: by default it carries models and the listed utilities, not views, forms or services. - Forgetting that `-c` and piped input also get the auto-imports, which is why a one-line `shell -c` can reference `Coupon` without importing it — and why the same snippet fails on a Django version older than 5.2. - Leaving a long-lived `shell` session open against production: every expression runs with the application's full database privileges. ## Choosing between them - Investigating or fixing application data: **`shell`**, so business rules still apply. - Inspecting tables, indexes or the SQL-level truth of the schema: **`dbshell`**. - Anything run on production through either tool deserves the same care as a deploy: a written plan, a transaction where appropriate, and a second pair of eyes.

  • Two installed apps each define a model named Order; which one does the 5.2+ shell put in the namespace?
    The one from the app listed earlier in `INSTALLED_APPS`. `get_auto_imports()` walks the models in reverse registry order, so earlier apps overwrite later ones on a name collision. Import the other explicitly under an alias, or override `get_auto_imports()` if the collision is permanent.
  • Why might you fix bad production data through shell rather than an UPDATE in dbshell?
    `shell` goes through the ORM, so model methods, custom managers, `save()` overrides and signal receivers run and keep denormalised data and side effects consistent. A raw `UPDATE` in `dbshell` bypasses all of that. Raw SQL is still right for bulk operations the ORM expresses poorly, as long as you account for what it skips.

saying these in an interview costs you the question

  • dbshell opens a Python prompt with the ORM preloaded
  • Auto-imports are configured through a SHELL_IMPORTS setting
  • shell auto-imports have existed since Django 1.x
  • Writes through dbshell still trigger model signals
  • dbshell can be pointed at a client binary outside PATH