Skip to content

Commit

Permalink
Merge branch 'django:main' into ticket_24048
Browse files Browse the repository at this point in the history
  • Loading branch information
ryancheley committed Nov 29, 2022
2 parents 7981d11 + 85b52d2 commit 13a0112
Show file tree
Hide file tree
Showing 10 changed files with 136 additions and 5 deletions.
1 change: 1 addition & 0 deletions AUTHORS
Original file line number Diff line number Diff line change
Expand Up @@ -802,6 +802,7 @@ answer newbie questions, and generally made Django that much better:
Rachel Tobin <rmtobin@me.com>
Rachel Willmer <http://www.willmer.com/kb/>
Radek Švarz <https://www.svarz.cz/translate/>
Rafael Giebisch <rafael@giebisch-mail.de>
Raffaele Salmaso <raffaele@salmaso.org>
Rajesh Dhawan <rajesh.dhawan@gmail.com>
Ramez Ashraf <ramezashraf@gmail.com>
Expand Down
2 changes: 2 additions & 0 deletions django/contrib/auth/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,8 @@ def save(self, commit=True):
user.set_password(self.cleaned_data["password1"])
if commit:
user.save()
if hasattr(self, "save_m2m"):
self.save_m2m()
return user


Expand Down
23 changes: 22 additions & 1 deletion django/views/debug.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import functools
import itertools
import re
import sys
import types
Expand All @@ -15,7 +16,7 @@
from django.utils.encoding import force_str
from django.utils.module_loading import import_string
from django.utils.regex_helper import _lazy_re_compile
from django.utils.version import get_docs_version
from django.utils.version import PY311, get_docs_version

# Minimal Django templates engine to render the error templates
# regardless of the project's TEMPLATES setting. Templates are
Expand Down Expand Up @@ -546,6 +547,24 @@ def get_exception_traceback_frames(self, exc_value, tb):
pre_context = []
context_line = "<source code not available>"
post_context = []

colno = tb_area_colno = ""
if PY311:
_, _, start_column, end_column = next(
itertools.islice(
tb.tb_frame.f_code.co_positions(), tb.tb_lasti // 2, None
)
)
if start_column and end_column:
underline = "^" * (end_column - start_column)
spaces = " " * (start_column + len(str(lineno + 1)) + 2)
colno = f"\n{spaces}{underline}"
tb_area_spaces = " " * (
4
+ start_column
- (len(context_line) - len(context_line.lstrip()))
)
tb_area_colno = f"\n{tb_area_spaces}{underline}"
yield {
"exc_cause": exc_cause,
"exc_cause_explicit": exc_cause_explicit,
Expand All @@ -562,6 +581,8 @@ def get_exception_traceback_frames(self, exc_value, tb):
"context_line": context_line,
"post_context": post_context,
"pre_context_lineno": pre_context_lineno + 1,
"colno": colno,
"tb_area_colno": tb_area_colno,
}
tb = tb.tb_next

Expand Down
4 changes: 2 additions & 2 deletions django/views/templates/technical_500.html
Original file line number Diff line number Diff line change
Expand Up @@ -242,7 +242,7 @@ <h2>Traceback{% if not is_email %} <span class="commands"><a href="#" onclick="r
</ol>
{% endif %}
<ol start="{{ frame.lineno }}" class="context-line">
<li onclick="toggle('pre{{ frame.id }}', 'post{{ frame.id }}')"><pre>{{ frame.context_line }}</pre>{% if not is_email %} <span></span>{% endif %}</li>
<li onclick="toggle('pre{{ frame.id }}', 'post{{ frame.id }}')"><pre>{{ frame.context_line }}{{ frame.colno }}</pre>{% if not is_email %} <span></span>{% endif %}</li>
</ol>
{% if frame.post_context and not is_email %}
<ol start='{{ frame.lineno|add:"1" }}' class="post-context" id="post{{ frame.id }}">
Expand Down Expand Up @@ -327,7 +327,7 @@ <h2>Local Vars</h2>
{% else %}
During handling of the above exception ({{ frame.exc_cause|force_escape }}), another exception occurred:
{% endif %}{% endif %}{% endifchanged %} {% if frame.tb %}File "{{ frame.filename }}"{% if frame.context_line %}, line {{ frame.lineno }}{% endif %}, in {{ frame.function }}
{% if frame.context_line %} {% spaceless %}{{ frame.context_line }}{% endspaceless %}{% endif %}{% elif forloop.first %}None{% else %}Traceback: None{% endif %}{% endfor %}
{% if frame.context_line %} {% spaceless %}{{ frame.context_line }}{% endspaceless %}{{ frame.tb_area_colno }}{% endif %}{% elif forloop.first %}None{% else %}Traceback: None{% endif %}{% endfor %}

Exception Type: {{ exception_type }}{% if request %} at {{ request.path_info }}{% endif %}
Exception Value: {{ exception_value|force_escape }}{% if exception_notes %}{{ exception_notes }}{% endif %}
Expand Down
2 changes: 1 addition & 1 deletion django/views/templates/technical_500.txt
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ Traceback (most recent call last):
{% for frame in frames %}{% ifchanged frame.exc_cause %}{% if frame.exc_cause %}
{% if frame.exc_cause_explicit %}The above exception ({{ frame.exc_cause }}) was the direct cause of the following exception:{% else %}During handling of the above exception ({{ frame.exc_cause }}), another exception occurred:{% endif %}
{% endif %}{% endifchanged %} {% if frame.tb %}File "{{ frame.filename }}"{% if frame.context_line %}, line {{ frame.lineno }}{% endif %}, in {{ frame.function }}
{% if frame.context_line %} {% spaceless %}{{ frame.context_line }}{% endspaceless %}{% endif %}{% elif forloop.first %}None{% else %}Traceback: None{% endif %}
{% if frame.context_line %} {% spaceless %}{{ frame.context_line }}{% endspaceless %}{{ frame.tb_area_colno }}{% endif %}{% elif forloop.first %}None{% else %}Traceback: None{% endif %}
{% endfor %}
{% if exception_type %}Exception Type: {{ exception_type }}{% if request %} at {{ request.path_info }}{% endif %}
{% if exception_value %}Exception Value: {{ exception_value }}{% endif %}{% if exception_notes %}{{ exception_notes }}{% endif %}{% endif %}{% endif %}
Expand Down
6 changes: 5 additions & 1 deletion docs/releases/4.2.txt
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,9 @@ Minor features
* The default iteration count for the PBKDF2 password hasher is increased from
390,000 to 480,000.

* :class:`~django.contrib.auth.forms.UserCreationForm` now saves many-to-many
form fields for a custom user model.

:mod:`django.contrib.contenttypes`
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Expand Down Expand Up @@ -158,7 +161,8 @@ Email
Error Reporting
~~~~~~~~~~~~~~~

* The debug page now shows :pep:`exception notes <678>` on Python 3.11+.
* The debug page now shows :pep:`exception notes <678>` and
:pep:`fine-grained error locations <657>` on Python 3.11+.

File Storage
~~~~~~~~~~~~
Expand Down
5 changes: 5 additions & 0 deletions docs/topics/auth/customizing.txt
Original file line number Diff line number Diff line change
Expand Up @@ -840,6 +840,11 @@ extend these forms in this manner::
model = CustomUser
fields = UserCreationForm.Meta.fields + ('custom_field',)

.. versionchanged:: 4.2

In older versions, :class:`~django.contrib.auth.forms.UserCreationForm`
didn't save many-to-many form fields for a custom user model.

Custom users and :mod:`django.contrib.admin`
--------------------------------------------

Expand Down
5 changes: 5 additions & 0 deletions docs/topics/auth/default.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1665,6 +1665,11 @@ provides several built-in forms located in :mod:`django.contrib.auth.forms`:
sets the user's password using
:meth:`~django.contrib.auth.models.User.set_password()`.

.. versionchanged:: 4.2

In older versions, :class:`UserCreationForm` didn't save many-to-many
form fields for a custom user model.

.. currentmodule:: django.contrib.auth

Authentication data in templates
Expand Down
20 changes: 20 additions & 0 deletions tests/auth_tests/test_forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
)
from .models.with_custom_email_field import CustomEmailField
from .models.with_integer_username import IntegerUsernameUser
from .models.with_many_to_many import CustomUserWithM2M, Organization
from .settings import AUTH_TEMPLATES


Expand Down Expand Up @@ -252,6 +253,25 @@ class Meta(UserCreationForm.Meta):
form = CustomUserCreationForm(data)
self.assertTrue(form.is_valid())

def test_custom_form_saves_many_to_many_field(self):
class CustomUserCreationForm(UserCreationForm):
class Meta(UserCreationForm.Meta):
model = CustomUserWithM2M
fields = UserCreationForm.Meta.fields + ("orgs",)

organization = Organization.objects.create(name="organization 1")

data = {
"username": "testclient@example.com",
"password1": "testclient",
"password2": "testclient",
"orgs": [str(organization.pk)],
}
form = CustomUserCreationForm(data)
self.assertIs(form.is_valid(), True)
user = form.save(commit=True)
self.assertSequenceEqual(user.orgs.all(), [organization])

def test_password_whitespace_not_stripped(self):
data = {
"username": "testuser",
Expand Down
73 changes: 73 additions & 0 deletions tests/view_tests/tests/test_debug.py
Original file line number Diff line number Diff line change
Expand Up @@ -766,6 +766,79 @@ def test_reporting_of_nested_exceptions(self):
self.assertIn(implicit_exc.format("<p>Second exception</p>"), text)
self.assertEqual(3, text.count("<p>Final exception</p>"))

@skipIf(
sys._xoptions.get("no_debug_ranges", False)
or os.environ.get("PYTHONNODEBUGRANGES", False),
"Fine-grained error locations are disabled.",
)
@skipUnless(PY311, "Fine-grained error locations were added in Python 3.11.")
def test_highlight_error_position(self):
request = self.rf.get("/test_view/")
try:
try:
raise AttributeError("Top level")
except AttributeError as explicit:
try:
raise ValueError(mark_safe("<p>2nd exception</p>")) from explicit
except ValueError:
raise IndexError("Final exception")
except Exception:
exc_type, exc_value, tb = sys.exc_info()

reporter = ExceptionReporter(request, exc_type, exc_value, tb)
html = reporter.get_traceback_html()
self.assertIn(
"<pre> raise AttributeError(&quot;Top level&quot;)\n"
" ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^</pre>",
html,
)
self.assertIn(
"<pre> raise ValueError(mark_safe("
"&quot;&lt;p&gt;2nd exception&lt;/p&gt;&quot;)) from explicit\n"
" "
"^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^</pre>",
html,
)
self.assertIn(
"<pre> raise IndexError(&quot;Final exception&quot;)\n"
" ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^</pre>",
html,
)
# Pastebin.
self.assertIn(
" raise AttributeError(&quot;Top level&quot;)\n"
" ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n",
html,
)
self.assertIn(
" raise ValueError(mark_safe("
"&quot;&lt;p&gt;2nd exception&lt;/p&gt;&quot;)) from explicit\n"
" ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n",
html,
)
self.assertIn(
" raise IndexError(&quot;Final exception&quot;)\n"
" ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n",
html,
)
# Text traceback.
text = reporter.get_traceback_text()
self.assertIn(
' raise AttributeError("Top level")\n'
" ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n",
text,
)
self.assertIn(
' raise ValueError(mark_safe("<p>2nd exception</p>")) from explicit\n'
" ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n",
text,
)
self.assertIn(
' raise IndexError("Final exception")\n'
" ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n",
text,
)

def test_reporting_frames_without_source(self):
try:
source = "def funcName():\n raise Error('Whoops')\nfuncName()"
Expand Down

0 comments on commit 13a0112

Please sign in to comment.