美文网首页
Django02身份验证

Django02身份验证

作者: JunChow520 | 来源:发表于2020-09-20 17:45 被阅读0次

    Django提供了一套身份验证和授权的权限系统,允许验证用户凭证,并定义每个用户允许执行的操作。

    权限系统框架包括了用户和分组的内置模型,用于登录用户的权限,指定用户是否可以执行任务、表单、视图,以及查看限制内容的工具。

    Django身份验证系统为通用设计因此不提供其它Web身份验证系统中所提供的功能,对于某些常见问题可作为第三方软件包提供,比如限制登录尝试和针对第三方的身份验证

    登录授权

    manage应用路由设置后台登录URL

    $ vim apps/manage/urls.py
    
    from django.urls import path, re_path
    
    from apps.manage.apps import ManageConfig
    from .views import login, home
    
    app_name = ManageConfig.name
    
    urlpatterns = [
        path("", home.index, name="home"),
    ]
    

    随着功能开发views.py视图文件中的代码会越来越多,整个文件会越来越臃肿,不便于维护且不符合单一原则。因此应该将views.py中内容拆分到同一个目录下。

    manage应用下创建views文件夹同时在该目录下创建__init__.py文件使其作为一个Python包package

    $ mkdir -p apps/manage/views
    $ touch apps/manage/views/__init__.py
    

    views目录下将功能类似的方法存到同一个文件下,这里针对登录和首页,两个独立的功能模块划分出两个视图文件。

    $ vim apps/manage/views/home.py
    $ vim apps/manage/views/login.py
    

    manage应用的url.py文件中使用from .views import login, home导入当前目录下views包下的home模块和login模块。将登录UI界面中涉及到操作放在login模块下,将后台首页UI界面中涉及到的操作放到home模块中。

    path("", home.index, name="home")
    

    同样为了反转解析URL,在URL的路由规则中添加name为当前路由设置别名。

    为方便测试在视图中直接输出字符串文本

    $ vim apps/manage/views/home.py
    
    from django.http import HttpResponse
    
    def index(request):
      return HttpResponse("home index")
    

    运行测试

    启动Django开发服务器测试

    • 使用Ctrl+C快捷键关闭服务器
    • 使用Ctrl+Z会将服务器进程挂起端口一直会被占用,重启后会提示端口占用。
    $ python3 manage.py runserver 127.0.0.1:8000
    
    • Windows10查找指定端口运行的进程
    $ netstat -ano | findstr 8000
      TCP    127.0.0.1:8000         0.0.0.0:0              LISTENING       32036
      TCP    127.0.0.1:8000         127.0.0.1:61366        ESTABLISHED     32036
      TCP    127.0.0.1:61366        127.0.0.1:8000         ESTABLISHED     24016
      TCP    127.0.0.1:61920        127.0.0.1:8000         TIME_WAIT       0
      TCP    127.0.0.1:61983        127.0.0.1:8000         TIME_WAIT       0
    
    • 强制/f杀死指定PID进程及其子进程/t
    $ taskkill /f /t /pid 32036
    成功: 已终止 PID 32036 (属于 PID 33980 子进程)的进程。
    

    浏览器输入后台首页地址测试

    http://127.0.0.1:8000/manage/
    

    项目管理后台

    Dajango自动管理后台地址为http://127.0.0.1:8000/admin,使用前需使用管理员账号登录。

    创建超级管理员账号

    $ python3 manage.py createsuperuser
    

    创建成功后会在auth_user表中生成一条记录,对管理员表进行进一步调整。创建的超级用户已经经过身份验证并拥有所有权限。

    CREATE TABLE `auth_user` (
      `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '自增主键',
      `username` varchar(150) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '用户名',
      `password` varchar(128) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '密码',
      `first_name` varchar(150) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '名字',
      `last_name` varchar(150) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '姓氏',
      `email` varchar(254) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '邮箱',
      `is_superuser` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否为超级管理员 0否 1是',
      `is_staff` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否员工 0否 1是',
      `is_active` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否激活 0否 1是',
      `date_joined` datetime(6) NOT NULL COMMENT '创建时间',
      `last_login` datetime(6) DEFAULT NULL COMMENT '最近登录时间',
      PRIMARY KEY (`id`),
      UNIQUE KEY `username` (`username`)
    ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='管理员';
    

    内置身份验证

    当使用django-admin startproject命令创建项目是,所有必要配置都已完成,当第一次调用python3 manage.py migrate命令时会自动创建用户和权限的数据表。

    身份验证的配置在项目配置文件settings.pyINSTALLED_APPSMIDDLEWAREA

    $ vim settings.py
    
    INSTALLED_APPS = [
        'django.contrib.admin',
        'django.contrib.auth',
        'django.contrib.contenttypes',
        'django.contrib.sessions',
        'django.contrib.messages',
        'django.contrib.staticfiles',
    ]
    
    MIDDLEWARE = [
        'django.middleware.security.SecurityMiddleware',
        'django.contrib.sessions.middleware.SessionMiddleware',
        'django.middleware.common.CommonMiddleware',
        'django.middleware.csrf.CsrfViewMiddleware',
        'django.contrib.auth.middleware.AuthenticationMiddleware',
        'django.contrib.messages.middleware.MessageMiddleware',
        'django.middleware.clickjacking.XFrameOptionsMiddleware',
    ]
    

    Django的django.contrib.auth.views自带的身份授权框架中内置了登录视图LoginView

    创建用户登录URL调度器

    • 应用的URL调度器文件中使用app_name添加命名空间
    • 使用django.urls模块提供的re_path方法支持路径与路径转化器使用正则表达式
    $ vim apps/manage/urls.py
    
    from django.urls import path, re_path
    
    from apps.manage.apps import ManageConfig
    from .views import login, home
    
    app_name = ManageConfig.name
    
    urlpatterns = [
        path("", home.index, name="home"),
        re_path(r"^login/$", login.Logon.as_view(), name="login"),
        re_path(r"^authimg/$", login.authimg, name="authimg"),
        re_path(r"^logout/$", home.logout, name="logout"),
    ]
    

    app_name

    一个项目下的多个应用中可能存在定义同名的URL,为了避免反转解析URL时出现的混淆问题,Django提供了为应用添加命名空间的方式来区分URL,使用的方式是在urls.py中添加app_name来命名当前URL所属的应用名称。简单来说app_name的作用是使用应用命名空间来区分不同应用的URL。

    使用视图函数views时Django会在URL解析完成后直接将request对象及URL解析器捕获的参数,比如使用re_path中正则捕获的未知参数或关键字丢给基于函数的视图。但在基于类的视图中,这些参数不能直接丢给一个类,因此就产生了as_view函数,as_view只做一件事儿就是返回一个闭包,这个闭包和视图函数views一样能够接收URL解析器传递过来的参数。

    path

    pathre_path源码中可以发现,它们都是partial类的实例,因此pathre_path并不是普通函数而是对象。

    path = partial(_path, Pattern=RoutePattern)
    re_path = partial(_path, Pattern=RegexPattern)
    

    pathre_path执行逻辑

    当启动Django项目时程序执行到urlpatterns位置,urlpatterns列表中各项依次得到执行,由于re_pathpath都是对象,当对象像函数一样调用时,其实是在调用对象的__call__方法,执行的结果是每个pathre_path调用都会返回一个URLPattern类的实例对象(django.urls.resolves.URLPattern)。

    class URLPattern:
      def __init__(self, pattern, callback, default_args=None, name=None):
        self.pattern = pattern
        self.callback = callback
        self.default_args = default_args or {}
        self.name = name
    

    URLPattern类的__init__方法中的各个参数基本对应了传入的pathre_path参数,callback属性包含了回调函数的引用。pathre_path执行时自身传入的第二个参数是as_view()立即执行函数,注意是as_view()函数而非as_view,此时as_view()会立即执行。as_view()执行完毕会返回一个闭包,因此callback中保存的实际上是这个闭包的引用。需要注意的是as_view()函数只会执行依次,即在Django项目启动后,之后所有请求的处理都是由as_view返回的闭包,即URLPattern实例对象中的callback回调函数执行。

    当每次请求来临时,URL解析器首先会完成对URL的解析以匹配到相应的回调函数,然后立即去执行。

    内置登录处理

    Django内置用户认证系统django.contrib.auth模块,使用默认创建的auth_user表来存储登录用户数据。

    登录处理需使用auth模块的处理方法

    authenticate()

    authenticate方法提供了用户认证功能,即验证用户名username和密码password是否正确。

    user = auth.authenticate(username="user", password="pwd")
    

    authenticate方法如果认证成功会返回User对象,若查询失败则返回None

    login(HttpRequest, user)

    login方法接受一个HttpRequest对象以及一个经过认证的User对象。

    auth.login(request, user)
    

    auto.login方法执行会做两件事儿

    1. 完成会话操作,将用户数据保存到数据库,并生成随机sessionid保存到cookie中发送给客户端。
    2. 将验证后的user用户对象保存到request请求对象的request.user属性中

    只要使用auth.login(request, user)登录操作后,后续即可从request.user拿到当前登录的用户对象。否则request.user得到的是一个匿名用户对象AnonymouseUser ObjectAnonymouseUserrequest.user的默认值。

    logout(request)

    logout函数接收一个HttpRequest请求对象,无返回值。当调用logout函数时当前请求的session会话信息会全部清。也就是说即使没有登录,执行logout函数也不会报错。

    User

    request.user.is_authenticated()
    

    authenticate方法判断当前user是不是一个真正的User对象,用于检查用户是否已经通过认证,若通过返回True否则返回False

    通过认证并不意味着用户拥有任何权限,甚至不会检查用户是否处于激活状态,只是表名用户成功的通过了认证。

    为方便判断用户通过认证,auth模块提供了一个装饰器工具@login_required,用来快捷地给某个视图添加登录检测。

    @login_required
    def home(request):
      return redirect("login")
    

    request.user.is_authenticated()出错误错误

    'bool' object is not callable
    

    错误原因是应该使用request.user.is_authenticated访问属性,而非使用方法访问。

    前端模板

    • 前端图标采用SVG类型的字体图标 FontAwesome
    <script src="https://cdn.bootcdn.net/ajax/libs/font-awesome/5.13.0/js/all.min.js"></script>
    
    • 前端CSS采用 TailwindCSS
    <link href="https://cdn.bootcdn.net/ajax/libs/tailwindcss/1.4.6/tailwind.min.css" rel="stylesheet">
    
    • 前端JS采用 AlpineJS
    <script src="https://cdn.jsdelivr.net/gh/alpinejs/alpine@v2.x.x/dist/alpine.min.js" defer></script>
    

    前端资源

    用户登录 后台首页

    用户登录

    用户登录流程

    1. 进入登录页面用户输入用户名和密码提交登录表单
    2. 用户登录视图接收到POST过来的用户名和密码并认证判断
    3. 认证判断成功后执行登录操作
    4. 登录成功重定向到首页,登录失败返回登录页面并携带错误提示。
    5. 首页需要判断当前用户是否已经登录,若已经登录则渲染视图,否则跳转安全退出。

    编写登录URL规则

    $ vim apps/manage/urls.py
    
    from django.urls import path, re_path
    
    from apps.manage.apps import ManageConfig
    from .views import login, home
    
    app_name = ManageConfig.name
    
    urlpatterns = [
        re_path(r"^login/$", login.Login.as_view(), name="login"),
        re_path(r"^authimg/$", login.authimg, name="authimg"),
    ]
    

    这里提供了URL地址分别是

    编写登录视图

    $ vim apps/manage/views/login.py
    
    from io import BytesIO
    
    from django.contrib import auth
    from django.http import HttpResponse
    from django.shortcuts import render, redirect
    from django.views.generic.base import View
    
    from apps.manage.utils import Utils
    
    # 登录
    class Login(View):
        template_name = "login.html"
    
        def error(self, request, message=""):
            return render(request, self.template_name, {"error":message})
        def get(self, request):
            return render(request, self.template_name)
        def post(self, request):
            username = request.POST.get("username", None)
            password = request.POST.get("password", None)
            authcode = request.POST.get("authcode", None)
            print(username, password, authcode.lower(), request.session["authcode"])
            # 图片验证码
            if not authcode:
                return self.error(request, "请填写验证码")
            # 验证码判断
            if authcode.lower() != request.session["authcode"]:
                return self.error(request, "验证码输入有误")
            # 输入判断
            if not username or not password:
                return self.error(request, "请填写账号或密码")
            # 使用auth模块去auth_user表查找
            user = auth.authenticate(username=username, password=password)
            if not user:
                return self.error(request, "账号或密码输入有误")
            # 执行登录
            auth.login(request, user)
            # 跳转首页
            return redirect("manage:home")
    
    
    # 生成随机图片验证码
    def authimg(request):
        fd = BytesIO()
        # 生成随机图片二维码
        im,code = Utils.makeAuthImg()
        # 保存图片格式
        im.save(fd, "PNG")
        # 保存验证码 统一转化为小写
        request.session["authcode"] = code.lower()
        # 生成图片
        return HttpResponse(fd.getvalue())
    

    为什么需要使用反向解析URL呢?

    redirect("manage:home")
    

    随着功能的增加会出现更多地视图,可能之前配置的正则表达式不够准确,于是就需要修改URL的正则表达式。但是正则表达式一旦修改,之前与之对应的超链接都需要重新修改,这是一件非常繁琐且容易遗漏的操作。有没有办法让连接根据正则表达式动态生成呢?这是就出现了反向解析。

    反向解析主要用于模板中的超链接和视图中的重定向。如何使用反向解析呢?首先需要在定义URL时为include定义namespace命名空间,为url定义name别名属性。在模板中使用url标签时,在视图中利用reverse函数根据正则表达式动态生成地址,以降低后期维护成本。

    编写登录模板

    $ vim apps/manage/templates/login.html
    
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <meta http-equiv="X-UA-Compatible" content="ie=edge">
        <title>登录GM游戏管理平台</title>
        <link href="https://cdn.bootcdn.net/ajax/libs/tailwindcss/1.4.6/tailwind.min.css" rel="stylesheet">
        <script src="https://cdn.bootcdn.net/ajax/libs/font-awesome/5.13.0/js/all.min.js"></script>
        <script src="https://cdn.jsdelivr.net/gh/alpinejs/alpine@v2.x.x/dist/alpine.min.js" defer></script>
    </head>
    <body>
        <div class="h-screen flex flex-col items-center md:flex-row">
            <div class="w-full h-screen bg-black hidden lg:block md:w-1/2 xl:w-2/3">
                <img src="https://source.unsplash.com/1441x768" class="w-full h-full object-cover object-center">
            </div>
            <div class="w-full h-screen px-6 bg-white flex items-center justify-center  md:max-w-md md:mx-auto md:mx-0 md:w-1/2 lg:max-w-full lg:px-16 xl:w-1/3 xl:px-12">
                <div class="w-full h-100">
                    <h1 class="text-xl font-bold text-center md:text-2xl"><i class="fas fa-dragon"></i> GM游戏管理平台</h1>
                    {% if error %}
                        <div class="relative px-4 py-3 mt-12 bg-red-100 border border-red-400 rounded text-red-700" role="alert" x-data="{showAlert:true}" x-show="showAlert">
                            <strong class="font-bold">温馨提示</strong>
                            <span class="block sm:inline">{{ error }}</span>
                            <span class="absolute top-0 bottom-0 right-0 px-4 py-3 cursor-pointer" @click="showAlert=false">&times;</span>
                        </div>
                        {% else %}
                        <h2 class="text-xl leading-tight mt-12">欢迎使用,请输入您的账号和密码!</h2>
                    {% endif %}
                    <form action="{% url 'manage:login' %}" method="post" class="mt-6">
                        {% csrf_token %}
                        <label class="block text-gray-700" for="username">账号</label>
                        <input type="text"
                               name="username"
                               id="username"
                               class="w-full px-4 py-3 mt-2 bg-gray-200 border rounded-lg focus:border-blue-500 focus:bg-white focus:outline-none"
                               autofocus autocomplete required/>
                        <label class="block mt-4 text-gray-700" for="password">密码</label>
                        <input type="password"
                               name="password"
                               id="password"
                               class="w-full px-4 py-3 mt-2 bg-gray-200 border rounded-lg focus:border-blue-500 focus:bg-white focus:outline-none"
                               required/>
                        <div class="mt-2 text-right">
                            <a href="" class="text-sm text-gray-799 focus:text-blue-700 hover:text-blue-700">忘记密码</a>
                        </div>
                        <div class="flex flex-wrap mb-6 -mx-3">
                            <div class="w-full md:w-1/2 px-3">
                                <label for="authcode" class="block mb-2 text-gray-700 tracking-wide">验证码</label>
                                <input type="text"
                                       name="authcode"
                                       id="authcode"
                                       class="block w-full px-4 py-3 mb-3 leading-tight bg-gray-200 appearance-none border border-gray-200 rounded-lg focus:outline-none focus:bg-white focus:border-blue-500"
                                       required/>
                            </div>
                            <div class="w-full md:w-1/2 px-3" >
                                <p class="block text-gray-500 tracking-wide mb-2">点击图片更换</p>
                                <img src="{% url 'manage:authimg' %}"
                                     class="max-w-full h-auto border-none align-middle"
                                     onclick="this.src = '{% url 'manage:authimg' %}'+'?_='+Math.random()"
                                />
                            </div>
                        </div>
                        <button type="submit" class="block w-full px-4 py-3 mt-6 bg-blue-500 rounded-lg text-white focus:bg-blue-400 hover:bg-blue-400">
                            登录
                        </button>
                    </form>
                    <hr class="my-6 w-full border-gray-300">
                    <button class="block w-full px-4 py-3 bg-white border border-gray-300 rounded-lg text-gray-900 focus:bg-gray-100 hover:bg-gray-100">
                        <div class="flex items-center justify-center">
                            <span class="ml-2">微信登录</span>
                        </div>
                    </button>
                    <div class="mt-12 text-sm text-gray-500 text-center">&copy; 2020 JunChow - All Rights Reserved.</div>
                </div>
            </div>
        </div>
    </body>
    </html>
    

    图片验证码

    目标:为登录表单添加随机图片验证码

    图片验证码

    创建随机图片验证码

    为了创建图片验证码需引入图片处理类用生成图片验证码,这里采用的Python中的pillow模块。

    安装PIL模块

    $ pip3 install pillow
    

    生成随机图片验证码流程

    1. 创建画布,并指定画布尺寸与背景色。
    2. 创建画笔
    3. 创建字体,并随机设置字体。
    4. 随机循环生成字体并使用画笔绘制文本
    5. 随机循环生成干扰线并使用画笔绘制弧线
    6. 随机循环生成干扰点并使用画笔绘制点

    字体保存位置

    字体属于全局静态资源,在项目根目录下创建static文件夹用于保存全局静态资源文件,在static目录下创建fonts文件夹用于保存字体文件。

    创建工具类

    $ vim apps/manage/utils.py
    
    import random
    from io import BytesIO
    from PIL import Image, ImageDraw, ImageFont
    
    # 自定义工具类
    class Utils:
        # 生成图片验证码
        @staticmethod
        def makeAuthImg(len=4, width=270, height=50):
            # 定义随机颜色生成函数
            def make_random_color(start=0, stop=255):
                r = random.randrange(start, stop)
                g = random.randrange(start, stop)
                b = random.randrange(start, stop)
                return (r, g, b)
    
            # 定义随机字符生成函数
            def make_random_char():
                return random.choice([
                    str(random.randint(0, 9)),
                    chr(random.randint(97, 122)),
                    chr(random.randint(65, 90))
                ])
    
            # 创建画布
            canvas = Image.new(
                mode="RGB",
                size=(width, height),
                color=make_random_color(218, 255)
            )
            # 创建画笔
            draw = ImageDraw.Draw(canvas, mode="RGB")
            # 创建字体
            font = ImageFont.truetype(
                font="static/fonts/Mogul-Arial.ttf",
                size=random.randint(int(height/3), height-10)
            )
            # 随机生成字符
            code = ""
            for i in range(len):
                char = make_random_char()
                x = i * width / 4 + random.randint(0, 30)
                y = random.randint(0, int(height/3))
                draw.text(
                    (x, y),
                    char,
                    make_random_color(128, 192),
                    font
                )
                code += char
            # 随机干扰线
            for i in range(len):
                x = random.randint(0, int(width/6))
                y = random.randint(0, int(height/2))
                draw.arc(
                    (x, y, width-x, height-y),
                    0,
                    180,
                    make_random_color(64, 128)
                )
            # 随机干扰点
            for i in range(len*50):
                draw.point(
                    (random.randint(0, width), random.randint(0, height)),
                    fill=make_random_color(0, 64)
                )
            return canvas, code
    

    创建URL

    • 通过URL地址http://127.0.0.1:8000/manage/authimg获取图片验证码
    • 注意直接访问地址返回的将是乱码需放在img标签使用
    $ vim apps/manage/urls.py
    
    re_path(r"^authimg/$", login.authimg, name="authimg")
    

    创建视图

    $ vim apps/manage/views/login.py
    
    from io import BytesIO
    from django.http import HttpResponse
    
    from apps.manage.utils import Utils
    
    # 生成随机图片验证码
    def authimg(request):
        fd = BytesIO()
        # 生成随机图片二维码
        im,code = Utils.makeAuthImg()
        # 保存图片格式
        im.save(fd, "PNG")
        # 保存验证码 统一转化为小写
        request.session["authcode"] = code.lower()
        # 生成图片
        return HttpResponse(fd.getvalue())
    

    登录模板中添加图片验证码选项

    • 前端HTML添加点击图片更换img标签的src属性,为了保证每次请求不同,在URL后添加随机数以示区分。
    $ vim apps/manage/templates/login.html
    
    <div class="flex flex-wrap mb-6 -mx-3">
        <div class="w-full md:w-1/2 px-3">
            <label for="authcode" class="block mb-2 text-gray-700 tracking-wide">验证码</label>
            <input type="text"
                   name="authcode"
                   id="authcode"
                   class="block w-full bg-gray-200 text-gray-700 appearance-none border border-gray-200 rounded py-3 px-4 mb-3 leading-tight focus:outline-none focus:border-gray-500">
        </div>
        <div class="w-full md:w-1/2 px-3" >
            <p class="block text-gray-500 tracking-wide mb-2">点击图片更换</p>
            <img src="{% url 'backend:authimg' %}"
                 id="authimg"
                 class="max-w-full h-auto border-none align-middle"
                 onclick="this.src = '{% url 'manage:authimg' %}'+'?_='+Math.random()"
            />
        </div>
    </div>
    

    登录表单提交

    $ vim apps/manage/views/login.py
    
    from io import BytesIO
    
    from django.contrib import auth
    from django.http import HttpResponse
    from django.shortcuts import render, redirect
    from django.views.generic.base import View
    
    from apps.manage.utils import Utils
    
    # 登录
    class Login(View):
        template_name = "login.html"
    
        def error(self, request, message=""):
            return render(request, self.template_name, {"error":message})
        def get(self, request):
            return render(request, self.template_name)
        def post(self, request):
            username = request.POST.get("username", None)
            password = request.POST.get("password", None)
            authcode = request.POST.get("authcode", None)
            print(username, password, authcode.lower(), request.session["authcode"])
            # 图片验证码
            if not authcode:
                return self.error(request, "请填写验证码")
            # 验证码判断
            if authcode.lower() != request.session["authcode"]:
                return self.error(request, "验证码输入有误")
            # 输入判断
            if not username or not password:
                return self.error(request, "请填写账号或密码")
            # 使用auth模块去auth_user表查找
            user = auth.authenticate(username=username, password=password)
            if not user:
                return self.error(request, "账号或密码输入有误")
            # 执行登录
            auth.login(request, user)
            # 跳转首页
            return redirect("manage:home")
    

    前端登录错误错误提示Alert组件

    • 使用Alphine.js处理点击错误关闭按钮隐藏提示栏
    登录错误Alert提示
    <div class="relative px-4 py-3 mt-12 bg-red-100 border border-red-400 rounded text-red-700" role="alert" x-data="{showAlert:true}" x-show="showAlert">
        <strong class="font-bold">温馨提示</strong>
        <span class="block sm:inline">{{ error }}</span>
        <span class="absolute top-0 bottom-0 right-0 px-4 py-3 cursor-pointer" @click="showAlert=false">&times;</span>
    </div>
    

    后台首页

    配置首页路由

    $ vim apps/manage/urls.py
    
    from django.urls import path, re_path
    
    from apps.manage.apps import ManageConfig
    from .views import login, home
    
    app_name = ManageConfig.name
    
    urlpatterns = [
        path("", home.index, name="home"),
        re_path(r"^logout/$", home.logout, name="logout"),
    ]
    

    创建首页视图完成首页登录验证

    $ vim apps/manage/views/home.py
    
    from django.contrib import auth
    from django.shortcuts import render, redirect
    
    # 首页
    def index(request):
        template_name = 'home.html'
        print(request.user.is_authenticated)
        # 判断用户是否登录
        if(request.user.is_authenticated == False):
            return redirect("manage:logout")
        # 渲染模板
        return render(request, template_name)
    
    # 退出
    def logout(request):
        auth.logout(request)
        return redirect("manage:login")
    

    创建模板

    $ vim apps/manage/templates/home.html
    
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <meta http-equiv="X-UA-Compatible" content="ie=edge">
        <title>GM游戏管理平台</title>
        <link href="https://cdn.bootcdn.net/ajax/libs/tailwindcss/1.4.6/tailwind.min.css" rel="stylesheet">
        <script src="https://cdn.bootcdn.net/ajax/libs/font-awesome/5.13.0/js/all.min.js"></script>
        <script src="https://cdn.jsdelivr.net/gh/alpinejs/alpine@v2.x.x/dist/alpine.min.js" defer></script>
    </head>
    <body>
    <!--container-->
    <div class="mx-auto bg-gray-100" x-data="{dropdown:false, aside:true, menu:false}">
        <!--screen-->
        <div class="min-h-screen flex flex-col">
            <!--header-->
            <header class="relative bg-black text-white flex items-center justify-between px-4 py-1">
                <!--logo-->
                <div class="inline-flex items-center">
                    <i class="fas fa-bars" @click="aside=!aside"></i>
                </div>
                <!--avatar-->
                <div class="flex flex-row items-center justify-center">
                    <img src="http://source.unsplash.com/100x100/?avatar" class="h-8 h-8 rounded-full">
                    <span class="p-2 hidden md:block text-xs">Admin</span>
                    <i class="fas fa-caret-down" @click="dropdown=!dropdown"></i>
                </div>
                <!--downdrop-->
                <div class="absolute right-0 mt-16 mr-2 bg-white border rounded shadow-xl" x-show="dropdown">
                    <ul class="list-reset divide-y text-gray-700 text-xs">
                        <li>
                            <a href="" class="no-underline block px-4 py-2 hover:bg-gray-100">个人资料</a>
                        </li>
                        <li>
                            <a href="{% url 'backend:logout' %}" class="no-underline block px-4 py-2 hover:bg-gray-100">安全退出</a>
                        </li>
                    </ul>
                </div>
            </header>
            <!--main-->
            <main class="flex-1 flex">
                <!--mini-->
                <nav class="p-2 bg-black text-white flex flex-col items-center justify-star" x-show="!aside">
                    <span class="mb-2 last:mb-0 w-8 h-8 rounded-full hover:bg-gray-900 flex items-center justify-center" @click="aside=!aside">
                        <i class="fas fa-users"></i>
                    </span>
                    <span class="mb-2 last:mb-0 w-8 h-8 rounded-full hover:bg-gray-900 flex items-center justify-center" @click="aside=!aside">
                        <i class="fas fa-cogs"></i>
                    </span>
                </nav>
                <!--sidebar-->
                <aside class="bg-gray-900 text-white hidden md:block lg:block" x-show="aside">
                    <!--nav-->
                    <ul class="list-reset flex flex-col divide-y divide-gray-900 text-gray-400">
                        <li class="w-full h-full">
                            <!--level1-->
                            <div class="px-2 py-3 flex items-center justify-between bg-gray-800 text-base">
                                <a href="" class="no-underline block flex items-center">
                                    <i class="fas fa-bug"></i>
                                    <span class="ml-1 w-48">一级菜单</span>
                                </a>
                                <i class="fas fa-angle-right" @click="menu=!menu"></i>
                            </div>
                            <!--level2-->
                            <ul class="list-reset flex flex-col divide-y divide-gray-800 text-sm" x-show="menu">
                                <li class="w-full h-full">
                                    <div class="py-2 pl-4 pr-2 flex items-center justify-between">
                                        <a href="" class="no-underline block">
                                            <i class="fas fa-caret-right"></i>
                                            <span class="ml-1">二级菜单</span>
                                        </a>
                                        <i class="fas fa-angle-right"></i>
                                    </div>
                                </li>
                                <li class="w-full h-full">
                                    <div class="py-2 pl-4 pr-2 flex items-center justify-between text-gray-500">
                                        <a href="" class="no-underline block text-sm">
                                            <i class="fas fa-caret-right"></i>
                                            <span class="ml-1">二级菜单</span>
                                        </a>
                                        <i class="fas fa-angle-right"></i>
                                    </div>
                                </li>
                            </ul>
                        </li>
                    </ul>
                </aside>
                <!--content-->
                <div class="flex-1 p-4 overflow-hidden">
                    <!--card-->
                    <div class="my-2 border border-solid border-gray-200 rounded bg-white shadow-sm w-full">
                        <div class="px-2 py-3 border-b border-gray-200 font-base">卡片标题</div>
                        <div class="p-4 text-sm">卡片内容</div>
                    </div>
                </div>
            </main>
            <!--footer-->
            <footer></footer>
        </div>
    </div>
    </body>
    </html>
    

    由于后台页面存在多个高度复用的区块,在下一步会将模板进行抽象提取出公共部分,使用模板导入和模板加载的方式,将拆分出模板碎片有机地整合在一起,以增加模板文件代码的复用。

    版本控制

    $ cd gamesite
    $ git init
    

    在本地项目下使用git init命令后会在项目根目录下生成隐藏的.git文件夹

    • 上传所有代码到本地仓库
    $ git add .
    $ git commit -m "initial commit"
    
    • 关联本地仓库和远程仓库
    $ git remote add origin https://gitee.com/junchow/gmws.git
    $ git push origin master
    To https://gitee.com/junchow/gmws.git
     ! [rejected]        master -> master (fetch first)
    error: failed to push some refs to 'https://gitee.com/junchow/gmws.git'
    hint: Updates were rejected because the remote contains work that you do
    hint: not have locally. This is usually caused by another repository pushing
    hint: to the same ref. You may want to first integrate the remote changes
    hint: (e.g., 'git pull ...') before pushing again.
    hint: See the 'Note about fast-forwards' in 'git push --help' for details.
    

    错误原因远程仓库与本地仓库不一致,这里由于远程仓库中存在.README.md文件而本地仓库并不存在,因此需要将远程仓库先pull拉下来,对齐后再提交。另外本地文件中由于IDE自身的.idea文件夹随时处于变动状态,需要将其设置为不提交到远程仓库中。

    $ git pull origin master
    warning: no common commits
    remote: Enumerating objects: 5, done.
    remote: Counting objects: 100% (5/5), done.
    remote: Compressing objects: 100% (5/5), done.
    remote: Total 5 (delta 0), reused 0 (delta 0), pack-reused 0
    Unpacking objects: 100% (5/5), done.
    From https://gitee.com/junchow/gmws
     * branch            master     -> FETCH_HEAD
     * [new branch]      master     -> origin/master
    fatal: refusing to merge unrelated histories
    

    错误原因是由于本地仓库和远程仓库两个分支是两个不同的版本,具有不同的提交历史。

    解决方案是添加--allow-unrelated-histories允许不相关的历史提交,强制合并。

    $ git pull origin master --allow-unrelated-histories
    

    如果使用--rebase参数

    $ git pull origin master --rebase
    error: Cannot pull with rebase: You have unstaged changes.
    

    pull实际上是fetch + merge的操作即将远程仓库的更新合并到本地仓库,--rebase是取消本地仓库最近的commit并将它们接到更新后的版本库中。

    之所以出现错误是由于git pull -rebase的工作机制是

    1. 将本地commit提交到本地仓库的内容,取出来放到暂存区(stash),此时本地工作区是干净的。
    2. 从远程拉取代码到本地,由于工作区是干净的,因此会参数冲突。
    3. 从暂存区将之前提交的内容取出来跟拉下来的代码合并

    查看GIT状态

    $ git status
    On branch master
    Changes not staged for commit:
      (use "git add <file>..." to update what will be committed)
      (use "git checkout -- <file>..." to discard changes in working directory)
    
            modified:   .idea/workspace.xml
    
    no changes added to commit (use "git add" and/or "git commit -a")
    

    远程提交忽略文件.gitignore

    .idea文件夹添加到GIT的.gitignore文件内,远程提交时不包含idea文件夹。这个做法的前提条件是当前远程分支中并不存在.idea文件,如果已经存在则本地设置并提交是无效的,因此需要先将本地仓库的.idea文件删除。

    $ git rm -r --cached .idea
    rm '.idea/dataSources.local.xml'
    rm '.idea/dataSources.xml'
    rm '.idea/dataSources/037855b3-43da-4cb1-834b-35fcad4afe26.xml'
    rm '.idea/gamesite.iml'
    rm '.idea/misc.xml'
    rm '.idea/modules.xml'
    rm '.idea/vcs.xml'
    rm '.idea/workspace.xml'
    

    本地项目添加.ignore文件

    $ vim .gitignore
    
    # Intellij Pycharm
    .idea/
    

    再次提交

    git add . && git commit -m "local add gitignore" && git push
    [master 186c6f5] local add gitignore
     9 files changed, 2 insertions(+), 1869 deletions(-)
     create mode 100644 .gitignore
     delete mode 100644 .idea/dataSources.local.xml
     delete mode 100644 .idea/dataSources.xml
     delete mode 100644 .idea/dataSources/037855b3-43da-4cb1-834b-35fcad4afe26.xml
     delete mode 100644 .idea/gamesite.iml
     delete mode 100644 .idea/misc.xml
     delete mode 100644 .idea/modules.xml
     delete mode 100644 .idea/vcs.xml
     delete mode 100644 .idea/workspace.xml
    fatal: The current branch master has no upstream branch.
    To push the current branch and set the remote as upstream, use
    
        git push --set-upstream origin master
    

    错误The current branch master has no upstream branch.To push the current branch and set the remote as upstream表示没有将本地分支和远程仓库的分支进行关联。使用git pull默认会上传到origin下的master分支。

    解决的方案有两种

    第一种方式:保证远程分支存在的情况下,设置本地分支追踪远程分支。

    $ git push --set-upstream origin master
    Counting objects: 3, done.
    Delta compression using up to 6 threads.
    Compressing objects: 100% (2/2), done.
    Writing objects: 100% (3/3), 299 bytes | 0 bytes/s, done.
    Total 3 (delta 1), reused 0 (delta 0)
    remote: Powered by GITEE.COM [GNK-5.0]
    To https://gitee.com/junchow/gmws.git
       d95be9b..186c6f5  master -> master
    Branch master set up to track remote branch master from origin.
    
    D:\python\django\project\gamesite>git status
    On branch master
    Your branch is up-to-date with 'origin/master'.
    nothing to commit, working directory clean
    

    查看当前项目仓库的所有分支,红色表示远程分支,remotes/origin/master表示远程的主分支。

    $ git branch -a
    * master
      remotes/origin/master
    

    注意git中的origin表示关联或克隆远程仓库名称,git为你创建指向这个远程仓库的标签,它会指向repository

    查看指向的repository

    $ git remote -v
    origin  https://gitee.com/junchow/gmws.git (fetch)
    origin  https://gitee.com/junchow/gmws.git (push)
    

    第二种方式:如果远程没有需要关联的分支则自动创建以实现关联

    $ git push -u origin master
    

    相关文章

      网友评论

          本文标题:Django02身份验证

          本文链接:https://www.haomeiwen.com/subject/tgseyktx.html