My aim - Trying to create an authentication system in Django, and allows user to signup again with same username if their account is not activated.
If an user try to register with a certain username and that username already exists then update that user with this current user.
My Approach - I have created a form using "UserCreationForm" and defining my own methods to validate the form, and allows the user even if username already exists but user.is_active = False.
Code
forms.py
from django import forms
from django.contrib.auth.forms import UserCreationForm
from .models import User
class SignupForm(UserCreationForm):
email = forms.EmailField(max_length=200, help_text='Required')
name = forms.CharField()
institution = forms.CharField()
def clean_username(self):
username = self.cleaned_data.get('username')
user = None
try:
try:
user = User.objects.get(username=username)
print("is user active username", user.is_active)
except ObjectDoesNotExist as e:
pass
except Exception as e:
raise e
if not user:
pass
elif not user.is_active:
pass
else:
raise forms.ValidationError("This Username Already Exists")
except Exception as e:
raise e
return username
class Meta:
model = User
fields = ('username', 'email', 'institution', 'password1', 'password2')
views.py
from .forms import SignupForm
def regUser(form):
'''
It will save Django user and student.
It recieves form.cleaned_data as argument
'''
print("reg user line 1")
user = User.objects.create_user(username=form['username'],
email=form['email'],
password=form['password1'],
is_active=False,
)
# notice is_active = False, becuase we will activate account if user will
# verify the email
user.save()
student = Student(user=user, name=form['name'], institution=form['institution'])
student.save()
return user
def signup(request):
if request.user.is_authenticated:
return redirect('accounts:home')
if request.method == 'POST':
form = SignupForm(request.POST)
if form.is_valid():
user = regUser(form.cleaned_data)
current_site = get_current_site(request)
message = render_to_string('accounts/acc_active_email.html', {
'user':user, 'domain':current_site.domain,
'uid': urlsafe_base64_encode(force_bytes(user.pk)),
'token': account_activation_token.make_token(user),
})
mail_subject = 'Activate your account.'
to_email = form.cleaned_data.get('email')
email = EmailMessage(mail_subject, message, to=[to_email])
email.send()
return render(request, 'accounts/signup.html', {'email_sent': True})
else:
for field in form:
for error in field.errors:
messages.error(request, error)
return redirect("accounts:signup")
return render(request, 'accounts/signup.html')
models.py
from django.db import models
from django.contrib.auth.models import AbstractUser
class User(AbstractUser):
subject = models.CharField(max_length=250, default="default_value")
class Student(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE, primary_key=True)
name = models.CharField(max_length=250, default="default_value")
institution = models.CharField(max_length=250, default="default_value")
def save(self):
super().save()
def __str__(self):
return self.user.username
My problem - I am getting error " A user with that username already exists" I don't why and from where this error is coming when I am trying to created a new user with already registered username but it account is not activated yet (user.is_active = False).
As much I know clean_fields functions are called to validate the form when we use "form.is_valid()" in our views.py, But I have already overridden the "clean_username" according to my need so why I am getting this error ? and from where it is generated ? and how to solve this ?
Also I would like to mention when user is registered it is also registered "student" that's why I am using "regUser" function. Also I read some other questions similar to this on stackoverflow this and this some answer was that inherit the form from "forms.Form" not from "UserCreationForm" but why can anyone explain?
Please not mark this question as already answered I have gone through all questions and it was not helpful.
The User model you defined inherited the AbstactUser, and username is defined there.
class AbstractUser(AbstractBaseUser, PermissionsMixin):
"""
An abstract base class implementing a fully featured User model with
admin-compliant permissions.
Username and password are required. Other fields are optional.
"""
username_validator = UnicodeUsernameValidator()
username = models.CharField(
_('username'),
max_length=150,
unique=True,
help_text=_('Required. 150 characters or fewer. Letters, digits and #/./+/-/_ only.'),
validators=[username_validator],
error_messages={
'unique': _("A user with that username already exists."),
},
)
...
The attribute unique for username field means that username must be unique among all users, not only active users.
(ref. https://docs.djangoproject.com/en/3.1/ref/models/fields/#unique)
If what you want is to allow users to have the same username among inactive users, set unique=False.
But, if 'inactive' means 'discarded' in your User model, I would recommend changing user's username to garbage value when the user is being inactivated.
Related
In the system I created with Django, users should not log in without the admin approval after registration. For example, after the user fills in the registration form, user will see the warning
waiting for admin approval
and cannot login to the system without approval from the admin panel.
views.py
def signup(request):
form_class = SignUpForm
if request.method == 'POST':
form = SignUpForm(request.POST)
if form.is_valid():
user = form.save()
user.refresh_from_db() # load the profile instance created by the signal
user.save()
raw_password = form.cleaned_data.get('password1')
user = authenticate(username=user.username, password=raw_password)
user.first_name = form.data['first_name']
user.last_name = form.data['last_name']
user.rank = form.data['rank']
user.comp_name = form.data['comp_name']
login(request, user)
return redirect('/')
else:
form = form_class()
return render(request, 'signup.html', {'form': form})
models.py
class UserProfile(AbstractUser):
ranks = (
('analyst', 'Analyst'),
...
)
comp_name = models.CharField(max_length=200, default="Choose")
user_id = models.UUIDField(default=uuid.uuid4(), editable=False, unique=True)
username = models.CharField(max_length=500, unique=True)
first_name = models.CharField(max_length=200)
last_name = models.CharField(max_length=200)
password = models.CharField(max_length=50)
email = models.EmailField(max_length=254)
rank = models.CharField(max_length=200, choices=ranks)
So, you are using a custom user model based on the built-in abstract model. The AbstractUser model has a field named is_active which you can use here.
During the registration process, set the value for is_active to False and use the default AuthenticationForm form for the login view. This form will automatically check for the is_active flag and provide an error when it is False.
Default error messages (you can customize them):
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."),
}
You can get the form from the auth package:
from django.contrib.auth.forms import AuthenticationForm
In your custom admin for the UserProfile model add a filter for the is_active flag:
class UserProfileAdmin(admin.ModelAdmin):
list_filters = ["is_active"]
That way, you will be able to list non-active profiles and activate them in the admin.
I am relatively new to Django, and web development in general. Basically, I'm trying to build a website with two types of users: customers, and suppliers. Both the customers and suppliers have user accounts (containing a username, email, and password) created using Django's built-in 'from django.contrib.auth import login -forms.Form' and stored in the table 'auth_user' in my mySQL database.
But, the suppliers can also create a more detailed profile (name, bio, images etc) which is created using a 'forms.ModelForm' called 'SupplierSignupForm' and stored in a separate table to 'auth_user', called 'home_suppliersignup'.
What I want to happen is, when a supplier is logged in, they can click on their 'my profile' link in the header and be taken to their profile located in the 'home_suppliersignup' table in my database. Currently, I know how to call a logged in users ID from the table 'auth_user' using their session info, and then use that to link to their respective 'auth_user' profile:
views.py
user = request.user
template
My profile
urls.py
url(r'^supplier-profile/(?P<id>[0-9]+)', supplier_profile, name="supplier_profile")
But I don't know how to use this to pull up their information from another database table (i.e. home_suppliersignup).
Any help would be much appreciated!
My current code:
models.py
from __future__ import unicode_literals
from django.db import models
from django.core.urlresolvers import reverse
from django.contrib.auth.models import User
class SupplierSignUp(models.Model):
LOCATION_CHOICES=(
("Central London", "Central London"),
("North London", "North London"),
("East London", "East London"),
("South London", "South London"),
("West London", "West London"),
)
user = models.ForeignKey(User, on_delete=models.CASCADE, null=True)
name = models.CharField(max_length=50)
bio = models.CharField(max_length=50)
area = models.CharField(max_length=50, choices=LOCATION_CHOICES, null=True)
booking_link = models.CharField(max_length=100, null=True)
timestamp = models.DateTimeField(auto_now_add=True, auto_now=False)
def __str__(self):
return self.email
def get_absolute_url(self):
return reverse("supplier_profile", kwargs={"id": self.id})
forms.py
from django import forms
from .models import SupplierSignUp
from django.contrib.auth import (
authenticate,
get_user_model,
login,
logout,
)
from django.contrib.auth.models import User, Group
User = get_user_model()
class SupplierSignUpForm(forms.ModelForm):
class Meta:
model = SupplierSignUp
fields = ['name', 'bio', 'area', 'booking_link']
class UserLoginForm(forms.Form):
username = forms.CharField()
password = forms.CharField(widget=forms.PasswordInput)
def clean(self, *args, **kwargs):
username = self.cleaned_data.get("username")
password = self.cleaned_data.get("password")
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)
class UserRegisterForm(forms.ModelForm):
username = forms.CharField()
email = forms.EmailField(label="Email Address")
email2 = forms.EmailField(label="Confirm Email", widget=forms.TextInput(attrs={'autocomplete':'false'}))
password = forms.CharField(widget=forms.PasswordInput)
class Meta:
model = User
fields = [
'username',
'email',
'email2',
'password',
]
def clean_email2(self):
email = self.cleaned_data.get('email')
email2 = self.cleaned_data.get('email2')
if email != email2:
raise forms.ValidationError("Emails must match")
email_qs = User.objects.filter(email=email)
if email_qs.exists():
raise forms.ValidationError("This email has already been registered")
return email
Assuming that you are not using a class based generic views, try this:
user=request.user
SupplierSignUp.objects.get(user=user)
You are taking the logged in user from the request, do there is no need to specify the user id in your urls.py / template (unless you want users to be able to see each others profile).
I wrote two views as the class in Django in order to do the Registration and Login for my website. But the problem is that the user objects get created successfully. But when I try to authenticate later getting the warning message showing that user with that username already exists in Django
The two views are given below
class RegistrationView(View):
form_class=RegistrationForm
template_name='eapp/user_registration_form.html'
def get(self,request):
form=self.form_class(None)
return render(request,self.template_name,{'form':form})
def post(self,request):
form=self.form_class(request.POST)
if form.is_valid():
user=form.save(commit=False)
#cleaned (normalized) data
username =form.cleaned_data['username']
password =form.cleaned_data['password']
email=form.cleaned_data['email']
user.set_password(password)
user.save()
return render(request,self.template_name,{'form':form,})
class LoginView(View):
form_class=LoginForm
template_name='eapp/user_login_form.html'
def get(self,request):
form=self.form_class(None)
return render(request,self.template_name,{'form':form})
def post(self,request):
form=self.form_class(request.POST)
if form.is_valid():
#cleaned (normalized) data
username =form.cleaned_data['username']
password =form.cleaned_data['password']
#authenticatin
user=authenticate(username=username,password=password)
if user is not None:
if user.is_active:
login(request,user)
return render(request,'eapp/index.html',{})
return render(request,self.template_name,{'form':form,})
here is my forms.py'
from django.contrib.auth.models import User
from django import forms
class RegistrationForm(forms.ModelForm):
password=forms.CharField(widget=forms.PasswordInput)
class Meta:
model=User
fields=['username','email','password']
class LoginForm(forms.ModelForm):
password=forms.CharField(widget=forms.PasswordInput)
class Meta:
model=User
fields=['username','password'
]
How can I solve this? ThankYou
Change your LoginForm for a Form without model:
from django import forms
class LoginForm(forms.Form):
username = forms.CharField(label = 'Nombre de usuario')
password = forms.CharField(label = 'ContraseƱa', widget = forms.PasswordInput)
This way your form will validate that the fields are entered and will not take validations from the User model
def sign_in(request):
#we need to handle all the data that was just typed, we'll add a condition for that
form = NameForm(request.POST)
if form.is_valid():
post = form.save()
post.save()
username = request.POST.get('username')
password = request.POST.get('password')
#auth = authenticate(username=username, password=password)
# If the username and password are provided try to auth them
if username and password:
print username, password
user = authenticate(username=username, password=password)
# if it authenticates successfully, check if the user is not an admin, log them in
if user:
if not user.is_staff:
login(request,user)
return HttpResponseRedirect('success')
else:
form = NameForm()
return render(request, 'checkin/sign_in_new.html', {'form': form})
EDIT: Changed code to work with backend. Added user.is_staff but it still won't return any web page, it just stays on the same one
models.py:
from __future__ import unicode_literals
from django.db import models
from django import forms
from django.forms import ModelForm
# Create your models here.
class Question(models.Model):
question_text = models.CharField("What is your ID?", max_length=100, null=True)
#pub_date = models.DateTimeField('date published')
id_text = models.CharField("", max_length=200, null=True)
def __str__(self):
return self.question_text
forms.py:
from django.forms import ModelForm
from .models import Question
#put the form here
class NameForm(ModelForm):
class Meta:
model = Question
fields = ['question_text', 'id_text']
class IdForm(ModelForm):
class Meta:
model = Question
fields = ['id_text']
Edit 2 These are my model and form files, should these not effect the naming of certain parameters in my backend?
Django authentication system.
I think you are familiar with saving the user data with models
you can use the authentication backend for solving this problem please refer following steps. create custom backends. in project root
backends/staff_backends.py
from project_app_path.staffs.models import MODEL_NAME
from django.contrib.auth.hashers import check_password
from django.contrib.auth.models import User
class StaffBackend:
def authenticate(self, username=None, password=None):
try:
user = MODEL_NAME.objects.get(username=username)
if check_password(password, user.password):
return user
else:
return None
except MODEL_NAME.DoesNotExist:
return None
def get_user(self, user_id):
try:
return MODEL_NAME.objects.get(pk=user_id)
except MODEL_NAME.DoesNotExist:
return None
Include the backends with project settings.
AUTHENTICATION_BACKENDS = [
'django.contrib.auth.backends.ModelBackend',
'backends.staff_backends.StaffBackend',
]
in views.py
from django.contrib.auth import authenticate, login ,logout
def any_view(request):
rg = request.POST.get
username = rg('username')
password = rg('password')
if username and password:
print username,password
user = authenticate(username=username,password=password)
if user:
if not user.is_staff:
login(request,user)
### Redirect where you want , you can use this code in to you signupage also.
This is how I do it, and it works... but I create users automatically, so no form input, but you should get the idea:
user, created = User.objects.get_or_create(username=user_email, email=user_email)
if created:
secret = str(uuid4())
user.set_password(secret)
user.save()
user = User.objects.get(username=user_email)
user.backend = 'django.contrib.auth.backends.ModelBackend'
login(request, user)
so for you, you could probably just use User.objects.get_or_create... instead of create_user
and you would obviously have to add the auth step.
I want to capture the username of the user currently logged in, NOT pk.
incident.username = request.user doesn't work
incident.username = request.user.username doesn't work
incident.username = request.username doesn't work
Uggg. This can't be this hard.
models.py
class Incident(models.Model):
username = models.ForeignKey(User) ## field in db will be auto-populated by view.py
date_reported = models.DateField() ## field in db will be auto-populated by view.py
date_occurred = models.DateField()
number_of_samples_affected = models.IntegerField()
capa = models.CharField(max_length=9)
title = models.CharField(max_length=100)
description = models.TextField()
status = models.ForeignKey(Status) ## field in db will be auto-populated by view.py to "Open" at form submission
category = models.ForeignKey(Category)
lab = models.TextField(Lab)
views.py
from submit_app.forms import IncidentForm
from submit_app.models import Incident, Status
from django.shortcuts import render
from django.contrib.auth.decorators import login_required
import datetime
from django.http import HttpResponseRedirect
from django.core.urlresolvers import reverse
#login_required(login_url='/login/')
def submit(request):
if request.GET:
form = IncidentForm()
template = 'submit.html'
context = {'form': form}
return render(request, template, context)
# if this is a POST request we need to process the form data
if request.POST:
# create a form instance and populate it with the data from the request:
form = IncidentForm(request.POST)
# check whether it's valid:
if form.is_valid():
incident = form.save(False) # creating an incident object, but not saving it to the db just yet
incident.username = request.user # auto capture logged in user
incident.date_reported = datetime.date.today() # auto capture the incident submission date
incident.status = Status.objects.get(status="open") # trying to auto populate status with 'Open' upon submission (foreign key)
incident.save()
return HttpResponseRedirect(reverse('dashboard_app:dashboard'))
form = IncidentForm()
template = 'submit.html'
context = {'form': form}
return render(request, template, context)
Right now, your model has a foreign key relation to the User model, which by default relates to the primary key field. To change that and relate to the username field itself, add a to_field keyword argument to your model, makemigrations, and migrate.
username = models.ForeignKey(User,to_field='username')
Afterwards, you'll be able to access the user for the current request via request.user.username, assuming that username is a field/attribute of User (and not a related model).
...
However, there's generally no need to do this. You can still relate to the User model (relation built via PK) and access the username from there. Easiest way to do this is perhaps to create a method to read the username.
class Incident(models.Model):
user = models.ForeignKey(User, related_name='incidents')
def username(self):
if self.user:
return getattr(self.user,'username',None) # attempt to access username
...
>>> first_incident = Incident.objects.create(user=User.objects.get(username='a'))
>>> print(first_incident.username())
a
There is some obvious confusion. Incident.username is a foreign key to a User model, so it needs to be assigned a User object, not just a username. For that incident.username = request.user should work. You can later access the user name by accessing incident.username.username, although I would rename the field to user to avoid confusion. If this doesn't work, something is not working as it should. It would help if you posted the error you are getting.
You should use custom user model and specify usename field to be primary key. But in django abstract base classes for models can't have "overriden fields" so you will need to sublcass AbstractBaseUser instead of AbstractUser. You may eventually end up with a copy of AbstractUser code (https://github.com/django/django/blob/1.8.9/django/contrib/auth/models.py#L378)
with just one line changed:
from django.contrib.auth.models import AbstractBaseUser, PermissionsMixin, validators, UserManager
class MyUser(AbstractBaseUser, PermissionsMixin):
username = models.CharField(_('username'), max_length=30, unique=True,
primary_key=True, ## the only difference from AbstractUser help_text=_('Required. 30 characters or fewer. Letters, digits and '
'#/./+/-/_ only.'),
validators=[
validators.RegexValidator(r'^[\w.#+-]+$',
_('Enter a valid username. '
'This value may contain only letters, numbers '
'and #/./+/-/_ characters.'), 'invalid'),
],
error_messages={
'unique': _("A user with that username already exists."),
})
first_name = models.CharField(_('first name'), max_length=30, blank=True)
last_name = models.CharField(_('last name'), max_length=30, blank=True)
email = models.EmailField(_('email address'), blank=True)
is_staff = models.BooleanField(_('staff status'), default=False,
help_text=_('Designates whether the user can log into this admin '
'site.'))
is_active = models.BooleanField(_('active'), default=True,
help_text=_('Designates whether this user should be treated as '
'active. Unselect this instead of deleting accounts.'))
date_joined = models.DateTimeField(_('date joined'), default=timezone.now)
objects = UserManager()
USERNAME_FIELD = 'username'
REQUIRED_FIELDS = ['email']
class Meta:
verbose_name = _('user')
verbose_name_plural = _('users')
abstract = True
def get_full_name(self):
"""
Returns the first_name plus the last_name, with a space in between.
"""
full_name = '%s %s' % (self.first_name, self.last_name)
return full_name.strip()
def get_short_name(self):
"Returns the short name for the user."
return self.first_name
def email_user(self, subject, message, from_email=None, **kwargs):
"""
Sends an email to this User.
"""
send_mail(subject, message, from_email, [self.email], **kwargs)
After this you will be able to point FK fields to username field. But do you realy need this? Why do you need to have such FK? Primary keys should better be "static". By using username as primary key you will have problems changing your usernames.
I can imagine several reasons for such a requirement:
You want your incidents to point specific username instead of actual user (maybe your instances of User may be deleted and later recreated with same username?). This is strange but can be done: use username = CharField(...) and also specify property for user with getter and setter.
class Incident(models.Model):
username = models.CharField(max_length=30)
#property
def user(self):
return User.objects.get(username=self.username)
#user.setter
def user(self, user):
if user.is_authenticated():
self.username = user.username
else:
self.username = '#anonymous' # by default '#' is disallowed in username. You can also make your username nullable
you want to "optimize" database calls (to not query users table). In this case you'd better use prefetching or denormalization:
from django.db import models
# prefetch user with only "username" field. Assuming that you have `Incident.user = models.ForeignKey(...)`. Read https://docs.djangoproject.com/en/1.9/topics/db/managers/ and https://docs.djangoproject.com/en/1.9/ref/models/querysets/#prefetch-related
class IncidentManager(models.Manager):
def get_queryset(self):
return super(IncidentManager, self).get_queryset().prefetch_related(models.Prefetch('user', queryset=User.objects.all().only('username'))
class Incident(models.Model):
user = models.ForeignKey(User)
# ...
objects = IncidentManager()
In case of denormalization you should create receiver for post_save and post_delete signals for User model which should update Incident.username field with actual username. You must also create similar signal receivers for Incident's post_save/post_delete (or you can modify Incident.save and Incident.delete methods). You may also create signal receiver for admin.models.LogAction post_save signal (from django.contrib.admin.models import DELETEION; if instance.action_flag == DELETEION and instance.content_type_id=get_content_type_for_model(Incident).pk:) because mass deletion from django-admin does not call Incident.delete and does not trigger post_delete for deleted incidnets. And even after this denormalized data may be invalid if you use User.object.update(username=something) anywhere in your project or if data is changed directly in database.