Extending django.contrib.auth.User model - python

I'm using django.contrib.auth and I have a working login/registration system, however I want to extend my User model to have street address and phone number for registration purposes only, and I'm not sure how can I do that properly. What I have done works but feels wrong and I would like to know a proper way to do this.
I created accounts app and in models.py I have this:
from django.db import models
from django.contrib.auth.models import AbstractUser
class User(AbstractUser):
address = models.CharField(max_length=200)
address_number = models.CharField(max_length=20)
phone = models.CharField(max_length=20)
...then I registered that in admin.py:
from django.contrib import admin
from .models import User
admin.site.register(User)
...then in settings.py I added this line:
AUTH_USER_MODEL = 'accounts.User'
...and wherever I used django.contrib.auth.User, now I use accounts.models.User.
As I said, this works however it somehow feels wrong and it easily could be as this is my first time extending the User model. Why I think this is wrong? For one, Users has disappeared in admin from "Authentication and Authorization" and is instead under "Accounts" (as expected).
How can I accomplish this in a proper way?
#EDIT:
Here is my register form:
from django import forms
from accounts.models import User
class RegistrationForm(forms.ModelForm):
email = forms.EmailField(max_length=200, help_text='Required')
password = forms.CharField(label='Password', widget=forms.PasswordInput)
password2 = forms.CharField(label='Repeat password', widget=forms.PasswordInput)
class Meta:
model = User
fields = (
'username',
'email',
'first_name',
'last_name',
'address',
'address_number',
'phone'
)
def clean_password2(self):
cd = self.cleaned_data
if cd['password'] != cd['password2']:
raise forms.ValidationError('Passwords do not match.')
return cd['password2']
def clean_email(self):
email = self.cleaned_data['email']
if User.objects.filter(email=email).exists():
raise forms.ValidationError(
'Please use another e-mail, that is already taken.')
return email

Related

Show a logged in user their own information from a database model

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).

cleaned_data not working in django 1.8.6

I am working on a django tutorials, and try to validate the forms using cleaned_data properties.
the code of the forms.py file is
from django import forms
from .models import SignUp
class SignUpForm(forms.ModelForm):
class Meta:
model = SignUp
fields = ['email']
def clean_email(self):
email = self.cleaned_data.get('email')
if not '.edu' in email:
raise forms.ValidationError('Please use a valid academic email address')
print(email)
return email
The models.py file contain the following code
from django.db import models
# Create your models here.
class SignUp(models.Model):
email = models.EmailField()
full_name = models.CharField(max_length=255)
timestamp = models.DateTimeField(auto_now_add=True, auto_now=False)
updated = models.DateTimeField(auto_now_add=False, auto_now=True)
def __str__(self):
return self.email
and the admin.py has the following line of code
from django.contrib import admin
from .models import SignUp
from .forms import SignUpForm
class SignUpAdmin(admin.ModelAdmin):
list_display = ['full_name', 'email', 'timestamp', 'updated']
class Meta:
form = SignUpForm
admin.site.register(SignUp, SignUpAdmin)
The problem what I am getting is, cannot grab the email field and cannot process the email field,infact the cleaned_data contains no data, the print(email) not printing the email in the console. and I cannot validate to the .edu email address
I am using python3 and django1.8.6
You have defined your admin class wrong, so it is not using your custom form. You don't use a class Meta inside a modeladmin; you just define the class attribute directly.
class SignUpAdmin(admin.ModelAdmin):
list_display = ['full_name', 'email', 'timestamp', 'updated']
form = SignUpForm

Django: Not able to validate UserCreationForm

Below is the content of my forms.py
from django import forms
from django.contrib.auth.forms import UserCreationForm
from django.contrib.auth.models import User
class RegistrationForm(UserCreationForm):
email = forms.EmailField(required=True)
first_name = forms.CharField(max_length=20, required=True)
last_name = forms.CharField(max_length=20, required=True)
class Meta:
model = User
fields = ('username', 'email', 'password1', 'password2', 'first_name', 'last_name')
def save(self, commit=True):
user = super(UserCreationForm, self).save(commit=False)
user.email = self.cleaned_data['email']
user.first_name = self.cleaned_data['first_name']
user.last_name = self.cleaned_data['last_name']
#validation of email id----------
email1 = user.email
(fisrt, second,) = email1.split("#")
(domain, exn,) = second.split(".")
if not domain == "tmail":
raise forms.ValidationError("Domain must be 'tmail'")
if commit:
user.save()
return user
I am able to print form (including fields email, first_name & last_name) & register the user successfully only issue I have is, Its not performing the validation step. (code below the "#validation of email id----------")
Even if the domain is other than "tmail" it is accepting it without raising any validation error & then registering the user into database. Please help & let me know in case you need more info.
It is too late to validate the email in the save() method. Django lets you validate individual fields by defining a method clean_<fieldname>. This will be called when the form is validated.
def clean_email(self):
email = self.cleaned_data['email']
(first, second,) = email1.split("#")
(domain, exn,) = second.split(".")
if domain != "tmail":
raise forms.ValidationError("Domain must be 'tmail'")
return email
See the docs on cleaning a specific field attribute for more info.
Why are you doing validation in save? All validations must happen before save, such that if any exception does comes in save it is most probably bug in your code. Forms specifically have 'clean' methods especially for this purpose. For specific field validation, you can use "clean_{field_name}". Though if your validation depends on multiple fields, you need to use "clean" method.

Django: Add another field to django-registration

I am pretty new to django and I am very lost with django-registration. At the moment I have the django-registration set up an going but I need to add another field to it for a phone number. I need the phone number field in the registration field instead so I can use the api from twilio to send a verification link through text instead of email. How would I go about adding this one field to django-registration?
I work with django at work and for that kind of issue we used to attach a model to the user, example:
You create a new model, for example profile with a OneToOneField to user
Add your desired fields to that profile model, such as (tlf, country, language,log...)
Create admin.py to manage this model (profile) at the same time you manage users in django admin
Profile Model Example
class Profile(models.Model):
user = models.OneToOneField(User)
phone = models.CharField(max_length=255, blank=True, null=True, verbose_name='phone')
description = models.TextField(blank=True, verbose_name='descripction')
...
...
class Meta:
ordering = ['user']
verbose_name = 'user'
verbose_name_plural = 'users'
admin.py example
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin
from django.contrib.auth.models import User
class ProfileInline(admin.StackedInline):
model = Profile
can_delete = False
filter_horizontal = ['filter fields'] # example: ['tlf', 'country',...]
verbose_name_plural = 'profiles'
fk_name = 'user'
class UserAdmin(UserAdmin):
inlines = (ProfileInline, )
list_display = ('username', 'email', 'first_name', 'last_name', 'is_staff')
list_filter = ('is_staff', 'is_superuser', 'is_active')
admin.site.unregister(User) # Unregister user to add new inline ProfileInline
admin.site.register(User, UserAdmin) # Register User with this inline profile
Create an user and attach a profile to him
# Create user
username = 'TestUser'
email = 'test#example.com'
passw = '1234'
new_user = User.objects.create_user(username, email, passw)
# Create profile
phone = '654654654'
desc = 'Test user profile'
new_profile = Profile(user=new_user, phone = phone, description=desc)
new_profile.profile_role = new_u_prole
new_profile.user = new_user
# Save profile and user
new_profile.save()
new_user.save()
Now you'll have this Profile model attached to each user, and you could add the fields you wish to Profile Model, and for example if you make:
user = User.objects.get(id=1)
you can access to his profile doing:
user.profile
and to access phone
user.profile.phone
Not django-registration, but I customized django-userena once to add a custom field to the signup form.
You can view the code here.
I am sure the process works more or less the same in django-registration too: overriding the signup form and adding custom fields.
However, django-registration is no longer maintained, I believe. It is a classic and works so well, but there are other options too.

Changing password in Django Admin

I recently created the admin.py based in the Django Project Document:
https://docs.djangoproject.com/en/dev/topics/auth/customizing/#django.contrib.auth.models.AbstractBaseUser
But I really missed the functionality that allow the administrator the possibility to change the users passwords. How is possible to add this functionality? I just copied and pasted the code the is in the link above.
from django import forms
from django.contrib import admin
from django.contrib.auth.models import Group
from django.contrib.auth.admin import UserAdmin
from django.contrib.auth.forms import ReadOnlyPasswordHashField
from customauth.models import MyUser
class UserCreationForm(forms.ModelForm):
"""A form for creating new users. Includes all the required
fields, plus a repeated password."""
password1 = forms.CharField(label='Password', widget=forms.PasswordInput)
password2 = forms.CharField(label='Password confirmation', widget=forms.PasswordInput)
class Meta:
model = MyUser
fields = ('email', 'date_of_birth')
def clean_password2(self):
# Check that the two password entries match
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):
# Save the provided password in hashed format
user = super(UserCreationForm, self).save(commit=False)
user.set_password(self.cleaned_data["password1"])
if commit:
user.save()
return user
class UserChangeForm(forms.ModelForm):
"""A form for updating users. Includes all the fields on
the user, but replaces the password field with admin's
password hash display field.
"""
password = ReadOnlyPasswordHashField()
class Meta:
model = MyUser
def clean_password(self):
# Regardless of what the user provides, return the initial value.
# This is done here, rather than on the field, because the
# field does not have access to the initial value
return self.initial["password"]
class MyUserAdmin(UserAdmin):
# The forms to add and change user instances
form = UserChangeForm
add_form = UserCreationForm
# The fields to be used in displaying the User model.
# These override the definitions on the base UserAdmin
# that reference specific fields on auth.User.
list_display = ('email', 'date_of_birth', 'is_admin')
list_filter = ('is_admin',)
fieldsets = (
(None, {'fields': ('email', 'password')}),
('Personal info', {'fields': ('date_of_birth',)}),
('Permissions', {'fields': ('is_admin',)}),
('Important dates', {'fields': ('last_login',)}),
)
add_fieldsets = (
(None, {
'classes': ('wide',),
'fields': ('email', 'date_of_birth', 'password1', 'password2')}
),
)
search_fields = ('email',)
ordering = ('email',)
filter_horizontal = ()
# Now register the new UserAdmin...
admin.site.register(MyUser, MyUserAdmin)
# ... and, since we're not using Django's builtin permissions,
# unregister the Group model from admin.
admin.site.unregister(Group)
[UPDATE - Added Information]
I changed the following information but I still seeing just the password (crypted) in a read-only field. How is possible to add a link to change the password?
fieldsets = (
('Permissions', {'fields': ('is_active', 'is_admin','password')}),
)
add_fieldsets = (
(None, {
'classes': ('wide',),
'fields': ('email', 'password')}
),
)
Put this in your UserChangeForm:
password = ReadOnlyPasswordHashField(label=("Password"),
help_text=("Raw passwords are not stored, so there is no way to see "
"this user's password, but you can change the password "
"using this form."))
password = ReadOnlyPasswordHashField(label= ("Password"),
help_text= ("Raw passwords are not stored, so there is no way to see "
"this user's password, but you can change the password "
"using this form."))
There is change in the href, for previous versions of django you can use
this form.
For django 1.9+
this form
I added this method to my UserAdmin class:
def save_model(self, request, obj, form, change):
# Override this to set the password to the value in the field if it's
# changed.
if obj.pk:
orig_obj = models.User.objects.get(pk=obj.pk)
if obj.password != orig_obj.password:
obj.set_password(obj.password)
else:
obj.set_password(obj.password)
obj.save()
You can the show the password field normally, but admins will only see the hashed password. If they alter it, the new value is then hashed and save.
This adds a single query to each time you save a user via the admin. It should generally not be an issue, since most systems don't have admins intensively editing users.
You can also do like this, in this way you just have to write over the field password and once you will save it, it will create the hash for it :
class UserModelAdmin(admin.ModelAdmin):
"""
User for overriding the normal user admin panel, and add the extra fields added to the user
"""
def save_model(self, request, obj, form, change):
user_database = User.objects.get(pk=obj.pk)
# Check firs the case in which the password is not encoded, then check in the case that the password is encode
if not (check_password(form.data['password'], user_database.password) or user_database.password == form.data['password']):
obj.password = make_password(obj.password)
else:
obj.password = user_database.password
super().save_model(request, obj, form, change)
You could also consider extending the UserAdmin this way:
from django.contrib import admin
from myapp.models import CustomUser
from django.contrib.auth.admin import UserAdmin
class CustomUserAdmin(UserAdmin):
list_display = []
admin.site.register(CustomUser, CustomUserAdmin)
For a django version independent solution you can reverse the url in the UserChangeForm.__init__ with something like:
from django.core.urlresolvers import reverse
class UserChangeForm(forms.ModelForm):
password = ReadOnlyPasswordHashField()
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['password'].help_text = (
"Raw passwords are not stored, so there is no way to see "
"this user's password, but you can <a href=\"%s\"> "
"<strong>Change the Password</strong> using this form</a>."
) % reverse_lazy('admin:auth_user_password_change', args=[self.instance.id])
('Permissions', {'fields': ('is_active', 'is_superuser',)}),
Just delete "password" input in your class form:
class MyUserChangeForm(forms.ModelForm):
# password = forms.CharField(label='Password', required=True, widget=forms.PasswordInput)
# password = ReadOnlyPasswordHashField()
class Meta:
model = CustomUser
fields = '__all__'
def save(self, commit=True):
user = super().save(commit=False)
user.set_password(self.cleaned_data["password"])
if commit:
user.save()
return user
django 3.2.8

Categories