創(chuàng)建第一個應(yīng)用user
在項目目錄中創(chuàng)建 apps 文件夾用于存放所有應(yīng)用。
my_project/
manage.py
my_project/
apps/
users/
__init__.py
settings.py
urls.py
asgi.py
wsgi.py
logs/
info.log
先創(chuàng)建好應(yīng)用名文件夾,通過運行命令在指定目錄創(chuàng)建新應(yīng)用
python manage.py startapp users my_project/my_project/apps/users
# 或進(jìn)入apps目錄
python ../../manage.py startapp users
在settings.py文件中,追加導(dǎo)包路徑,原因如下:
1.使注冊子應(yīng)用時,可以省略apps的路徑,
2.修改Django認(rèn)證模型類時,必須應(yīng)用名.模型名的格式,通過追加導(dǎo)包路徑解決apps這一層路徑。
sys.path.insert(0, os.path.join(BASE_DIR, 'apps'))
sys.path為系統(tǒng)導(dǎo)包路徑,這句話的意思是在系統(tǒng)導(dǎo)包路徑列表中,在前位插入一個有BASE_DIR加apps拼接的路徑。
在settings.py中,INSTALLED_APPS配置項中追加應(yīng)用
INSTALLED_APPS = [
...
'users.apps.UsersConfig', # 用戶
]
編輯users下的models.py文件,根據(jù)需求修改user的數(shù)據(jù)模型,本例中增加手機號。
Django中擴展內(nèi)置用戶模型有兩種方式:
1.擴展Profile模型:創(chuàng)建一個名為 Profile 的新模型并與 User 模型關(guān)聯(lián)。例如:
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
mobile = models.CharField(max_length=11, unique=True, verbose_name='手機號')
# 其他想添加的字段
在這種方法中,我們創(chuàng)建一個新的模型(通常稱為Profile),該模型包含用戶額外的信息,并通過一對一關(guān)系字段與內(nèi)置的User模型關(guān)聯(lián)。通常用于在不修改內(nèi)置User模型的前提下添加額外信息。
用途:
- 當(dāng)你對Django的默認(rèn)用戶模型基本滿意,但還需要存儲一些額外的用戶信息,如手機號、地址、生日等。
- 需要保留使用Django內(nèi)置的用戶認(rèn)證和權(quán)限系統(tǒng)的能力。
- 稍后如果有新的字段需求,可以輕松地添加到
Profile模型中。
在這個Profile模型中,通過OneToOneField與User模型創(chuàng)建了一對一的關(guān)系。這意味著每一個User實例都可以有一個與之對應(yīng)的Profile實例。
2.自定義用戶模型
在這種方法中,我們通過繼承AbstractUser(包含了User的全部功能)或AbstractBaseUser(需要自行實現(xiàn)一些功能)來創(chuàng)建完全定制的用戶模型。
用途:
- 當(dāng)Django的默認(rèn)用戶模型和認(rèn)證系統(tǒng)的許多方面都不符合你的需求時。
- 當(dāng)你想要一個比較干凈的用戶模型,可能包含很少的默認(rèn)字段,或者想要使用不同的字段作為用戶名字段。
- 當(dāng)你想完全控制用戶表的數(shù)據(jù)庫層面實現(xiàn)時。
在這個自定義用戶模型中,通過繼承AbstractUser實現(xiàn)了擴展。如果需要更大的靈活性,可以從AbstractBaseUser繼承并定義更多的自定義行為。
注意:
- 自定義用戶模型需要在第一次運行
migrate之前在你的項目中定義,否則會很難更改。 - 設(shè)置自定義用戶模型之后,你應(yīng)該在
settings.py中指定AUTH_USER_MODEL去使用這個新模型,如下設(shè)置:
本例中采用第二種方法:
users/model.py
from django.db import models
from django.contrib.auth.models import AbstractUser
# Create your models here.
class User(AbstractUser):
mobile = models.CharField(max_length=11, unique=True, verbose_name='手機號')
class Meta:
db_table = 'user_users' #可以不填,不填默認(rèn)應(yīng)用名_模型名
verbose_name = '用戶'
verbose_name_plural = verbose_name
settings.py中添加:
# 修改Django認(rèn)證系統(tǒng)使用的模型類
AUTH_USER_MODEL = 'users.User'
執(zhí)行遷移命令
python manage.py makemigrations
python manage.py migrate
注冊用戶
檢驗用戶名是否存在
思路
- 視圖層創(chuàng)建
UsernameCountView類,通過get方法獲取username值,通過User模型過濾器查詢出該username有幾條,返回前端。 - URL中添加接口地址,在url中直接通過屬性名和正則驗證用戶名格式。
代碼
users/view.py
class UsernameCountView(APIView):
"""檢測用戶名是否重復(fù)"""
def get(self, request, username):
# 查詢用戶名是否存在
count = User.objects.filter(username=username).count()
# 返回查詢結(jié)果
data = {'username': username, 'count': count}
return Response(data)
users/url.py
from django.urls import path, re_path
from . import views
urlpatterns = [
...
re_path(r'^username/(?P<username>[a-zA-Z0-9_]{5,20})/count/$', views.UsernameCountView.as_view()), # 檢查用戶名是否已存在
...
]
檢驗手機號是否存在
思路
- 視圖層創(chuàng)建
MobileCountView類,通過get方法獲取mobile值,通過User模型過濾器查詢出該mobile有幾條,返回前端。 - URL中添加接口地址,在url中直接通過屬性名和正則驗證手機號格式。
代碼
users/view.py
class MobileCountView(APIView):
"""檢測手機號是否重復(fù)"""
def get(self, request, mobile):
# 查詢手機號是否存在
count = User.objects.filter(mobile=mobile).count()
# 返回查詢結(jié)果
data = {'mobile': mobile, 'count': count}
return Response(data)
users/url.py
from django.urls import path, re_path
from . import views
urlpatterns = [
...
re_path(r'^mobile/(?P<mobile>1[3-9]\d{9})/count/$', views.MobileCountView.as_view()), # 檢查手機號是否已存在
...
]
新增用戶
思路
- 用戶可以復(fù)用Django的用戶模型和方法,所以
UserView繼承CreateAPIView,但是由于注冊時要填寫的信息與Django默認(rèn)出入比較大,所以自定義新的序列化器。 - 新增用戶序列化器中要添加模型中沒有的數(shù)據(jù)
password2和agree,對密碼、用戶名的默認(rèn)屬性進(jìn)行修改,對各需要驗證的參數(shù)進(jìn)行驗證。最后添加用戶。 - 新增用戶時要刪除模型中沒有的字段
password2和agree,將password存儲在一個變量里并從validated_data中刪除。存儲密碼時要通過set_password方法加密。 - url中添加接口地址。
代碼
users/views.py
class UserView(CreateAPIView):
"""用戶注冊"""
# 指定序列化器
serializer_class = CreateUserSerializer
users/serializer.py
from rest_framework import serializers
from .models import User
import re
class CreateUserSerializer(serializers.ModelSerializer):
"""注冊用戶序列化器"""
password2 = serializers.CharField(style={'input_type': 'password'}, write_only=True)
agree = serializers.CharField(write_only=True)
class Meta:
model = User
fields = ('id', 'username', 'password', 'password2', 'mobile', 'agree')
extra_kwargs = {
'password': {
'write_only': True,
'min_length': 8,
'max_length': 20,
'error_messages': {
'min_length': '密碼長度8-20個字符',
'max_length': '密碼長度8-20個字符',
},
},
'username': {
'min_length': 5,
'max_length': 20,
'error_messages': {
'min_length': '用戶名長度5-20個字符',
'max_length': '用戶名長度5-20個字符',
},
},
}
def validate_password2(self, value):
"""驗證密碼"""
password = self.initial_data.get('password')
if password != value:
raise serializers.ValidationError('兩次輸入的密碼不一致')
return value
def validate_mobile(self, value):
"""驗證手機號格式"""
if not re.match(r'^1[3-9]\d{9}$', value):
raise serializers.ValidationError('手機號格式錯誤')
return value
def validate_agree(self, value):
"""驗證協(xié)議"""
if value != 'true':
raise serializers.ValidationError('請同意用戶協(xié)議')
return value
def create(self, validated_data):
"""創(chuàng)建用戶"""
del validated_data['password2']
del validated_data['agree']
# 刪除validated_data中的password屬性并將password賦值給password變量
password = validated_data.pop('password')
user = User(**validated_data)
user.set_password(password)
user.save()
return user
users/url.py
urlpatterns = [
path('users/', views.UserView.as_view()), # 注冊用戶
...
]
jwt
jwt使用方法
安裝
pip install djangorestframework-simplejwt
配置
settings.py
REST_FRAMEWORK = {
...
'DEFAULT_AUTHENTICATION_CLASSES': (
...
'rest_framework_simplejwt.authentication.JWTAuthentication',
)
...
}
SIMPLE_JWT = {
'ACCESS_TOKEN_LIFETIME': datetime.timedelta(days=1), # 設(shè)置JWT認(rèn)證的token的過期時間
'REFRESH_TOKEN_LIFETIME': datetime.timedelta(days=7), # 設(shè)置JWT認(rèn)證的token的刷新時間
'ROTATE_REFRESH_TOKENS': False, # True時,每次使用刷新令牌獲取新的訪問令牌后,原刷新令牌將失效
'BLACKLIST_AFTER_ROTATION': True,
}
代碼
注冊后直接返回token
users/serializers.py create方法
# 導(dǎo)包
from rest_framework_simplejwt.tokens import RefreshToken
# 注冊序列化器
token = serializers.CharField(read_only=True)
refresh = RefreshToken.for_user(user) # 使用Simple JWT的方法創(chuàng)建新的令牌
user.token = {
'refresh': str(refresh), # 獲取刷新令牌字符串
'access': str(refresh.access_token), # 獲取訪問令牌字符串
}
登錄
利用simplejwt進(jìn)行登錄
配置路由
# JWT登錄
from rest_framework_simplejwt.views import TokenObtainPairView, TokenRefreshView
urlpatterns = [
...
# 登錄接口
path('authorizations/', TokenObtainPairView.as_view(), name='token_obtain_pair'),
# 刷新token
path('authorizations/refresh/', TokenRefreshView.as_view(), name='token_refresh'),
...
]
修改登錄成功返回響應(yīng)結(jié)果
users/utils.py 沒有此文件就創(chuàng)建一個
from rest_framework_simplejwt.serializers import TokenObtainPairSerializer
class MyTokenObtainPairSerializer(TokenObtainPairSerializer):
"""自定義返回數(shù)據(jù)"""
def validate(self, attrs):
# 獲取token
token = super().validate(attrs)
data = {
'username': self.user.username, # 響應(yīng)結(jié)果增加用戶名
'userId': self.user.id, # 響應(yīng)結(jié)果增加用戶id
'refresh': token['refresh'],
'access': token['access']
}
return data
配置setting.py
SIMPLE_JWT = {
...
# 用于生成訪問令牌和刷新令牌的序列化器
"TOKEN_OBTAIN_SERIALIZER": "users.utils.MyTokenObtainPairSerializer", # 指向自定義的序列化器
}
多賬號登錄
user/utils.py
def get_user_by_account(account):
"""
根據(jù)帳號獲取用戶對象
:param account: 帳號
:return: User對象或者None
"""
try:
# 手機號
if re.match(r'1[3-9]\d{9}', account):
user = User.objects.get(mobile=account)
# 用戶名
else:
user = User.objects.get(username=account)
except User.DoesNotExist:
return None
else:
return user
class UsernameMobileAuthBackend(ModelBackend):
def authenticate(self, request, username=None, password=None, **kwargs):
# 獲取用戶對象
user = get_user_by_account(username)
if user and user.check_password(password):
return user
配置setting.py
# 修改Django認(rèn)證系統(tǒng)使用的模型類
AUTHENTICATION_BACKENDS = [
'users.utils.UsernameMobileAuthBackend',
]