In my project I have two 'types' of users: Customers and Businesses. They are an extension of the django base User from django.contrib.auth.models.User.
I have in my models.py:
class Customer(models.Model):
user = models.OneToOneField(User, related_name='user', on_delete=models.CASCADE)
birth_date = models.DateField(blank=True, null=True)
phone = PhoneNumberField(unique=True)
def __str__(self):
return self.user.username
class Business(models.Model):
user = models.OneToOneField(User, related_name='business', on_delete=models.CASCADE)
cf = models.CharField(max_length=16, validators=[ssn_validation])
birth_date = models.DateField(null=True)
city = models.CharField(max_length=50, blank=False)
address = models.CharField(max_length=150, blank=False)
Ok, then I have two different registration, one for Customers and one for Businesses.
A problem is that, to validate the password, sent from a REST API, I need to compare password with password2, create a User (django base), and pass it to my Customer.objects.create, like:
I have in my serializers.py:
class CustomerRegistationSerializer(serializers.ModelSerializer):
username = serializers.CharField(source='user.username',
validators=[UniqueValidator(queryset=User.objects.all())])
email = serializers.CharField(source='user.email',
validators=[UniqueValidator(queryset=User.objects.all())])
first_name = serializers.CharField(source='user.first_name')
last_name = serializers.CharField(source='user.last_name')
password = serializers.CharField(source='user.password', write_only=True)
password2 = serializers.CharField(style={'input_style': 'password'}, write_only=True)
birth_date = serializers.CharField(required=False)
class Meta:
model = Customer
fields = ['id', 'username', 'email', 'password', 'password2', 'first_name', 'last_name',
'birth_date', 'phone']
def save(self):
username = self.validated_data['user']['username']
password = self.validated_data['user']['password']
password2 = self.validated_data['password2']
email = self.validated_data['user']['email']
first_name = self.validated_data['user']['first_name']
last_name = self.validated_data['user']['last_name']
phone = self.validated_data['phone']
try:
birth_date = self.validated_data['birth_date']
except KeyError:
birth_date = None
if password != password2:
raise serializers.ValidationError({'password': 'Passwords must match!'})
user = User.objects.create(username=username, email=email, first_name=first_name, last_name=last_name)
user.set_password(password)
user.is_active = False
user.save()
customer = Customer.objects.create(user=user,
birth_date=birth_date,
phone=phone)
return customer
That's actually working, but in case of errors can happen that a User is created, but a Customer not.
Is there a cleaner way to make Customers registration, always checking for password == password2?
EDIT: I found a more elegant way to handle this:
#transaction.atomic
def save(self):
password = self.validated_data['user']['password']
password2 = self.validated_data['password2']
user = User.objects.create(**self.validated_data['user'])
if password != password2:
raise serializers.ValidationError({'password': 'Passwords must match!'})
user.set_password(password)
user.is_active = False
user.save()
update_last_login(None, user)
del self.validated_data['user']
del self.validated_data['password2']
customer = Customer.objects.create(user=user, **self.validated_data)
return customer
If you want to require that all of the DB transactions you are making during save() method are successful to effectively write it on DB, and not to write anything if there is an error at any point in the process, you are typically asking for atomicity (one of the four ACID capabilities of a Database)
Use this Django decorator, typically made for this:
from django.db import transaction
#transaction.atomic
def save(self):
<...>
Related
I am trying to populate a field on a template form in Django. I have tried to do it on the view as well as a value on the form template but no luck. Here is my work
The outcome would be to populate username and email field from the resource model, but I am getting an UNIQUE constraint failed: auth_user.username error.
Thanks
forms.py
class AdminRegistrationForm(UserCreationForm):
is_superuser = forms.BooleanField(),
password1 = forms.CharField(
label="Password",
widget=forms.PasswordInput)
password2 = forms.CharField(
label="Password Confirmation",
widget=forms.PasswordInput)
class Meta:
model = User
fields = ['password1', 'password2']
def clean_password2(self):
password1 = self.cleaned_data.get('password1')
password2 = self.cleaned_data.get('password2')
if not password1 or not password2:
raise ValidationError("Please confirm your password")
if password1 != password2:
raise ValidationError("Passwords must match")
return password2
view.py
def admin_registration(request, username):
resources = Resource.objects.get(username=username)
if request.user.is_authenticated:
return redirect(reverse('index'))
if request.method == "POST":
resources = Resource.objects.get(username=username)
admin_registration_form = AdminRegistrationForm(request.POST)
if admin_registration_form.is_valid():
obj = admin_registration_form.save(commit=False)
obj.is_superuser = True
obj.save()
user = auth.authenticate(username=[resources.username],
password=request.POST['password1'])
if user:
auth.login(user=user, request=request)
messages.success(request, "You have successfully registered")
return redirect(reverse('index'))
else:
messages.error(request, "Unable to register your account at this time")
else:
admin_registration_form = AdminRegistrationForm()
return render(request, 'registration/registration.html', {
"registration_form": admin_registration_form, 'email': resources.email })
models.py
class Resource(models.Model):
ROLE = [
('Analyst', "Analyst"),
('Team Manager', "Team Manager"),
('Quality Auditor', "Quality Auditor"),
('Senior Analyst', "Senior Analyst"),
('', "")
]
username = models.CharField(max_length=254, default='')
status = models.IntegerField(default=1)
email = models.EmailField(max_length=254, null=False, default='')
email_sent = models.IntegerField(default=0)
name = models.CharField(max_length=254, default='')
surname = models.CharField(max_length=254, default='')
role = models.CharField(max_length=30, choices=ROLE, default='')
start_date = models.DateField()
end_date = models.DateField(null=True)
USERNAME_FIELD = "eid"
def __str__(self):
return self.email
In comment you said that you use generic User that Django is providing. Then your mistake is that you didn't specify username field in your Form. This field is needed if you haven't specified other USERNAME_FIELD but in User model. And you haven't, because you just import it. In that case every User created with AdminRegistrationForm has no username, or actually has - None. So as long as first User might be created, second would have same (None) username and it wouldn't be unique.
To fix that issue just add username field. Example:
class AdminRegistrationForm(UserCreationForm):
username = forms.CharField('username', max_length=150)
...
PS. USERNAME_FIELD = "eid" does nothing, because Resource is not an User model. Just create your own CustomUser model which is highly recommended by Django itself :)
Context
I have a project in which there are three entities : Account, Community and JoinRequest.
A JoinRequest binds an Account (user) with a Community. And there should not be more than one JoinRequest for any couple (Account, Community).
Problem
I coded the respective models, serializers and unittest, which you can see below. But when I run my test, it fails when returning join_request_serializer.is_valid() = False
Which leads to the following error :
join_request_serializer.errors
{'user': {'email': [ErrorDetail(string='account with this email already exists.', code='unique')], 'username': [ErrorDetail(string='account with this username already exists.', code='unique')], 'password': [ErrorDetail(string='This field is required.', code='required')]}, 'community': {'name': [ErrorDetail(string='community with this name already exists.', code='unique')]}}
It seems the .is_valid() method of the JoinRequestSerializer tries to recreate an Account and a Community, whose data were previously passed as arguments at construction of the instance...
Any idea why this error appears?
Unit Test
class JoinRequestSerializerTestCase(TestCase):
def test_join_request_serializer_create_success(self):
account = register()
account_serializer = AccountSerializer(account)
community_data = {
'name': 'CommunityNameExample'
}
community = Community(community_data)
community_serializer = CommunitySerializer(community)
data = {'user':account_serializer.data, 'community':community_serializer}
join_request_serializer = JoinRequestSerializer(data=data)
self.assertEqual(join_request_serializer.is_valid(), True)
join_request_serializer.save()
self.assertEqual(JoinRequest.objects.count(), 1)
Account
class MyAccountManager(BaseUserManager):
def create_user(self, email, username, password=None):
if not email:
raise ValueError('Users must have an email address')
if not username:
raise ValueError('Users must have an username')
user = self.model(
email=self.normalize_email(email),
username=username,
)
user.set_password(password)
user.save(using=self._db)
return user
def create_superuser(self, email, username, password):
# This method must be overridden to use MyAccountManager class
user = self.create_user(
email=self.normalize_email(email),
username=username,
password=password,
)
user.is_admin = True
user.is_staff = True
user.is_superuser = True
user.save(using=self._db)
return user
class Account(AbstractBaseUser):
email = models.EmailField(verbose_name="email", max_length=60, unique=True)
username = models.CharField(
max_length=30,
unique=True,
validators = [
RegexValidator(
regex='^[a-zA-Z0-9]*$',
message='Username must be Alphanumeric.',
code='invalid_username'
)
]
)
date_joined = models.DateTimeField(verbose_name='date joined', auto_now_add=True)
last_login = models.DateTimeField(verbose_name='last login', auto_now_add=True)
is_admin = models.BooleanField(default=False)
is_active = models.BooleanField(default=True)
is_staff = models.BooleanField(default=False)
is_superuser = models.BooleanField(default=False)
USERNAME_FIELD = 'username' # Generic (not explicit) keyword for the login field
REQUIRED_FIELDS = ['email']
objects = MyAccountManager() # What is the point of this line?
def create(self, email, username, password, **kwargs):
account = Account.objects.create_user(
username = username,
email = email,
password = password
)
post_save(sender=Account, instance=account, created = True, raw=True)
return account
Community
class Community(models.Model):
name = models.CharField(
max_length=30,
unique=True,
validators = [
RegexValidator(
regex='^[a-zA-Z0-9]*$',
message='Community name must be Alphanumeric.',
code='invalid_username'
)
]
)
bio = models.CharField(max_length=150, blank=True, default='')
slug = models.SlugField(max_length=30, blank=True, default=slugify(name))
class Meta(object):
verbose_name_plural = 'Communities'
def __str__(self):
return self.name
def save(self, *args, **kwargs):
self.slug = slugify(self.name)
super(Community, self).save(*args, **kwargs)
JoinRequest
class JoinRequest(models.Model):
community = models.ForeignKey(Community, on_delete=models.CASCADE)
user = models.ForeignKey(Account, on_delete=models.CASCADE)
creation_date = models.DateTimeField(auto_now_add=True)
class Meta:
constraints = [
models.UniqueConstraint(fields=['community', 'user'], name='unique_joinrequest')
]
AccountSerializer
class AccountSerializer(serializers.ModelSerializer):
class Meta:
model = Account
fields = ['email', 'username', 'password']
extra_kwargs = {'password':{'write_only':True}}
def create(self):
account = Account.objects.create_user(
email = self.validated_data['email'],
username = self.validated_data['username'],
password = self.validated_data['password'],
)
return account
CommunitySerializer
class CommunitySerializer(serializers.ModelSerializer):
class Meta:
model = Community
fields = ['name', 'bio']
lookup_field = 'slug'
extra_kwargs = {
'url':{'lookup_field':'slug'}
}
JoinRequestSerializer
class JoinRequestSerializer(serializers.ModelSerializer):
user = AccountSerializer(
source='account_set',
)
community=CommunitySerializer(
source='community_set',
)
class Meta:
model = JoinRequest
fields = ['user', 'community']
read_only_fields = ('user', 'community')
validators = [
UniqueTogetherValidator(
queryset=JoinRequest.objects.all(),
fields=['user', 'community'],
message='A Join Request for this couple of User'\
' and Community already exists.'
)
]
extra_kwargs = {
'user':{'read_only':True},
'commnuity':{'read_only':True}
}
def create(self):
join_request = JoinRequest(
user = self.validated_data['user'],
community = self.validated_data['community'],
)
return join_request
I have created 3 custom user models. However only one user under the models Users() is able to login in into a sells dashboard that I have created. I want the two user namelly, Buyer() and Supplier() to be able to login to the dashboard but not to the admin area. The following is my code. Please help me see the error.
# models.py
# These are my three custom models
from django.db import models
from django.contrib.auth.models import AbstractUser, AbstractBaseUser, UserManager, BaseUserManager, PermissionsMixin
from django.conf import settings
# Superuser model
class Users(AbstractUser):
username = models.CharField(max_length=25, unique=True)
email = models.EmailField(unique=True, null="True")
objects = UserManager()
USERNAME_FIELD = 'username'
REQUIRED_FIELDS = ['email']
# Returns username
def __str__(self):
return self.username
# Supplier user model
class Supplier(AbstractBaseUser):
sname = models.CharField(max_length=255, verbose_name='Supplier Name', unique=True)
phone_number = models.CharField(max_length=255, verbose_name='Phone Number')
email_address = models.CharField(max_length=255, verbose_name='Email Address', null=True)
physical_address = models.CharField(max_length=255, verbose_name='Physical Address')
description = models.TextField(max_length=255, verbose_name='Describe yourself')
is_active = models.BooleanField(default=True)
objects = Users()
USERNAME_FIELD = 'sname'
def __str__(self):
return self.sname
# This model save inventory of a supplier
class Inventory(models.Model):
pname = models.CharField(max_length=255, verbose_name='Product Name')
quantity = models.PositiveIntegerField(verbose_name='Quantity (kgs)')
measurement = models.CharField(max_length=255, verbose_name='Measurement')
orginal_price = models.PositiveIntegerField(verbose_name='Original Price')
commission = models.PositiveIntegerField(verbose_name='Commission')
selling_price = models.PositiveIntegerField(verbose_name='Selling Price (MWK)')
supplier = models.ForeignKey(Supplier, on_delete=models.CASCADE, verbose_name='Supplier')
def __str__(self):
return self.pname
# This model saves the transaction a buyer has made
class Transaction(models.Model):
cust_name = models.CharField(max_length=255, verbose_name='Customer Name')
pid = models.ForeignKey(Inventory, on_delete=models.CASCADE, verbose_name='Product')
quantity_r = models.PositiveIntegerField(verbose_name='Quantity (KGS)')
success = models.BooleanField(default=False)
uid = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, verbose_name='User Id')
# Calculates actual price
def actual_price(self):
return int(self.quantity_r) * int(self.pid.selling_price)
# Returns customer name
def __str__(self):
return self.cust_name
# Calculates total costs of suppliers products
class SupplierProductCostView(models.Model):
id = models.PositiveIntegerField(primary_key=True)
sname = models.CharField(max_length=255)
price = models.PositiveIntegerField()
class Meta:
db_table = 'home_supplierproductcostview'
managed = False
def __str__(self):
return str(self.id) + ' ' + self.sname + ' ' + str(self.price)
# Buyer user model
class Buyer(AbstractBaseUser):
username = models.CharField(max_length=255, unique=True)
company_name = models.CharField(max_length=255, verbose_name='Company Name')
phone_number = models.CharField(max_length=255, verbose_name='Phone Number')
email_address = models.CharField(max_length=255, verbose_name='Email Address')
address = models.CharField(max_length=500, verbose_name='Physical Address')
description = models.TextField(max_length=255, verbose_name='Describe your company')
is_active = models.BooleanField(default=True)
objects = Users()
USERNAME_FIELD = 'username'
# Returns username
def __str__(self):
return self.username
# forms.py
# This is my registration and login form
from django import forms
from .models import Supplier, Buyer
# Custom supplier registration form
class SupplierRegistrationForm(forms.ModelForm):
password = forms.CharField(label='Password', widget=forms.PasswordInput)
password2 = forms.CharField(label='Repeat password', widget=forms.PasswordInput)
# Supplier registration form fields
class Meta:
model = Supplier
fields = ('sname', 'phone_number', 'email_address', 'physical_address', 'description')
# Password check
def clean_password2(self):
cd = self.cleaned_data
if cd['password'] != cd['password2']:
raise forms.ValidationError('Passwords don\'t match.')
return cd['password2']
# Custom buyer registration form
class BuyerRegistrationForm(forms.ModelForm):
password = forms.CharField(label='Password', widget=forms.PasswordInput)
password2 = forms.CharField(label='Repeat password', widget=forms.PasswordInput)
# Buyer registration form fields
class Meta:
model = Buyer
fields = ('username', 'company_name', 'phone_number', 'email_address', 'address', 'description')
# Password check
def clean_password2(self):
cd = self.cleaned_data
if cd['password'] != cd['password2']:
raise forms.ValidationError('Passwords don\'t match.')
return cd['password2']
# Login form
class LoginForm(forms.Form):
username = forms.CharField()
password = forms.CharField(widget=forms.PasswordInput)
#views.py
# These are my login and logout views
from django.shortcuts import render, redirect, reverse
from django.contrib.auth import authenticate, login, logout
from users.forms import LoginForm
# User logs in to dashboard
def index(request):
if request.method == 'POST':
loginform = LoginForm(request.POST)
# Check if form is valid
if loginform.is_valid():
username = loginform.cleaned_data['username']
password = loginform.cleaned_data['password']
user = authenticate(username=username, password = password)
# If user does not exist
if user is not None:
login(request, user)
return redirect(reverse('home'))
else:
context = {
'form':loginform,
'error': 'Could not login, Please try again...',
}
return render(request, 'users/index.html', context)
loginform = LoginForm()
context = {
'form' : loginform,
}
# Returns login form
return render(request, 'users/index.html', context)
# Logout
def logout_user(request):
logout(request)
return redirect(reverse('login'))
I extended the custom user model with a OneToOneField but now I Am getting this error "django.db.utils.IntegrityError: null value in column "users_id" violates not-null constraint" apparently there is something wrong with my registration view. Can you help?
# Buyer registration view
def buyer_form_register(request):
if request.method == 'POST':
buyer_form = BuyerRegistrationForm(request.POST)
if buyer_form.is_valid():
# Create a new user object but avoid saving it yet
new_user = buyer_form.save(commit=False)
# Set the chosen password
new_user.set_password(
buyer_form.cleaned_data['password'])
# Save the User object
new_user.save()
# Create the user profile
return render(request, 'register/register_done.html')
else:
buyer_form = BuyerRegistrationForm()
return render(request, 'register/register_buyer.html', {'buyer_form': buyer_form})
# Edited model
class Buyer(AbstractBaseUser):
# New field
users = models.OneToOneField(Users, on_delete=models.CASCADE)
username = models.CharField(max_length=255, unique=True)
company_name = models.CharField(max_length=255, verbose_name='Company Name')
phone_number = models.CharField(max_length=255, verbose_name='Phone Number')
email_address = models.CharField(max_length=255, verbose_name='Email Address')
address = models.CharField(max_length=500, verbose_name='Physical Address')
description = models.TextField(max_length=255, verbose_name='Describe your company')
objects = Users()
USERNAME_FIELD = 'username'
def __str__(self):
return self.username
You can't have 3 custom user models, in settings.py you can only set AUTH_USER_MODEL to one model.
Extend your user model with profiles (OneToOneField) instead to differentiate between the different user types.
I have a problem with my User Model. The deal is that when I update any field or fields in the admin panel, my email field gets the same data as at the username field.
I have no idea how to solve this problem. So, I really rely on your help.
Here are all my files that may be important:
models.py
class User(AbstractBaseUser):
email = models.EmailField(
verbose_name = 'email address',
max_length=255,
unique=True
)
real_name = models.CharField(max_length=20, blank=True)
username = models.CharField(max_length=40, unique=True, verbose_name='username')
active = models.BooleanField(default=True)
staff = models.BooleanField(default=False)
admin = models.BooleanField(default=False)
grade = models.ForeignKey(GradeUser, on_delete=models.CASCADE, blank=True, null=True)
country = models.ForeignKey(Country, on_delete=models.CASCADE, blank=True, null=True)
reputation = models.IntegerField(default=0)
image = models.ImageField(upload_to='accounts/media', blank=True)
about_me = models.TextField(blank=True)
USERNAME_FIELD= 'email'
REQUIRED_FIELDS = ['username']
objects = UserManager()
def get_email(self):
return self.email
def get_username(self):
return self.username
def __str__(self):
return self.email
def has_perm(self, perm, obj=None):
return True
def has_module_perms(self, app_label):
return True
#property
def is_staff(self):
return self.staff
#property
def is_admin(self):
return self.admin
#property
def is_active(self):
return self.active
my forms.py
class UserAdminCreationForm(forms.ModelForm):
password1 = forms.CharField(label='Password', widget=forms.PasswordInput)
password2 = forms.CharField(label='Password confirmation', widget=forms.PasswordInput)
class Meta:
model = User
fields = ('email', '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("Passwords don't match")
return password2
def save(self, commit=True):
user = super(UserAdminCreationForm, self).save(commit=False)
user.set_password(self.cleaned_data["password1"])
user.active = False
if commit:
user.save()
return user
class UserAdminChangeForm(forms.ModelForm):
password = ReadOnlyPasswordHashField()
class Meta:
model = User
fields = ('email', 'username', 'password', 'active', 'admin')
def clean_password(self):
return self.initial["password"]
P.S: These two forms are used in admin.py
Ok, so here is the whole explaining of the problem
First:
I shouldn't have used AbstractBaseUser the way I did. I should have never put fields, like image, about_me, country and others in AbstractBaseUser, because when you make it complicated there are a lot of unexpected consequences, which happens specifically to that model, that just doesn't happen with other models.
Second:
I should have created a user-profile model connected to AbstractBaseUser to keep it as much simple as possible for my web application
I hope this post helped you with your issue. If it did, then vote up this post for other people
I'm new to Django, and I've been fighting with this form for over a week now. I started this first because it is the core of my project. What I ultimately want is a single form that has a condition that the user can set, to add data to a second form when they check a radio box (will do that in jquery). I want to give admins the ability to register users, but I have a special subclass of users called operators that need additional information in a separate model. I almost have it working right now, but all users get added to the special subclass. Please help!
EDIT: What's not working is that I want to have the Admins register a user on a single form that can create users and if they check a button, then fill out the rest of the form for operators. I have the second part working (they can create an operator), but I also want them be able to create a regular user with the same form (that doesn't use the operator model). Can this be done?
Here is my code. NOTE: I messed up the password registration in this code, but I'll fix that later. Just working on this core functionality right now.
Models
class UserProfile(AbstractUser):
bio = models.TextField(max_length=500, blank=True)
birth_date = models.DateField(null=True, blank=True)
profile_pic = models.ImageField(null=True, blank=True)
notes = models.TextField(null=True, blank=True)
def __str__(self):
return self.first_name + ' ' + self.last_name
class OperatorProfile(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
cdl = models.ManyToManyField('CDL', blank=True)
endorsement = models.ManyToManyField('Endorsement', blank=True)
cdl_expiration = models.DateField(blank=True, null=True)
def __str__(self):
return str(self.user)
Views
class OperatorCreateView(CreateView):
model = OperatorProfile
template_name = 'pages/operatorprofile_form.html'
form_class = UserCreationMultiForm
success_url = reverse_lazy('index')
def form_valid(self, form):
# Save the user first, because the profile needs a user before it
# can be saved.
user = form['user'].save()
user.groups.add(Group.objects.get(name='Operators'))
profile = form['profile'].save(commit=False)
profile.user = user
profile.save()
form['profile'].save_m2m()
return redirect(reverse_lazy('index'))
Forms
# Operator Creation Form
class OperatorProfileForm(forms.ModelForm):
class Meta:
model = OperatorProfile
exclude = ['user']
class UserProfileForm(forms.ModelForm):
first_name = forms.CharField(widget=forms.TextInput(attrs={'class': 'form-control'}))
last_name = forms.CharField(widget=forms.TextInput(attrs={'class': 'form-control'}))
username = forms.CharField(widget=forms.TextInput(attrs={'class': 'form-control'}))
password = forms.CharField(widget=forms.PasswordInput(attrs={'class': 'form-control'}))
password_confirm = forms.CharField(widget=forms.PasswordInput(attrs={'class': 'form-control'}))
email = forms.CharField(widget=forms.EmailInput(attrs={'class': 'form-control'}))
bio = forms.CharField(widget=forms.Textarea(attrs={'class': 'form-control'}))
def clean_password2(self):
password = self.cleaned_data.get('password1')
password_confirm = self.cleaned_data.get('password2')
if not password_confirm:
raise forms.ValidationError("You must confirm your password")
if password != password_confirm:
raise forms.ValidationError("Your passwords do not match")
return password_confirm
class Meta:
model = UserProfile
fields = ['username', 'password', 'password_confirm', 'first_name', 'last_name', 'bio']
class UserCreationMultiForm(MultiModelForm):
form_classes = {
'user': UserProfileForm,
'profile': OperatorProfileForm,
}