Django 2.2: Break-down User model into two different Models? - python

I am using built-in Django Admin, I have two types of users, need to call both using different urls i.e http://127.0.0.1:8000/admin/staff & http://127.0.0.1:8000/admin/customer need to break-down user model into two separate models Staff & Customer. Staff model will be used for Admin users & Customer model will be used for front-end users.
Note: I have kept db table names same as default auth_user, auth_group ...etc
I have created app named users in project
So far I have tried following things:
models.py
from django.contrib.auth.models import (
AbstractUser, Permission, Group, GroupManager)
from django.db import models
from django.utils.translation import gettext_lazy as _
class Staff(AbstractUser):
class Meta(AbstractUser.Meta):
swappable = 'AUTH_USER_MODEL'
verbose_name = _('staff')
verbose_name_plural = _('staffs')
db_table = 'auth_user'
class Customer(AbstractUser):
class Meta(AbstractUser.Meta):
swappable = 'AUTH_USER_MODEL'
verbose_name = _('customer')
verbose_name_plural = _('customers')
db_table = 'auth_user'
admin.py
from django.contrib import admin
from django.contrib.auth.models import Group, User, Permission
from django.contrib.auth.admin import GroupAdmin, UserAdmin
from django.utils.translation import ugettext_lazy as _
from django import forms
from django.forms import ModelForm, Select
from users.models import Staff, Customer
class MyGroupAdminForm(forms.ModelForm):
class Meta:
model = Group
fields = ('name', 'permissions')
permissions = forms.ModelMultipleChoiceField(
Permission.objects.exclude(content_type__app_label__in=[
'auth', 'admin', 'sessions', 'users', 'contenttypes']),
widget=admin.widgets.FilteredSelectMultiple(_('permissions'), False))
class MyUserAdminForm(forms.ModelForm):
model = User
groups = forms.ModelChoiceField(Group.objects, label='Role')
class Meta:
model = User
fields = '__all__'
class CustomUserAdmin(UserAdmin):
form = MyUserAdminForm
fieldsets = (
(None, {'fields': ('username', 'password')}),
(_('Personal info'), {'fields': ('first_name', 'last_name', 'email')}),
(_('Permissions'), {'fields': ('is_active', 'is_staff', 'is_superuser',
'groups',)}),
(_('Important dates'), {'fields': ('last_login', 'date_joined')}),
)
class CustomGroupAdmin(GroupAdmin):
form = MyGroupAdminForm
# admin.site.unregister(User)
admin.site.unregister(Group)
# admin.site.register(User, CustomUserAdmin)
# admin.site.register(Group, CustomGroupAdmin)
admin.site.register(Staff, CustomUserAdmin)
admin.site.register(Group, CustomGroupAdmin)
settings.py
AUTH_USER_MODEL = "users.Staff"
Now when I am trying to change any staff user I am getting following error:
OperationalError at /admin/users/staff/1/change/
(1054, "Unknown column 'auth_user_groups.staff_id' in 'where clause'")
Request Method: GET
Request URL: http://127.0.0.1:8000/admin/users/staff/1/change/
Django Version: 2.2.8
Exception Type: OperationalError
Exception Value:
(1054, "Unknown column 'auth_user_groups.staff_id' in 'where clause'")
Exception Location: C:\Users\kalim.ullah\Envs\Cert\lib\site-packages\MySQLdb\connections.py in query, line 239
Python Executable: C:\Users\kalim.ullah\Envs\Cert\Scripts\python.exe
Python Version: 3.8.0
Python Path:
['D:\\projects\\certdashboard',
'C:\\Users\\kalim.ullah\\Envs\\Cert\\Scripts\\python38.zip',
'C:\\Users\\kalim.ullah\\Envs\\Cert\\DLLs',
'C:\\Users\\kalim.ullah\\Envs\\Cert\\lib',
'C:\\Users\\kalim.ullah\\Envs\\Cert\\Scripts',
'c:\\users\\kalim.ullah\\appdata\\local\\programs\\python\\python38\\Lib',
'c:\\users\\kalim.ullah\\appdata\\local\\programs\\python\\python38\\DLLs',
'C:\\Users\\kalim.ullah\\Envs\\Cert',
'C:\\Users\\kalim.ullah\\Envs\\Cert\\lib\\site-packages']
Server time: Fri, 10 Jan 2020 13:00:41 +0000
Please guide me the proper way of doing this
Thanks

Related

Password field is visible and not encrypted in Django admin site

So to use email as username I override the build-in User model like this (inspired by Django source code)
models.py
class User(AbstractUser):
username = None
email = models.EmailField(unique=True)
objects = UserManager()
USERNAME_FIELD = "email"
REQUIRED_FIELDS = []
def __str__(self):
return self.email
admin.py
#admin.register(User)
class UserAdmin(admin.ModelAdmin):
fieldsets = (
(None, {"fields": ("email", "password")}),
(("Personal info"), {"fields": ("first_name", "last_name")}),
(
("Permissions"),
{
"fields": (
"is_active",
"is_staff",
"is_superuser",
"groups",
"user_permissions",
),
},
),
(("Important dates"), {"fields": ("last_login", "date_joined")}),
)
add_fieldsets = (
(
None,
{
"classes": ("wide",),
"fields": ("email", "password1", "password2"),
},
),
)
list_display = ("email", "is_active", "is_staff", "is_superuser")
list_filter = ("is_active", "is_staff", "is_superuser")
search_fields = ("email",)
ordering = ("email",)
filter_horizontal = ("groups", "user_permissions",)
But this is how it looks like when I go to Admin site to change a user:
Password is visible and not hashed and no link to change password form.
Comparing to what it looks like on a default Django project:
Password is not visible and there's a link to change password form
So clearly I'm missing something but I can't figure out what it is.
It's likely that the Django Admin look problem has to do with inheritance. More precisely, change the class to inherit from UserAdmin.
from django.contrib.auth.admin import UserAdmin as DefaultUserAdmin
class UserAdmin(DefaultUserAdmin):
Doing this will make it look closer to the default Django project.
The problem of the password not being encrypted is likely because it's not encrypted in the database. OP has this question and that's not a problem of the Django Admin.
Notes:
I also use AbstractUser when wanting to remove username.
Since this current question doesn't have enough info to answer the problem of the pwd being hashed, I'll leave that to OP's other question.
You are not able to see the password in hashed state because the password field is a CharField which renders it as normal text field. In Django's admin side there's a field called ReadOnlyPasswordHashField in django.contrib.auth.forms which renders the password field to be in hashed state with password change link.
Django's UserAdmin uses different form classes for user creation and updation.
form = UserChangeForm
add_form = UserCreationForm
change_password_form = AdminPasswordChangeForm
To edit user details UserAdmin uses form = UserChangeForm(source code) where the password field is set as ReadOnlyPasswordHashField(source code).
class UserChangeForm(forms.ModelForm):
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.'
),
)
So, Just by inheriting from UserAdmin from django.contrib.auth.admin would make the password to be in hashed state with all the other essentials as seen in default admin site for users.
OR
you could simply import UserChangeForm from django.contrib.auth.forms and set form = UserChangeForm in custom UserAdmin
from django.contrib.auth.forms import UserChangeForm,AdminPasswordChangeForm
# code
#admin.register(User)
class UserAdmin(admin.ModelAdmin):
# code
form = UserChangeForm
change_password_form = AdminPasswordChangeForm
# code
Django Documentation clearly explains how to do this by Customizing authentication in Django
from django import forms
from django.contrib import admin
from django.contrib.auth.models import Group
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
from django.contrib.auth.forms import ReadOnlyPasswordHashField
from django.core.exceptions import ValidationError
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 ValidationError("Passwords don't match")
return password2
def save(self, commit=True):
# Save the provided password in hashed format
user = super().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
disabled password hash display field.
"""
password = ReadOnlyPasswordHashField()
class Meta:
model = MyUser
fields = ('email', 'password', 'date_of_birth', 'is_active', 'is_admin')
class UserAdmin(BaseUserAdmin):
# 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',)}),
)
# add_fieldsets is not a standard ModelAdmin attribute. UserAdmin
# overrides get_fieldsets to use this attribute when creating a user.
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, UserAdmin)
One way is to define a custom fieldset for the UserAdmin in admin.py.
fieldsets = (
(None, {"fields": ("username")}),
(_("Personal info"), {"fields": ("first_name", "last_name", "email")}),
(
_("Permissions"),
{
"fields": (
"is_active",
"is_staff",
"is_superuser",
"groups",
"user_permissions",
),
},
),
(_("Important dates"), {"fields": ("last_login", "date_joined")}),
)

How to use django-select2 widgets in django admin site?

In my django app I have modified the User entity to include a worker field (OneToOneField). But from the django admin site that field yiels so many result , so it is difficult for the logged user to select a worker. Is there any way to use the select2 (ModelSelect2Widget) widgets from the django admin site?
For any regular form I have define the widgets in the following way:
from django_select2.forms import ModelSelect2Widget
class ProcessForm(forms.ModelForm):
class Meta:
model = ProcessModel
exclude = ('id',)
widgets = {
'name':forms.TextInput(attrs={'class': 'form-control'}),
'code':forms.TextInput(attrs={'class': 'form-control'}),
'description':forms.Textarea(attrs={'class': 'form-control'}),
'geom': LeafletWidget(),
'product': ModelSelect2Widget(model=ProductModel, queryset=ProductModel.objects.filter(),
search_fields=['name__icontains'],
attrs={'style': 'width: 100%;'}),
}
Is there any way to use the ModelSelect2Widget for the worker field in the admin site form?
Here is my code:
class User(AbstractUser):
worker = models.OneToOneField(WorkerModel, on_delete=models.CASCADE,
related_name="user", verbose_name=_("Trabajador"), null=True, blank=True)
class Meta:
default_permissions = ()
verbose_name="Usuario"
verbose_name_plural="Usuarios"
permissions = (
("secretario", "Secretario(a)"),
("director", "Director"),
)
from django.contrib.auth.admin import UserAdmin
class UserAdminInherited(UserAdmin):
fieldsets = (
(None, {'fields': ('username', 'password')}),
(_('Personal info'), {'fields': ('first_name', 'last_name', 'email')}),
(_('Worker info'), {'fields': ('worker',)}),
(_('Permissions'), {
'fields': ('is_active', 'is_staff', 'is_superuser', 'groups', 'user_permissions'),
}),
(_('Important dates'), {'fields': ('last_login', 'date_joined')}),
)
admin.site.register(User, UserAdminInherited)
Since Django 2.0., we can use autocomplete_fields.
autocomplete_fields is a list of ForeignKey and/or ManyToManyField
fields you would like to change to Select2 autocomplete inputs.
ModelAdmin(is parent of UserAdmin) has the property autocomplete_fields (Django Docs):
from django.contrib.auth.admin import UserAdmin
from django.contrib.auth import get_user_model
User = get_user_model()
class UserAdminInherited(UserAdmin):
autocomplete_fields = ['worker']
...
admin.site.register(User, UserAdminInherited)
You must define search_fields on the related object’s ModelAdmin
because the autocomplete search uses it.
from django.contrib import admin
#admin.register(WorkerModel)
class WorkerModelAdmin(admin.ModelAdmin):
search_fields = ['model_field']

django custom user field won't appear in admin form

I created a custom user model in django with an additional field.
However although I can see it appear in the list view in Django's admin it won't appear in the form to create or update a user, even though I've amended these.
models.py
from django.contrib.auth.models import AbstractUser
from django.db import models
class CustomUser(AbstractUser):
# Need to support codes with leading 0, hence CharField
secret = models.CharField(max_length=5)
def __str__(self):
return self.username
forms.py
from django.contrib.auth.forms import UserCreationForm, UserChangeForm
from .models import CustomUser
class CustomUserCreationForm(UserCreationForm):
class Meta:
model = CustomUser
fields = ('username', 'email', 'secret')
class CustomUserChangeForm(UserChangeForm):
class Meta:
model = CustomUser
fields = ('username', 'email', 'secret')
admin.py
from django.contrib import admin
from django.contrib.auth import get_user_model
from django.contrib.auth.admin import UserAdmin
from .forms import CustomUserCreationForm, CustomUserChangeForm
from .models import CustomUser
class CustomUserAdmin(UserAdmin):
add_form = CustomUserCreationForm
form = CustomUserChangeForm
model = CustomUser
list_display = ['email', 'username', 'secret']
admin.site.register(CustomUser, CustomUserAdmin)
Have you tried editing the fieldsets parameter in the CustomUserAdmin class?
admin.py:
from django.contrib.auth.admin import UserAdmin
class CustomUserAdmin(UserAdmin, ...)
fieldsets = (
(('User Details'), {'fields': (
'first_name',
'last_name',
'password',
)}),
(('Section Title'), {'fields': (
'field1',
'field2',
...
)}),
...
)

How to implement Django SSO with custom user model and object permission (EX/django-guardian) in multiple projects

I tried to search and study some information on the google, but I couldn't find the good solution.
I have several Django projects and want to create "Member" project store user information which implement Single-Sign-On(SSO) feature (ex/"django-simple-sso"). Then I need to classify user permission, I study the package "django-guardian" which maybe can help me do object permission.
I do below things to create custom model in "Member" project. Then I just post the code that I add/change.
# setting.py of "Member" project
INSTALLED_APPS = [
'users',
'guardian'
]
AUTHENTICATION_BACKENDS = (
'guardian.backends.ObjectPermissionBackend',
)
I add an app which called users in the project named "Member".
# admin.py in the users app of Member project.
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin
from .forms import CustomUserCreationForm, CustomUserChangeForm
from .models import CustomUser
class CustomUserAdmin(UserAdmin):
add_form = CustomUserCreationForm
form = CustomUserChangeForm
model = CustomUser
list_display = ('email', 'is_staff', 'is_active', 'employee_account', 'location', 'chinese_name', 'english_name', 'extension', 'last_login', 'date_joined')
list_filter = ('email', 'is_staff', 'is_active', 'employee_account', 'location', 'chinese_name', 'english_name', 'extension', 'last_login', 'date_joined')
fieldsets = (
(None, {'fields': ('employee_account', 'password')}),
('Permissions', {'fields': ('is_staff', 'is_active')}),
)
add_fieldsets = (
(None, {
'classes': ('wide',),
'fields': ('employee_account', 'password1', 'password2', 'is_staff', 'is_active')}
),
)
search_fields = ('employee_account',)
ordering = ('employee_account',)
admin.site.register(CustomUser, CustomUserAdmin)
# apps.py in the users app of Member project.
from django.apps import AppConfig
class UsersConfig(AppConfig):
name = 'users'
# forms.py in the users app of Member project.
from django.contrib.auth.forms import UserCreationForm, UserChangeForm
from .models import CustomUser
class CustomUserCreationForm(UserCreationForm):
class Meta(UserCreationForm):
model = CustomUser
fields = ('employee_account',)
class CustomUserChangeForm(UserChangeForm):
class Meta:
model = CustomUser
fields = ('employee_account',)
# manager.py in the users app of Member project.
from django.contrib.auth.base_user import BaseUserManager
from django.utils.translation import ugettext_lazy as _
class CustomUserManager(BaseUserManager):
"""
Custom user model manager where email is the unique identifiers
for authentication instead of usernames.
"""
def create_user(self, email, password, **extra_fields):
"""
Create and save a User with the given email and password.
"""
if not email:
raise ValueError(_('The Email must be set'))
email = self.normalize_email(email)
user = self.model(email=email, **extra_fields)
user.set_password(password)
user.save()
return user
def create_superuser(self, email, password, **extra_fields):
"""
Create and save a SuperUser with the given email and password.
"""
extra_fields.setdefault('is_staff', True)
extra_fields.setdefault('is_superuser', True)
extra_fields.setdefault('is_active', True)
if extra_fields.get('is_staff') is not True:
raise ValueError(_('Superuser must have is_staff=True.'))
if extra_fields.get('is_superuser') is not True:
raise ValueError(_('Superuser must have is_superuser=True.'))
return self.create_user(email, password, **extra_fields)
# models.py in the users app of Member project
from django.db import models
from django.db import models
from django.contrib.auth.models import AbstractBaseUser
from django.contrib.auth.models import PermissionsMixin
from django.utils.translation import gettext_lazy as _
from django.utils import timezone
from .managers import CustomUserManager
class CustomUser(AbstractBaseUser, PermissionsMixin):
employee_account = models.CharField(max_length=100, unique=True)
location = models.CharField(max_length=50)
chinese_name = models.CharField(max_length=30)
english_name = models.CharField(max_length=30)
extension = models.CharField(max_length=30)
email = models.EmailField(max_length=125)
is_staff = models.BooleanField(default=False)
is_active = models.BooleanField(default=True)
date_joined = models.DateTimeField(default=timezone.now)
USERNAME_FIELD = 'employee_account'
REQUIRED_FIELDS = ['location','chinese_name','english_name','extension', 'email']
objects = CustomUserManager()
def __str__(self):
return self.employee_account
Then I use django shell to add user and assign permission.
>>> from django.contrib.auth.models import Group
>>> from users.models import CustomUser
>>> jack = CustomUser.objects.create_user('123456789#gamil.com', '123456789')
>>> admins = Group.objects.create(name='admins')
>>> jack.has_perm('change_group', admins)
False
>>> from guardian.models import UserObjectPermission
>>> UserObjectPermission.objects.assign_perm('change_group', jack, obj=admins)
<UserObjectPermission: admins | | change_group>
>>> jack.has_perm('change_group', admins)
True
>>> jack.has_perm('change_group')
False
After that I create an book app in another project named book_web.
# model.py in the book app of book_web project
from django.db import models
class Book(models.Model):
sn = models.CharField(max_length=200)
book = models.CharField(max_length=100)
author = models.CharField(max_length=100)
price = models.PositiveIntegerField(null=True, blank=True)
number = models.PositiveIntegerField(null=True, blank=True)
1. It seems work abnormally when key this command UserObjectPermission.objects.assign_perm('change_group', jack, obj=admins). I found that response doesn't show the user name which is different with django-guardian example code.
2. I don't know how to assign Book object permission in Member project such as django-guardian example code. Because there is not same model as Book model in the Member project.
Does anyone have similar experience about this?
If you have another recommendation package to implement the feature, welcome to share your experience. Thanks for your response.

Merging "add" form in Django Admin from 2 or more Models (connected with one-to-one relationship)

I have a Django's default UserCreationForm to add a new user via Admin app. I want to add new fields from another custom model called UserProfile. The UserProfile has a One-to-One relationship with Django's User model. The additional fields which UserProfile model has are phone number, company name etc.
Is there a way where I can merge UserProfile form with Django's default User form while creation of the new user from Admin panel?
I looked at Django's documentation here on Inline forms but seems their require foreign key relationship.
Django 2.1
Assuming you have Profile with extra field phone_number. Like this
class Profile(models.Model):
user = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
phone_number = models.CharField(max_length=24)
You can follow following steps to add extra fields in UserCreationForm in Admin.
1. Create custom UserCreationForm
Inherit from UserCreationForm and Add extra fields and functionality.
from django import forms
from django.contrib.auth.forms import UserCreationForm
from account.models import Profile
class UserWithProfileCreationForm(UserCreationForm):
phone_number = forms.CharField(max_length=32)
def save(self, commit=True):
instance = super().save(commit=True)
profile = Profile(user=instance, phone_number=self.cleaned_data['phone_number'])
profile.save()
return instance
2. Unregister (already registered) User model and register again with custom form and fields
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin
from django.contrib.auth.models import User
# unregister first
admin.site.unregister(User)
#admin.register(User)
class UserWithProfileAdmin(UserAdmin):
add_form = UserWithProfileCreationForm
add_fieldsets = (
(None, {
'classes': ('wide',),
# add any custom fields here
'fields': ('username', 'password1', 'password2', 'phone_number'),
}),
)
There are two ways to do this.
First, you can create a custom User model from AbstractBase
# models.py
class MyUser(AbstractBaseUser):
profile = models.OneToOneField(UserProfile)
And then update your admin view.
#admin.py
from django.contrib.auth.admin import UserAdmin
class UserProfileInline(admin.TabularInline):
model = UserProfile
class MyUserAdmin(UserAdmin):
inlines = [
UserProfileInline,
]
admin.site.register(MyUser, MyUserAdmin)
Second, you can simply add the OneToOneField(User) in your UserProfile model, and follow the similar method to update the admin view.

Categories

Resources