Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion django/core/mail/message.py
Original file line number Diff line number Diff line change
Expand Up @@ -360,8 +360,14 @@ def message(self, *, policy=email.policy.default):
# Use cached DNS_NAME for performance
msg["Message-ID"] = make_msgid(domain=DNS_NAME)
for name, value in self.extra_headers.items():
header = name.lower()
if header == "bcc":
raise ValueError(
'Bcc is not a valid email header. Use the "bcc" '
"argument to specify blind carbon copy recipients."
)
# Avoid headers handled above.
if name.lower() not in {"from", "to", "cc", "reply-to"}:
if header not in {"from", "to", "cc", "reply-to"}:
msg[name] = force_str(value, strings_only=True)
self._idna_encode_address_header_domains(msg)
return msg
Expand Down
16 changes: 9 additions & 7 deletions django/views/decorators/csp.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,22 @@ def _make_csp_decorator(config_attr_name, config_attr_value):
raise TypeError("CSP config should be a mapping.")

def decorator(view_func):
@wraps(view_func)
async def _wrapped_async_view(request, *args, **kwargs):
response = await view_func(request, *args, **kwargs)
setattr(response, config_attr_name, config_attr_value)
return response
if iscoroutinefunction(view_func):

@wraps(view_func)
async def _wrapped_async_view(request, *args, **kwargs):
response = await view_func(request, *args, **kwargs)
setattr(response, config_attr_name, config_attr_value)
return response

return _wrapped_async_view

@wraps(view_func)
def _wrapped_sync_view(request, *args, **kwargs):
response = view_func(request, *args, **kwargs)
setattr(response, config_attr_name, config_attr_value)
return response

if iscoroutinefunction(view_func):
return _wrapped_async_view
return _wrapped_sync_view

return decorator
Expand Down
32 changes: 23 additions & 9 deletions docs/ref/contrib/admin/index.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1244,8 +1244,11 @@ subclass::
This should be set to a list of field names that will be searched whenever
somebody submits a search query in that text box.

These fields should be some kind of text field, such as ``CharField`` or
``TextField``. You can also perform a related lookup on a ``ForeignKey`` or
These fields are typically text fields such as ``CharField`` or
``TextField``, but non-text fields like ``IntegerField`` can also be
searched. Adding an explicit lookup, such as ``age__exact``, lets the
search use that lookup directly instead of casting the value to text. You
can also perform a related lookup on a ``ForeignKey`` or
``ManyToManyField`` with the lookup API "follow" notation::

search_fields = ["foreign_key__related_fieldname"]
Expand All @@ -1257,17 +1260,23 @@ subclass::
search_fields = ["user__email"]

When somebody does a search in the admin search box, Django splits the
search query into words and returns all objects that contain each of the
words, case-insensitive (using the :lookup:`icontains` lookup), where each
word must be in at least one of ``search_fields``. For example, if
``search_fields`` is set to ``['first_name', 'last_name']`` and a user
searches for ``john lennon``, Django will do the equivalent of this SQL
``WHERE`` clause:
search query into search terms and returns all objects that contain each
of them in at least one of ``search_fields``. By default, the
:lookup:`icontains` lookup is used for text fields. For non-text fields
with an explicit lookup (e.g., ``age__exact``), the specified lookup is
used, and search terms that are invalid for that field type are skipped.

For example, if ``search_fields`` is set to
``['first_name', 'last_name', 'age__exact']`` and a user searches for
``john 25``, Django will do the equivalent of this SQL ``WHERE`` clause:

.. code-block:: sql

WHERE (first_name ILIKE '%john%' OR last_name ILIKE '%john%')
AND (first_name ILIKE '%lennon%' OR last_name ILIKE '%lennon%')
AND (first_name ILIKE '%25%' OR last_name ILIKE '%25%' OR age = 25)

Note that ``john`` is not searched on ``age`` because it's not a valid
integer, while ``25`` is searched on all three fields.

The search query can contain quoted phrases with spaces. For example, if a
user searches for ``"john winston"`` or ``'john winston'``, Django will do
Expand Down Expand Up @@ -1298,6 +1307,11 @@ subclass::
:meth:`ModelAdmin.get_search_results` to provide additional or alternate
search behavior.

.. versionchanged:: 6.1

On earlier versions, search terms invalid for a given field type were
not skipped.

.. attribute:: ModelAdmin.search_help_text

Set ``search_help_text`` to specify a descriptive text for the search box
Expand Down
4 changes: 4 additions & 0 deletions docs/releases/6.1.txt
Original file line number Diff line number Diff line change
Expand Up @@ -470,6 +470,10 @@ Email
``EmailMessage``. (This behavior was never documented. The ``send()`` method
will still *use* a ``connection`` that is set on the message before sending.)

* :meth:`.EmailMessage.message` now raises a ``ValueError`` if ``Bcc`` is
included in the ``headers`` argument or ``extra_headers`` attribute. Use the
``bcc`` argument instead.

Models
------

Expand Down
2 changes: 1 addition & 1 deletion docs/topics/email.txt
Original file line number Diff line number Diff line change
Expand Up @@ -505,7 +505,7 @@ email backend API :ref:`provides an alternative
* ``cc``: A list or tuple of recipient addresses used in the "Cc" header
when sending the email.

* ``bcc``: A list or tuple of addresses used in the "Bcc" header when
* ``bcc``: A list or tuple of addresses used for blind carbon copies when
sending the email.

* ``reply_to``: A list or tuple of recipient addresses used in the
Expand Down
12 changes: 12 additions & 0 deletions tests/mail/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -397,6 +397,18 @@ def test_bcc_not_in_headers(self):
self.assertNotIn("bcc@example.com", message.as_string())
self.assertEqual(email.recipients(), ["to@example.com", "bcc@example.com"])

def test_bcc_in_headers_raises_error(self):
email = EmailMessage(
to=["to@example.com"],
headers={"Bcc": "bcc@example.com"},
)
msg = (
'Bcc is not a valid email header. Use the "bcc" argument to '
"specify blind carbon copy recipients."
)
with self.assertRaisesMessage(ValueError, msg):
email.message()

def test_reply_to(self):
with self.subTest("Single Reply-To"):
email = EmailMessage(
Expand Down
Loading