diff --git a/debug_toolbar/panels/alerts.py b/debug_toolbar/panels/alerts.py
new file mode 100644
index 000000000..27a7119ee
--- /dev/null
+++ b/debug_toolbar/panels/alerts.py
@@ -0,0 +1,148 @@
+from html.parser import HTMLParser
+
+from django.utils.translation import gettext_lazy as _
+
+from debug_toolbar.panels import Panel
+
+
+class FormParser(HTMLParser):
+ """
+ HTML form parser, used to check for invalid configurations of forms that
+ take file inputs.
+ """
+
+ def __init__(self):
+ super().__init__()
+ self.in_form = False
+ self.current_form = {}
+ self.forms = []
+ self.form_ids = []
+ self.referenced_file_inputs = []
+
+ def handle_starttag(self, tag, attrs):
+ attrs = dict(attrs)
+ if tag == "form":
+ self.in_form = True
+ form_id = attrs.get("id")
+ if form_id:
+ self.form_ids.append(form_id)
+ self.current_form = {
+ "file_form": False,
+ "form_attrs": attrs,
+ "submit_element_attrs": [],
+ }
+ elif (
+ self.in_form
+ and tag == "input"
+ and attrs.get("type") == "file"
+ and (not attrs.get("form") or attrs.get("form") == "")
+ ):
+ self.current_form["file_form"] = True
+ elif (
+ self.in_form
+ and (
+ (tag == "input" and attrs.get("type") in {"submit", "image"})
+ or tag == "button"
+ )
+ and (not attrs.get("form") or attrs.get("form") == "")
+ ):
+ self.current_form["submit_element_attrs"].append(attrs)
+ elif tag == "input" and attrs.get("form"):
+ self.referenced_file_inputs.append(attrs)
+
+ def handle_endtag(self, tag):
+ if tag == "form" and self.in_form:
+ self.forms.append(self.current_form)
+ self.in_form = False
+
+
+class AlertsPanel(Panel):
+ """
+ A panel to alert users to issues.
+ """
+
+ messages = {
+ "form_id_missing_enctype": _(
+ 'Form with id "{form_id}" contains file input, but does not have the attribute enctype="multipart/form-data".'
+ ),
+ "form_missing_enctype": _(
+ 'Form contains file input, but does not have the attribute enctype="multipart/form-data".'
+ ),
+ "input_refs_form_missing_enctype": _(
+ 'Input element references form with id "{form_id}", but the form does not have the attribute enctype="multipart/form-data".'
+ ),
+ }
+
+ title = _("Alerts")
+
+ template = "debug_toolbar/panels/alerts.html"
+
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ self.alerts = []
+
+ @property
+ def nav_subtitle(self):
+ alerts = self.get_stats()["alerts"]
+ if alerts:
+ alert_text = "alert" if len(alerts) == 1 else "alerts"
+ return f"{len(alerts)} {alert_text}"
+ else:
+ return ""
+
+ def add_alert(self, alert):
+ self.alerts.append(alert)
+
+ def check_invalid_file_form_configuration(self, html_content):
+ """
+ Inspects HTML content for a form that includes a file input but does
+ not have the encoding type set to multipart/form-data, and warns the
+ user if so.
+ """
+ parser = FormParser()
+ parser.feed(html_content)
+
+ # Check for file inputs directly inside a form that do not reference
+ # any form through the form attribute
+ for form in parser.forms:
+ if (
+ form["file_form"]
+ and form["form_attrs"].get("enctype") != "multipart/form-data"
+ and not any(
+ elem.get("formenctype") == "multipart/form-data"
+ for elem in form["submit_element_attrs"]
+ )
+ ):
+ if form_id := form["form_attrs"].get("id"):
+ alert = self.messages["form_id_missing_enctype"].format(
+ form_id=form_id
+ )
+ else:
+ alert = self.messages["form_missing_enctype"]
+ self.add_alert({"alert": alert})
+
+ # Check for file inputs that reference a form
+ form_attrs_by_id = {
+ form["form_attrs"].get("id"): form["form_attrs"] for form in parser.forms
+ }
+
+ for attrs in parser.referenced_file_inputs:
+ form_id = attrs.get("form")
+ if form_id and attrs.get("type") == "file":
+ form_attrs = form_attrs_by_id.get(form_id)
+ if form_attrs and form_attrs.get("enctype") != "multipart/form-data":
+ alert = self.messages["input_refs_form_missing_enctype"].format(
+ form_id=form_id
+ )
+ self.add_alert({"alert": alert})
+
+ return self.alerts
+
+ def generate_stats(self, request, response):
+ html_content = response.content.decode(response.charset)
+ self.check_invalid_file_form_configuration(html_content)
+
+ # Further alert checks can go here
+
+ # Write all alerts to record_stats
+ self.record_stats({"alerts": self.alerts})
diff --git a/debug_toolbar/settings.py b/debug_toolbar/settings.py
index ca7036c34..48483cf40 100644
--- a/debug_toolbar/settings.py
+++ b/debug_toolbar/settings.py
@@ -67,6 +67,7 @@ def get_config():
"debug_toolbar.panels.sql.SQLPanel",
"debug_toolbar.panels.staticfiles.StaticFilesPanel",
"debug_toolbar.panels.templates.TemplatesPanel",
+ "debug_toolbar.panels.alerts.AlertsPanel",
"debug_toolbar.panels.cache.CachePanel",
"debug_toolbar.panels.signals.SignalsPanel",
"debug_toolbar.panels.redirects.RedirectsPanel",
diff --git a/debug_toolbar/templates/debug_toolbar/panels/alerts.html b/debug_toolbar/templates/debug_toolbar/panels/alerts.html
new file mode 100644
index 000000000..df208836d
--- /dev/null
+++ b/debug_toolbar/templates/debug_toolbar/panels/alerts.html
@@ -0,0 +1,12 @@
+{% load i18n %}
+
+{% if alerts %}
+
{% trans "Alerts found" %}
+ {% for alert in alerts %}
+
+
{{ alert.alert }}
+
+ {% endfor %}
+{% else %}
+
{% trans "No alerts found" %}
+{% endif %}
diff --git a/docs/changes.rst b/docs/changes.rst
index 2eaece240..2a4a52ac9 100644
--- a/docs/changes.rst
+++ b/docs/changes.rst
@@ -4,6 +4,8 @@ Change log
Pending
-------
+* Added alert panel with warning when form is using file fields
+ without proper encoding type.
* Fixed overriding font-family for both light and dark themes.
* Restored compatibility with ``iptools.IpRangeList``.
diff --git a/docs/configuration.rst b/docs/configuration.rst
index 04694aceb..42f90178a 100644
--- a/docs/configuration.rst
+++ b/docs/configuration.rst
@@ -29,6 +29,7 @@ default value is::
'debug_toolbar.panels.sql.SQLPanel',
'debug_toolbar.panels.staticfiles.StaticFilesPanel',
'debug_toolbar.panels.templates.TemplatesPanel',
+ 'debug_toolbar.panels.alerts.AlertsPanel',
'debug_toolbar.panels.cache.CachePanel',
'debug_toolbar.panels.signals.SignalsPanel',
'debug_toolbar.panels.redirects.RedirectsPanel',
diff --git a/docs/panels.rst b/docs/panels.rst
index 33359ea46..c9ea6bbf0 100644
--- a/docs/panels.rst
+++ b/docs/panels.rst
@@ -9,6 +9,17 @@ Default built-in panels
The following panels are enabled by default.
+Alerts
+~~~~~~~
+
+.. class:: debug_toolbar.panels.alerts.AlertsPanel
+
+This panel shows alerts for a set of pre-defined cases:
+
+- Alerts when the response has a form without the
+ ``enctype="multipart/form-data"`` attribute and the form contains
+ a file input.
+
History
~~~~~~~
diff --git a/example/templates/bad_form.html b/example/templates/bad_form.html
new file mode 100644
index 000000000..f50662c6e
--- /dev/null
+++ b/example/templates/bad_form.html
@@ -0,0 +1,14 @@
+{% load cache %}
+
+
+
+
+ Bad form
+
+
+