In Django's messages framework, why does messages.debug() show nothing by default, and how do MESSAGE_LEVEL, MESSAGE_TAGS and extra_tags work?
answer
- levels are plain integers
- a threshold at add time
- default recording level is INFO
- tags merge over defaults
basics
~10 sDjango records only messages at or above MESSAGE_LEVEL, which defaults to INFO (20), so DEBUG (10) messages are dropped when added. MESSAGE_TAGS overrides level-to-CSS tags, and extra_tags adds per-message tags to message.tags.
solid answer
~30 sLevels are integers: `DEBUG` 10, `INFO` 20, `SUCCESS` 25, `WARNING` 30, `ERROR` 40. `BaseStorage.add()` ignores messages below the storage's level, which comes from `MESSAGE_LEVEL` or defaults to `INFO`, so `messages.debug()` is silently dropped at add time. Lower it in settings (`MESSAGE_LEVEL = message_constants.DEBUG`, importing `django.contrib.messages.constants` to avoid circular imports) or per request with `messages.set_level(request, messages.DEBUG)`. `message.tags` is `extra_tags` plus the level tag; `MESSAGE_TAGS` is merged over the default tags, so `{message_constants.ERROR: "danger"}` changes just one. Custom levels are just integers with an optional `MESSAGE_TAGS` entry.
code
python · 5 lines# settings.py
from django.contrib.messages import constants as message_constants
MESSAGE_LEVEL = message_constants.DEBUG # development only; default is INFO
MESSAGE_TAGS = {message_constants.ERROR: "danger"} # merged over the defaultsgo deeper
Remember the five levels and that the default threshold is INFO, which is why messages.debug() shows nothing out of the box.
Explain filtering at add time, MESSAGE_LEVEL versus set_level, how message.tags combines extra_tags with the level tag, and MESSAGE_TAGS merging.
Map levels to the project's CSS once in MESSAGE_TAGS, use extra_tags for placement or grouping, and keep debug notices out of production by level.
Define a notice taxonomy across apps, including any custom levels, so reusable apps and the design system agree on severity and styling.
## The level constants Every Django message has an integer **level**. `django.contrib.messages.constants` defines five: | Constant | Value | Default tag | Shortcut | |---|---|---|---| | `DEBUG` | 10 | `debug` | `messages.debug()` | | `INFO` | 20 | `info` | `messages.info()` | | `SUCCESS` | 25 | `success` | `messages.success()` | | `WARNING` | 30 | `warning` | `messages.warning()` | | `ERROR` | 40 | `error` | `messages.error()` | The shortcuts call `messages.add_message(request, level, message, extra_tags="", fail_silently=False)`. ## The recording threshold: MESSAGE_LEVEL The storage has a **minimum recorded level**. `BaseStorage.add()` silently ignores any message whose level is below it, and also ignores empty messages. The threshold comes from the `MESSAGE_LEVEL` setting; if it is not set, Django uses `INFO` (20). That is the answer to "why does `messages.debug()` do nothing?": `DEBUG` is 10, below the default threshold of 20, so the message is never queued. No error is raised. Ways to change it: - **Project-wide**: `MESSAGE_LEVEL = message_constants.DEBUG` in settings. The docs advise importing `django.contrib.messages.constants` directly in settings (not `django.contrib.messages`) to avoid circular imports, or using the numeric value. - **Per request**: `messages.set_level(request, messages.DEBUG)`; `messages.get_level(request)` reads it. `set_level(request, None)` restores the default. ## Tags: level tags, MESSAGE_TAGS and extra_tags A message's `tags` property is a space-separated string built from: 1. its **`extra_tags`**, a free string passed to `add_message()` or a shortcut, and 2. its **level tag**, looked up from the level. The default level tags are the lower-case names above. The `MESSAGE_TAGS` setting is **merged over** those defaults, so you override only what you need. A common case is a CSS framework that expects `danger` rather than `error`: ```python # settings.py from django.contrib.messages import constants as message_constants MESSAGE_TAGS = {message_constants.ERROR: "danger"} ``` Now `messages.error(request, "Upload failed.", extra_tags="avatar")` renders with `message.tags == "avatar danger"`, and `message.level_tag == "danger"`. ## Custom levels Levels are just integers, so you can define your own: ```python CRITICAL = 50 messages.add_message(request, CRITICAL, "Your account is locked.") ``` Give it a tag with `MESSAGE_TAGS = {50: "critical"}`; without an entry, its `level_tag` is an empty string. Keep custom values distinct from the built-ins, and remember that the threshold applies to them too. ## Using levels in templates The `messages` context processor also provides `DEFAULT_MESSAGE_LEVELS`, a mapping of level names to numbers: ```django {% for message in messages %} <li class="{{ message.tags }}"> {% if message.level == DEFAULT_MESSAGE_LEVELS.ERROR %}Important: {% endif %}{{ message }} </li> {% endfor %} ``` ## What add_message() does, step by step 1. It finds the storage that `MessageMiddleware` attached to the request; without it, `MessageFailure` (or nothing, with `fail_silently=True`). 2. The storage ignores the message if the text is empty. 3. It converts the level to `int` and compares it with the storage's level; anything lower is ignored. 4. It creates a `Message(level, message, extra_tags)` and queues it; `MessageMiddleware` stores queued messages when the response goes out. Because the check happens in step 3, the threshold in force **when the message is added** is the one that matters. Reusable apps that emit informational notices therefore cannot assume they will be shown: a project may raise `MESSAGE_LEVEL` to `WARNING` to keep the interface quiet, and every `info()` and `success()` call from those apps is then dropped without a trace. ## Common mistakes - Expecting `messages.debug()` to show in development without lowering `MESSAGE_LEVEL`. - Replacing the whole tag mapping by hand when `MESSAGE_TAGS` only needs the changed entries. - Putting CSS classes into the message text instead of `extra_tags`. - Setting `MESSAGE_LEVEL` in settings via `from django.contrib import messages`, which can cause a circular import. - Assuming filtering happens at display time; it happens when the message is **added**, so a lowered level later in the request does not bring back dropped messages.
- How do you enable debug messages for a single Django request without changing MESSAGE_LEVEL?Call `messages.set_level(request, messages.DEBUG)` before adding them; it changes the threshold on that request's storage only and returns `True` if the storage exists. `messages.set_level(request, None)` restores the default. Messages added before the change were already filtered and do not come back.
- What does message.level_tag return for a custom level such as 50 in Django if MESSAGE_TAGS has no entry for it?An empty string. `level_tag` looks the level up in the default tags merged with `MESSAGE_TAGS`, and 50 is in neither, so `message.tags` contains only any `extra_tags`. Add `MESSAGE_TAGS = {50: "critical"}` to give it a CSS-ready tag.
saying these in an interview costs you the question
- messages.debug() is shown in development automatically when DEBUG = True
- MESSAGE_TAGS replaces all default tags, so every level must be listed
- Messages below the level are stored but hidden in templates
- The default MESSAGE_LEVEL is DEBUG
- extra_tags replaces the level tag in message.tags