[Django高级之Auth模块、importlib模块、settings源码]

[Django高级之Auth模块、importlib模块、settings源码]

auth模块 ←详情点击查看

1、Auth模块是什么

Auth模块是Django自带的用户认证模块:

我们在开发一个网站的时候,无可避免的需要设计实现网站的用户系统。此时我们需要实现包括用户注册、用户登录、用户认证、注销、修改密码等功能,这还真是个麻烦的事情呢。

Django作为一个完美主义者的终极框架,当然也会想到用户的这些痛点。它内置了强大的用户认证系统–auth,它默认使用 auth_user 表来存储用户数据。

1 django提供的用户认证,创建,修改密码。。。用户相关操作
2 不需要创建用户表了,默认带了
3 插入数据(创建用户):
    python3 manage.py createsuperuser   # 创建超级用户  通常只有一到两个超级用户

2、auth模块常用方法

from django.contrib import auth   # 导入auth模块

urls.py

from app01 import views


urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^login/',views.MyLogin.as_view()),  # 登录界面
    url(r'^aa/',views.aa,name='aa'),  
    url(r'^set_password/',views.set_password,)  # 修改密码
]

views.py

from django.contrib import auth   # 导入auth模块
# 登录界面
def login(request):
    
    if request.method == 'POST':
        username = request.POST.get('username')
        password = request.POST.get('password')
        
        # 返回一个对象 即验证用户名以及密码是否正确,一般需要username 、password两个关键字参数。request不是必填参数
        user_obj = auth.authenticate(request,username=username,password=password)
        # print(user_obj)
        # print(user_obj.username)
        # print(user_obj.password)
        
        if user_obj:	# 返回值有可能为假 所以需要判断是否为真
            auth.login(request,user_obj)   # 为真的话 保存用户状态 用auth.login()方法
            
            '''只要执行了login方法、那么在任意位置都可以通过request.user获取当前对象'''

    return render(request,'login.html',locals())



def aa(request):
    '''只要执行了login方法、那么在任意位置都可以通过request.user获取当前对象'''
    
    print(request.user) # 获取用户对象 如果没有登录就是匿名用户
    # print(request.user.username)
    # print(request.user.password)
    print(request.user.is_authenticated())  # 判断用户是否登录
    return HttpResponse('哈哈')

----------------------------------------------------------------------

# 修改密码 
# 验证是否登录装饰器 导入模块↓
from django.contrib.auth.decorators import login_required

# @login_required(login_url='dl')  # 验证是否登录装饰器
@login_required    #  全局验证是否登录装饰器
def set_password(request):
    
    old_password = request.POST.get('old_password')  # 老密码
    new_password = request.POST.get('new_password')  # 新密码
    
    # 校验原密码是否相同 得到一个返回时 True or false
    # 用 request.user.check_password(老密码) 可以 自动校验
    is_right = request.user.check_password(old_password)
    # print(is_right)
    
    if is_right:  
        
        # 修改新密码  使用 request.user.set_password(新密码)方法
        request.user.set_password(new_password)
        
        # 一定要保存 不保存 修改无效
        request.user.save()
    return render(request,'set_password.html',locals())


-----------------------------------------------------------------------------------
from django.contrib.auth.models import AbstractUser,User
# 注册
def register(request):
    
    if request.method == 'POST':
        
        username = request.POST.get('username')
        
        password = request.POST.get('password')
        
        email = request.POST.get('email')

        # 操作auth_user写入数据不能使用create方法 密码不会自动加密
        # User.objects.create(username=username,password=password,email=email)
        # 创建普通用户
        User.objects.create_user(username=username,password=password,email=email)
		# 创建超级用户  
        User.objects.create_superuser(username=username, password=password, email=email)

    return render(request,'register.html')

----------------------------------------------------------------------------
# 注销
@login_required   
def mylogout(request):
    auth.logout(request)
    # 清除浏览器客户端的保存的session;即为清除session保存的用户登录信息
    return HttpResponse('注销成功')

login.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
    <script src="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.4.1/js/bootstrap.min.js"></script>
    <link href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.4.1/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<h1>用户登录</h1>
<form action="" method="post">
    {% csrf_token %}
    <p>username:<input type="text" name="username"></p>
    <p>password:<input type="text" name="password"></p>

    <button>提交</button>

</form>

</body>
</html>

set_password.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
    <script src="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.4.1/js/bootstrap.min.js"></script>
    <link href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.4.1/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>

<h1>密码修改</h1>

<form action="" method="post">
    {% csrf_token %}  
    
    <p>username:<input type="text" name="username" value="{{ request.user.username }}" disabled></p>
        
    <p>old_password:<input type="text" name="old_password"></p>  # 输入老密码
        
    <p>new_password:<input type="text" name="new_password"></p>  # 输入新密码
        
    <input type="submit">

 
</form>

</body>
</html>

register.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
    <script src="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.4.1/js/bootstrap.min.js"></script>
    <link href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.4.1/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
    <h1>注册界面</h1>
<form action="" method="post">
    {% csrf_token %}
    <p>username:<input type="text" name="username"></p>
    <p>password:<input type="text" name="password"></p>
    <p>Email:<input type="email" name="email"></p>
    <p><input type="submit"></p>
</form>

</body>
</html>

总结操作:

from django.contrib import auth

# 校验用户名密码是否正确
auth.authenticate(request,username=username,password=password)

# 保存用户状态
auth.login(request,user_obj)

# 查看用户是否登录
request.user.is_authenticated()

# 获取用户对象
request.user

# 校验原密码是否正确
request.user.check_password()

# 修改密码 
request.user.set_password()
request.user.save()

# 用户注册
from django.contrib.auth.models import AbstractUser,User
	# 操作auth_user写入数据不能使用create方法 密码不会自动加密
        # User.objects.create(username=username,password=password,email=email)
        # 创建普通用户
        User.objects.create_user(username=username,password=password,email=email)
        # 创建超级用户
        User.objects.create_superuser(username=username, password=password, email=email)

        
# 用户注销
	auth.logout(request)

    
# 校验是否登录装饰器
from django.contrib.auth.decorators import login_required
"""
跳转全局配置
	只需在settings配置文件下写入
	LOGIN_URL = '/lg/'
	直接在你想要装饰的对象上写入@login_required即可
	
跳转局部配置
	@login_required(login_url='/lg/')
"""

User对象的属性

User对象属性:username, password

is_staff : 用户是否拥有网站的管理权限.

is_active : 是否允许用户登录, 设置为 False,可以在不删除用户的前提下禁止用户登录。

扩展默认的auth_user表

这内置的认证系统这么好用,但是auth_user表字段都是固定的那几个,我在项目中没法拿来直接使用啊!

比如,我想要加一个存储用户手机号的字段,怎么办?

聪明的你可能会想到新建另外一张表然后通过一对一和内置的auth_user表关联,这样虽然能满足要求但是有没有更好的实现方式呢?

答案是当然有了。

# 第一种方案:创建一对一关联表
    
from django.contrib.auth.models import User 
class user_detail(models.Model):
    user=models.OneToOneField(to=User)
    phone=models.CharField(max_length=32)

我们还可以通过继承内置的 AbstractUser 类,来定义一个自己的Model类。

这样既能根据项目需求灵活的设计用户表,又能使用Django强大的认证系统了。

# 第二种方案,继承AbstractUser类来扩写

# 使用步骤 
"""
大前提是auth_user表没有创建之前干*******
    写一个类,继承AbstractUser
    在类中扩写字段(可以重写原来有的字段)

"""
from django.contrib.auth.models import AbstractUser
class UserInfo(AbstractUser):
    username=models.CharField(max_length=12)
    phone = models.CharField(max_length=11, null=True, unique=True)
    
    def __str__(self):
        return self.username

注意:

按上面的方案二扩展了内置的auth_user表之后,一定要在settings.py中告诉Django,我现在使用我新定义的UserInfo表来做用户认证。写法如下:

# 在配置文件中配置*******
# 引用Django自带的User表,继承使用时需要设置
AUTH_USER_MODEL = "app名.UserInfo"

再次注意:

一旦我们指定了新的认证系统所使用的表,我们就需要重新在数据库中创建该表,而不能继续使用原来默认的auth_user表了。

# 如果auth_user表已经有了,还想扩写
    -删库
    -清空项目中所有migration的记录
    -清空源码中admin,auth俩app的migration的记录
在auth_user表的基础之上还想增加额外的字段 并且还可以使用auth模块所有的功能

# 配合文件配置
# 告诉django使用我们自己定义的表来取代auth_user表
AUTH_USER_MODEL = 'app01.Userinfo'  # 应用名.表名


# 一对一表关联(了解)
# 面向对象继承(掌握)
from django.contrib.auth.models import AbstractUser
class Userinfo(AbstractUser):  # 必须继承AbstractUser  继承之后我们就有了auth的所有属性
    # 扩展AbstractUser表中没有的字段
    phone = models.BigIntegerField()
    info = models.CharField(max_length=255)

importlib模块

用字符串的形式导入模块
from b import c
print(c)  # <module 'b.c' from 'D:\\day54_1\\b\\c.py'>


import importlib
res = 'b.c'
# 下面操作相当于 from b import c   把字符串按照. 来切割成下面形式
ret = importlib.import_module(res)  # from b import c

print(ret)  # <module 'b.c' from 'D:\\day54_1\\b\\c.py'>

例如:

# settings.py

NOTIFI_LIST = [
    'notify.email.Email',
    'notify.qq.QQ',
    'notify.weixin.WeiXin',

]
---------------------------------------------------------------------------
# notify  包
# 包下有 __init__方法 以及三个应用 weixin.py  qq.py  email.py

#  __init__下
import settings
import importlib

def send_all(content):

    for path_str in settings.NOTIFI_LIST:
        module_path,class_name = path_str.rsplit('.',maxsplit=1)
        # print(module_path)
        # 1、利用字符串导入模块
        module = importlib.import_module(module_path)  # from notify import email,qq,weixin
        # 2、利用反射获取类名
        cls = getattr(module,class_name)  # email qq weixin
        # 3、生成类的对象
        obj = cls()
        # 4、利用鸭子类型直接send方法
        obj.send(content)
---------------------------------------------------------------
# 应用
class WeiXin(object):

    def __init__(self):
        pass

    def send(self,content):
        print('微信通知:%s'%content)
        
        
class QQ(object):

    def __init__(self):
        pass

    def send(self, content):
        print('QQ通知:%s' % content)  
        
        
class Email(object):

    def __init__(self):
        pass

    def send(self, content):
        print('Email通知:%s' % content)
-------------------------------------------------------------------------------
# start.py  启动文件

import notify

notify.send_all('开学了')
# 只需要在里面写相应的通知即可发送到全应用
# 拓展性强 
# 哪个不用了只需要到settings中注释即可

settings源码

django有两个配置文件
	一个是暴露给用户自定义的配置
    一个是项目默认的配置
用户没有配置的情况下使用的是项目默认的配置
用户配置了的情况下则使用用户配置的

如何实现
	先读取项目默认配置
    再读取自定义配置
    # 字典的键存在则替换 不存在则创建
    
from django.conf import settings

class LazySettings(...):
	def _setup(self, name=None):
        settings_module = os.environ.get(ENVIRONMENT_VARIABLE)  # 'day53.settings'
		self._wrapped = Settings(settings_module)  # Settings('day53.settings')
    
class Settings(object):
    def __init__(self, settings_module):  # 'day53.settings'
        for setting in dir(global_settings):  # 获取全局配置文件可以点的变量名
            if setting.isupper():  # 校验配置必须全大写
                setattr(self, setting, getattr(global_settings, setting))
                # setting指代的是所有全大写的变量名
                # getattr(global_settings, setting)获取全大写变量名对应的值
                # 给对象设置属性 可以简单的理解为 给字典添加键值对
        self.SETTINGS_MODULE = settings_module  # 'day53.settings'
        mod = importlib.import_module(self.SETTINGS_MODULE)
settings = LazySettings(...)
	   for setting in dir(mod):  # 获取
            if setting.isupper():
                setting_value = getattr(mod, setting)
                setattr(self, setting, setting_value)

基于源码实现插拔式设计

- conf  # 文件夹
	- settings.py  # 用户默认配置
- lib  # 文件夹
	-conf  # 文件夹
		- __init__.py 
		- global_settings.py  # 系统默认配置
- start.py # 启动文件
start.py

import os
import sys

BASE_DIR = os.path.dirname(__file__)

sys.path.append(BASE_DIR)  # 加入环境变量

# 将自定义配置文件路径添加到全局环境大字典
os.environ.setdefault('xxx','conf.settings')

from lib.conf import  settings  # 导入实例化的对象
if __name__ == '__main__':
    print(settings.NAME)  对象点变量名 就可以拿到对象的值
------------------------------------------------------------------------------------
settings.py

NAME = '我是自定义的配置'

-----------------------------------------------------------------------------------
globel_settings.py

NAME = '我是系统默认的配置'
-------------------------------------------------------------------------------------
__init__.py


from lib.conf import global_settings  # 导入系统配置文件
import os
import importlib
# from conf import settings


class Settings(object):
    
    def __init__(self):
        for i in dir(global_settings):  # 拿到系统配置里面的所有变量名
            # print(dir(global_settings))
            # print(i)
            if i.isupper():  # 判断是否大写

                setattr(self,i,getattr(global_settings,i)) 
				# i 是拿到的变量名
				# 而getattr(global_settings,i) 利用反射拿到的是配置文件里变量名对应的值
				# 之后利用反射把系统配置里面的变量名添加到当前对象
				# 给对象设置属性 可以简单的理解为 给字典添加键值对
                
        ooo = os.environ.get('xxx')  # 获取start.py 文件里自定义配置文件路径
        # os.environ.setdefault('xxx','conf.settings')  这是自定义配置文件的路径
        
        # 相当于 from conf import settings
        path_name = importlib.import_module(ooo) 
		# path_name 拿到的就是一个文件路径
        
        for x in dir(path_name): # 拿这个路径下settings.pyp配置文件中的所有可以点的变量名
            if x.isupper():
                key = x
                value = getattr(path_name,key)

                setattr(self,key,value)
				# 利用反射把用户配置里面的变量名添加到当前对象
				# 如果有重复的变量名,则覆盖掉
				# 这就是为什么我们既可以拿到用户配置里面的变量名
				# 也可以拿到系统配置里面的变量名
                 # 因为这里把两个配置文件的变量名都整合到一起  
                 # 用户配置有自定义的就用用户自己的,没有的话用系统配置
                 # 这就是 有则修改 无则添加

settings = Settings()  # 实例化得到一个对象 之后在start.py 中导入
posted @ 2021-06-02 09:00  刘较瘦丫  阅读(246)  评论(0)    收藏  举报