Python Django restframework 美多商城项目(二)——用户模块

创建第一个应用user

在项目目录中创建 apps 文件夹用于存放所有应用。

my_project/
    manage.py
    my_project/
        apps/
            users/
        __init__.py
        settings.py
        urls.py
        asgi.py
        wsgi.py
    logs/
        info.log

先创建好应用名文件夹,通过运行命令在指定目录创建新应用

python manage.py startapp users my_project/my_project/apps/users

# 或进入apps目录
python ../../manage.py startapp users

在settings.py文件中,追加导包路径,原因如下:

1.使注册子应用时,可以省略apps的路径,

2.修改Django认证模型类时,必须应用名.模型名的格式,通过追加导包路径解决apps这一层路径。

sys.path.insert(0, os.path.join(BASE_DIR, 'apps'))

sys.path为系统导包路径,这句话的意思是在系统导包路径列表中,在前位插入一个有BASE_DIR加apps拼接的路径。

在settings.py中,INSTALLED_APPS配置项中追加应用

INSTALLED_APPS = [
    ...
    'users.apps.UsersConfig',  # 用户
]

编辑users下的models.py文件,根据需求修改user的数据模型,本例中增加手机号。

Django中扩展内置用户模型有两种方式:

1.扩展Profile模型:创建一个名为 Profile 的新模型并与 User 模型关联。例如:

    class Profile(models.Model):
        user = models.OneToOneField(User, on_delete=models.CASCADE)
        mobile = models.CharField(max_length=11, unique=True, verbose_name='手机号')
        # 其他想添加的字段

在这种方法中,我们创建一个新的模型(通常称为Profile),该模型包含用户额外的信息,并通过一对一关系字段与内置的User模型关联。通常用于在不修改内置User模型的前提下添加额外信息。

用途

  • 当你对Django的默认用户模型基本满意,但还需要存储一些额外的用户信息,如手机号、地址、生日等。
  • 需要保留使用Django内置的用户认证和权限系统的能力。
  • 稍后如果有新的字段需求,可以轻松地添加到Profile模型中。

在这个Profile模型中,通过OneToOneFieldUser模型创建了一对一的关系。这意味着每一个User实例都可以有一个与之对应的Profile实例。

2.自定义用户模型

在这种方法中,我们通过继承AbstractUser(包含了User的全部功能)或AbstractBaseUser(需要自行实现一些功能)来创建完全定制的用户模型。

用途

  • 当Django的默认用户模型和认证系统的许多方面都不符合你的需求时。
  • 当你想要一个比较干净的用户模型,可能包含很少的默认字段,或者想要使用不同的字段作为用户名字段。
  • 当你想完全控制用户表的数据库层面实现时。

在这个自定义用户模型中,通过继承AbstractUser实现了扩展。如果需要更大的灵活性,可以从AbstractBaseUser继承并定义更多的自定义行为。

注意

  • 自定义用户模型需要在第一次运行migrate之前在你的项目中定义,否则会很难更改。
  • 设置自定义用户模型之后,你应该在settings.py中指定AUTH_USER_MODEL去使用这个新模型,如下设置:

本例中采用第二种方法:

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' #可以不填,不填默认应用名_模型名
        verbose_name = '用户'
        verbose_name_plural = verbose_name

settings.py中添加:

# 修改Django认证系统使用的模型类
AUTH_USER_MODEL = 'users.User'

执行迁移命令

python manage.py makemigrations
python manage.py migrate

注册用户

检验用户名是否存在

思路

  • 视图层创建 UsernameCountView 类,通过get方法获取username值,通过User模型过滤器查询出该username有几条,返回前端。
  • URL中添加接口地址,在url中直接通过属性名和正则验证用户名格式。

代码

users/view.py

class UsernameCountView(APIView):
    """检测用户名是否重复"""

    def get(self, request, username):
        # 查询用户名是否存在
        count = User.objects.filter(username=username).count()
        # 返回查询结果
        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()),  # 检查用户名是否已存在
    ...
]

检验手机号是否存在

思路

  • 视图层创建 MobileCountView 类,通过get方法获取mobile值,通过User模型过滤器查询出该mobile有几条,返回前端。
  • URL中添加接口地址,在url中直接通过属性名和正则验证手机号格式。

代码

users/view.py

class MobileCountView(APIView):
    """检测手机号是否重复"""

    def get(self, request, mobile):
        # 查询手机号是否存在
        count = User.objects.filter(mobile=mobile).count()
        # 返回查询结果
        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()),  # 检查手机号是否已存在
    ...
]

新增用户

思路

  • 用户可以复用Django的用户模型和方法,所以UserView继承CreateAPIView,但是由于注册时要填写的信息与Django默认出入比较大,所以自定义新的序列化器。
  • 新增用户序列化器中要添加模型中没有的数据password2agree,对密码、用户名的默认属性进行修改,对各需要验证的参数进行验证。最后添加用户。
  • 新增用户时要删除模型中没有的字段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):
        """验证协议"""
        if value != 'true':
            raise serializers.ValidationError('请同意用户协议')
        return value

    def create(self, validated_data):
        """创建用户"""
        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),  # 设置JWT认证的token的过期时间
    'REFRESH_TOKEN_LIFETIME': datetime.timedelta(days=7),  # 设置JWT认证的token的刷新时间
    'ROTATE_REFRESH_TOKENS': False,  # True时,每次使用刷新令牌获取新的访问令牌后,原刷新令牌将失效
    'BLACKLIST_AFTER_ROTATION': True,
}

代码

注册后直接返回token

users/serializers.py create方法

# 导包
from rest_framework_simplejwt.tokens import RefreshToken

# 注册序列化器
token = serializers.CharField(read_only=True)

refresh = RefreshToken.for_user(user)  # 使用Simple JWT的方法创建新的令牌
user.token = {
    'refresh': str(refresh),  # 获取刷新令牌字符串
    'access': str(refresh.access_token),  # 获取访问令牌字符串
}

登录

利用simplejwt进行登录

配置路由

# 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'),
    ...
]

修改登录成功返回响应结果

users/utils.py 没有此文件就创建一个

from rest_framework_simplejwt.serializers import TokenObtainPairSerializer


class MyTokenObtainPairSerializer(TokenObtainPairSerializer):
    """自定义返回数据"""
    def validate(self, attrs):
        # 获取token
        token = super().validate(attrs)

        data = {
            'username': self.user.username,  # 响应结果增加用户名
            'userId': self.user.id,  # 响应结果增加用户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):
    """
    根据帐号获取用户对象
    :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认证系统使用的模型类
AUTHENTICATION_BACKENDS = [
    'users.utils.UsernameMobileAuthBackend',
]
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,686评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,668评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,160评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,736评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,847评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,043评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,129评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,872评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,318评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,645评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,777评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,470评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,126评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,861评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,095评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,589评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,687评论 2 351