I found below in Django source code
class AbstractBaseUser(models.Model):
...
def get_username(self):
"""Return the username for this User."""
return getattr(self, self.USERNAME_FIELD)
...
I searched out the whole Django source code, but did not find out where the USERNAME_FIELD was defined.
Can anyone help on this please?
https://github.com/django/django/blob/main/django/contrib/auth/models.py#L377
USERNAME_FIELD was defined in AbstractUser
You can find USERNAME_FIELD in AuthenticationForm class of django which inherites forms.FORM class of Form Api.
It is also defined in models.py in AbstractUser class, refer Django GitHub Code.
It is given in constructor method that is __init__() of AuthenticationForm.
From Django original Code:
class AuthenticationForm(forms.Form):
"""
Base class for authenticating users. Extend this to get a form that accepts
username/password logins.
"""
username = UsernameField(widget=forms.TextInput(attrs={'autofocus': True}))
password = forms.CharField(
label=_("Password"),
strip=False,
widget=forms.PasswordInput(attrs={'autocomplete': 'current-password'}),
)
error_messages = {
'invalid_login': _(
"Please enter a correct %(username)s and password. Note that both "
"fields may be case-sensitive."
),
'inactive': _("This account is inactive."),
}
def __init__(self, request=None, *args, **kwargs):
"""
The 'request' parameter is set for custom auth use by subclasses.
The form data comes in via the standard 'data' kwarg.
"""
self.request = request
self.user_cache = None
super().__init__(*args, **kwargs)
# Set the max length and label for the "username" field.
self.username_field = UserModel._meta.get_field(UserModel.USERNAME_FIELD)
username_max_length = self.username_field.max_length or 254
self.fields['username'].max_length = username_max_length
self.fields['username'].widget.attrs['maxlength'] = username_max_length
if self.fields['username'].label is None:
self.fields['username'].label = capfirst(self.username_field.verbose_name)
def clean(self):
username = self.cleaned_data.get('username')
password = self.cleaned_data.get('password')
if username is not None and password:
self.user_cache = authenticate(self.request, username=username, password=password)
if self.user_cache is None:
raise self.get_invalid_login_error()
else:
self.confirm_login_allowed(self.user_cache)
return self.cleaned_data
def confirm_login_allowed(self, user):
"""
Controls whether the given User may log in. This is a policy setting,
independent of end-user authentication. This default behavior is to
allow login by active users, and reject login by inactive users.
If the given user cannot log in, this method should raise a
``ValidationError``.
If the given user may log in, this method should return None.
"""
if not user.is_active:
raise ValidationError(
self.error_messages['inactive'],
code='inactive',
)
def get_user(self):
return self.user_cache
def get_invalid_login_error(self):
return ValidationError(
self.error_messages['invalid_login'],
code='invalid_login',
params={'username': self.username_field.verbose_name},
)
Here focus on __init__() for get clearification about USERNAME_FIELD.
It is used for getting user field from User model or you can normally say for getting username.
You can find it in your local machine using below path:
C:\Users\Username\AppData\Local\Programs\Python\Python39\Lib\site-packages\django\contrib\admin\forms.py.
Remember: AppData folder will only appear if you select hidden items.
Related
I am trying to understand login methods in Django. So because I want to login with email not username. I wrote a custom authentication backend and exposed it in settings.py
AUTHENTICATION_BACKENDS = (
'accounts.backend.EmailAuthenticationBackend',
'django.contrib.auth.backends.ModelBackend',
)
class EmailAuthenticationBackend(ModelBackend):
def authenticate(self, request, email=None, password=None, **kwargs):
try:
user = Account.objects.get(email=email)
if user.check_password(password):
return user
except user.DoesNotExist:
pass
And in view user_login how I authenticate the user. And it's actually works. I mean kind of...
def user_login(request):
next = request.GET.get("next")
form = UserLoginForm(request.POST or None)
if form.is_valid():
email = form.cleaned_data.get("email")
password = form.cleaned_data.get("password")
user = authenticate(email=email, password=password)
login(request, user)
if next:
return redirect(next)
return redirect("/")
context = {
"form": form
}
return render(request, "accounts/user_login.html", context)
the authenticate() method is returns the user. But redirect() is not working because in browser thinks user not logged in. I am not sure about how to handle the session part.
and custom UserLoginForm
class UserLoginForm(forms.Form):
email = forms.CharField()
password = forms.CharField()
def clean(self, *args, **kwargs):
email = self.cleaned_data.get("email")
password = self.cleaned_data.get("password")
if email and password:
user = authenticate(email=email, password=password)
if not user:
raise forms.ValidationError("user not exist")
if not user.check_password(password):
raise forms.ValidationError("incorrect password")
return super(UserLoginForm, self).clean(*args, **kwargs)
The second issue is if email or password wrong. it doesn't raise any error.
what is the best common way to handle custom login with email.
I think the reason, it doesn't redirect is you've assigned the
request.GET.get("next") into nxt variable, however you are
checking the next variable for redirecting, probably you should
change the variable nxt to next.
The reason that it doesn't raise any error is, you've put try and except to handle the thrown errors in the authenticate() method and you just put pass in the excpetion part. If you want the error to be thrown, just remove the try and except part from authenticate().
I want to edit the login form provided by django and don't want to build a new one because of the security issues. I have looked at other solutions like How to use another field for logging in with Django Allauth? it's a good example but it assigns email id based on mobile number. However I want to add another field that isn't particularly to authenticate just for input based on which redirection is done. I am quite confused about my approach and whether or not it is possible to do so. Kindly suggest. Thanks.
You can do that in your forms.py file by doing this.
class UserLoginForm(forms.Form):
username = forms.CharField(widget=forms.TextInput(attrs={'class':'form-control','placeholder':'Username'}))
password = forms.CharField(widget=forms.PasswordInput(attrs={'class':'form-control','placeholder':'Password'}))
yourfield = forms.CharField(widget=forms.TextInput(attrs={'class':'form-control','placeholder':'yourfield'}))
def clean(self, *args, **kwargs):
username = self.cleaned_data.get("username")
password = self.cleaned_data.get("password")
#user_qs = User.objects.filter(username=username)
#if user_qs.count() == 1:
# user = user_qs.first()
if username and password:
user = authenticate(username=username, password=password)
if not user:
raise forms.ValidationError("This user does not exist")
if not user.check_password(password):
raise forms.ValidationError("Incorrect password")
if not user.is_active:
raise forms.ValidationError("This user is no longer active")
return super(UserLoginForm, self).clean(*args, **kwargs)
Apologies if I've misunderstood your question, but here is how I've added extra fields to user registration it which seems pretty straightforward. I've included some extra related methods just to be verbose:
../forms.py:
class CustomRegistrationForm(RegistrationForm):
"""
Form for registering a new user account.
Subclasses should feel free to add any additional validation they
need, but should avoid defining a ``save()`` method -- the actual
saving of collected user data is delegated to the active
registration backend.
"""
username = forms.RegexField(regex=r'^[\w.#+-]+$',
max_length=30,
label="Username",
error_messages={'invalid': "This value may contain only letters, numbers and #/./+/-/_ characters."})
email = forms.EmailField(label="E-mail")
password1 = forms.CharField(widget=forms.PasswordInput,
label="Password")
password2 = forms.CharField(widget=forms.PasswordInput,
label="Password (again)")
extra_field = forms.CharField([field options])
def clean(self):
if 'password1' in self.cleaned_data and 'password2' in self.cleaned_data:
if self.cleaned_data['password1'] != self.cleaned_data['password2']:
raise forms.ValidationError("The two password fields didn't match.")
return self.cleaned_data
Then, simply set your register URL to use the proper form class:
../urls.py:
url(r'^accounts/register/$', RegistrationView.as_view(form_class=accounts.forms.CustomRegistrationForm), name='registration_register'),
Is this field not part of your standard model, or does your input need to do some extra work? You can set a signal to make some extra magic happen when the user is registered:
from forms import CustomRegistrationForm
def user_created(sender, user, request, **kwargs):
form = CustomRegistrationForm(request.POST)
user_account = get_user_account(user)
user_account.persona = form.data['persona_tier']
user_account.save()
from registration.signals import user_registered
user_registered.connect(user_created)
FYI I'm using django-registration-redux backend but this approach should help get you close regardless.
I made a custom registration form, that inherits from the UserCreationForm. However, when you try to submit, with one of the fields empty, i get a KeyError on required.
This seems to happen somewhere in the django source code, however I'm pretty sure it comes because of my custom clean method.
Form:
class RegistrationForm(UserCreationForm):
"""
edit the User Registration form to add an emailfield
"""
class Meta:
model = User
fields = ('username', 'password1', 'password2')
def __init__(self, *args, **kwargs):
super(RegistrationForm, self).__init__(*args, **kwargs)
#add custom errormessages
self.fields['username'].error_messages = {
'invalid': 'Invalid username'
}
self.fields['password2'].label = "Confirm Password"
#make sure username is lowered and unique
def clean_username(self):
username = self.cleaned_data.get('username')
try:
User.objects.get(username__iexact=username)
raise forms.ValidationError("This username is already in use.")
except User.DoesNotExist:
pass
return username
def save(self, commit=True):
user = super(RegistrationForm, self).save(commit=False)
if commit:
user.save()
return user
The errorlog http://pastebin.com/8Y6Tp7Rw
Note: I am using django 1.8
You are replacing all of the 'username' field error_messages dict with your own dict. Instead, you should update the error_messages dict with your custom message like so:
self.fields['username'].error_messages.update({
'invalid': 'Invalid username'
})
It looks like that you have changed the original error messages for the 'username' field (you are not adding, but overriding):
#add custom errormessages
self.fields['username'].error_messages = {
'invalid': 'Invalid username'
}
So when you leave the username empty, it failed to find the key 'required'.
I am using Python socia auth for face-book. I have modified default Django behavior of User Model and removed username field .
I have added this in custom user model : USERNAME_FIELD = 'email'
BUt I am getting this error when trying to login
TypeError at /complete/facebook/
'username' is an invalid keyword argument for this function
I know when it is trying to create user it doesn't find username field and this throwing this error.
I have defined below settings but still my issue remains as it is :
SOCIAL_AUTH_USER_MODEL = 'accounts.User'
SOCIAL_AUTH_USERNAME_IS_FULL_EMAIL = True
Any solution for this ?
I know it's few months since the question was posted, but I hit the same problem and found the solution.
This problem can be solved using pipeline. The default pipeline does this:
def create_user(strategy, details, user=None, *args, **kwargs):
if user:
return {'is_new': False}
fields = dict((name, kwargs.get(name) or details.get(name))
for name in strategy.setting('USER_FIELDS',
USER_FIELDS))
if not fields:
return
return {
'is_new': True,
'user': strategy.create_user(**fields)
}
Override USER_FIELDS in your settings end leave only email. Alternatively you can create completely new create_user method.
i have same issue and solved problem by adding user argument in create user field ..
None: Argument 'username' is needed for social-auth. It is not actually used.
class UserManager(BaseUserManager):
def create_user(self, email,username=None, full_name=None, password='&btCqv"}#,4TWd6A'):
if not email:
raise ValueError("Users must have an email address")
if not password:
raise ValueError("Users must have a password")
user = self.model(
email=self.normalize_email(email),
)
user.set_password(password) # change user password
user.staff = is_staff
user.admin = is_admin
user.is_active = is_active
user.save(using=self._db)
return user
if still problem persist refer: https://github.com/python-social-auth/social-app-django/issues/15#issuecomment-276118574
I ran today into a special situation. Previously I had the following in my view.py
def register_page(request):
if request.method == 'POST':
form = RegistrationForm(request.POST)
if form.is_valid():
user = User.objects.create_user(
username=form.cleaned_data['username'],
password=form.cleaned_data['password2'],
email=form.cleaned_data['email']
)
return HttpResponseRedirect('/register/success/')
else:
form = RegistrationForm()
variables = RequestContext(request, {'form':form})
return render_to_response('registration/register.html', variables)
It was pretty straight forward retrieving the username, email and password to create a new user after she has registered. But now I have refactored it to use a hash code as the username and utilize the email alone to register and login.
The shortened RegistrationForm looks like this:
class RegistrationForm(forms.ModelForm):
email = forms.EmailField(label=_("Email"))
password1 = forms.CharField(label=_("Password"), widget=forms.PasswordInput)
password2 = forms.CharField(label=_("Password confirmation"), widget=forms.PasswordInput))
class Meta:
model = User
fields = ("email",)
def save(self, commit=True):
user = super(RegistrationForm, self).save(commit=False)
user.set_password(self.cleaned_data["password1"])
email = self.cleaned_data['email']
user.username = md5(email).digest().encode('base64')[:-1]
if commit:
user.save()
return user
The new form doesn't have the username any longer, since it is calculated and not entered by the user any more. But how do I retrieve the username from the view ? The new code is not from me and I have it from a blog. Maybe the key is here in the Meta class? From the documentation I wasn't able to fully understood what he is trying to achieve with the Meta class here...
Many Thanks,
EDIT:
Ok I think I understand now how the subclassing should work. I tried to subclass the User class like this:
class cb_user_model_backend(ModelBackend):
def create_user(self, email=None, password=None):
"""
Creates and saves a User with the given email and password only.
"""
now = timezone.now()
username = md5(email).digest().encode('base64')[:-1]
email = UserManager.normalize_email(email)
user = self.model(username=username, email=email,
is_staff=False, is_active=True, is_superuser=False,
last_login=now, date_joined=now)
user.set_password(password)
user.save(using=self._db)
return user
The problem I am facing now are two errors, self._db and self.model, were meant to be on the base user class. How do get to them from here?
Edit 2:
PyCharm complains that the two self._db and seld.model don't exit on current cb_user_model_backend.
Note the View is refactored to take two parameters:
user = User.objects.create_user(
password=form.cleaned_data['password2'],
email=form.cleaned_data['email']
)
When running it stack trace is:
Exception Type: TypeError
Exception Value:
create_user() takes at least 2 arguments (3 given)
Try subclassing your save method in your models.py:
def save(self, *args, **kwargs):
if not self.id:
self.username = md5(self.email).digest().encode('base64')[:-1]
super(ModelName, self).save(*args, **kwargs)
After calling user.save(), user.username should yield the generated username in your views. Hope this helps.
EDIT:
If you want to call create_user(**kwargs), you could do the following in your views.py:
email = self.cleaned_data['email']
username = md5(email).digest().encode('base64')[:-1]
u = User.objects.create_user(username = username, email = email, password = password)