I am a beginner in Django an am using version 2.2 .I created a user form to sign a user in the site but it cant add other field information to the database
I have tried adding other fields in the fields list add adding fields but nothing works`
forms.py
from django import forms
from django.contrib.auth import (
authenticate,
get_user_model
)
User = get_user_model()
class UserRegisterForm(forms.ModelForm):
username = forms.CharField(label='PUsername')
email = forms.EmailField(label='Email address')
email2 = forms.EmailField(label='Confirm Email')
password = forms.CharField(widget=forms.PasswordInput,label='Password')
password2 = forms.CharField(widget=forms.PasswordInput,label='ConfirmPassword')
age = forms.CharField(label='your age')
info = forms.CharField(label='info about you')
class Meta:
model = User
fields = [
'username',
'email',
'email2',
'password',
'password2',
'age'
'info'
]
def clean(self, *args, **kwargs):
username = self.cleaned_data.get('email')
email = self.cleaned_data.get('email')
email2 = self.cleaned_data.get('email2')
password = self.cleaned_data.get('password')
password2 = self.cleaned_data.get('password2')
if email != email2:
raise forms.ValidationError("Emails must match")
email_qs = User.objects.filter(email=email)
if password != password2:
raise forms.ValidationError("Passwords must match")
email_qs = User.objects.filter(email=email)
if email_qs.exists():
raise forms.ValidationError(
"This email has already been registered")
username_ex = User.objects.filter(username=username)
if username_ex.exists():
raise forms.ValidationError("This username is taken")
return super(UserRegisterForm, self).clean(*args, **kwargs)
views.py
from django.shortcuts import render, redirect
from django.contrib.auth import authenticate,get_user_model,login,logout
from .forms import CreateUserForms
import random
import string
def register_view(request):
if request.method=='POST':
frm=CreateUserForm(request.POST)
if frm.is_valid():
username, email, password = frm.cleaned_data['username'], frm.cleaned_data['email'], frm.cleaned_data['password1']
new_user = User.objects.create_user(username, email, password)
new_user.is_active = True # if you want to set active
new_user.save()
else:
frm=CreateUserForm()
return render(request,'registration/signup.html',{'form':frm})
def logout_view(request):
logout(request)
return redirect('/')
Expected results are the working form,but actual results are only saving the username, password and email
read the documentation on python super . you are using it in wrong way. calling super at last of the clean method mean it it execute the modelform clean method and code before super get modified .
The title of your question is
How can i create custom users with only forms?
The answer to that is: That's not possible. Whatever you want to save to the database, you have to have a corresponding model for it. Forms only determine what data is submitted by the clients and how that data is processed.
So if you want to save columns age and info for your users, you'll have to create a Model and the corresponding table and columns in your database to hold these values.
Now, the default Django User model only takes username, email and password (plus some booleans like is_staff, is_superuser, is_active etc...). If you want to save additional information, you have a few options that are well described in the official docs, either by extending the default User model or by substituting it.
The recommendation when starting a new Django project is to always at least substitute with your own User model (see this). If you will only ever have one type of user, then add the age and info fields on your custom User model. But if there's any chance that you might have different types of users where age and info might not be relevant in the future, extend your custom User model with a profile.
Related
Edit (trying to clarify the problem):
The custom validator I created for my flask app to check for duplicate usernames and emails does not work for admin users to modify other user accounts. When an admin user edits another user's username or email, the validator indicates it is a duplicate because it is trying to compare the current (admin) user's username and email, not the username and email of the user being edited. How do I write a custom validator to use the user object passed to the form in the view function below to compare username and email for the user being edited and not the logged in user?
Original post:
I am working on a Flask app that includes the ability for users with admin privileges to modify user account information. Currently, I am using Flask-WTF and WTForms to create a form on a user profile page that the account owner or a user with admin privileges (through role assignment in the user model) can edit user information. In the view function, I pass the user information to the form to pre-populate the form fields with the user information. When I made the profile editing form for the account owner, when the form is submitted I have a custom validator that compares the submitted data to the data for the currently logged in user to detect if there were any fields edited if then check if some of those fields (like username, email, etc.) are already in the database to avoid duplicates. For the form that admin users can edit other users, this validator keeps preventing writing new values to the database because the validator is comparing the logged in user information to the database and not the user information belonging to the account that is being edited. I want to know how to pass the user object that I use to populate the form fields to the form validation when the form is submitted? The Flask app is using a blueprint structure. I am pretty new to this so I hope the question makes sense. Here is some code that I think is helpful.
Here is the view function:
# Edit account view function for admin users
#users.route('/edit-account/<int:user_id>', methods=['GET', 'POST'])
#login_required
def edit_account(user_id):
# Get the info for the user being edited
user = User.query.get_or_404(user_id)
# Check to be sure user has admin privileges
if current_user.role != 'admin' and current_user.role != 'agent':
abort(403)
form = AccountEditForm()
if form.validate_on_submit():
# Update the profile picture, if a file is submitted
if form.profile_pic.data:
picture_file = save_picture(form.profile_pic.data)
user.profile_pic = picture_file
# Update the database entries for the user
user.username = form.username.data
user.first_name = form.first_name.data
user.last_name = form.last_name.data
user.email = form.email.data
user.role = form.role.data
db.session.commit()
flash(f'The account for {user.first_name} {user.last_name} has been updated.', 'success')
return redirect(url_for('users.users_admin'))
# Pre-populate the form with existing data
elif request.method == 'GET':
form.username.data = user.username
form.first_name.data = user.first_name
form.last_name.data = user.last_name
form.email.data = user.email
form.role.data = user.role
image_file = url_for('static', filename=f'profile_pics/{user.profile_pic}')
return render_template('account.html', title='account',
image_file=image_file, form=form, user=user)
Here is the form class that doesn’t work for the validator when an admin user is trying to edit another user’s account:
class AccountEditForm(FlaskForm):
username = StringField('Username',
validators=[DataRequired(), Length(min=2, max=20)])
first_name = StringField('First Name',
validators=[DataRequired(), Length(min=2, max=32)])
last_name = StringField('Last Name',
validators=[DataRequired(), Length(min=2, max=32)])
email = StringField('Email',
validators=[DataRequired(), Email()])
profile_pic = FileField('Update Profile Picture',
validators=[FileAllowed(['jpg', 'png'])])
role = SelectField('Role', validators=[DataRequired()],
choices=[('admin', 'Admin'), ('agent', 'Agent'), ('faculty', 'Faculty'),
('staff', 'Staff'), ('student', 'Student')])
submit = SubmitField('Update')
# Custom validation to check for duplicate usernames
def validate_username(self, username):
# Check to see if the form data is different than the current db entry
if username.data != username:
# Query db for existing username matching the one submitted on the form
user = User.query.filter_by(username=username.data).first()
if user:
raise ValidationError('Username is taken, please choose another.')
# Custom validation to check for duplicate email
def validate_email(self, email):
# Check to see if the form data is different than the current db entry
if email.data != user.email:
# Query db for existing email matching the one submitted on the form
user = User.query.filter_by(email=email.data).first()
if user:
raise ValidationError('Email is already used, please choose another or login.')
Here is the custom validator that works when the user is editing their own account from a different form class:
# Custom validation to check for duplicate usernames
def validate_username(self, username):
# Check to see if the form data is different than the current db entry
if username.data != current_user.username:
# Query db for existing username matching the one submitted on the form
user = User.query.filter_by(username=username.data).first()
if user:
raise ValidationError('Username is taken, please choose another.')
# Custom validation to check for duplicate email
def validate_email(self, email):
# Check to see if the form data is different than the current db entry
if email.data != current_user.email:
# Query db for existing email matching the one submitted on the form
user = User.query.filter_by(email=email.data).first()
if user:
raise ValidationError('Email is already used, please choose another or login.')
here is an example of passing object to the custom validator, and the scenario is exactly like yours:
class EditProfileAdminForm(EditProfileForm):
email = StringField('Email', validators=[DataRequired(), Length(1, 254), Email()])
role = SelectField('Role', coerce=int)
active = BooleanField('Active')
confirmed = BooleanField('Confirmed')
submit = SubmitField()
def __init__(self, user, *args, **kwargs): # accept the object
super(EditProfileAdminForm, self).__init__(*args, **kwargs)
self.role.choices = [(role.id, role.name)
for role in Role.query.order_by(Role.name).all()]
self.user = user # set the object as class attr
def validate_username(self, field):
# use self.user.username to get the user's username
if field.data != self.user.username and User.query.filter_by(username=field.data).first():
raise ValidationError('The username is already in use.')
def validate_email(self, field):
if field.data != self.user.email and User.query.filter_by(email=field.data.lower()).first():
raise ValidationError('The email is already in use.')
# view function
#admin_bp.route('/profile/<int:user_id>', methods=['GET', 'POST'])
#login_required
#admin_required
def edit_profile_admin(user_id):
user = User.query.get_or_404(user_id)
form = EditProfileAdminForm(user=user) # pass the object
# ...
Example codes come from a photo-sharing application.
While i am authenticating the login form using authenticate function i am getting user as none eventhough getting the username and password.
settings.py:
---------
AUTHENTICATION_BACKENDS = ("django.contrib.auth.backends.ModelBackend",)
forms.py:
----------
class UserRegistrationForm(forms.Form):
fname = forms.CharField(required=True,label='FirstName',max_length=32)
lname = forms.CharField(required=True,label='LastName',max_length=32)
username = forms.CharField(required = True,label = 'Username',max_length = 32)
password = forms.CharField(required = True,label = 'Password',max_length = 32,min_length=8)
class login_form(forms.Form):
username = forms.CharField()
password1 = forms.CharField(widget=forms.PasswordInput)
views.py:
--------
def signup(request):
if request.method == 'POST':
form = UserRegistrationForm(request.POST)
if form.is_valid():
userObj = form.cleaned_data
username = userObj['username']
password = userObj['password']
fname = userObj['fname']
lname = userObj['lname']
print (username,password,fname,lname)
if(len(password)<8):
messages.error(request,"This password length should be minimum 8 characters")
# raise ValidationError("This password length should be minimum 8 characters ")
if not (User.objects.filter(username=username).exists()):
p = Event(fname=fname, lname=lname, username=username, password=password)
p.save()
# return HttpResponseRedirect('Login.html')
return redirect('/Login/')
else:
raise forms.ValidationError('Looks like a username with that username or password already exists')
else:
form = UserRegistrationForm()
return render(request, 'signup.html', {'form':form})
def Login(request):
form = login_form(request.POST or None)
if form.is_valid():
username = form.cleaned_data.get("username")
password = form.cleaned_data.get("password1")
print (username,password)
user = authenticate(username=username, password=password)
print('user is', user)
models.py
:--------
class MyUserManager(BaseUserManager):
def create_user(self, fname,lname,username, password):
"""
Creates and saves a User with the given username, date of
birth and password.
"""
if not username:
raise ValueError('Users must have an username')
user = self.model(username=username,fname=fname,lname=lname)
user.set_password(password)
user.save(using=self._db)
return user
def create_superuser(self, fname,lname,username, password,email=None):
"""
Creates and saves a superuser with the given username and password.
"""
user = self.create_user(
fname=fname,
lname=lname,
username=username,
password=password,
)
user.is_admin = True
user.is_superuser = True
user.save(using=self._db)
return user
class Event(AbstractBaseUser):
fname = models.CharField('fname', max_length=120)
lname = models.CharField('lname',max_length=120)
username = models.CharField('username',max_length = 60,unique=True)
password = models.CharField('password',max_length=120,default='pavi#2789')
USERNAME_FIELD = 'username'
REQUIRED_FIELDS = ['fname','lname']
objects = MyUserManager()
def __unicode__(self):
return self.username
class Meta:
# managed = False
db_table = "user"
# fields = ('username', 'fname', 'lname', 'password', 'password2')
In database side the login credentials are saved.I dont know what is going wrong here.
Here Event is nothing but the model which i have created .
I have updated my models.py with the AbstractBaseUser, BaseUserManager and login using the superuser credentials it is working but when i am creating the user with the singup form the login is not working and throwing me the error as Manager isn't available; 'auth.User' has been swapped for 'Provisioning.Event'
This isn't right at all. You can't just declare a random model and expect it to work for authentication. You need to subclass AbstractBaseUser and add your fields, declare your model in the AUTH_USER_MODEL setting, and set the password appropriately on save.
from django.contrib.auth.base_user import AbstractBaseUser, BaseUserManager
class Event(AbstractBaseUser):
...
And in settings.py:
AUTH_USER_MODEL = 'myapp.Event'
Now, when you create the user in the view, you need to use set_password to hash the password:
p = Event(fname=fname, lname=lname, username=username)
p.set_password(password)
p.save()
Also, note that the checking of existing usernames should be taken care of in the view - which would happen automatically if you used a ModelForm. Even better, use the built-in UserCreationForm from django.contrib.auth.forms. (But whatever you do, note that it makes no sense at all to filter on User, since you aren't using that model at all.)
The problem is not with the call to authenticate, but probably with how you are implementing the custom user model.
Using a custom user model is totally fine, and is very useful, but if you want to keep things easy for yourself, let Django handle the password part.
There's a great guide on how to write your own user model and still have it play nicely with built-in Django functionality, like authentication: https://docs.djangoproject.com/en/2.1/topics/auth/customizing/#specifying-a-custom-user-model
The reason for this is because Django hashes passwords for storage in the database for security. There's a lot of nuance to how Django handles passwords. It's quite fascinating if you're interested: https://docs.djangoproject.com/en/2.1/topics/auth/passwords/#how-django-stores-passwords
I am using django's inbuilt authentication system. Everything seems to be working fine. There are two fields that the user is requested to input at the time of signup: username and email. While logging in they are required to enter username and password.
I'd like to change this behavior so that username field is gone. I want to treat the email as the users username. So while signing in user will be required to put email / password
Is this possible while still using django's inbuilt auth system? I'm on django 1.7
Update
I had the need to add additional fields so I added the following to models.py
from django.db import models
from django.contrib.auth.models import User
class UserProfile(models.Model):
user = models.OneToOneField(User)
telephone_number = models.CharField(max_length=100)
website_url = models.CharField(max_length=100)
User.profile = property(lambda u: UserProfile.objects.get_or_create(user=u)[0])
The answer is directly in django documentation, in short words you should subclass AbstractBaseUser or AbstractUser (in second case you can't totally remove username field), create your own user manager based on BaseUserManager or UserManager and customize built-in auth forms if you're using it (or any app that you're using is using it).
This is not strictly cannon, but to avoid creating a new User class or Auth backend, I tend to let users log in with both username or email.
Anyways you'll want to ensure emails are unique as django does not check this by default.
You'll then have to override the default login view to support this. You can create something along the lines of:
class EmailUsernameLoginView(View):
def post(self, request):
next = request.POST.get('next', None)
username = request.POST.get('username', None)
password = request.POST.get('password', None)
error = ''
if username and password:
try:
usr = User.objects.get(email=username)
username = usr.username
except User.DoesNotExist:
pass # If the user doesn't exist, it's an username
user = authenticate(username=username, password=password)
if user is not None:
if user.is_active:
login(request, user)
return redirect(next)
else:
error = 'Your account is not active'
else:
error = 'The username / email - password comb is wrong'
else:
error = 'Please provide a username / email and password'
ctx = {'error': error} # Fill with needed feedback
return render(request, ctx, 'registration/login.html')
This is just a draft and you should probably include a form to help with validation / cleaning
I have created an Update Profile page. So the fields are: Email, First Name, Last Name.
In the validation, I'm trying to exclude logged user's email address, and also I'm filtering other user's email addresses. I think if you see the code you will understand what I'm talking about.
I read several questions here but couldn't find something. Some users had the same issue.
So with the code below, the issue I'm getting is:
type object 'User' has no attribute 'email'.
I tried many ways to get the current user's email address(before I save the form) but still nothing.
forms.py
class UpdateProfile(forms.ModelForm):
email = forms.EmailField(required=True)
first_name = forms.CharField(required=False)
last_name = forms.CharField(required=False)
class Meta:
model = User
fields = ('email', 'first_name', 'last_name')
def clean_email(self):
email = self.cleaned_data.get('email')
current_user_email = User.email
if User.objects.filter(email__iexact=email).exclude(email__iexact=current_user_email).count() > 0:
raise forms.ValidationError('This email address is already in use.'
'Please supply a different email address.')
return email
def save(self, commit=True):
user = super(UpdateProfile, self).save(commit=False)
if commit:
user.save()
return user
I suppose that in line current_user_email = User.email "User" isn't really a actual user instance, it is a model class which you imported for setting model = User in Meta.
If you want to use this form only for editing user data You should do something like this:
urls.py
urlpatterns = patterns('project.apps.app_name.views',
url(r'^edit-user/(?P<pk>\d+)/?$','edit_user',name='edit_user'),
)
app_name/views.py:
def edit_user(request,pk):
user_instance = User.objects.get(pk=pk)
form = UpdateProfile(request.POST, instance=user_instance)
.... more code ....
And in forms.py you should change line from:
current_user_email = User.email
to:
current_user_email = self.instance.email
But the proper way to do that (if you use django >1.6) would be to create custom User model, and set email field attribute unique=True.
I'm creating a registration form for my site.
I'm going with just the standard field entries of username, email, password in my User object.
The database tables are already created.
Models.py
class RegistrationForm(ModelForm):
class Meta:
model = User
views.py
def Registration(request):
RegForm = RegistrationForm(request.POST or None)
if request.method == 'POST':
if RegForm.is_valid():
newUser = User.objects.create_user(username, email, password)
RegForm.save()
try:
return HttpResponseRedirect('/Newuser/?userNm=' + clearUserName)
except:
raise ValidationError(('Invalid request'), code='300') ## [ TODO ]: add a custom error page here.
My question is, how do I represent these fields:
username, email, password
in this line (from the view):
newUser = User.objects.create_user(username, email, password)
that are part of the RegistrationForm() in models.py ?
Additionally: How do I properly represent this default model (User) in the modelform in forms.py?
To answer your second question:
Use UserCreationForm to represent registration process for User class, it handles validation and saving for you and you can always extend it (i.e. to add email field)
User class has a lot of related forms, depending of the use case: authentication, registration, password change etc. read the docs on authentication to find more.
https://docs.djangoproject.com/en/1.5/topics/auth/default/#module-django.contrib.auth.forms
You can get the validated data from the form using cleaned_date:
username = form.cleaned_data['username']
email = form.cleaned_data['email']
password = form.cleaned_data['password']
newUser = User.objects.create_user(username, email, password)
Now I think your approach has serious problems since you are saving the form again after creating the user object, also the User object expects the password field to be a hash of the password and some meta data, etc.
I highly recommend you use django-registration (or at least have a look at it) which handles all this stuff for you in addition to email validation, all you need is to provide the templates(and also you can find some templates for it like this )