[Django] 自訂 User Model

以下以 Django 1.7 為環境。

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

以下建立一個使用 email 和 password 登入的 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。