I want to create a SINGLE form which gives the ability to the admin to create a new user with extended profile. Please note that, I don't want to use admin and registration apps.
I have extended the user with the UserProfile model. I have read all the documents related to extending user profile. But, I really don't know how to save these information.
I coded the following django form for this issue:
class CreateUserForm(forms.Form):
username = forms.CharField(max_length=30)
first_name = forms.CharField()
last_name = forms.CharField()
password1=forms.CharField(max_length=30,widget=forms.PasswordInput()) #render_value=False
password2=forms.CharField(max_length=30,widget=forms.PasswordInput())
email=forms.EmailField(required=False)
title = forms.ChoiceField(choices=TITLE_CHOICES)
def clean_username(self): # check if username dos not exist before
try:
User.objects.get(username=self.cleaned_data['username']) #get user from user model
except User.DoesNotExist :
return self.cleaned_data['username']
raise forms.ValidationError("this user exist already")
def clean(self): # check if password 1 and password2 match each other
if 'password1' in self.cleaned_data and 'password2' in self.cleaned_data:#check if both pass first validation
if self.cleaned_data['password1'] != self.cleaned_data['password2']: # check if they match each other
raise forms.ValidationError("passwords dont match each other")
return self.cleaned_data
def save(self): # create new user
new_user=User.objects.create_user(username=self.cleaned_data['username'],
first_name=self.cleaned_data['first_name'],
last_name=self.cleaned_data['last_name'],
password=self.cleaned_data['password1'],
email=self.cleaned_data['email'],
)
return new_user
Is it OK? however it gives me an error in first_name and last_name. Says django doesn't expect first_name and last_name in save() method.
create_user only supports the username, email and password arguments. First call create_user, then add the extra values to the saved object.
new_user=User.objects.create_user(self.cleaned_data['username'],
self.cleaned_data['email'],
self.cleaned_data['password1'])
new_user.first_name = self.cleaned_data['first_name']
new_user.last_name = self.cleaned_data['last_name']
new_user.save()
Related
I was working with Django Forms , I was doing custom validation for a field, but encountered weird problem.
forms.py
class RegistrationForm(forms.Form):
username = forms.CharField(max_length=50)
email = forms.EmailField(required=False)
password = forms.CharField(max_length=50)
password1 = forms.CharField(max_length=50)
def clean_password(self):
password = self.cleaned_data['password']
print(self.cleaned_data) # all fields are present except password1 in cleaned Data
re_password = self.cleaned_data['password1'] #Gives Key Error here
# Do something
Here when I try to do some validation for password field in clean_password function, It gives key error for password1 field ,I don't get why that happens. I tried searching a lot but couldn't find anything relevant, about what causes this error.But then I tried making some change in code and It worked but I don't know why it worked.
modified_forms.py
class RegistrationForm(forms.Form):
username = forms.CharField(max_length=50)
email = forms.EmailField(required=False)
password1 = forms.CharField(max_length=50) #swapped position
password = forms.CharField(max_length=50)
def clean_password(self):
password = self.cleaned_data['password']
print(self.cleaned_data) # all fields are present in cleaned Data
re_password = self.cleaned_data['password1'] #Doesn't Give Key Error here
# Do something
The changes I made was I just swapped the line position of password1 and password ,that is I just changed the order of password and password1. I changed order password1 at line of password, password at position where was password1. And it solved error I don't understand this behaviour from what I know field order shouldn't effect anything. Can someone please explain what is happening here? Thanks :)
It is not weird behavior. Django forms work this way. Please look at the source code here to understand how field cleaning works for django forms. Here is a stripped down version of _clean_fields method.
def _clean_fields(self):
for name, field in self.fields.items():
# skipped
try:
# skipped
value = field.clean(value)
self.cleaned_data[name] = value
if hasattr(self, 'clean_%s' % name):
# skipped
value = getattr(self, 'clean_%s' % name)()
self.cleaned_data[name] = value
except ValidationError as e:
self.add_error(name, e)
What it does is loop over form fields, and for every field, puts its cleaned value in cleaned_data dict. If a method for cleaning field (clean_<FIELD_NAME>) is defined, it calls that method on cleaned data for that field and puts it in cleaned_data dict.
In your first case password1 comes after password. Because fields are cleaned in order, password1 is not yet cleaned when you are trying to access it in clean_password method. Which means its cleaned value is not yet present in cleaned_data dict.
In second case, you swap the position of password and password1. Now cleaning has been performed for password1 and you can access its value.
The rule of thumb is that for clean_<FIELD_NAME> method, you can only access cleaned values of those fields that are declared before that specific field.
Solution
You should do this as django does in its UserCreationForm. They check password match on second field i-e password1 should match password not vice versa (which is essentially same). Snippet with modification from here .
def clean_password1(self):
password = self.cleaned_data.get("password")
password1 = self.cleaned_data.get("password1")
if password and password1 and password != password1:
raise forms.ValidationError('Passwords do not match')
return password1
You use python. python is scripting type language and use interpreter so code will evaluate line by line
in your case you create def clean_password(self) so this method call after taking password from form in lazy evaluation. if you rename your method without swapping of password and password1 rename you method with def clean_password1(self) it work fine
forms.py
class RegistrationForm(forms.Form):
username = forms.CharField(max_length=50)
email = forms.EmailField(required=False)
password = forms.CharField(max_length=50)
password1 = forms.CharField(max_length=50)
def clean_password1(self):
password = self.cleaned_data['password']
print(self.cleaned_data) # all fields are present
re_password = self.cleaned_data['password1']
if you satisfied of my answer let me know
I think it's better practise to make separate clean methods for password and password1 fields
def clean_password(self):
password = self.cleaned_data['password']
# Do something
def clean_password1(self):
password1 = self.cleaned_data['password1']
# Do something
Or validate both in clean() method
def clean(self):
cleaned_data = super().clean()
password = cleaned_data['password']
re_password = cleaned_data['password1']
# Do something
I am trying to implement registration with email and phone on a website. A user can register with either phone or email or both. If a user keeps both phone and email field empty, a ValidationError is raised, "You cannot leave both phone and email fields blank. You must fill at least one of the fields."
We have separate clean methods for username, email, phone, password. I do not want to implement the above-mentioned validation on save(). I don't want to define a clean method in the User model, either.
I have written tests for this form, and they pass. But what errors could possibly arise if I use both clean and clean_fieldname together? Could it become a problem when working with views?
I have 3 questions:
Can I use both clean_fieldname and clean methods in a
form?
In what other way can I make sure that user registers with at least
phone or email?
How do clean() and validate() works? I have read django documentation, but I don't understand it completely.
Here's the code I implemented.
class RegisterForm(SanitizeFieldsForm, forms.ModelForm):
email = forms.EmailField(required=False)
message = _("Phone must have format: +9999999999. Upto 15 digits allowed."
" Do not include hyphen or blank spaces in between, at the"
" beginning or at the end.")
phone = forms.RegexField(regex=r'^\+(?:[0-9]?){6,14}[0-9]$',
error_messages={'invalid': message},
required=False)
password = forms.CharField(widget=forms.PasswordInput())
MIN_LENGTH = 10
class Meta:
model = User
fields = ['username', 'email', 'phone', 'password',
'full_name']
class Media:
js = ('js/sanitize.js', )
def clean(self):
super(RegisterForm, self).clean()
email = self.data.get('email')
phone = self.data.get('phone')
if (not phone) and (not email):
raise forms.ValidationError(
_("You cannot leave both phone and email empty."
" Signup with either phone or email or both."))
def clean_username(self):
username = self.data.get('username')
check_username_case_insensitive(username)
if username.lower() in settings.CADASTA_INVALID_ENTITY_NAMES:
raise forms.ValidationError(
_("Username cannot be “add” or “new”."))
return username
def clean_password(self):
password = self.data.get('password')
validate_password(password)
errors = []
email = self.data.get('email')
if email:
email = email.split('#')
if email[0].casefold() in password.casefold():
errors.append(_("Passwords cannot contain your email."))
username = self.data.get('username')
if len(username) and username.casefold() in password.casefold():
errors.append(
_("The password is too similar to the username."))
phone = self.data.get('phone')
if phone:
if phone_validator(phone):
phone = str(parse_phone(phone).national_number)
if phone in password:
errors.append(_("Passwords cannot contain your phone."))
if errors:
raise forms.ValidationError(errors)
return password
def clean_email(self):
email = self.data.get('email')
if email:
if User.objects.filter(email=email).exists():
raise forms.ValidationError(
_("Another user with this email already exists"))
return email
def clean_phone(self):
phone = self.data.get('phone')
if phone:
if User.objects.filter(phone=phone).exists():
raise forms.ValidationError(
_("Another user with this phone already exists"))
return phone
def save(self, *args, **kwargs):
user = super().save(*args, **kwargs)
user.set_password(self.cleaned_data['password'])
user.save()
return user
You can get a lot out of reading the Django code; it is a well-commented codebase! The relevant section is in django/forms/forms.py. When a form is cleaned/validated, it will call full_clean. This will first call _clean_fields, which calls the field clean and looks for a clean_{fieldname} method on the form to call. Then, the form clean is called.
def full_clean(self):
"""
Clean all of self.data and populate self._errors and self.cleaned_data.
"""
self._errors = ErrorDict()
if not self.is_bound: # Stop further processing.
return
self.cleaned_data = {}
# If the form is permitted to be empty, and none of the form data has
# changed from the initial data, short circuit any validation.
if self.empty_permitted and not self.has_changed():
return
self._clean_fields()
self._clean_form()
self._post_clean()
def _clean_fields(self):
for name, field in self.fields.items():
# value_from_datadict() gets the data from the data dictionaries.
# Each widget type knows how to retrieve its own data, because some
# widgets split data over several HTML fields.
if field.disabled:
value = self.get_initial_for_field(field, name)
else:
value = field.widget.value_from_datadict(self.data, self.files, self.add_prefix(name))
try:
if isinstance(field, FileField):
initial = self.get_initial_for_field(field, name)
value = field.clean(value, initial)
else:
value = field.clean(value)
self.cleaned_data[name] = value
if hasattr(self, 'clean_%s' % name):
value = getattr(self, 'clean_%s' % name)()
self.cleaned_data[name] = value
except ValidationError as e:
self.add_error(name, e)
def _clean_form(self):
try:
cleaned_data = self.clean()
except ValidationError as e:
self.add_error(None, e)
else:
if cleaned_data is not None:
self.cleaned_data = cleaned_data
I want to create a SINGLE form which gives the ability to the admin to create a new user with extended profile. Please note that, I don't want to use admin and registration apps.
I have extended the user with the UserProfile model. I have read all the documents related to extending user profile. But, I really don't know how to save these information.
I coded the following django form for this issue:
class CreateUserForm(forms.Form):
username = forms.CharField(max_length=30)
first_name = forms.CharField()
last_name = forms.CharField()
password1=forms.CharField(max_length=30,widget=forms.PasswordInput()) #render_value=False
password2=forms.CharField(max_length=30,widget=forms.PasswordInput())
email=forms.EmailField(required=False)
title = forms.ChoiceField(choices=TITLE_CHOICES)
def clean_username(self): # check if username dos not exist before
try:
User.objects.get(username=self.cleaned_data['username']) #get user from user model
except User.DoesNotExist :
return self.cleaned_data['username']
raise forms.ValidationError("this user exist already")
def clean(self): # check if password 1 and password2 match each other
if 'password1' in self.cleaned_data and 'password2' in self.cleaned_data:#check if both pass first validation
if self.cleaned_data['password1'] != self.cleaned_data['password2']: # check if they match each other
raise forms.ValidationError("passwords dont match each other")
return self.cleaned_data
def save(self): # create new user
new_user=User.objects.create_user(username=self.cleaned_data['username'],
first_name=self.cleaned_data['first_name'],
last_name=self.cleaned_data['last_name'],
password=self.cleaned_data['password1'],
email=self.cleaned_data['email'],
)
return new_user
Is it OK? however it gives me an error in first_name and last_name. Says django doesn't expect first_name and last_name in save() method.
create_user only supports the username, email and password arguments. First call create_user, then add the extra values to the saved object.
new_user=User.objects.create_user(self.cleaned_data['username'],
self.cleaned_data['email'],
self.cleaned_data['password1'])
new_user.first_name = self.cleaned_data['first_name']
new_user.last_name = self.cleaned_data['last_name']
new_user.save()
I have a date time field called bk_time. Now, I would like to write custom validation for bk_time based on different users.
For example, I have users Staff_A, Staff_B and Superuser:
Staff_A can only set the time = Mon-Fri 9am-12am
Staff_B can only set the time = Monday only
Superuser no limitation
I have referred Django Doc Validators. But it seems not working for multiple validation
I have tried to write save_formsetDjango Doc Admin.But it seems not able to raise ValidationError
models.py
class Location(models.Model):
name = models.CharField('Location', max_length=100)
class Room(models.Model):
room_label = models.CharField('Room Lebel', max_length=100)
bk_time= models.DateTimeField('Booking Time')
admin.py
class RoomInline(admin.StackedInline):
model = Room
extra = 0
class LocationAdmin(admin.ModelAdmin):
list_display = ['id', 'name']
fields = ('name')
inlines = [RoomInline]
If this is relevant, I'm using Django 1.4.
I think this has to come on the form validation, and not on the field validation. This is because your validation depends on two independent fields.
In particular, this is very similar to an authentication: your validation depends on the user and on another field. Take a look how Django implements its authentication (from django.contrib.auth):
class AuthenticationForm(forms.Form):
[...]
def clean(self):
username = self.cleaned_data.get('username')
password = self.cleaned_data.get('password')
if username and password:
self.user_cache = authenticate(username=username,
password=password)
if self.user_cache is None:
raise forms.ValidationError(
self.error_messages['invalid_login'],
code='invalid_login',
params={'username': self.username_field.verbose_name},
)
elif not self.user_cache.is_active:
raise forms.ValidationError(
self.error_messages['inactive'],
code='inactive',
)
return self.cleaned_data
In your case, you want to raise a ValidationError on a given constraint, and return cleaned_data otherwise.
I have an app with 2 user levels; Superuser, and Staff. Superuser will login to this app and create a new user and assign the user either Superuser or Staff permissions, but they are not logging in through the default Django admin, they will be logging into a custom admin. How do I display the permission checkboxes in my form and how do I save them for that new user being created? Is it merely just a normal checkbox or is there something I need to override to be able to accomplish this?
Here is my CreateUserForm as it is now.
class CreateUserForm(forms.Form):
username = forms.EmailField(max_length=50)
email = forms.EmailField()
first_name = forms.CharField(max_length=150)
last_name = forms.CharField(max_length=150)
password1 = forms.CharField(max_length=30, widget=forms.PasswordInput(render_value=False), label='Password')
password2 = forms.CharField(max_length=30, widget=forms.PasswordInput(render_value=False), label='Password Confirmation')
address_1 = forms.CharField(max_length=50)
address_2 = forms.CharField(max_length=50)
city = forms.CharField(max_length=50)
province = forms.CharField(max_length=2)
country = forms.CharField(max_length=50)
postal_code = forms.CharField(max_length=10)
work_phone = forms.CharField(max_length=20)
mobile_phone = forms.CharField(max_length=20)
fax = forms.CharField(max_length=20)
url = forms.CharField()
comments = forms.CharField(widget=forms.Textarea)
def clean_username(self):
try:
User.objects.get(username=self.cleaned_data['username'])
except User.DoesNotExist:
return self.cleaned_data['username']
raise forms.ValidationError("Sorry, this username has already been taken. Please choose another.")
def clean_email(self):
try:
User.objects.get(email=self.cleaned_data['email'])
except User.DoesNotExist:
return self.cleaned_data['email']
raise forms.ValidationError("Sorry, this email has already been taken. Please choose another.")
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("You must type the same password each time.")
if ' ' in self.cleaned_data['username']:
raise forms.ValidationError("username must not contain spaces")
return self.cleaned_data
def save(self):
new_user = User.objects.create_user(
username = self.cleaned_data['username'],
email = self.cleaned_data['email']
)
new_user.first_name = self.cleaned_data['first_name']
new_user.last_name = self.cleaned_data['last_name']
new_user.set_password(self.cleaned_data['password'])
new_user.is_active = True
new_user.save()
new_profile = UserProfile(
# TODO:
)
Thanks
If you mean the permission checkboxes for is_staff and is_superuser, then you're probably best off using a ModelForm w/ the User model. This will automatically take care of everything for you.
class UserForm(forms.ModelForm):
class Meta:
model = User
exclude = ('last_login', 'date_joined')
EDIT per OP update: You can just add 2 forms.BooleanField() fields to your form for is_superadmin and is_staff (or name them differently), and much like you're already doing in the save method you can do new_user.is_staff = self.cleaned_data['is_staff']. You may consider using a choice field instead, w/ a dropdown w/ 3 entries, "Normal User", "Staff User", "Admin User", and then set is_staff and is_superadmin according to the user's selection.
It's also still possible to use a ModelForm on the User model and just add the necessary extra fields in addition. It may or may not be worth it to you, depending how custom you're getting.
Another note about your form - when it comes to editing existing users, this won't be able to be used for that w/ the current clean methods on username/email. But if you tweak those to exclude the current instance (if set) from the lookup, you will be able to use this form for editing existing users (though since it's not a model form you'd need to populate the initial data manually).