I've overwrite "save_model" method to manage user's password in my admin's panel application. What I want is:
Create a new random password when I create new user (if password field is empty)
Encrypt the password (if I set it)
Use user's password (if I change user but not set the password)
How can I define the last condition?
def save_model(self, request, obj, form, change):
if not change and (not form.cleaned_data['password']) :
password = User.objects.make_random_password()
obj.set_password(password)
elif form.cleaned_data['password'] :
obj.set_password(form.cleaned_data['password'])
else
?
super(UserAdmin, self).save_model(request, obj, form, change)
You are doing all this a bit too late. The user creation form won't even allow you to post a form without fields password1 and password2 filled in. If those fields are empty you will never even get to UserAdmin.save_model() method.
But you can override the user creation form's save() method, in it allow password fields to be empty and set random password if fields are empty (or use user's password if entered).
As a bonus, this code keeps in place form validation if user entered the password (password mismatch) as well as checking AUTH_PASSWORD_VALIDATORS (all except 'django.contrib.auth.password_validation.MinimumLengthValidator' since empty password field mean 0 chars).
### admin.py
from django import forms
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin
from django.contrib.auth.forms import UserCreationForm
from django.contrib.auth.models import User
class CustomUserCreationForm(UserCreationForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['password1'].required = False
self.fields['password2'].required = False
def _clean_form(self):
super()._clean_form()
password1 = self.cleaned_data.get("password1")
password2 = self.cleaned_data.get("password2")
if password1 != password2:
self.add_error('password2', self.error_messages['password_mismatch'])
def save(self, commit=True):
user = super().save(commit=False)
user_password = self.cleaned_data["password1"]
if not user_password:
user_password = User.objects.make_random_password()
user.set_password(user_password)
if commit:
user.save()
return user
class CustomUserAdmin(UserAdmin):
add_form = CustomUserCreationForm
# Re-register UserAdmin
admin.site.unregister(User)
admin.site.register(User, CustomUserAdmin)
Related
I understand that django comes with a User model built in and the following code works and properly saves a user to the db, I just don't understand why it works:
from django.shortcuts import render
from django.http import HttpResponse
from .forms import UserCreationForm
def index(request):
if request.method == "POST":
form = UserCreationForm(request.POST)
if form.is_valid():
user = form.save()
return HttpResponse('Saved')
else:
form = UserCreationForm()
return render(request, 'core/index.html', {'form':form})
what is the line form = UserCreationForm(request.POST) saying? is the (request.POST) the contents of the submitted form? and how does user = form.save() save a user to the database? I was thinking that maybe the variable had to be named user for django to recognize it as a User object but this is not the case as I changed the statement to test = form.save()
and it still saved a User to my database.
The line form = UserCreationForm(request.POST) talks of itself actually. It posts data passed through your form to the variable form
The 'request' object is an HttpRequest object that carries metadata about the request (for example in our case, we have a post request , and the request object carries the user data). Check this for more details.
Yes, user = form.save() saves a User instance in the database. UserCreationForm has a function called save() and here we're just calling that function.
The name of the variable has nothing to do with it because in the UserCreationForm's definition, the model is already defined as User. So, it already recognizes that it's the User model.
I this you should read the UserCreationForm code to make things clear in your head.
This is django doc :
[docs]class UserCreationForm(forms.ModelForm):
"""
A form that creates a user, with no privileges, from the given username and
password.
"""
error_messages = {
'password_mismatch': _("The two password fields didn't match."),
}
password1 = forms.CharField(label=_("Password"),
widget=forms.PasswordInput)
password2 = forms.CharField(label=_("Password confirmation"),
widget=forms.PasswordInput,
help_text=_("Enter the same password as above, for verification."))
class Meta:
model = User
fields = ("username",)
def clean_password2(self):
password1 = self.cleaned_data.get("password1")
password2 = self.cleaned_data.get("password2")
if password1 and password2 and password1 != password2:
raise forms.ValidationError(
self.error_messages['password_mismatch'],
code='password_mismatch',
)
return password2
def save(self, commit=True):
user = super(UserCreationForm, self).save(commit=False)
user.set_password(self.cleaned_data["password1"])
if commit:
user.save()
return user
You can see the "save" method, it use user model with super. Result UserCreationForm use User model and can save in User database
Am trying to force a user to provide a profile before they are saved from the django admin.
here is my profile model
class UserProfile(models.Model):
user=models.OneToOneField(AUTH_USER_MODEL,related_name='profile',primary_key=True)
#other fields
def get_user_info(user):
return UserProfile.objects.get(user=user)
#receiver(post_save, sender=AUTH_USER_MODEL)
def create_profile_for_new_user(sender, created, instance, **kwargs):
if created:
profile = UserProfile(user=instance)
profile.save()
Am using authtools so the user profile sender is AUTH_USER_MODEL.
If I add a new user from within admin,they are saved even if they have not provided or filled in the profile.
I want to prevent them from being saved until they fill in the profile fields.
Any insights on how to do this?
Here is my admin.py
from django import forms
from django.contrib.auth import get_user_model
from django.contrib.auth.forms import PasswordResetForm
from django.utils.crypto import get_random_string
from authtools.admin import NamedUserAdmin
from authtools.forms import UserCreationForm
User = get_user_model()
class UserCreationForm(UserCreationForm):
"""
A UserCreationForm with optional password inputs.
"""
def __init__(self, *args, **kwargs):
super(UserCreationForm, self).__init__(*args, **kwargs)
self.fields['password1'].required = False
self.fields['password2'].required = False
# If one field gets autocompleted but not the other, our 'neither
# password or both password' validation will be triggered.
self.fields['password1'].widget.attrs['autocomplete'] = 'off'
self.fields['password2'].widget.attrs['autocomplete'] = 'off'
def clean_password2(self):
password1 = self.cleaned_data.get("password1")
password2 = super(UserCreationForm, self).clean_password2()
if bool(password1) ^ bool(password2):
raise forms.ValidationError("Fill out both fields")
return password2
class UserAdmin(NamedUserAdmin):
"""
A UserAdmin that sends a password-reset email when creating a new user,
unless a password was entered.
"""
inlines = [ UserProfileInline, ]
add_form = UserCreationForm
add_fieldsets = (
(None, {
'description': (
"Enter the new user's name and email address and click save."
" The user will be emailed a link allowing them to login to"
" the site and set their password."
),
'fields': ('email', 'name',),
}),
('Password', {
'description': "Optionally, you may set the user's password here.",
'fields': ('password1', 'password2'),
'classes': ('collapse', 'collapse-closed'),
}),
)
def save_model(self, request, obj, form, change):
if not change and not obj.has_usable_password():
# Django's PasswordResetForm won't let us reset an unusable
# password. We set it above super() so we don't have to save twice.
obj.set_password(get_random_string())
reset_password = True
else:
reset_password = True
super(UserAdmin, self).save_model(request, obj, form, change)
if reset_password:
reset_form = PasswordResetForm({'email': obj.email})
assert reset_form.is_valid()
reset_form.save(
subject_template_name='registration/account_creation_subject.txt',
email_template_name='registration/account_creation_email.html',
)
admin.site.unregister(User)
admin.site.register(User, UserAdmin)
The best way to do this is to create a custom middleware and check, if the logged in user has filled out the profile. If not, the middleware would redirect the user to a view, where he can fill out the profile.
Example:
class ProfileMiddleware(MiddlewareMixin):
def __init__(self, get_response=None):
self.get_response = get_response
def process_view(self, request, view_func, view_args, view_kwargs):
if not request.user.profile:
return HttpResponseRedirect(reverse('create-profile'))
return None
Haven't tested the code, but it should get you started. It's the cleanest way of doing this and it will also force the user to fill out the profile, otherwise he cannot open a page on the website.
Hope this helps.
Good day, I have the following two questions:
1) I'm using a custom login as shown on the django docs, but however I'm receiving an error when trying to save a new user.
This is my forms.py:
class UserCreationForm(forms.ModelForm):
"""
A form for creating a user, with no privileges.
Includes all the required fields, plus a repeated password.
"""
password1 = forms.CharField(label=_("Mot de passe"), widget=forms.PasswordInput)
password2 = forms.CharField(
label=_("Confirmer le mote de passe"),
widget=forms.PasswordInput,
help_text=_("Les deux mots de passe, doivent etre identique."))
class Meta:
model = IfasicUser
fields = ("email",)
def clean_email(self):
email = self.cleaned_data.get('email')
email_base, provider = email.split("#")
domain, extension = provider.split(".")
if not domain == "ifasic":
raise forms.ValidationError("domain doesn't exist")
if not extension == "cd":
raise forms.ValidationError("Utilisateur non identifie, reessayer.")
return email
def clean_password2(self):
password1 = self.cleaned_data.get("password1")
password2 = self.cleaned_data.get("password2")
if password1 and password2 and password1 != password2:
raise forms.ValidationError("Les deux mots de passe, doivent etre identiques.")
return self.clean_password2
# TODO: make sure a student email must contains a student number as part of it.
# def validate_email(email):
# if '#' not in email:
# raise ValidationError('Invalid email. # not found')
# if '.' not in email:
# raise ValidationError('Invalid email. Incorrect domain?')
#
# # lowercase domain
# name, domain = email.split('#')
# email = '#'.join([name, domain.lower()])
# return email
def save(self, commit=True):
user = super(UserCreationForm, self).save(commit=False)
user.set_password(self.cleaned_data["password1"])
if commit:
user.save()
return user
class UserChangeForm(forms.ModelForm):
"""
A form for updating a user. Includes all the fields on
the user, but replaces the password field with admin's
password hash display field.
"""
password = ReadOnlyPasswordHashField(
label=_("Password"),
help_text=_("Raw passwords are not stored, so there is no way to see "
"this user's password, but you can change the password "
"using this form."))
class Meta:
model = IfasicUser
fields = '__all__'
def clean_password(self):
# Regardless of what the user provides, return the initial value.
# This is done here, rather than on the field, because the
# field does not have access to the initial value
return self.initial["password"]
class MyAuthenticationForm(AuthenticationForm):
"""
A custom authentication form that extends the base AuthenticationForm
and overrides the username field to set it to an EmailField.
"""
username = forms.EmailField()
2) I would like to redirect a user to his content manager systems depending on the flag is_staff.
This is my attempted code. but with no results:
class AdminLogin(generic.TemplateView):
model = models.Staff
template_name = 'registration/login.html'
#sensitive_post_parameters()
#csrf_protect
#never_cache
def login(request, template_name='registration/login.html',
redirect_field_name=REDIRECT_FIELD_NAME,
authentication_form=AuthenticationForm,
current_app=None, extra_context=None):
"""
Displays the login form and handles the login action.
"""
redirect_to = request.POST.get(redirect_field_name,
request.GET.get(redirect_field_name, ''))
if request.method == "POST":
form = authentication_form(request, data=request.POST)
if form.is_valid():
# Ensure the user-originating redirection url is safe.
if not is_safe_url(url=redirect_to, host=request.get_host()):
redirect_to = resolve_url(settings.LOGIN_REDIRECT_URL)
# Okay, security check complete. Log the user in.
auth_login(request, form.get_user())
# here where to redirect the user
if request.user is not None:
if request.user.is_active and request.user.is_staff:
redirect_to = 'home'
print(u'the request request.user')
# if Student.filter(user=request.user).exists():
# ...
return HttpResponseRedirect(redirect_to)
else:
form = authentication_form(request)
current_site = get_current_site(request)
context = {
'form': form,
redirect_field_name: redirect_to,
'site': current_site,
'site_name': current_site.name,
}
if extra_context is not None:
context.update(extra_context)
if current_app is not None:
request.current_app = current_app
return TemplateResponse(request, template_name, context)
Any help will be appreciated.
1) The KeyError is for password as you are using password1 and password2. One solution would be to make the fields password and then password_confirm. This happens because you are using the ModelForm, so you a password field in IfasicUseralready.
2) Right after you set the redirect_to for the user you have another if statement that might be getting triggered and overwriting the redirect_to value. So in this case, if the user is_staff and also a student, then they would redirect to wherever the students go after login.
I have created a custom User registration form, from the UserCreationForm. When I try to register, it does register successfully, and I can see a newly created user with the username and its email. But there's no password for that user.
In the admin, the password field for that user is No password set.. Please correct me where I am wrong. Thank you.
forms.py:
from album.forms import MyRegistrationForm
from django import forms
from django.contrib.auth.models import User
from django.contrib.auth.forms import UserCreationForm
class MyRegistrationForm(UserCreationForm):
email = forms.EmailField(required=True)
class Meta:
model = User
fields = ('username', 'email', 'password1', 'password2',)
def save(self, commit=True):
user = super(UserCreationForm, self).save(commit=False)
user.email = self.cleaned_data['email']
if commit:
user.save()
return user
views.py:
def register_user(request):
if request.method == "POST":
form = MyRegistrationForm(request.POST)
if form.is_valid():
form.save()
return HttpResponseRedirect('/accounts/register_success/')
else:
form = MyRegistrationForm()
return render(request, 'register.html', {'form':form})
When calling save on the superclass using super, use the form MyRegistrationForm, not its superclass UserCreationForm.
user = super(MyRegistrationForm, self).save(commit=False)
Django newbie here.
I wrote simplified login form which takes email and password. It works great if both email and password are supplied, but if either is missing i get KeyError exception. According to django documentation this should never happen:
By default, each Field class assumes the value is required, so if you pass an empty value -- either None or the empty string ("") -- then clean() will raise a ValidationError exception
I tried to write my own validators for fields (clean_email and clean_password), but it doesn't work (ie I get KeyError exception). What am I doing wrong?
class LoginForm(forms.Form):
email = forms.EmailField(label=_(u'Your email'))
password = forms.CharField(widget=forms.PasswordInput, label=_(u'Password'))
def clean_email(self):
data = self.cleaned_data['email']
if not data:
raise forms.ValidationError(_("Please enter email"))
return data
def clean_password(self):
data = self.cleaned_data['password']
if not data:
raise forms.ValidationError(_("Please enter your password"))
return data
def clean(self):
try:
username = User.objects.get(email__iexact=self.cleaned_data['email']).username
except User.DoesNotExist:
raise forms.ValidationError(_("No such email registered"))
password = self.cleaned_data['password']
self.user = auth.authenticate(username=username, password=password)
if self.user is None or not self.user.is_active:
raise forms.ValidationError(_("Email or password is incorrect"))
return self.cleaned_data
You could leverage Django's built-in way to override how Authentication happens by setting
AUTHENTICATION_BACKENDS in your settings.py
Here's my EmailAuthBackend:
#settings.py
AUTHENTICATION_BACKENDS = (
'auth_backend.auth_email_backend.EmailBackend',
'django.contrib.auth.backends.ModelBackend',
)
#auth_email_backend.py
from django.contrib.auth.backends import ModelBackend
from django.forms.fields import email_re
from django.contrib.auth.models import User
class EmailBackend(ModelBackend):
"""
Authenticate against django.contrib.auth.models.User
"""
def authenticate(self, **credentials):
return 'username' in credentials and \
self.authenticate_by_username_or_email(**credentials)
def authenticate_by_username_or_email(self, username=None, password=None):
try:
user = User.objects.get(email=username)
except User.DoesNotExist:
try:
user = User.objects.get(username=username)
except User.DoesNotExist:
user = None
if user:
return user if user.check_password(password) else None
else:
return None
def get_user(self, user_id):
try:
return User.objects.get(pk=user_id)
except User.DoesNotExist:
return None
#forms.py
#replaces the normal username CharField with an EmailField
from django import forms
from django.contrib.auth.forms import AuthenticationForm
class LoginForm(AuthenticationForm):
username = forms.EmailField(max_length=75, label='Email')
next = forms.CharField(widget=forms.HiddenInput)
Hope that helps!
With Django 2.0 it's even simpler to achive this.
Create your own UserModel
class User(AbstractBaseUser, PermissionsMixin):
and set the USERNAME_FIELD = 'email'
For reference: Documentation