[Django] 自訂 User Model

以下以 Django 1.7 為環境。

一般來說要擴充 Django 內建的 user model 欄位,可以自行建立隨便一個想要的 model,然後 foreign key 接到 user 上,不知怎的這種方法感覺有點噁心,而且當你不想用 username 當作使用者登入的 id、想改用其他欄位時(例如 email)好像就沒辦法了,所以還是自己自訂好。

以下建立一個使用 emailpassword 登入的 user model 範例。

首先 python manage.py startapp users

users/models.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
from django.db import models

# Create your models here.
from django.db import models
from django.utils import timezone
from django.utils.http import urlquote
from django.utils.translation import ugettext_lazy as _
from django.core.mail import send_mail
from django.contrib.auth.models import AbstractBaseUser, PermissionsMixin
from django.core import validators

from django.contrib.auth.models import BaseUserManager

class CustomUserManager(BaseUserManager):

def _create_user(self, email, password,
is_staff, is_superuser, **extra_fields):
"""
Creates and saves a User with the given email and password.
"""
now = timezone.now()
if not email:
raise ValueError('The given email must be set')
email = self.normalize_email(email)
user = self.model(email=email,
is_staff=is_staff,
is_active=True,
is_superuser=is_superuser,
last_login=now,
date_joined=now,
**extra_fields)
user.set_password(password)
user.save(using=self._db)
return user

def create_user(self, email, password=None, **extra_fields):
return self._create_user(email, password, False, False,
**extra_fields)

def create_superuser(self, email, password, **extra_fields):
return self._create_user(email, password, True, True,
**extra_fields)


class CustomUser(AbstractBaseUser, PermissionsMixin):
"""
A fully featured User model with admin-compliant permissions that uses
a full-length email field as the username.

Email and password are required. Other fields are optional.
"""
email = models.EmailField(_('email address'), max_length=254, unique=True)
first_name = models.CharField(_('first name'), max_length=30, blank=False)
last_name = models.CharField(_('last name'), max_length=30, blank=False)
# Phones
phone_number = models.CharField(_('home phone number'), max_length=30, blank=False,
help_text=_('Required. digits and +-() only.'),
validators=[validators.RegexValidator(r'^[0-9+()-]+$',
_('Enter a valid phone number.'),
'invalid')])
mobile_number = models.CharField(_('mobile number'), max_length=30, blank=False,
help_text=_('Required. digits and +-() only.'),
validators=[validators.RegexValidator(r'^[0-9+()-]+$',
_('Enter a valid mobile number.'),
'invalid')])
# Address
zip_code = models.CharField(_('zip code'), max_length=5, blank=False,
help_text=_('Required. digits only.'),
validators=[validators.RegexValidator(r'^[0-9]+$',
_('Enter a valid bank number.'),
'invalid')])
home_address = models.CharField(_('home address'), max_length=60, blank=False)
# Bank ID
bank_id_first = models.CharField(_('bank number'), max_length=3, blank=False,
help_text=_('Required. digits only.'),
validators=[validators.RegexValidator(r'^[0-9]+$',
_('Enter a valid bank id number.'),
'invalid')])
bank_id_last = models.CharField(_('bank account'), max_length=3, blank=False,
help_text=_('Required. digits only.'),
validators=[validators.RegexValidator(r'^[0-9]+$',
_('Enter a valid bank account id number.'),
'invalid')])

# Admin
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 = CustomUserManager()

USERNAME_FIELD = 'email'
REQUIRED_FIELDS = []

class Meta:
verbose_name = _('user')
verbose_name_plural = _('users')

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):
"""
Sends an email to this User.
"""
send_mail(subject, message, from_email, [self.email])

users/admin.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
from django.contrib import admin

from django.contrib.auth.admin import UserAdmin
from django.utils.translation import ugettext_lazy as _

from .models import CustomUser
from .forms import CustomUserChangeForm, CustomUserCreationForm

class CustomUserAdmin(UserAdmin):
# The forms to add and change user instances

# The fields to be used in displaying the User model.
# These override the definitions on the base UserAdmin
# that reference the removed 'username' field
fieldsets = (
(None, {'fields': ('email', 'password')}),
(_('Personal info'), {'fields': ('first_name', 'last_name',
'phone_number', 'mobile_number',
'zip_code', 'home_address',
'bank_id_first', 'bank_id_last')}),
(_('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')}
),
)
form = CustomUserChangeForm
add_form = CustomUserCreationForm
list_display = ('email', 'first_name', 'last_name',
'mobile_number', 'phone_number',
'is_staff')
search_fields = ('email', 'first_name', 'last_name', 'mobile_number', 'phone_number')
ordering = ('email',)

admin.site.register(CustomUser, CustomUserAdmin)

users/forms.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
from django.contrib.auth.forms import UserCreationForm, UserChangeForm

from .models import *
# extend Django's built-in UserCreationForm and UserChangeForm to
# remove the username field (and optionally add any others that are
# required)

class CustomUserCreationForm(UserCreationForm):
"""
A form that creates a user, with no privileges, from the given email and
password.
"""

def __init__(self, *args, **kargs):
super(CustomUserCreationForm, self).__init__(*args, **kargs)
del self.fields['username']

class Meta:
model = CustomUser
fields = '__all__'

class CustomUserChangeForm(UserChangeForm):
"""A form for updating users. Includes all the fields on
the user, but replaces the password field with admin's
password hash display field.
"""

def __init__(self, *args, **kargs):
super(CustomUserChangeForm, self).__init__(*args, **kargs)
del self.fields['username']

class Meta:
model = CustomUser
fields = '__all__'

也許我們還會需要讓使用者能夠自行修改自己的帳號資料內容(而不是以管理員的權限來修改,所以一些 fields 不應該顯示出來),所以再在 forms.py 加上下列:

注意,有使用 crispy_forms 套件,沒用過的話詳情請自行 Google。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
 # ======================================================
# Forms for users themselves edit their profiles
# ======================================================
from django import forms
from crispy_forms.helper import FormHelper
from crispy_forms.layout import Submit
from .models import CustomUser

class CurrentCustomUserForm(forms.ModelForm):
class Meta:
model = CustomUser
fields = ('email',
'last_name', 'first_name',
'phone_number',
'mobile_number',
'zip_code', 'home_address',
'bank_id_first', 'bank_id_last')
def __init__(self, *args, submit_title="儲存編輯", **kwargs):
super().__init__(*args, **kwargs)
self.helper = FormHelper()
self.helper.add_input(Submit('submit', submit_title))
my_field_text= [
('email', '電子郵件', '電子郵件將會作為您往後登入時使用'),
('last_name', '姓', ''),
('first_name', '名', ''),
('phone_number', '電話', '手機與住家電話請至少要有一支確定能找得到人'),
('mobile_number', '手機', ''),
('zip_code', '郵遞區號', ''),
('home_address', '地址', ''),
('bank_id_first', '銀行代碼', ''),
('bank_id_last', '銀行帳號', ''),
]
for x in my_field_text:
self.fields[x[0]].label=x[1]
self.fields[x[0]].help_text=x[2]

settings.py

這個很重要不要忘記加了:把原本的 User 替換成我們剛剛定義的 CustomUser

AUTH_USER_MODEL = 'users.CustomUser'

INSTALLED_APPS 也記得加上 'users'。最後再更新 db:

1
2
python manage.py makemigrations users
python manage.py migrate users

如果改到

python manage.py createsuperuser

然後 runserver 看看 admin。

參考資料


[2015-02-23 月 02:18] 之前一直忘記 deploy
[2015-02-23 月 03:54] 加上使用者編輯用的 forms。