Skip to content
Open
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
30 changes: 25 additions & 5 deletions rest_framework/authtoken/serializers.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from django.contrib.auth import authenticate
from django.contrib.auth import authenticate, get_user_model
from django.utils.translation import gettext_lazy as _

from rest_framework import serializers
Expand All @@ -20,13 +20,33 @@ class AuthTokenSerializer(serializers.Serializer):
read_only=True
)

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
User = get_user_model()
username_field = User.USERNAME_FIELD
if username_field != 'username' and 'username' in self.fields:
# Rebuild the fields mapping to preserve the original position
# of the login field when renaming it.
original_items = list(self.fields.items())
new_fields = self.fields.__class__(self)
for name, field in original_items:
if name == 'username':
name = username_field
field.label = _(username_field.capitalize())
new_fields[name] = field
self.fields = new_fields

def validate(self, attrs):
username = attrs.get('username')
User = get_user_model()
username_field = User.USERNAME_FIELD
username = attrs.get(username_field)
password = attrs.get('password')
Comment on lines +41 to 43
Copy link

Copilot AI Mar 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The local variable name username is now potentially an email/phone/etc (depending on USERNAME_FIELD), which makes the control flow harder to follow. Consider renaming it to something neutral like identifier/login (and similarly for username_field if you store it on self).

Copilot uses AI. Check for mistakes.

if username and password:
user = authenticate(request=self.context.get('request'),
username=username, password=password)
user = authenticate(
request=self.context.get('request'),
**{username_field: username, 'password': password}
)

# The authenticate call simply returns None for is_active=False
# users. (Assuming the default ModelBackend authentication
Expand All @@ -35,7 +55,7 @@ def validate(self, attrs):
msg = _('Unable to log in with provided credentials.')
raise serializers.ValidationError(msg, code='authorization')
else:
msg = _('Must include "username" and "password".')
msg = _(f'Must include "{username_field}" and "password".')
raise serializers.ValidationError(msg, code='authorization')
Comment on lines 57 to 59
Copy link

Copilot AI Mar 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_(f'Must include "{username_field}" and "password".') creates a different msgid depending on USERNAME_FIELD, which breaks translations. Use a constant translatable string with interpolation (e.g., a %(username_field)s placeholder) and consider using the field’s human-readable label/verbose_name in the message rather than the internal model field name.

Copilot uses AI. Check for mistakes.

attrs['user'] = user
Expand Down