Python Django restframework 美多商城項目(二)——用戶模塊

創(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模型中,通過OneToOneFieldUser模型創(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ù)password2agree,對密碼、用戶名的默認(rèn)屬性進(jìn)行修改,對各需要驗證的參數(shù)進(jìn)行驗證。最后添加用戶。
  • 新增用戶時要刪除模型中沒有的字段password2agree,將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',
]
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容