gin49sz

导航

 

1.初识Django

安装Django

pip install django
python37
	-python.exe		【解释器】
	-Scripts
		-pip.exe
		-django-admin.exe	【工具,创建django项目,创建django项目的文件和文件夹】
	-Lib
		-内置模块
		-site-packages
			-openpyxl
			-python-docx
			-flask
			-django		【框架的源码】

2.创建项目

django项目会有一些默认的文件和默认的文件夹

2.1基于终端创建

  • 打开终端
  • 进入项目所在目录
  • 执行命令创建项目:复制环境中django_admin.exe的地址,然后加上startproject 项目名称
(TestEnv) E:\TestFile\django_demo>"E:\TestEnv\Scripts\django-admin.exe" startproject my_django_demo
  • 也可以把目录加入到环境变量当中,然后通过django_admin startproject 项目名称直接添加
(TestEnv) E:\TestFile\django_demo>django_admin startproject my_django_demo

2.2基于软件创建(Pycharm企业版)

2.3创建文件说明

标准模式:通过命令行创建

通过pycharm:额外添加两个东西(目前均删除)
-templates目录
-setting.py文件下的TEMPLATES列表中第一个元素(字典)有一个DIRS的value添加了一些数据

2.4文件含义

默认项目的文件介绍:

(TestEnv) E:\TestFile\django_demo\Django_test>dir /b /s
E:\TestFile\django_demo\Django_test\Django_test
E:\TestFile\django_demo\Django_test\manage.py			"项目管理脚本,启动项目,创建app,数据管理,全部都要基于															 这个文件(无需修改)"
    E:\TestFile\django_demo\Django_test\Django_test\__init__.py
    E:\TestFile\django_demo\Django_test\Django_test\asgi.py		"接收本地网路请求,异步(无需修改)"
    E:\TestFile\django_demo\Django_test\Django_test\wsgi.py		"接收本地网路请求,同步(无需修改)"
    E:\TestFile\django_demo\Django_test\Django_test\urls.py		"url和函数的对应关系(经常修改)"
    E:\TestFile\django_demo\Django_test\Django_test\settings.py	"项目配置文件,链接数据库,注册app等(经常修改)"

3.APP

3.1APP创建

Django支持将大型项目拆分成多个小型APP

  • 通过命令行在该目录下创建一个APP
(TestEnv) E:\TestFile\django_demo\Django_test>python manage.py startapp app01

3.2文件的含义

E:\TestFile\django_demo\Django_test\app01\__init__.py
E:\TestFile\django_demo\Django_test\app01\apps.py		"apps启动类,固定文件,不用动"

E:\TestFile\django_demo\Django_test\app01\tests.py		"写单元测试用到,一般不用动"

E:\TestFile\django_demo\Django_test\app01\admin.py		"Django默认提供admin管理内容,一般也不用动"

E:\TestFile\django_demo\Django_test\app01\models.py		"⭐对数据库进行操作"
E:\TestFile\django_demo\Django_test\app01\views.py		"⭐urls.py文件中定义的函数具体内容写在views.py中"


E:\TestFile\django_demo\Django_test\app01\migrations	"记录数据库字段变更记录,固定文件,不用动"
	E:\TestFile\django_demo\Django_test\app01\migrations\__init__.py

4.快速上手

4.1创建页面

  • 确保APP已注册

    • 先创建一个APP
    • 在这个app的目录文件中找到apps文件【apps.py】中找到
    class App01Config(AppConfig):
        default_auto_field = 'django.db.models.BigAutoField'
        name = 'app01'
    
    • 在django项目的setting文件【setting.py】中找到
    # Application definition
    
    INSTALLED_APPS = [
        'django.contrib.admin',
        'django.contrib.auth',
        'django.contrib.contenttypes',
        'django.contrib.sessions',
        'django.contrib.messages',
        'django.contrib.staticfiles',
    ]
    
    • 添加一条关于目标app的路径
    'app01.apps.App01Config'	#'app01'或者'app01.apps.App01config'
    
    # Application definition
    
    INSTALLED_APPS = [
        'django.contrib.admin',
        'django.contrib.auth',
        'django.contrib.contenttypes',
        'django.contrib.sessions',
        'django.contrib.messages',
        'django.contrib.staticfiles',
        'app01.apps.App01config'
    ]
    
  • 编写url和视图函数的对应关系【urls.py】

    • 修改urls文件中默认的初始页面映射
    urlpatterns = [
        path('admin/', admin.site.urls),
    ]
    
    urlpatterns = [
        #path('admin/', admin.site.urls),
        
        #用户访问 www.xxxx.com/index/ --> 执行函数
        path('index/', admin.site.urls),
    ]
    
    • 添加app01的引用
    from app01 import views
    
    • 添加对views文件中对应函数的引用
    urlpatterns = [
        #path('admin/', admin.site.urls),
        
        #用户访问 www.xxxx.com/index/ --> 到app01的views.py文件中找到index函数并执行
        path('index/', views.index),
    ]
    
  • 添加对应的函数到app01下的views文件【views.py】

    • 找到views文件
    from django.shortcuts import render
    
    # Create your views here.
    
    • 添加index函数
    from django.shortcuts import render, HttpResponse#默认添加
    
    # Create your views here.
    
    #添加index函数,默认要有一个request参数
    def index(rquest):
        return HttpResponse("xxx")
    
  • xxxxxxxxxx @app.route('/login', methods=['GET', 'POST'])def login():    if request.method == "GET":        return render_template('login.html')    else:        return 'login success'python

    • 通过命令行启动
    python manage.py runserver
    
    • 通过pycharm启动,基于创建的django程序点击启动
    • 通过VSCode启动: 安装Django插件 -> 打开运行和调试 -> 添加配置:Python:Django ->在launch.json文件中添加django项目的地址
    {
        // 使用 IntelliSense 了解相关属性。 
        // 悬停以查看现有属性的描述。
        // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
        "version": "0.2.0",
        "configurations": [
    
            {
                "name": "Python: Django",
                "type": "python",
                "request": "launch",
                "program": "${workspaceFolder}\\django_demo\\Django_test\\manage.py",//在此处添加
                "args": [
                    "runserver"
                ],
                "django": true,
                "justMyCode": true
            }
        ]
    }
    

    注意:

    当有多个app文件时,若要在urls中为函数配置路径,必须在import后面为每个app的views设置别称,否则会重名报错

    from django.contrib import admin
    from django.urls import path
    
    from mainmenu import views as mainmenuviews
    from firstpage import views as firstpageviews
    
    urlpatterns = [
        path('main/', mainmenuviews.main),
        path('main/firstpage/', firstpageviews.firstpage),
    ]
    

4.2templates模板

使用html文件的方式

def user_list(request):
    return render(request, "user_list.html")

那么,Django所需要的html文件应该存储的位置是哪里?

--app目录文件下的templates目录文件,实际上,并非直接去views文件所在的app目录下,而是根据app的注册顺序逐一去每个app下寻找对应的templates文件,并且,如果初始创建时候setting文件设置了DIR中的一些内容(由Pycharm创建时候)

import os

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [os.path.join(BASE_DIR, 'templates')], #<--这里如果是这样,则优先到根目录下的templates文件中														寻找,如果找不到,再到app文件下的templates中寻找
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

总结:如果根目录下没有templates文件且项目文件夹下的setting.py文件中DIR的value是空,则app目录下的views中的函数在找模板时候会按照app注册顺序依次去每一个app目录下的templates中寻找

4.3静态文件

4.3.1创建static目录

开发过程中一般将图片,css文,js文件一般一起当作静态文件处理,静态文件放在app目录下的static目录中

  • 在app目录下创建static文件夹
<img src="/static/img/xxx.png" alt=""> <!--这是一般方式引用静态文件-->

4.3.2使用Django提供的方式引用静态文件

Django的引用方式

在html文件头部

{%load static%}

引用文件的时候

{% static '文件相对地址'%}

一个使用Django的基本html样式:

{%load static%}

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Django_Test</title>
    <link rel="stylesheet" href="{% static 'plugins/bootstrap-3.4.1/css/bootstrap.css' %}">
</head>
<body>
    
    <div>
        <input type="text" class="btn btn-primary" value="新建">
    </div>
    
    
    
    <script src="{% static 'js/jquery-3.7.1.min.js' %}"></script>  
    <script src="{% static 'plugins/bootstrap-3.4.1/js/bootstrap.js' %}">
        
    </script>  
</body>
</html>

5.模板语法

本质上:在HTML中写一些占位符,由数据对这些占位符进行替换和处理。

列表

def user_add(request):
    return HttpResponse("User_add")

def tpl(request):

    name = "xxx"
    roles = ['管理员', 'CEO', '保安']
    user_info = {
        "name":"Yu Nian",
        "salary":10000,
        "role":"CEO"
    }
    datalist = [
    {"name":"Zhang He", "salary":10000, "role":"CFO"},
    {"name":"Zhang Chunhua", "salary":12000, "role":"CTO"},
    {"name":"Zhang Xincai", "salary":9000, "role":"CEO"}
	]

    return render(
        request, 
        "tpl.html", 
        {
            "n1":name,
            "n2":roles,
            "n3":user_info,
            "n4":datalist
        }
    )
<div>{{ n1 }}</div>		<!--xxx-->
<div>{{ n2 }}</div> 	<!--['管理员', 'CEO', '保安']-->
<div>{{ n2.0 }}</div>	<!--管理员-->
<div>{{ n2.1 }}</div>	<!--CEO-->
<div>{{ n2.2 }}</div>	<!--保安-->
<div>
    {% for item in n2%}
    <span>{{ item }}</span>
    {% endfor %}
</div>					<!--管理员 CEO 保安-->

</hr>

<div>{{ n3 }}</div>		<!--{'name': 'Yu Nian', 'salary': 10000, 'role': 'CEO'}-->
<div>{{ n3.name }}</div>	<!--Yu Nian-->
<div>{{ n3.salary }}</div>	<!--10000-->
<div>{{ n3.role }}</div>	<!--CEO-->
<div>
    {% for item in n3.items%}
    <span>{{ item }}</span>
    {% endfor %}
</div>						<!--('name', 'Yu Nian') ('salary', 10000) ('role', 'CEO')-->
<div>
    {% for item in n3.keys%}
    <span>{{ item }}</span>
    {% endfor %}
</div>						<!--name salary role-->
<div>
    {% for item in n3.values%}
    <span>{{ item }}</span>
    {% endfor %}
</div>						<!--Yu Nian 10000 CEO-->
<div>
    {% for k,v in n3.items%}
    <span>{{ k }}{{ v }}</span>
    {% endfor %}
</div>						<!--nameYu Nian salary10000 roleCEO-->

<hr/>
<div>{{ n4.0 }}</div>		<!--{'name': 'Zhang He', 'salary': 10000, 'role': 'CFO'}-->
<div>{{ n4.0.name }}</div>	<!--Zhang He-->
<div>
    {% for item in n4%}		
    <span>{{ item.name }}</span>
    <span>{{ item.salary }}</span>
    <span>{{ item.role }}</span>
    {% endfor %}
</div>						<!--Zhang He 10000 CFO Zhang Chunhua 12000 CTO Zhang Xincai 9000 CEO-->
<hr/>
<div>
    {% if n1 == 'xxx' %}
        <span>xxx<span>
    {% else %}
        <span>yyy</span>
    {% endif %}
</div>						<!--xxx-->

案例:消息中心

def news(request):
    #定义一些新闻
    #找到新闻页面
    #利用第三方模块爬取:requests
    import requests
    res = requests.get("https://www.chinaunicom.com.cn/43/menu01/1/column05?pageNo=0&pageSize=10&year=2024&month=")
    datalist = res.json()
    return render(request, "news.html", {"newsList":datalist})
{%load static%}

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Django_Test</title>
    <link rel="stylesheet" href="{% static 'plugins/bootstrap-3.4.1/css/bootstrap.css' %}">
</head>
<body>
    <h1>NeWS Center</h1>
    <ul>
        {% for item in newsList %}
        <li>{{item.news_title}} 时间:{{ item.post_time }}</li>
        {% endfor %}
    </ul>
    <script src="{% static 'js/jquery-3.7.1.min.js' %}"></script>  
    <script src="{% static 'plugins/bootstrap-3.4.1/js/bootstrap.js' %}">
        
    </script>  
</body>
</html>

6.请求和响应

def something(request):
    #request是一个对象,封装了用户通过浏览器发送过来请求的所有数据

    #1.获取请求方式GET/POST
    print(request.method)

    #2.在URL上传递值 /something/?oid=937929511&bvid=BV1rT4y1v7uQ
    print(request.GET)

    #3.在请求体中提交数据
    print(request.POST)

    #4.HttpResponse("返回内容")字符串内容返回给请求者
    #return HttpResponse("返回内容")

    #5.读取HTML内容 + 渲染(替换) -> 渲染新的字符串
    #return render(request, 'something.html', {"title":"来了"})

    #6.【响应】浏览器重定向
    return redirect("https://www.baidu.com")

浏览器重定向的过程:重定向的地址发送给浏览器,由浏览器发起对目标地址的访问

案例:用户登录

在输入密码的表单中要加上{% csrf_token %},否则会触发403forbidden

def login(request):
    
    if request.method == 'GET' :
        return render(request, "login.html")
    username = request.POST.get("username")
    password = request.POST.get("password") 
    # return HttpResponse("登录成功")
    if username == "root" and password == "123":
        #return HttpResponse("登录成功")
        return redirect("https://www.baidu.com")
    #return HttpResponse("登录失败")
    return render(request, "login.html", {"login_fail":"账号或密码错误"})
{%load static%}

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Django_Test</title>
    <link rel="stylesheet" href="{% static 'plugins/bootstrap-3.4.1/css/bootstrap.css' %}">
</head>
<body>
    <h1>用户登录</h1>

    <form method="POST" action="/login/">

        {% csrf_token %}<!--带上随机字符串的返回,否则会触发403Forbidden-->

        <input type="text" name="username" placeholder="用户名">
        <input type="password" name="password" placeholder="密码">
        <input type="submit" value="提交"><span style="color:red;">{{ login_fail }}</span>
    </form>

    <script src="{% static 'js/jquery-3.7.1.min.js' %}"></script>  
    <script src="{% static 'plugins/bootstrap-3.4.1/js/bootstrap.js' %}">
        
    </script>  
</body>
</html>

7.数据库操作

  • MySQL + pymysql :有些繁琐
  • Django开发操作数据库更为简单,内部提供ORM框架:介于代码与数据库操作插件之间,提供翻译

7.1安装第三方模块(底层交互插件)

pip install mysqlclient

注意:mysqlclient已不再支持3.8版本以下的python,请确保当前环境的python版本至少为3.8.0

若无法通过pip安装请转至mysqlclient官网找到对应的pp3x版本下载

7.2ORM

ORM作用:

  • 创建,修改,删除数据库中的表,且无需使用SQL语句(无法创建数据库)
  • 操作表中的数据,无需使用SQL语句

1.创建数据库

  • 启动MySQL
  • 创建数据库

2.Django连接数据库

在setting.py文件中进行配置和修改

DATABASES = {
    'default':{
        'ENGINE':'django.db.backends.mysql', #使用django连接mysql,如此还可以连接orcale, postgresql等数据库
        'NAME':'dbname', 	#数据库的名字
        'USER':'root', 		#用户名字
        'PASSWORD':'xxx', 	#密码
        'HOST':'',			#数据库安装的服务器,本机即localhost或者127.0.0.1
        'PORT':'',			#my.ini -- 3306
    }
}
DATABASES = {
    'default':{
        'ENGINE':'django.db.backends.mysql',
        'NAME':'testdb', 	
        'USER':'root', 		
        'PASSWORD':'123', 	
        'HOST':'127.0.0.1',			
        'PORT':'3306',			
    }
}

3.基于Django操作数据表

  • 创建表
  • 删除表
  • 修改表

创建表:基于models.py文件

from django.db import models

# Create your models here.

class UserInfo(models.Model):
    name = models.CharField(max_length=32)
    password = models.CharField(max_length=64)
    age = models.IntegerField()
"""
create table app01_user_info(
    id bigint auto_increment primary key,
    name varchar(32),
    password varchar(64),
    age int
)
"""

执行命令:

python3.x manage.py makemigrations
python3.x manage.py migrate

注意,需要当前app已经注册,并mysql版本应该至少为8.0,否则请关闭版本检测E:\MyEnv\Devenv312\Lib\site-packages\django\db\backends\base\base.py中line239的self.check...注释掉

  • 添加表
class Department(models.Model):
    title = models.CharField(max_length=16)

class Role(models.Model):
    caption = models.CharField(max_length=16)
  • 删除表(注释掉即可)
#class Role(models.Model):
#    caption = models.CharField(max_length=16)
  • 删除字段(注释掉即可)
class UserInfo(models.Model):
    name = models.CharField(max_length=32)
    password = models.CharField(max_length=64)
    # age = models.IntegerField()
  • 新建字段(要提供default设置默认值,因为表中可能有一些数据,或者将该字段设为允许空字段)
class UserInfo(models.Model):
    name = models.CharField(max_length=32)
    password = models.CharField(max_length=64)
    # age = models.IntegerField()
    size = models.CharField(max_length=16, default='xxl')
    size_num = models.IntegerField(default=32)
    #也可以允许为空
    data = models.IntegerField(null=True, blank=True)
  • 增加数据
#from app01 import models 然后使用models.Department.objects.create("...")
from app01.models import Department, UserInfo #可以直接使用Department.objects.create("...")
def orm(request):
    #测试ORM
    Department.objects.create(title = "销售部")
    Department.objects.create(title = "运营部")
    Department.objects.create(title = "开发部")
    Department.objects.create(title = "广告部")
    return HttpResponse("Success")
  • 删除数据
#from app01 import models 然后使用models.Department.objects.create("...")
from app01.models import Department, UserInfo #可以直接使用Department.objects.create("...")
def orm(request):    
    UserInfo.objects.filter(id=2).delete() #filter是过滤出要删除的数据
    #UserInfo.objects.all().delete() 删除所有数据
    return HttpResponse("Success")
  • 筛选(获取)数据
#from app01 import models 然后使用models.Department.objects.create("...")
from app01.models import Department, UserInfo #可以直接使用Department.objects.create("...")
def orm(request):    
    queryset = UserInfo.objects.all()
    #获取所有数据,返回一个queryset类型 [行, 行, 行, 行 ...],每一行是一个对象,封装了对应的属性
    for obj in queryset:
        print(obj.id, obj.name, obj.size)# 1 Zhang He xxl
        
    row_obj = UserInfo.objects.filter(id=1).first() #first取对应queryset对象中的第一个元素,即第一行
    print(row_obj.id, row_obj.name, row_obj.size)
    return HttpResponse("Success")
  • 更新数据
#from app01 import models 然后使用models.Department.objects.create("...")
from app01.models import Department, UserInfo #可以直接使用Department.objects.create("...")
def orm(request):    
    UserInfo.objects.filter(id=1).update(name = "Zhang Chunhua")#指定修改
    UserInfo.object.all().update(password='111')#全部修改
    return HttpResponse("Success")

案例:用户管理

1.展示用户列表

  • url
  • 函数
    • 获取用户所有信息
    • HTML渲染
def info_list(request):
    data_list = UserInfo.objects.all()
    return render(request, "info_list.html",{"datalist":data_list})
{%load static%}

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Django_Test</title>
    <link rel="stylesheet" href="{% static 'plugins/bootstrap-3.4.1/css/bootstrap.css' %}">
</head>
<body>
    <h1>用户列表</h1>
    <table border="1">
        <thead>
            <tr>
                <th>id</th>
                <th>name</th>
                <th>password</th>
                <th>size</th>
            </tr>
        </thead>
        <tbody>
            {% for item in datalist %}
            <tr>
                <td>{{ item.id }}</td>
                <td>{{ item.name }}</td>
                <td>{{ item.password }}</td>
                <td>{{ item.size }}</td>
            </tr>
            {% endfor %}
        </tbody>
    </table>
    <a href="/info/add/">返回</a>
    <script src="{% static 'js/jquery-3.7.1.min.js' %}"></script>  
    <script src="{% static 'plugins/bootstrap-3.4.1/js/bootstrap.js' %}">
    </script>  
</body>
</html>

2.添加用户

  • url
  • 函数
    • GET看到页面,输入内容
    • POST,提交,写入到数据库
def info_add(request):
    if request.method == 'GET':
        return render(request, "info_add.html")
    
    # 获取用户提交的数据
    user = request.POST.get("user")
    psw = request.POST.get("psw")
    size = request.POST.get("size")

    #添加到数据库
    UserInfo.objects.create(name=user, password=psw, size=size)

    # return HttpResponse("添加成功")

    #自动跳转
    return redirect("/info/list/")
{%load static%}

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Django_Test</title>
    <link rel="stylesheet" href="{% static 'plugins/bootstrap-3.4.1/css/bootstrap.css' %}">
</head>
<body>
    <h1>用户添加</h1>
    <form method="post" action="/info/add/"> <!--如果表单提交的地址与当前页面一致,则可以省略action="/info/add/"-->
        {% csrf_token %}
        <input type="text" name="user" placeholder="用户名">
        <input type="password" name="psw" placeholder="密码">
        <div>
            大小:
            <input type="checkbox" name="size" value="l">l
            <input type="checkbox" name="size" value="xl">xl
            <input type="checkbox" name="size" value="s">s
            <input type="checkbox" name="size" value="m">m
        </div>
        <input type="submit" value="提交">
    </form>

    <script src="{% static 'js/jquery-3.7.1.min.js' %}"></script>  
    <script src="{% static 'plugins/bootstrap-3.4.1/js/bootstrap.js' %}">
    </script>  
</body>
</html>

3.删除用户

http://127.0.0.1:8000/info/delete/?nid=1

def 函数():
    nid = request.GET.get("nid")
    UserInfo.objects.filter(id=nid).delete()
    return HttpResponse("删除成功")

通过键入列表行所在对象的id实现操作表格来删除对应的数据

def info_delete(request):
    nid = request.GET.get("nid")
    UserInfo.objects.filter(id=nid).delete()
    return redirect("/info/list/")
<tbody>
            {% for item in datalist %}
            <tr>
                <td>{{ item.id }}</td>
                <td>{{ item.name }}</td>
                <td>{{ item.password }}</td>
                <td>{{ item.size }}</td>
                <td><a href="/info/delete/?nid={{ item.id }}">删除</a></td><!--拼凑get来获取对应的数据-->
            </tr>
            {% endfor %}
        </tbody>

Django开发:员工管理系统

1.新建项目

检查setting.py文件中的DIR的内容,默认为空

(Devenv312) E:\TestFile\project6-Django>"E:\MyEnv\Devenv312\Scripts\django-admin.exe" startproject System_Manage_User
'DIRS': [],

2.创建&注册APP

  • 创建app

命令行:

(Devenv312) E:\TestFile\project6-Django\System_Manage_User>python manage.py startapp app01

Pycharm

输入:startapp app01即可创建app

  • 注册app
'app01.apps.App01Config'
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'app01.apps.App01Config'
]

3.设计表结构

mdoels.py中添加表

from django.db import models

# Create your models here.

class Department(models.Model):
    #部门表
    id = models.BigAutoField(verbose_name='ID', primary_key=True) #类型为BigInt,默认自增的id初值为1,则可写可不写
    title = models.CharField(verbose_name='标题', max_length=32)

class User_Info(models.Model):
    #员工表
    name = models.CharField(verbose_name='姓名', max_length=16)
    password = models.CharField(verbose_name='密码', max_length=64)
    age = models.IntegerField(verbose_name='年龄')
    account = models.DecimalField(verbose_name='账户余额', max_digits=10, decimal_places=2, default=0)
    create_time = models.DateTimeField(verbose_name='入职时间', )

    """
    1.有约束,使用外键,这样此处只能填入部门表中已存在的ID
    - to :表示与对应的表关联
    - to_field :表示与对应的属性关联
    2.django自动
    - 写的depart
    - 实际表中生成的属性为depart_id
    3.部门表中的数据被删除 -
    -级联删除
    depart = models.ForeignKey(to='Department', to_field='id', on_delete = models.CASCADE)
    -修改对应外键的属性值为空
    depart = models.ForeignKey(to='Department', to_field='id', null=True, blank=True, on_delete=models.SET_NULL)
    """
    depart = models.ForeignKey(to='Department', to_field='id', on_delete=models.CASCADE)

4.在MySQL中生成表

  • 通过工具连接到MySQL,生成数据库
create database emplo_manage_system DEFAULT CHARSET utf8 COLLATE utf8_general_ci;
  • 修改配置文件,连接MySQL
DATABASES = {
    'default':{
        'ENGINE':'django.db.backends.mysql',
        'NAME':'emplo_manage_system', 	
        'USER':'root', 		
        'PASSWORD':'123', 	
        'HOST':'127.0.0.1',			
        'PORT':'3306',			
    }
}
  • Django命令生成数据表
python manage.py makemigrations
python manage.py migrate
  • Pycharm生成数据表

Pycahrm生成数据表

然后依次输入makemigrations, 回车, migrate, 回车

5.静态文件的管理

static, templates放入app01目录下面

6.部门管理

Django中提供了Form和ModelForm的组件,可以更为方便的制作

6.1部门列表

def depart_list(request):
    """部门列表"""
    department_list = Department.objects.all()
    return render(request, 'depart_list.html', {'department_list':department_list})
{%load static%}

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Manage System</title>
    <link rel="stylesheet" href="{% static 'plugins/bootstrap-3.4.1/css/bootstrap.min.css' %}">
    <link rel="stylesheet" href="{% static 'plugins/font-awesome-4.7.0/css/font-awesome.css' %}">
    <style>
        .navbar{
            border-radius:0;
        }
    </style>
</head>
<body>
    <nav class="navbar navbar-inverse">
        <div class="container-fluid">
          <div class="navbar-header">
            <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-9" aria-expanded="false">
              <span class="sr-only">Toggle navigation</span>
              <span class="icon-bar"></span>
              <span class="icon-bar"></span>
              <span class="icon-bar"></span>
            </button>
            <a class="navbar-brand" href="#">员工管理信息系统</a>
          </div>
          <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-9">
            <ul class="nav navbar-nav">
              <li><a href="#">主页</a></li>
              <li class="active"><a href="#">部门管理</a></li>
              <li><a href="#">用户管理</a></li>
            </ul>

            <ul class="nav navbar-nav navbar-right">
                <li><a href="#">切换用户</a></li>
                <li><a href="#">登出</a></li>
                <li class="dropdown">
                  <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">张郃<span class="caret"></span></a>
                  <ul class="dropdown-menu">
                    <li><a href="#">个人资料</a></li>
                    <li><a href="#">我的信息</a></li>
                    <li><a href="#">修改密码</a></li>
                    <li role="separator" class="divider"></li>
                    <li><a href="#">关于我们</a></li>
                  </ul>
                </li>
            </ul>
          </div>
        </div>
    </nav>

    <div>
        <div class="container">
            <div style="margin-bottom:10px;">
                <a class="btn btn-primary" href="#" >新建部门</a>
            </div>
            
            <div class="panel panel-default">
                <div class="panel-heading">
                    <h3 class="panel-title"><i class="fa fa-list-ul"></i>部门列表</h3>
                </div>
                <table class="table table-hover table-bordered" style="padding:0 20px 0 20px;">
                    <thead>
                        <tr>
                            <th>ID</th>
                            <th>部门名称</th>
                            <th>操作</th>
                        </tr>
                    </thead>
                    <tbody>
                        {% for item in department_list %}
                        <tr>
                            <td class="col-xs-3">{{ item.id }}</td>
                            <td class="col-xs-5">{{ item.title }}</td>
                            <td>
                                <a class="btn btn-primary btn-xs" href="#">
                                    <i class="fa fa-edit" aria-hidden="true"></i> 编辑
                                </a>
                                <span> </span>
                                <a class="btn btn-danger btn-xs" href="#">
                                    <i class="fa fa-trash" aria-hidden="true"></i> 删除
                                </a>
                            </td>
                        </tr>
                        {% endfor %}
                    </tbody>
                    </table>
            </div>
        </div>
    </div>

    <script src="{% static 'js/jquery-3.7.1.min.js' %}"></script>  
    <script src="{% static 'plugins/bootstrap-3.4.1/js/bootstrap.min.js' %}">
        
    </script>  
</body>
</html>

6.2添加部门

添加页面

{%load static%}

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Add Department</title>
    <link rel="stylesheet" href="{% static 'plugins/bootstrap-3.4.1/css/bootstrap.css' %}">
    <link rel="stylesheet" href="{% static 'plugins/font-awesome-4.7.0/css/font-awesome.css' %}">
    <style>
        .navbar{
            border-radius:0;
        }
    </style>
</head>
<body>
    <nav class="navbar navbar-inverse">...</nav>

    <div>
        <div class="container">
            <div class="panel panel-default">
                <div class="panel-heading">
                  <h3 class="panel-title">新建部门</h3>
                </div>
                <div class="panel-body">
                    <form action="/depart/add/" method="post">
                        {% csrf_token %}
                        <div class="form-group">
                            <label>标题</label>
                            <input type="text" class="form-control" placeholder="标题" name="title">
                        </div>
                        <button type="submit" class="btn btn-primary"><i class="fa fa-save" aria-hidden="true"></i> 保 存</button>
                        <span style="color:red;">{{ error_type }}</span>
                      </form>
                </div>
              </div>
        </div>
    </div>

    <script src="{% static 'js/jquery-3.7.1.min.js' %}"></script>  
    <script src="{% static 'plugins/bootstrap-3.4.1/js/bootstrap.js' %}">
        
    </script>  
</body>
</html>

添加功能

def depart_add(request):
    """添加部门"""
    if request.method == 'GET':
        return render(request, "depart_add.html")
    if request.POST.get('title') == "":
        error = "标题不可以为空"
        return render(request, "depart_add.html",{'error_type':error})
    
    title = request.POST.get('title')
    models.Department.objects.create(title=title)
    return redirect("/depart/list/")

6.3删除部门

删除功能

def depart_del(request):
    """删除部门"""
    tid = request.GET.get("tid")
    models.Department.objects.filter(id=tid).delete()
    return redirect("/depart/list/")
<a class="btn btn-danger btn-xs" href="/depart/del/?tid={{ item.id }}">
    <i class="fa fa-trash" aria-hidden="true"></i> 删除
</a>

6.4编辑部门

一种新的页面向后端传参的方式

urls.py

path('depart/<int:tid>/edit/', views.depart_edit),
#<int:tid>是类似正则一样,tid会以参数的方式传递到函数中,访问该页面地址会类似 127.0.0.1:8000/depart/3/edit
#也可以改成类似depart/tid=<int:tid>/edit/等等类似形式

views.py

def depart_edit(request, tid): 
    #在此处加入了新的参数,如果当前的页面地址为127.0.0.1:8000/depart/3/edit,则tid会直接等于3
    """修改部门"""
    if request.method == 'GET':
        #根据tid获取对应的数据
        depart_row = models.Department.objects.filter(id=tid).first()
        return render(request, "depart_edit.html", {'depart':depart_row})
    
    update_title = request.POST.get('title')
    if update_title == "":
        error = "新的标题不可以为空"
        return render(request, "depart_edit.html", {'depart':depart_row, 'error_type':error})
    
    models.Department.objects.filter(id=tid).update(title=update_title)
    return redirect("/depart/list/")

编辑按钮

<a class="btn btn-primary btn-xs" href="/depart/{{ item.id }}/edit/"><i class="fa fa-edit" aria-hidden="true"></i> 编辑
</a>

编辑页面

{%load static%}

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Department Edit</title>
    <link rel="stylesheet" href="{% static 'plugins/bootstrap-3.4.1/css/bootstrap.css' %}">
    <link rel="stylesheet" href="{% static 'plugins/font-awesome-4.7.0/css/font-awesome.css' %}">
    <style>
        .navbar{
            border-radius:0;
        }
    </style>
</head>
<body>
	<nav class="navbar navbar-inverse">...</nav>
    <div>
        <div class="container">
            <div class="panel panel-default">
                <div class="panel-heading">
                  <h3 class="panel-title">修改部门</h3>
                </div>
                <div class="panel-body">
                    <form method="post"><!--没有action默认提交到本页面-->
                        {% csrf_token %}
                        <div class="form-group">
                            <label>标题</label>
                            <input type="text" class="form-control" value="{{ depart.title }}" name="title"> <!--value相当于默认值-->
                        </div>
                        <button type="submit" class="btn btn-primary">
                            <i class="fa fa-save" aria-hidden="true"></i> 保 存
                        </button>
                        <span style="color:red;">{{ error_type }}</span>
                      </form>
                </div>
              </div>
        </div>
    </div>

    <script src="{% static 'js/jquery-3.7.1.min.js' %}"></script>  
    <script src="{% static 'plugins/bootstrap-3.4.1/js/bootstrap.js' %}">
        
    </script>  
</body>
</html>

7.模板的继承

Django支持模板的继承,通过使用{%%}可以创建一些模板供对应的html文件使用

通过block content创建一个模板

{%load static%}

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>{% block title %} {% endblock %}</title>
    <link rel="stylesheet" href="{% static 'plugins/bootstrap-3.4.1/css/bootstrap.css' %}">
    <link rel="stylesheet" href="{% static 'plugins/font-awesome-4.7.0/css/font-awesome.css' %}">
    <style>
        .navbar{
            border-radius:0;
        }
        {% block css %}{% endblock %}
    </style>
</head>
<body>
    <nav class="navbar navbar-inverse">...</nav>
    
    <div>
        {% block content %}{% endblock %}
    </div>

    <script src="{% static 'js/jquery-3.7.1.min.js' %}"></script>  
    <script src="{% static 'plugins/bootstrap-3.4.1/js/bootstrap.js' %}">
        {% block js %}{% endblock %}
    </script>  
</body>
</html>

在部门列表中使用这个模板


{% extends "layout.html" %}

{% block title %}
部门列表
{% endblock %}


{% block content %}
    <div class="container">
        <div style="margin-bottom:10px;">
            <a class="btn btn-success" href="/depart/add/"><i class="fa fa-plus-circle" aria-hidden="true"></i> 新建部门</a>
        </div>
        
        <div class="panel panel-default">

            <div class="panel-heading">
                <h3 class="panel-title"><i class="fa fa-list-ul"></i> 部门列表</h3>
            </div>

            <table class="table table-hover table-bordered" style="padding:0 20px 0 20px;">
                <thead>
                    <tr>
                        <th>ID</th>
                        <th>部门名称</th>
                        <th>操作</th>
                    </tr>
                </thead>
                <tbody>
                    {% for item in department_list %}
                    <tr>
                        <td class="col-xs-3">{{ item.id }}</td>
                        <td class="col-xs-5">{{ item.title }}</td>
                        <td>
                            <a class="btn btn-primary btn-xs" href="/depart/tid={{ item.id }}/edit/"><i class="fa fa-edit" aria-hidden="true"></i> 编辑</a>
                            <span> </span>
                            <a class="btn btn-danger btn-xs" href="/depart/del/?tid={{ item.id }}"><i class="fa fa-trash" aria-hidden="true"></i> 删除</a>
                        </td>
                    </tr>
                    {% endfor %}
                </tbody>
            </table>
        </div>

    </div>
{% endblock  %}

由此,通过block可以为一个html创建多个可填充部分:

{%load static%}

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>{% block title %} {% endblock %}</title>
    <link rel="stylesheet" href="{% static 'plugins/bootstrap-3.4.1/css/bootstrap.css' %}">
    <link rel="stylesheet" href="{% static 'plugins/font-awesome-4.7.0/css/font-awesome.css' %}">
    {% block css %}{% endblock %}
</head>
<body>
    <nav class="navbar navbar-inverse">...</nav>
    
    <div>
        {% block content %}{% endblock %}
    </div>

    <script src="{% static 'js/jquery-3.7.1.min.js' %}"></script>  
	{% block js %}{% endblock %} 
</body>
</html>

使用这个模板时候:

{% extends "layout.html" %}
{% block css %}
<style>
    .navbar{
        border-radius:0;
    }
</style>
{% endblock %}

{% block title %}
xxx
{% endblock %}

{% block content %}
yyy
{% endblock  %}

{% block js %}
    <script src="{% static 'plugins/bootstrap-3.4.1/js/bootstrap.js' %}">
        {% block js %}{% endblock %}
    </script> 
{% endblock %}

8.用户的管理

8.1用户列表

def user_list(request):
    """用户列表"""
    #获取所有的用户列表

    user_list = models.User_Info.objects.all()
    """
    for obj in user_list:
        print(obj.name)
        print(obj.age)
        print(obj.create_time.strftime("%Y-%m-%d"))#通过strftime转换日期类型为字符串
        # print(obj.gender)只会显示1或者2
        print(obj.get_gender_display())# get_字段名_display()直接显示约束的对应值1-男, 2-女
        # print(obj.depart_id)只会显示对应字段的值:1 2 3但不会显示外键所连接的表的title
        #print(models.Department.objects.filter(id=obj.depart_id).first().title)太麻烦
        print(obj.depart.title)
    """
    

    return render(request, "user_list.html", {'user_list':user_list})
{% extends "layout.html" %}

{% block title %}
用户列表
{% endblock %}

{% block 用户管理 %}class="active"{% endblock %}

{% block content %}
    <div class="container">
        <div style="margin-bottom:10px;">
            <a class="btn btn-success" href="#"><i class="fa fa-plus-circle" aria-hidden="true"></i> 新建用户</a>
        </div>
        
        <div class="panel panel-default">

            <div class="panel-heading">
                <h3 class="panel-title"><i class="fa fa-list-ul"></i> 部门列表</h3>
            </div>

            <table class="table table-hover table-bordered" style="padding:0 20px 0 20px;">
                <thead>
                    <tr>
                        <th>ID</th>
                        <th>用户名</th>
                        <th>密码</th>
                        <th>年龄</th>
                        <th>账户余额</th>
                        <th>创建时间</th>
                        <th>性别</th>
                        <th>部门</th>
                        <th>操作</th>
                    </tr>
                </thead>
                <tbody>
                    {% for item in user_list %}
                    <tr>
                        <td>{{ item.id }}</td>
                        <td>{{ item.name }}</td>
                        <td>{{ item.password }}</td>
                        <td>{{ item.age }}</td>
                        <td>{{ item.account }}¥</td>
                        <td>{{ item.create_time|date:"Y-m-d" }}</td>
    <!--
        模板语法规则:
            > 不允许出现(), 有()的地方会自动补上无需写
            >括号有内容一般参考对应的模板函数,如strftime("%Y-%m-%d"), 模板语言形式为date:"Y-m-d H:i:s"
    -->
                        <td>{{ item.get_gender_display }}</td>
                        <td>{{ item.depart.title }}</td>
                        <td>
                            <a class="btn btn-primary btn-xs" href="#"><i class="fa fa-edit" aria-hidden="true"></i> 编辑</a>
                            <span> </span>
                            <a class="btn btn-danger btn-xs" href="#"><i class="fa fa-trash" aria-hidden="true"></i> 删除</a>
                        </td>
                    </tr>
                    {% endfor %}
                </tbody>
            </table>

        </div><!--panel-->

    </div><!--container-->
{% endblock  %}

8.2新建用户

原始思路(不会采用的原因:麻烦,难以提供错误数据,难以校验)

def user_add(request):
    #提供展示页面所需要的信息
    context = {
        'depart_list':models.Department.objects.all(),
        'gender_choices':models.User_Info.gender_choices,
        'error_type':"",
    }    
    if request.method =='GET':
        return render(request, "user_add.html", context)
    
    #获取用户提交的数据
    querydata ={
        'name'         : request.POST.get('name'),
        'password'     : request.POST.get('password'),
        'age'          : request.POST.get('age'),
        'account'      : request.POST.get('account'),
        'create_time'  : request.POST.get('create_time'),
        'gender'       : request.POST.get('gender'),
        'depart_id'    : request.POST.get('depart_id'),
    }

    for i in querydata.values():
        if i == "":
            context['error_type'] = "用户名、密码、年龄和余额不可以为空"
            return render(request, "user_add.html", context)
    
    print(querydata)

    #将数据添加到数据库
    models.User_Info.objects.create(
        name=querydata['name'], 
        password=querydata['password'],
        age=querydata['age'],
        account=querydata['account'],
        create_time=querydata['create_time'],
        gender=querydata['gender'],
        depart_id=querydata['depart_id'],
        )

    return redirect("/user/list/")
{% extends "layout.html" %}

{% block title %}
添加用户
{% endblock %}

{% block content %}
    <div class="container">
        <div class="panel panel-default">
            <div class="panel-heading">
            <h3 class="panel-title">新建用户</h3>
            </div>

            <div class="panel-body">
                <form action="/user/add/" method="post">
                    {% csrf_token %}

                    <div class="form-group">
                        <label>用户名</label>
                        <input type="text" class="form-control" placeholder="用户名" name="name">
                    </div>
                    <div class="form-group">
                        <label>密码</label>
                        <input type="password" class="form-control" placeholder="密码" name="password">
                    </div>
                    <div class="form-group">
                        <label>年龄</label>
                        <input type="text" class="form-control" placeholder="年龄" name="age">
                    </div>
                    <div class="form-group">
                        <label>账户余额</label>
                        <input type="text" class="form-control" placeholder="账户余额" name="account">
                    </div>
                    <div class="form-group">
                        <label>入职时间</label>
                        <input type="date" class="form-control" name="create_time">
                    </div>
                    <div class="form-group">
                        <label>性别</label>
                        {% comment %} <input type="radio" value="1" name="gender" style="cursor: pointer;"> 男
                        <input type="radio" value="2" name="gender" style="cursor: pointer;"> 女 {% endcomment %}
                        <select name="gender" class="form-control">
                            {% for item in gender_choices %}
                                <option value="{{ item.0 }}">{{ item.1 }}</option>
                            {% endfor %}
                        </select>
                    </div>
                    <div class="form-group">
                        <label>部门</label>
                        <select name="depart_id" class="form-control">
                            {% for item in depart_list %}
                            <option value="{{ item.id }}">{{ item.title }}</option>
                            {% endfor %}
                        </select>
                    </div>

                    <button type="submit" class="btn btn-primary"><i class="fa fa-save" aria-hidden="true"></i> 保 存</button>
                    <span style="color:red;">{{ error_type }}</span>
                </form>
            </div>
            
        </div>
    </div>
{% endblock  %}

怎么办?使用Django提供的组件

  • Django组件

    • Form组件(简便)

    • ModelForm组件(超级简便)

1.Form组件

views.py(注意不是models.py)

class MyForm(Form):
    name = forms.CharField(widget=forms.Input)
    password = forms.CharField(widget=forms.Input)
    age = forms.CharField(widget=forms.Input)
    
def info_show(request):
    if reuqest.method == 'GET':
        form = MyForm()
        context = {
            'form':form,
        }
        return render(request, 'info_show.html', context)

info_show.html

<form method='post'>
	{% for field in form %}
    {{ field }}
    {% endfor %}
</form>

等价于

<form method='post'>
	{{ form.name }}
    {{ form.password }}
    {{ form.age }}
</form>
<form method='post'>
    <input type="text" name="name">
    <input type="text" name="password">
    <input type="text" name="age">
</form>

2.ModelFrom组件(推荐)

models.py

class Info(mdoels.Model):
    name = models.CharField(verbose_name='姓名', max_length=16)
    password = models.CharField(verbose_name='密码', max_length=64)
    age = models.IntegerField(verbose_name='年龄')

views.py

class MyForm(ModelForm):
    xx = form.CharField(widget=forms.Input)
    class Meta:
        model = Info
        fields = ["name", "password", "age", "xx"]
    
def info_show(request):
    if reuqest.method == 'GET':
        form = MyForm()
        context = {
            'form':form,
        }
        return render(request, 'info_show.html', context)

info_show.html

<form method='post'>
	{% for field in form %}
    {{ field }}
    {% endfor %}
</form>

3.新建用户(ModelForm版本)

models.py

class Department(models.Model):
    #部门表
    id = models.BigAutoField(verbose_name='ID', primary_key=True) #类型为BigInt,默认自增的id初值为1,则可写可不写
    title = models.CharField(verbose_name='标题', max_length=32)

    def __str__(self):
        return self.title #当调用Department类的时候,返回其title属性的值
    
    """
    class Obj():
    #__init__:类实例初始化函数   定义类自身的属性:
        def __init__(self, name):
            self.name = name    #其中name是外部初始化对象需要的参数,self.name是对象自身的属性

    #__str__:类实例字符串化函数   调用类的时候返回的值:
        def __str__(self):
            return self.name    #返回类自身的属性
    
    #即,调用这个类需要向其传递一个name的属性值,然后输出类的结果是类的name属性值
    
    obj = Obj('ABC')
    print(obj)
    >>>ABC
    """

depart在UserInfo表中是外键,自动生成input时候调用Department得到的是object:Department,因此需要设置此类的返回值,这样才能直接生成对应的选择框

views.py

#+---------------------------------------------------------------------------------------------------+#
from django import forms
class UserModelForm(forms.ModelForm):
    name = forms.CharField(min_length=3, label="用户名") #设定一个最小长度用于校验值合法
    # password = forms.CharField(validators=正则表达式, label="密码")
    class Meta:
        model = models.User_Info
        fields = ['name', 'password', 'age', 'account', 'create_time', 'age', 'gender', 'depart']
        #可以通过fields = "__all__"
        #depart默认返回的是

        #通过widgets为某些特定的类型生成指定的输入框模式(不推荐)
        widgets = {
            'password':forms.PasswordInput(),
            'account':forms.TextInput(),
            'create_time':forms.DateTimeInput(),
        }

    #快速为所有的输入框添加样式
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        # #重新部分input框的type --失效,为何?
        # self.fields['password'].widget.attrs.update({'type': 'password'})

        #循环找到所有的插件
        # print(self.fields)
        for name, field in self.fields.items():
            # if name == 'password':
            #     field.widget.attrs = {"class":"form-control"}
            #     continue
            field.widget.attrs = {"class":"form-control", "placeholder":field.label}

def user_add(request):
    """添加用户--ModelForm版"""
    if request.method == 'GET':
        form = UserModelForm()
        return render(request, "user_add.html", {'form':form})
    
    #用户POST提交数据,对数据进行空值校验
    form = UserModelForm(data=request.POST) #取数据
    if form.is_valid():#form.is_vaild()可以检验所有的输入值,如果均合法则返回True
        """form.cleaned_data是对通过校验的值的形式,保存为字典格式
        print(form.cleaned_data)
        #>>>{'name': '3123', 'password': '312323', 'age': 32,
        # 'account': Decimal('3123'), 
        #'create_time': datetime.datetime(2022, 11, 2, 0, 0, tzinfo=zoneinfo.ZoneInfo(key='UTC')), 
        #'gender': 1, 'depart': <Department: IT部门>}
        """
        form.save() #自动保存在数据库 数据库连接的来源:<model = models.User_Info>
        return redirect("/user/list/")
        
    else:
        # print(form.errors) form.errors包含了页面返回的全部的错误信息
        return render(request, "user_add.html", {'form':form})

为何会失效?

self.fields['password'].widget.attrs.update({'type': 'password'})

校验产生的错误会直接包含在form中每一项中,因此错误返回直接为return render(request, "user_add.html", {'form':form})即可

user_add.html

{% extends "layout.html" %}

{% block title %}
添加用户
{% endblock %}

{% block content %}
    <div class="container">
        <div class="panel panel-default">
            <div class="panel-heading">
            <h3 class="panel-title">新建用户</h3>
            </div>

            <div class="panel-body">
                <form action="/user/add/" method="post" novalidate>
                    {% comment %}novalidate 关闭浏览器自带的校验{% endcomment %}
                    
                    {% csrf_token %}
                    
                    {% for field in form %}
                    <div class="form-group">
                        <label>{{ field.label }}</label>
                        {{ field }} 
                        {% comment %}此处的label即为定义属性时候的verbose_name值{% endcomment %}
                        
                        <span style="color:red;">{{ field.errors.0 }}</span>
                        {% comment %}errors.0即为返回的错误信息{% endcomment %}
                    </div>
                    {% endfor %}
                    
                    <button type="submit" class="btn btn-primary">
                        <i class="fa fa-save" aria-hidden="true"></i> 保 存
                    </button>
      
                </form>
            </div>
            
        </div>
    </div>
{% endblock  %}

错误报告为英文

打开setting.py, 注释掉英文模式,添加如下内容即可

# LANGUAGE_CODE = 'en-us'
LANGUAGE_CODE = 'zh-hans'

8.3编辑用户

  • 点击编辑,跳转到编辑页面(将编辑行的ID携带过去)

  • 编辑页面(默认数据,根据ID获取并设置到)

  • 提交:

    • 错误提示
    • 数据校验
    • 在数据库更新
    models.UserInfo.filter(id=4).update(...)
    

urls.py

path('user/uid=<int:uid>/edit/', views.user_edit),

views.py

def user_edit(request, uid):
    """编辑用户"""
    if request.method == 'GET':

        #根据ID去数据库获取要编辑的那一行数据
        row_object = models.User_Info.objects.filter(id=uid).first()
        form = UserModelForm(instance=row_object) #instance标记原来的对象,此时保留原本的值为value


        return render(request, 'user_edit.html', {'form':form})
    
    row_object = models.User_Info.objects.filter(id=uid).first()
    form = UserModelForm(data=request.POST, instance=row_object) 
    #将页面返回的数据传递给form用于校验和保存
    #当instance为一个Object对象时候,标记为更新的位置,.save更新到该对象中,没有时.save默认新建数据
    if form.is_valid():#校验通过
        #默认保存的是用户输入的所有数据,如果想要在用户输入以外增加值 form.instance.字段名 = 值 保存的就会是此处的值
        form.save()
        return redirect("/user/list/")
    
    return render(request, "user_list.html", {'form':form})#校验未通过

user_edit.html


{% extends "layout.html" %}

{% block title %}
编辑用户
{% endblock %}
{% block 用户管理 %}class="active"{% endblock %}
{% block content %}
    <div class="container">
        <div class="panel panel-default">
            <div class="panel-heading">
            <h3 class="panel-title">编辑用户</h3>
            </div>

            <div class="panel-body">
                <form method="post" novalidate>{% comment %}novalidate 关闭浏览器自带的校验{% endcomment %}
                    {% csrf_token %}
                    
                    {% for field in form %}
                    <div class="form-group">
                        <label>{{ field.label }}</label>
                        {{ field }} {% comment %}此处的label即为定义属性时候的verbose_name值{% endcomment %}
                        <span style="color:red;">{{ field.errors.0 }}</span>
                    </div>
                    {% endfor %}
                    
                    <button type="submit" class="btn btn-primary">
                        <i class="fa fa-save" aria-hidden="true"></i> 保 存
                    </button>
                </form>
            </div>
            
        </div>
    </div>
{% endblock  %}

8.4删除用户

views.py

def user_del(request, uid):
    models.User_Info.objects.filter(id=uid).delete()
    return redirect("/user/list/")

urls.py

path('user/uid=<int:uid>/del',views.user_del)

9.靓号管理

9.1创建靓号表

models.py

class Cool_Number(models.Model):
    #靓号表
    mobile = models.CharField(
        verbose_name='移动号码', 
        validators=[
            MinLengthValidator(11, message='最小长度为11'), # message参数是不符合验证器的返回值
            MaxLengthValidator(11, message='最大长度为11')
        ],
    	unique=True,
    )
    price = models.DecimalField(verbose_name='价格', max_digits=4, decimal_place=2, default=0)
    level_choice = (
        (1, "普通"),
        (2, "稀有"),
        (3, "史诗"),
        (4, "传说"),
        (5, "传家宝"),
    )
    level = models.SmallIntegerField(verbose_name='等级', choices=level_choice, default=1)
    status_choice = (
        (0, "未占用"),
        (1, "占用"),
    )
    status = models.SmallIntegerField(verbose_name='状态', choices=status_choice. default=0)

创建一些初始数据

insert into app01_cool_number(mobile, price, level, status) values
	("16666666666", "99998", "5", "0" ),
	("17766667777", "9997", "4", "1");
	
+----+-------------+-------+-------+--------+
| id | mobile      | price | level | status |
+----+-------------+-------+-------+--------+
|  1 | 13733767633 |   998 |     3 |      0 |
|  2 | 17677676776 |   997 |     3 |      1 |
+----+-------------+-------+-------+--------+

9.2靓号列表

  • URL

  • 函数

    • 获取所有的靓号

    • 通过render结合HTML将靓号展示出来

      id 号码 价格 级别(中文) 状态(中文)
      

9.3新建靓号

  • 列表跳转:/num/add/

  • URL

  • ModelFrom类

    from django import forms
    
    class NumModelForm(forms.ModelForm):
        ...
    
  • 函数

    • 实例化类对象
    • 通过render将对象传入到HTML中
    • 模板的循环展示所有的字段
  • 点击提交

    • 数据校验
    • 保存到数据库
    • 跳转回靓号列表
    from django.core.validators import RegexValidator
    class NumModelForm(forms.ModelForm):
        #使用正则模块规定mobile字段的格式 方法一
        mobile = forms.CharField(
            label='移动号码',
            validators=[RegexValidator(r'^1\d{10}$', '号码格式错误,必须是以1开头的11位数字'), ],
        )
        class Meta:
            model = models.Cool_Number
            fields = '__all__' #所有字段
            # exclude = ['level'] 去除某些字段
    
        def __init__(self, *args, **kwargs):
            super().__init__(*args, **kwargs)
            for name, field in self.fields.items():
                field.widget.attrs = {"class":"form-control", "placeholder":field.label}
    
    def num_add(request):
        """添加靓号"""
    
        if request.method == 'GET':
            form = NumModelForm()
            return render(request, "num_add.html", {'form':form})
        
        form = NumModelForm(data=request.POST)
        if form.is_valid():
            form.save()
            return redirect("/num/list/")
        
        return render(request, "num_add.html", {'form':form})
    

    验证号码格式的另一种方法(钩子函数)

    class NumModelForm(forms.ModelForm):
    ...
    #验证:方式2 钩子方法,这里的函数名必须是<clean_字段名>的格式
    def clean_mobile(self):
        #cleaned_data返回的是用户输入的信息 以 字段名:字段值 组成的字典
        input_mobile = self.cleaned_data['mobile'] 
    
        if len(input_mobile) != 11 or input_mobile[0] !="1":
            #验证不通过
            raise ValidationError("格式错误,移动号码应该是以1开头的11位数字")
        #验证通过,将用户的输入的值返回
        return input_mobile
    

9.4编辑靓号

  • 列表页面:/num/nid=数字/edit

  • URL

  • 函数

    • 根据ID获取当前编辑的对象
    • ModelForm配合,默认显示数据
    • 提交修改
    #移动号码字段在编辑中应该是不可以修改或者隐藏
    class NumEditModelForm(forms.ModelForm):
        mobile = forms.CharField(disabled=True, label="手机号")#方法一,直接修改对应的属性来调整显示的效果为不可修改
        class Meta:
            model = models.Cool_Number
            # fields =['price', 'level', 'status']#方法二,去掉对应字段不显示
            fields = '__all__'
        def __init__(self, *args, **kwargs):
            super().__init__(*args, **kwargs)
            for name, field in self.fields.items():
                field.widget.attrs = {"class":"form-control", "placeholder":field.label}
    
    
    def num_edit(request, nid):
        """编辑靓号"""
        #进入编辑页面,获取原本的内容,填充字段
        row_object = models.Cool_Number.objects.filter(id=nid).first()
        if request.method == 'GET':
            form = NumEditModelForm(instance=row_object)
            return render(request, "num_edit.html",{'form':form})
        #获取编辑的内容并验证
        form = NumEditModelForm(data=request.POST, instance=row_object)#data获取编辑后的数据,instance标记原本数据,保证结果是覆盖而非创建新的靓号
        if form.is_valid():
            form.save()
            return redirect("/num/list/")
        #验证未通过,返回错误信息,错误信息已包含在form中
        return render(request, "num_edit.html",{'form':form})
    

不允许手机号重复

  • 添加:【正则表达式】【手机号不能存在 警告内容:不允许添加已存在的移动号码】

    #判断符合条件的内容是否存在
    exits = models.Cool_Number.objects.filter(mobile="xxx xxxx xxxx").exits()
    #exits
    #>>> True/False
    
    class NumModelForm(forms.ModelForm):
    ...
    def clean_mobile(self):
        input_mobile = self.cleaned_data['mobile'] #cleaned_data返回的是用户输入的信息 以 字段名:字段值 组成的字典
    
        exits = models.Cool_Number.objects.filter(mobile=input_mobile).exists()
        if exits:
            raise ValidationError("不允许添加已存在的移动号码")
    
        if len(input_mobile) != 11 or input_mobile[0] !="1":
            #验证不通过
            raise ValidationError("格式错误,移动号码应该是以1开头的11位数字")
        #验证通过,将用户的输入的值返回
        return input_mobile
    
  • 编辑:【正则表达式】【若要编辑移动号码,除了本号码以外的移动号码不能存在相同的】

    排除自己以外,其他数据是否存在重复的号码
    
    mdoels.Cool_Num.objects.filter(mobile="xxx xxxx xxxx").exclude(id=2)
    
    class NumEditModelForm(forms.ModelForm):
    ...
    def clean_mobile(self):
        # 当前编辑的哪一行ID
        # print(self.instacne.pk)    
    
        input_mobile = self.cleaned_data['mobile'] #cleaned_data返回的是用户输入的信息 以 字段名:字段值 组成的字典
    
        exits = models.Cool_Number.objects.exclude(id=self.instacne.pk).filter(mobile=input_mobile).exists()
        if exits:
            raise ValidationError("不允许添加已存在的移动号码")
    
        if len(input_mobile) != 11 or input_mobile[0] !="1":
            #验证不通过
            raise ValidationError("格式错误,移动号码应该是以1开头的11位数字")
        #验证通过,将用户的输入的值返回
        return input_mobile
    

    复杂的校验在ModelForm的钩子函数中实现

9.5搜索手机号

models.Cool_Num.objects.filter(mobile=xxx xxxxx xxxx, id=yy)

data_dict = {"mobile":"xxx xxxx xxxx", "id":123}
models.Cool_Num.objects.filter(**data_dict)
#选择id大于yy
models.Cool_Num.objects.filter(id__gt=yy)
#选择id大于等于yy
models.Cool_Num.objects.filter(id__gte=yy)
#选择id小于yy
models.Cool_Num.objects.filter(id__lt=yy)
#选择id小于等于yy
models.Cool_Num.objects.filter(id__lte=yy)

#选择mobile以zzz开头
models.Cool_Num.objects.filter(mobile__startwith=zzz)
#选择mobile包含zzz
models.Cool_Num.objects.filter(mobile__contains=zzz)
#选择mobile以zzz结尾
models.Cool_Num.objects.filter(mobile__endwith=zzz)

views.py

def num_list(request):
    data_dict={}
    value = request.GET.get('query_info')
    status = request.GET.get('status')
    if value:
        data_dict['mobile__contains'] = value
        if status:
            data_dict['status'] = status
        num_list = models.Cool_Number.objects.filter(**data_dict)
        return render(request, "num_list.html", {'num_list':num_list, 'search_data':value, 'status':status})
    if status and not value:
        data_dict['status'] = status
        num_list = models.Cool_Number.objects.filter(**data_dict)
        return render(request, "num_list.html", {'num_list':num_list, 'status':status})        


    num_list = models.Cool_Number.objects.all().order_by("-level")# select * from 表 order by level desc
    return render(request, "num_list.html", {'num_list':num_list})

num_list.html(搜索框)

<div style="float:right;width:400px">
    <form method='get'>
        <div class="input-group">
            <input type="text" class="form-control" placeholder="请输入搜索内容" name="query_info" value={{ search_data }}>
            <span class="input-group-btn">
            <button type="submit" class="btn btn-default" >
                <i class="fa fa-search" aria-hidden="true"></i> 搜索
                </button>
            </span>
            <select name="status" class="form-control">
                <option {% if status == None %}selected{% endif %} disabled>筛选</option>
                <option value="0" {% if status == 0 %}selected{% endif %}>未占用</option>
                <option value="1" {% if status == 1 %}selected{% endif %}>已占用</option>
                <option value="">全部</option>
            </select>
        </div><!-- /input-group -->
    </form>
</div>

9.6分页

import random
def generate_phone_number():
    phone_number = '1'  # 以1开头,符合电话号码的常见规则
    for _ in range(10):  # 生成后续10位数字
     phone_number += str(random.randint(0, 9))
    return phone_number

def num_list(request):
    """靓号列表"""
    # 随机生成一些电话号码
    for i in range(300):
        number = generate_phone_number()
        models.Cool_Number.objects.create(mobile=number, price=10, level=1, status=0)
#获取全部数据
queryset = models.Cool_Num.objects.all()

#获取前十条数据 左闭区间,右开区间
queryset = models.Cool_Num.objects.all()[0:10]

#获取符合条件的前十条数据
queryset = models.Cool_Num.objects.filter()[0:10 ]

那么获取的数据就可以分成页

queryset = models.Cool_Num.objects.all()[0:10] # 第一页
queryset = models.Cool_Num.objects.all()[10:20] # 第二页
queryset = models.Cool_Num.objects.all()[20:30] # 第三页
queryset = models.Cool_Num.objects.all()[30:40] # 第四页

获取数据库中符合条件的数据条数

data = models.Cool_Num.objects.all().count()
data = models.Cool_Num.objects.filter(id=1).count()

python内置函数divmod可以做到获取除法的商和余数

divmod(91, 10)
# >>> (9, 1) #(商, 余数)

1.直接写

views.py

def num_list(request):
    """靓号列表"""

    # 随机生成一些电话号码
    # for i in range(300):
    #     number = generate_phone_number()
    #     models.Cool_Number.objects.create(mobile=number, price=10, level=1, status=0)

    #定义分页函数,返回一个包含当前页面显示内容和分页页码的html字符的字典
    
    def DivPage(request, data_dict={}):
        # 获取当前的页面,没有默认是1   
        page = int(request.GET.get('page', 1))
        # 获取当前要显示的数据条数 [start: end]
        pagesize = 10
        start = (page-1)*pagesize
        end = page*pagesize
        # 获取全部数据
        if data_dict == {}:
            # select * from 表 order by level desc
            num_list = models.Cool_Number.objects.all().order_by("-level")[start: end]
        else:
            num_list = models.Cool_Number.objects.filter(**data_dict).order_by("-level")[start: end]
        # 获取数据总条数
        total_count = models.Cool_Number.objects.filter(**data_dict).order_by("-level").count()
        total_page, div = divmod(total_count, pagesize)
        # 获取页码条数
        if div:
            total_page += 1
        # 仅显示当前页的前5页和后5页
        # 总页数小于等于11时候无需扩展页面
        if total_page <= 11:
            start_page = 1
            end_page = total_page
        else:
            if page <= 6:
                start_page = 1
                end_page = 13
            if page > 6 and page < total_page:
                start_page = page - 5
                end_page = page + 5  
            if page >= total_page - 5:
                start_page = total_page - 12
                end_page = total_page      

        # 生成全部的页码
        page_str_list = []        
        if data_dict == {}:
            #添加上一页
            if page != 1 :
                prev = '<li><a href="?page={}" aria-label="Previous"><span aria-hidden="true">«</span></a></li>'.format(page-1)
                page_str_list.append(prev)
            else:
                prev = '<li class="disabled"><a href="?page=1" aria-label="Previous"><span aria-hidden="true">«</span></a></li>'
                page_str_list.append(prev)
            # 让第一页常驻
            ele1 = '<li><a href="?page=01">01</a></li>'
            ele_total = '<li><a href="?page={}">{}</a></li>'.format(total_page, total_page)
            ele_omit = '<li><a>...</a></li>'
            if page > 6 and total_page > 11:
                page_str_list.append(ele1)
                page_str_list.append(ele_omit)
            # 生成中间部分的页码
            for i in range(start_page, end_page+1):
                if i == page:
                    ele = '<li class="active"><a href="?page={}">{}</a></li>'.format(i, str(i).zfill(2))
                else:    
                    ele = '<li><a href="?page={}">{}</a></li>'.format(i, str(i).zfill(2))
                page_str_list.append(ele)
            # 让最后一页常驻
            if page < total_page - 5 and total_page > 11:
                page_str_list.append(ele_omit)
                page_str_list.append(ele_total)
            # 添加下一页
            if page != total_page:
                next = '<li><a href="?page={}" aria-label="Next"><span aria-hidden="true">»</span></a></li>'.format(page+1)
                page_str_list.append(next)
            else:
                next = '<li class="disabled"><a href="?page={}" aria-label="Next"><span aria-hidden="true">»</span></a></li>'.format(total_page)
                page_str_list.append(next)

            search_string = """            
            <li>
                <form style="float:left;margin-left:-1px" method="get">
                    <input type="text" name="page" style="position:relative;float:left;display:inline-block;width:80px;border-radius:0;" class="form-control" value={}>
                    <button style="border-radius:0" class="btn btn-default" type="submit">跳转</button>
                </form>
            </li>
            """.format(page)
            page_str_list.append(search_string)
            page_string = mark_safe("".join(page_str_list))
            return  {'num_list':num_list, 'page_string':page_string}      

        

    data_dict={}
    value = request.GET.get('query_info')
    status = request.GET.get('status')

    if value:
        data_dict['mobile__contains'] = value
        if status:
            data_dict['status'] = status
        reback_form = DivPage(request, data_dict)
        num_list = models.Cool_Number.objects.filter(**data_dict).order_by("-level") 


        return render(request, "num_list.html", {'num_list':num_list, 'search_data':value, 'status':status})
    if status and not value:
        data_dict['status'] = status
        num_list = models.Cool_Number.objects.filter(**data_dict).order_by("-level")
        return render(request, "num_list.html", {'num_list':num_list, 'search_data':value, 'status':status})

    
    
    reback_form = DivPage(request)
    return render(request, "num_list.html", reback_form)

num_list.html

{% extends "layout.html" %}

{% block title %}
靓号管理
{% endblock %}
{% block css %}
.unoccupied {
    background-color: #0066CC;
    color: #FFFFFF;
  }
{% endblock  %}
{% block 靓号管理 %}class="active"{% endblock %}
{% block content %}
    <div class="container">
        <div style="margin-bottom:10px;" class="clearfix">

            <a class="btn btn-success" href="/num/add/"><i class="fa fa-plus-circle" aria-hidden="true"></i> 新建靓号</a>

            <div style="float:right;width:400px">
                <form method='get'>
                    <div class="input-group">
                        <input type="text" class="form-control" placeholder="请输入搜索内容" name="query_info" value={{ search_data }}>
                        <span class="input-group-btn">
                        <button type="submit" class="btn btn-default" ><i class="fa fa-search" aria-hidden="true"></i> 搜索</button>
                        </span>
                        <select name="status" class="form-control">
                            <option {% if status == None %}selected{% endif %} disabled>筛选</option>
                            <option value="0" {% if status == 0 %}selected{% endif %}>未占用</option>
                            <option value="1" {% if status == 1 %}selected{% endif %}>已占用</option>
                            <option value="">全部</option>
                        </select>
                    </div><!-- /input-group -->
                </form>
             </div>

        </div>

        <div class="panel panel-default">

            <div class="panel-heading">
                <h3 class="panel-title"><i class="fa fa-list-ul"></i> 靓号列表</h3>
            </div>

            <table class="table table-hover table-bordered" style="padding:0 20px 0 20px;">
                <thead>
                    <tr>
                        <th>ID</th>
                        <th>移动号码</th>
                        <th>价格</th>
                        <th>等级</th>
                        <th>状态</th>
                        <th>操作</th>
                    </tr>
                </thead>
                <tbody>
                    {% for item in num_list %}
                    <tr>
                        <td style="width:100px;">{{ item.id }}</td>
                        <td>{{ item.mobile}}</td>
                        <td>{{ item.price }}</td>
                        <td>{{ item.get_level_display }}</td>
                        <td 
                        {% if item.status == 0 %}
                            class="unoccupied"
                        {% endif %}                     
                        >{{ item.get_status_display }}</td>
                        <td>
                            <a class="btn btn-primary btn-xs" href="/num/nid={{ item.id }}/edit/"><i class="fa fa-edit" aria-hidden="true"></i> 编辑</a>
                            <span> </span>
                            <a class="btn btn-danger btn-xs" href="/num/nid={{ item.id }}/del/"><i class="fa fa-trash" aria-hidden="true"></i> 删除</a>
                        </td>
                    </tr>
                    {% endfor %}
                </tbody>
            </table>

        </div><!--panel-->

        <ul class="pagination">
            {{ page_string }}
        </ul>
    </div><!--container-->
{% endblock  %}

2.将分页写成一个通用的类

在app01中创建utils目录,创建一个pagination.py文件

pagination.py

"""
自定义的分页组件
"""

from django.utils.safestring import mark_safe
import copy


class Pagination(object):
    def __init__(
        self,
        request,
        queryset,
        page_size=10,
        page_param="page",  # page_param是页码的名称,默认为page
    ):
        # 获取原本的包含get条件的url
        origin_url = copy.deepcopy(request.GET)
        origin_url._mutable = True
        self.origin_url = origin_url
        # 获取当前页
        page = request.GET.get(page_param, "1")
        if page.isdecimal():
            page = int(page)
        else:
            page = 1
        # 页码的名称
        self.page_param = page_param
        # 当前页码
        self.page = page
        # 页内容量
        self.page_size = page_size
        # 开始第start条数据
        self.start = (page - 1) * page_size
        # 结束第end条数据
        self.end = page * page_size
        # 获取页面内容
        self.page_queryset = queryset[self.start : self.end]
        # 计算总页数
        total_page, div = divmod(queryset.count(), page_size)
        if div:
            total_page += 1
        self.total_page_count = total_page

    def html(self):
        # 生成全部的页码
        if self.total_page_count <= 11:
            start_page = 1
            end_page = self.total_page_count
        else:
            if self.page <= 6:
                start_page = 1
                end_page = 13
            if self.page > 6 and self.page < self.total_page_count:
                start_page = self.page - 5
                end_page = self.page + 5
            if self.page >= self.total_page_count - 5:
                start_page = self.total_page_count - 12
                end_page = self.total_page_count
        # 设置用于添加html元素的列表
        page_str_list = []
        # 拼接含有页码的url  self.origin_url.setlist(self.page_param, [需要跳转的页面])
        # 添加上一页
        if self.page != 1:
            self.origin_url.setlist(self.page_param, [self.page - 1])
            prev = '<li><a href="?{}" aria-label="Previous"><span aria-hidden="true">«</span></a></li>'.format(
                self.origin_url.urlencode()
            )
            page_str_list.append(prev)
        else:
            self.origin_url.setlist(self.page_param, [1])
            prev = '<li class="disabled"><a href="?{}" aria-label="Previous"><span aria-hidden="true">«</span></a></li>'.format(
                self.origin_url.urlencode()
            )
            page_str_list.append(prev)
        # 让第一页常驻
        # 设置第一页标签
        self.origin_url.setlist(self.page_param, [1])
        ele1 = '<li><a href="?{}">01</a></li>'.format(self.origin_url.urlencode())
        ele_omit = "<li><a>...</a></li>"
        if self.page > 6 and self.total_page_count > 11:
            page_str_list.append(ele1)
            page_str_list.append(ele_omit)
        # 生成中间部分的页码
        for i in range(start_page, end_page + 1):
            if i == self.page:
                self.origin_url.setlist(self.page_param, [i])
                ele = '<li class="active"><a href="?{}">{}</a></li>'.format(
                    self.origin_url.urlencode(), str(i).zfill(2)
                )
            else:
                self.origin_url.setlist(self.page_param, [i])
                ele = '<li><a href="?{}">{}</a></li>'.format(
                    self.origin_url.urlencode(), str(i).zfill(2)
                )
            page_str_list.append(ele)
        # 让最后一页常驻
        # 设置最后一页标签
        self.origin_url.setlist(self.page_param, [self.total_page_count])
        ele_total = '<li><a href="?{}">{}</a></li>'.format(
            self.origin_url.urlencode(), self.total_page_count
        )
        if self.page < self.total_page_count - 5 and self.total_page_count > 11:
            page_str_list.append(ele_omit)
            page_str_list.append(ele_total)
        # 添加下一页
        if self.page != self.total_page_count:
            self.origin_url.setlist(self.page_param, [self.page + 1])
            next = '<li><a href="?{}" aria-label="Next"><span aria-hidden="true">»</span></a></li>'.format(
                self.origin_url.urlencode()
            )
        else:
            self.origin_url.setlist(self.page_param, [self.total_page_count])
            next = '<li class="disabled"><a href="?{}" aria-label="Next"><span aria-hidden="true">»</span></a></li>'.format(
                self.origin_url.urlencode()
            )
        page_str_list.append(next)

        search_string = """            
        <li>
            <form style="float:left;margin-left:-1px" method="get">
                <input type="text" name="page" style="position:relative;float:left;display:inline-block;width:80px;border-radius:0;" class="form-control" value={}>
                <button style="border-radius:0" class="btn btn-default" type="submit">跳转</button>
            </form>
        </li>
        """.format(
            self.page
        )
        page_str_list.append(search_string)
        page_string = mark_safe("".join(page_str_list))
        #返回一个被包装好的HTML标签,被调用时直接将这一部分添加到模板(<ul>{{ page_string }}</ul>)当中即可
        return page_string

views.py

def num_list(request):
    """靓号列表"""
    """    
    # request.GET.urlencode()#原本的搜索条件,但是不可以修改
    import copy
    # 对其进行深拷贝
    query_dict = copy.deepcopy(request.GET)
    # 修改属性让其可以修改
    query_dict._mutable = True
    # 通过.setlist('key', ['value'])修改url
    """

    data_dict = {}
    value = request.GET.get("query_info")
    status = request.GET.get("status")

    if value:
        data_dict["mobile__contains"] = value
        if status:
            data_dict["status"] = status
        queryset = models.Cool_Number.objects.filter(**data_dict).order_by("-level")
        page_object = Pagination(request, queryset)
        return render(
            request,
            "num_list.html",
            {
                "page": page_object.page,
                "num_list": page_object.page_queryset,
                "page_string": page_object.html(),
                "search_data": value,
                "status": status,
            },
        )
    if status and not value:
        data_dict["status"] = status
        num_list = models.Cool_Number.objects.filter(**data_dict).order_by("-level")
        return render(
            request,
            "num_list.html",
            {"num_list": num_list, "search_data": value, "status": status},
        )
    # 获取全部的数据
    queryset = models.Cool_Number.objects.filter(**data_dict).order_by("-level")
    page_object = Pagination(request, queryset)
    return render(
        request,
        "num_list.html",
        {"num_list": page_object.page_queryset, "page_string": page_object.html()},

num_list.py

{% extends "layout.html" %}

{% block title %}
靓号管理
{% endblock %}
{% block css %}
.unoccupied {
    background-color: #0066CC;
    color: #FFFFFF;
  }
{% endblock  %}
{% block 靓号管理 %}class="active"{% endblock %}
{% block content %}
    <div class="container">
        <div style="margin-bottom:10px;" class="clearfix">

            <a class="btn btn-success" href="/num/add/"><i class="fa fa-plus-circle" aria-hidden="true"></i> 新建靓号</a>

            <div style="float:right;width:400px">
                <form method='get'>
                    <div class="input-group">
                        <input type="text" class="form-control" placeholder="请输入搜索内容" name="query_info" value={{ search_data }}>
                        <span class="input-group-btn">
                        <button type="submit" class="btn btn-default" ><i class="fa fa-search" aria-hidden="true"></i> 搜索</button>
                        </span>
                        <select name="status" class="form-control">
                            <option {% if status == None %}selected{% endif %} disabled>筛选</option>
                            <option value="0" {% if status == 0 %}selected{% endif %}>未占用</option>
                            <option value="1" {% if status == 1 %}selected{% endif %}>已占用</option>
                            <option value="">全部</option>
                        </select>
                    </div><!-- /input-group -->
                </form>
             </div>

        </div>

        <div class="panel panel-default">

            <div class="panel-heading">
                <h3 class="panel-title"><i class="fa fa-list-ul"></i> 靓号列表</h3>
            </div>

            <table class="table table-hover table-bordered" style="padding:0 20px 0 20px;">
                <thead>
                    <tr>
                        <th>ID</th>
                        <th>移动号码</th>
                        <th>价格</th>
                        <th>等级</th>
                        <th>状态</th>
                        <th>操作</th>
                    </tr>
                </thead>
                <tbody>
                    {% for item in num_list %}
                    <tr>
                        <td style="width:100px;">{{ item.id }}</td>
                        <td>{{ item.mobile}}</td>
                        <td>{{ item.price }}</td>
                        <td>{{ item.get_level_display }}</td>
                        <td 
                        {% if item.status == 0 %}
                            class="unoccupied"
                        {% endif %}                     
                        >{{ item.get_status_display }}</td>
                        <td>
                            <a class="btn btn-primary btn-xs" href="/num/nid={{ item.id }}/edit/"><i class="fa fa-edit" aria-hidden="true"></i> 编辑</a>
                            <span> </span>
                            <a class="btn btn-danger btn-xs" href="/num/nid={{ item.id }}/del/"><i class="fa fa-trash" aria-hidden="true"></i> 删除</a>
                        </td>
                    </tr>
                    {% endfor %}
                </tbody>
            </table>

        </div><!--panel-->

        <ul class="pagination">
            {{ page_string }}
        </ul>
    </div><!--container-->
{% endblock  %}

10.时间插件

user_add.html

{% extends "layout.html" %}
{%load static%}

{% block title %}
添加用户
{% endblock %}
{% block css %}
    <link rel="stylesheet" href="{% static 'plugins/bootstrap-datepicker/css/bootstrap-datetimepicker.min.css' %}">
{% endblock  %}
{% block 用户管理 %}class="active"{% endblock %}
{% block content %}
    <div class="container">
        <div class="panel panel-default">
            <div class="panel-heading">
            <h3 class="panel-title">新建用户</h3>
            </div>

            <div class="panel-body">
                <form action="/user/add/" method="post" novalidate>{% comment %}novalidate 关闭浏览器自带的校验{% endcomment %}
                    {% csrf_token %}
                    
                    {% for field in form %}
                    <div class="form-group">
                        <label>{{ field.label }}</label>
                        {{ field }} {% comment %}此处的label即为定义属性时候的verbose_name值{% endcomment %}
                        <span style="color:red;">{{ field.errors.0 }}</span>
                    </div>
                    {% endfor %}
                    
                    <button type="submit" class="btn btn-primary"><i class="fa fa-save" aria-hidden="true"></i> 保 存</button>
                </form>
            </div>
            
        </div>
    </div>
{% endblock  %}
{% block js %}
    <script src="{% static 'plugins/bootstrap-datepicker/js/bootstrap-datetimepicker.js' %}"></script>
    <script src="{% static 'plugins/bootstrap-datepicker/locales/bootstrap-datetimepicker.zh-CN.js' %}"></script>
    <script>
        $(function () {
            $('#id_create_time').datetimepicker({
                format:'yyyy-mm-dd',
                startDate:'0',
                language:"zh-CN",
                autoclose:true,
            });
        })
    </script>
{% endblock  %}

注意:

  • 载入datetimepicker插件
  • js代码中包含对应input框的id,如果是用django生成的,默认是id_ + 对应的字段名

11.ModelForm和Bootstrap

  • ModelForm可以帮我们生成HTML标签

    class UserModelForm(forms.ModelForm):
        name = forms.CharField(min_length=3, label='用户名')
        class Meta:
            model = models.UserInfo
            fields = ['name', 'password']
    #调用这个类
    form = UserModelForm
    
    {{ form.name }}			普通的input框
    {{ form.password }}		普通的input框
    
  • 定义插件

    class UserModelForm(forms.ModelForm):
        name = forms.CharField(min_length=3, label='用户名')
        class Meta:
            model = models.UserInfo
            fields = ['name', 'password']
            widgets = {
            "name":forms.TextInput(attrs={"class":"form-control"})
            "password": forms.PasswordInput(attrs={"class":"form-control"}),
            }
    
    class UserModelForm(forms.ModelForm):
        name = forms.CharField(
            min_length=3, label='用户名', widget=forms.TextInput(attrs={"class":"form-control"})
        )
        class Meta:
            model = models.UserInfo
            fields = ['name', 'password']
    
    {{ form.name }}			Bootstrap的input框
    {{ form.password }}		Bootstrap的input框
    
  • 在__init__中重新定义

    class UserModelForm(forms.ModelForm):
        name = forms.CharField(min_length=3, label='用户名')
        class Meta:
            model = models.UserInfo
            fields = ['name', 'password']
        def __init__(self, *args, **kwargs):
            super().__init__(*args, **kwargs)
            #循环ModelForm中的所有字段,给每个字段的插件设置
            for name, field in self.fields.items():
                field.widget.attrs = {
                    "class":"form-control",
                    "placeholder":field.label
                }
    

    改进

    class UserModelForm(forms.ModelForm):
        name = forms.CharField(min_length=3, label='用户名')
        class Meta:
            model = models.UserInfo
            fields = ['name', 'password']
        def __init__(self, *args, **kwargs):
            super().__init__(*args, **kwargs)
            
            #循环ModelForm中的所有字段,给每个字段的插件设置
            for name, field in self.fields.items():
                
                #字段中有属性,保留原来的属性(向字典中添加值),没有属性添加默认属性(传递一个字典)
                if field.widget.attrs:
                    field.widget.attrs["class"] = "form-control",
                    field.widget.attrs["placeholder"] = field.label
                else:
                    field.widget.attrs = {
                        "class":"form-control",
                        "placeholder":field.label
                    }
    

    但是创建UserEditModelForm的时候还要再写一遍,怎么办?

  • 自定义类

    class BootStrapModelForm(forms.ModelForm):
        def __init__(self, *args, **kwargs):
            super().__init__(*args, **kwargs)   
            #循环ModelForm中的所有字段,给每个字段的插件设置
            for name, field in self.fields.items():
                #字段中有属性,保留原来的属性(向字典中添加值),没有属性添加默认属性(传递一个字典)
                if field.widget.attrs:
                    field.widget.attrs["class"] = "form-control",
                    field.widget.attrs["placeholder"] = field.label
                else:
                    field.widget.attrs = {
                        "class":"form-control",
                        "placeholder":field.label
                    }
    

    继承类

    class UserEditModelForm(BootstrapModelForm):
        class Meta:
            model = models.UserInfo
            fields = ["name", "password", "age"]
    

    在UserInfoModelForm中继承

    view.py

    # 继承BootStrapModelForm
    class UserModelForm(BootStrapModelForm):
        name = forms.CharField(
            min_length=3, label="用户名"
        )  # 设定一个最小长度用于校验值合法
    
        # password = forms.CharField(validators=正则表达式, label="密码")
        class Meta:
            model = models.User_Info
            fields = [
                "name",
                "password",
                "age",
                "account",
                "create_time",
                "age",
                "gender",
                "depart",
            ]  # 可以通过fields = "__all__"
    
            # 通过widgets为某些特定的类型生成指定的输入框模式(不推荐)
            # widgets = {
            #     "password": forms.PasswordInput(),
            #     "account": forms.TextInput(),
            # }
    
  • 也可以把所有的modelform类放到utils文件夹中的form.py文件中

    modelform.py

    from django import forms
    from app01 import models
    from django.core.validators import RegexValidator, ValidationError
    from app01.utils.bootstrap_model_form import BootStrapModelForm
    
    # +--------------------------------------------------------------------------------------------------------------------------------------------+#
    """# class UserModelForm(forms.ModelForm):
    #     name = forms.CharField(
    #         min_length=3, label="用户名"
    #     )  # 设定一个最小长度用于校验值合法
    
    #     # password = forms.CharField(validators=正则表达式, label="密码")
    #     class Meta:
    #         model = models.User_Info
    #         fields = [
    #             "name",
    #             "password",
    #             "age",
    #             "account",
    #             "create_time",
    #             "age",
    #             "gender",
    #             "depart",
    #         ]  # 可以通过fields = "__all__"
    
    #         # 通过widgets为某些特定的类型生成指定的输入框模式(不推荐)
    #         widgets = {
    #             "password": forms.PasswordInput(),
    #             "account": forms.TextInput(),
    #             "create_time": forms.DateInput(),
    #         }
    #         #    快速为所有的输入框添加样式
    #     def __init__(self, *args, **kwargs):
    #         super().__init__(*args, **kwargs)
    
    #         # #重新部分input框的type --失效,为何?
    #         # self.fields['password'].widget.attrs.update({'type': 'password'})
    #         # self.fields['account'].widget.attrs.update({'type': 'text'})
    #         # self.fields['create_time'].widget.attrs.update({'type': 'date'})
    
    #         # 循环找到所有的插件
    #         # print(self.fields)
    #         for name, field in self.fields.items():
    #             # if name == 'password':
    #             #     field.widget.attrs = {"class":"form-control"}
    #             #     continue
    #             field.widget.attrs = {"class": "form-control", "placeholder": field.label}
    
    
    # 继承BootStrapModelForm"""
    
    
    class UserModelForm(BootStrapModelForm):
        name = forms.CharField(
            min_length=3, label="用户名"
        )  # 设定一个最小长度用于校验值合法
    
        # password = forms.CharField(validators=正则表达式, label="密码")
        class Meta:
            model = models.User_Info
            fields = [
                "name",
                "password",
                "age",
                "account",
                "create_time",
                "age",
                "gender",
                "depart",
            ]  # 可以通过fields = "__all__"
    
            # 通过widgets为某些特定的类型生成指定的输入框模式(不推荐)
            widgets = {
                "password": forms.PasswordInput(),
                "account": forms.TextInput(),
            }
    
    
    class NumModelForm(forms.ModelForm):
        # #验证方式1
        # mobile = forms.CharField(
        #     label='移动号码',
        #     validators=[RegexValidator(r'^1\d{10}$', '号码格式错误,必须是以1开头的11位数字'), ],
        # )
    
        class Meta:
            model = models.Cool_Number
            fields = "__all__"  # 所有字段
            # exclude = ['level'] 去除某些字段
    
        def __init__(self, *args, **kwargs):
            super().__init__(*args, **kwargs)
            for name, field in self.fields.items():
                field.widget.attrs = {"class": "form-control", "placeholder": field.label}
    
        # 验证:方式2 钩子方法,这里的函数名必须是<clean_字段名>的格式
        def clean_mobile(self):
            input_mobile = self.cleaned_data[
                "mobile"
            ]  # cleaned_data返回的是用户输入的信息 以 字段名:字段值 组成的字典
    
            exits = models.Cool_Number.objects.filter(mobile=input_mobile).exists()
            if exits:
                raise ValidationError("不允许添加已存在的移动号码")
    
            if len(input_mobile) != 11 or input_mobile[0] != "1":
                # 验证不通过
                raise ValidationError("格式错误,移动号码应该是以1开头的11位数字")
            # 验证通过,将用户的输入的值返回
            return input_mobile
    
    
    # 移动号码字段在编辑中应该是不可以修改或者隐藏
    class NumEditModelForm(forms.ModelForm):
        mobile = forms.CharField(
            disabled=True, label="手机号"
        )  # 方法一,直接修改对应的属性来调整显示的效果为不可修改
    
        class Meta:
            model = models.Cool_Number
            # fields =['price', 'level', 'status']#方法二,去掉对应字段不显示
            fields = "__all__"
    
        def __init__(self, *args, **kwargs):
            super().__init__(*args, **kwargs)
            for name, field in self.fields.items():
                field.widget.attrs = {"class": "form-control", "placeholder": field.label}
    
        def clean_mobile(self):
            # 当前编辑的哪一行ID
            # print(self.instacne.pk)
    
            input_mobile = self.cleaned_data[
                "mobile"
            ]  # cleaned_data返回的是用户输入的信息 以 字段名:字段值 组成的字典
    
            exits = (
                models.Cool_Number.objects.exclude(id=self.instance.pk)
                .filter(mobile=input_mobile)
                .exists()
            )
            if exits:
                raise ValidationError("不允许添加已存在的移动号码")
    
            if len(input_mobile) != 11 or input_mobile[0] != "1":
                # 验证不通过
                raise ValidationError("格式错误,移动号码应该是以1开头的11位数字")
            # 验证通过,将用户的输入的值返回
            return input_mobile
    

    同理,可以将views.py中的函数放到一个新的文件夹views中,然后分别为每种类型新建py文件,但要记住修改urls.py中的引入

    from app01.views import depart, user, pretty 
    
    urlpatterns = {
    	path('depat/list/', depart.depart_list)
    }
    

12.管理员

12.1管理员表

view -> admin.py

def admin_list(request):

    # 搜索
    data_dict = {}
    value = request.GET.get("query_info")
    if value:
        data_dict["username__contains"] = value
        queryset = models.Admin.objects.filter(**data_dict)
        page_object = Pagination(request, queryset)
        return render(
            request,
            "admin_list.html",
            {
                "queryset": page_object.page_queryset,
                "page_string": page_object.html(),
                "search_data": value,
            },
        )

    # 分页
    queryset = models.Admin.objects.all()
    page_object = Pagination(request, queryset)
    return render(
        request,
        "admin_list.html",
        {"queryset": page_object.page_queryset, "page_string": page_object.html()},
    )

admin_list.html

{% extends "layout.html" %}

{% block title %}
管理员账户
{% endblock %}
{% block css %}
<style>

</style>
{% endblock  %}
{% block 管理员账户 %}class="active"{% endblock %}
{% block content %}
    <div class="container">
        <div style="margin-bottom:10px;">
            <a class="btn btn-success" href="/admin/add/"><i class="fa fa-plus-circle" aria-hidden="true"></i> 新建管理员</a>
            <div style="float:right;width:400px">
                <form method='get'>
                    <div class="input-group">
                        <input type="text" class="form-control" placeholder="请输入搜索内容" name="query_info" value={{ search_data }}>
                        <span class="input-group-btn">
                        <button type="submit" class="btn btn-default" ><i class="fa fa-search" aria-hidden="true"></i> 搜索</button>
                        </span>
                    </div><!-- /input-group -->
                </form>
             </div>
        </div>
        
        <div class="panel panel-default">

            <div class="panel-heading">
                <h3 class="panel-title"><i class="fa fa-list-ul"></i> 管理员列表</h3>
            </div>

            <table class="table table-hover table-bordered" style="padding:0 20px 0 20px;">
                <thead>
                    <tr>
                        <th>ID</th>
                        <th>用户名</th>
                        <th>密码</th>
                    </tr>
                </thead>
                <tbody>
                    {% for item in queryset %}
                    <tr>
                        <td class="col-xs-3">{{ item.id }}</td>
                        <td class="col-xs-3">{{ item.username }}</td>
                        <td class="col-xs-3">********</td>
                        <td>
                            <a class="btn btn-primary btn-xs" href="#"><i class="fa fa-edit" aria-hidden="true"></i> 编辑</a>
                            <span> </span>
                            <a class="btn btn-danger btn-xs" href="#"><i class="fa fa-trash" aria-hidden="true"></i> 删除</a>
                        </td>
                    </tr>
                    {% endfor %}
                </tbody>
            </table>
        </div>
        <ul class="pagination">
            {{ page_string }}
        </ul>        
    </div>

{% endblock  %}
{% block js %}
<script>

</script>
{% endblock  %}

12.2添加管理员

1.基础代码

view -> admin_add.py

def admin_add(request):
    if request.method == "GET":
        form = modelform.AdminModelForm()
        return render(request, "admin_add.html", {"form": form})
    form = modelform.AdminModelForm(data=request.POST)
    if form.is_valid():
        form.save()
        return redirect("/admin/list/")
    return render(request, "admin_add.html", {"form": form})

utils -> modelform.py

class AdminModelForm(BootStrapModelForm):
    # 限定username的最小字符数
    username = forms.CharField(min_length=3, label="用户名")

    class Meta:
        model = models.Admin
        fields = "__all__"

创建一个通用的add.html模板,之后使用含有xxx_add.html的模板只需要使用这个模板即可

templ_add.html

{% extends "layout.html" %}
{%load static%}

{% block title %}
{{ title_templ }}
{% endblock %}
{% block content %}
    <div class="container">
        <div class="panel panel-default">
            <div class="panel-heading">
            <h3 class="panel-title">{{ panel_title_templ}}</h3>
            </div>

            <div class="panel-body">
                <form action="/admin/add/" method="post" novalidate>
                    {% csrf_token %}
                    
                    {% for field in form %}
                    <div class="form-group">
                        <label>{{ field.label }}</label>
                        {{ field }}
                        <span style="color:red;">{{ field.errors.0 }}</span>
                    </div>
                    {% endfor %}
                    
                    <button type="submit" class="btn btn-primary"><i class="fa fa-save" aria-hidden="true"></i> 保 存</button>
                </form>
            </div>
            
        </div>
    </div>
{% endblock  %}

对应的admin_add.py

def admin_add(request):
    templ = {
        "titel_templ": "添加管理员",
        "panel_title_templ": "添加管理员",
    }
    if request.method == "GET":
        form = modelform.AdminModelForm()

        return render(request, "templ_add.html", {**templ, "form": form})
    form = modelform.AdminModelForm(data=request.POST)
    if form.is_valid():
        form.save()
        return redirect("/admin/list/")
    return render(request, "templ_add.html", {**templ, "form": form})

2.设置确认密码字段

utils -> modelform.py

class AdminModelForm(BootStrapModelForm):
    username = forms.CharField(min_length=3, label="用户名")
    confirm_password = forms.CharField(
        label="确认密码",
        widget=forms.PasswordInput(
            render_value=True
        ),  # render_value表示在提交raise错误后是否保持这个值
    )

    class Meta:
        model = models.Admin
        fields = ["username", "password", "confirm_password"]
        widgets = {"password": forms.PasswordInput(render_value=True)}

    # 通过钩子函数比对密码和确认密码
    def clean_confirm_password(self):
        pwd = self.cleaned_data.get("password")
        confirm = self.cleaned_data.get("confirm_password")
        # 确认密码与密码不一致
        if confirm != pwd:
            raise ValidationError("确认密码与密码不一致!")
        # 确认密码与密码一致,返回一个会保存到confirm_password字段的值
        return confirm

3.数据库密码加密

使用MD5加密,先创建一个加密函数

utils -> encrypt.py

from django.conf import settings
import hashlib


def md5(data_string):
    salt = settings.SECRET_KEY
    obj = hashlib.md5(salt.encode("utf-8"))
    obj.update(data_string.encode("utf-8"))
    return obj.hexdigest()

在modelform中调用这个加密函数

    # 通过钩子函数对密码进行加密
    def clean_password(self):
        pwd = self.cleaned_data.get("password")
        return md5(pwd)

    # 通过钩子函数比对密码和确认密码
    def clean_confirm_password(self):
        pwd = self.cleaned_data.get("password")
        confirm = md5(self.cleaned_data.get("confirm_password"))
        # 确认密码与密码不一致
        if confirm != pwd:
            raise ValidationError("确认密码与密码不一致!")
        # 确认密码与密码一致,返回一个会保存到confirm_password字段的值
        return confirm

13.用户登录 instance&render_value

什么是session和cookie?

  • http请求和https请求 - 无状态 & 短链接(一次请求一次响应然后断开链接)

网站可以返回响应体和响应头,响应体即html字符串,响应头 - Cookie(本质上是保存在浏览器的键值对)

基于Cookie,网站可以给用户发放凭证,用户可以使用凭证网站可以实现有记忆性的响应,即用户在发放请求时候浏览器会自动响应

同时网站会存储凭证和用户信息的映射关系,当用户提供凭证时候用于比对(由网站存储的内容叫做Session -> 一个抽象的概念,具体可以存储在数据库文件等等)

render_value和instance

render_value用在input框中发生错误后是否保留原数据

instance用于:

​ 1.在进入页面时候在input框中提前添加

首先写出对应的login.py和login.html文件

views -> login.py

def login(request):
    """登录"""
    if request.method == "GET":
        form = LoginForm()
        return render(request, "login.html", {"form": form})
    form = LoginForm(data=request.POST)
    if form.is_valid():
        # 验证成功,获取到的用户名和密码
        # print(form.cleaned_data)
        # 去数据库校验数据是否正确
        # admin_object = models.Admin.objects.filter(
        #     username=form.cleaned_data["username"],
        #     password=form.cleaned_data["password"],
        # ).first() 过于繁琐
        admin_object = models.Admin.objects.filter(**form.cleaned_data).first()
        """
        clean_data是一个包含验证后的数据的字典,
          如果用户名密码正确,admin_object是一个admin对象,
          如果不正确,admin_object=None
        """
        if admin_object:
            # 账号密码正确
            return redirect("/admin/list/")
        # 添加错误 .add_error("字段名", "错误信息")
        form.add_error("password", "用户名或密码错误")
        return render(request, "login.html", {"form": form})

    return render(request, "login.html", {"form": form})

login.html

{%load static%}

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>登录</title>
    <link rel="stylesheet" href="{% static 'plugins/bootstrap-3.4.1/css/bootstrap.css' %}">
    <link rel="stylesheet" href="{% static 'plugins/font-awesome-4.7.0/css/font-awesome.css' %}">
    <style>
        .account{
            width:500px;
            border: 1px solid #dddddd;
            height:350px;
            
            padding:30px;
            margin-left:auto;
            margin-right:auto;
            margin-top:100px;
    
            border-radius: 8px;
            box-shadow: 2px 2px 20px #aaa;
        }
        .account h1{
            text-align:center;
        }
        .navbar{
            border-radius:0;
        }
        .login-form{
            display:block-inline;
            hieght:500px;


        }
    </style>
</head>
<body>
    <div class="container account">
        <h1>用户登录</h1>

        <form method="post" novalidate>
            {% csrf_token %}
            <div class="form-group">
                <label >用户名</label>
                {{ form.username }}
                <span style="color:red;">{{ form.username.errors.0 }}</span>
            </div>
            <div class="form-group">
                <label >密码</label>
                {{ form.password }}
                <span style="color:red;">{{ form.password.errors.0 }}</span>
            </div>
            <button type="submit" class="btn btn-primary">登 录</button>
        </form>
    </div>
    <script src="{% static 'js/jquery-3.7.1.min.js' %}"></script>  
    <script src="{% static 'plugins/bootstrap-3.4.1/js/bootstrap.js' %}"></script>
</body>
</html>

添加Session

        admin_object = models.Admin.objects.filter(**form.cleaned_data).first()
        """
        clean_data是一个包含验证后的数据的字典,
          如果用户名密码正确,admin_object是一个admin对象,
          如果不正确,admin_object=None
        """
        if admin_object:
            # 账号密码正确
            # 网站生成一个随机字符串*Cookie*, 再写入到Session中
            # request.session["info"] = "xxx"
            # # 在Session中存储"[cookie | info":"xxx" ]

            # 在Session中以字典形式存储cookie的对应内容 - info= {id:1, name:admin}
            request.session["info"] = {
                "id": admin_object.id,
                "name": admin_object.username,
            }
            return redirect("/admin/list/")

查看网页获取的Cookie

查看数据库获取的Cookie

mysql> select * from django_session;
+----------------------------------+------------------------------------------------------------------------------------------------+----------------------------+
| session_key                      | session_data
            | expire_date                |
+----------------------------------+------------------------------------------------------------------------------------------------+----------------------------+
| 1bifn4ie9b5jbvr95v0q8yoseukmk0lq | eyJpbmZvIjp7ImlkIjo3LCJuYW1lIjoiYWRtaW4ifX0:1rYQ0Z:puh2U_7K_cS7x2tb9CRNG32_4FTySOsuR8H2dUo94lw | 2024-02-23 12:29:31.603149 |
+----------------------------------+------------------------------------------------------------------------------------------------+----------------------------+
1 row in set (0.00 sec)

mysql>

之后除了登录页面之外的所有页面都应该有对Cookie的验证,如果正确才能正常页面,否则返回/login/

13.1登录

登录成功后:

  • cookie,随机字符串
  • session,用户信息

在其他需要登录才能访问的页面中,都需要加入

def index(request):
    info = request.session.get("info")
    if not info:
        return redirect("/login/")
    ...

在django中,可以使用组件帮助快速完成登录跳转

目标:在18个视图函数前面统一加入判断 - 使用django的中间件

13.2中间件

中间件的本质是类,网页的请求和响应都要经过中间件,类中定义了process_request方法和process_response方法用于用户请求信息的传入和页面信息的传出,可以在中间件中直接禁止请求的访问,就可以直接返回而不需要经过最内层的函数

  • 定义中间件

​ 创建middleware文件夹 -> 创建auth.py文件

​ auth.py

from django.utils.deprecation import MiddlewareMixin
class M1(MiddlewareMixin):
    """中间件"""
    def process_request(self, request):
        # 如果没有返回值(return),默认为None,意味着可以继续往后走
        # 如果有返回值(必须是HttpResponse或者render或者redirect)
        print("进入M1")
    def process_response(self, request, response):
        print("退出M1")
        return response
class M2(MiddlewareMixin):
    """中间件2"""
    def process_request(self, request):
        print("进入M2")
    def process_response(self, request, response):
        print("退出M2")
        return response
  • 应用中间件

​ 修改settings.py中的MIDDLEWARE = ...

​ 添加app01.middleware.auth.M1和app01.middleware.auth.M2

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",
    "app01.middleware.auth.M1",
    "app01.middleware.auth.M2",
]
  • 运行结果
[10/Feb/2024 17:51:05] "GET /user/list/ HTTP/1.1" 200 11087
进入M1
进入M2
退出M2
退出M1
  • 在中间件的process_request方法中
# 如果没有返回值(return),默认为None,意味着可以继续往后走
# 如果有返回值(必须是HttpResponse或者render或者redirect)

中间件

13.3中间件实现登录校验

  • 编写中间件

    from django.utils.deprecation import MiddlewareMixin
    from django.shortcuts import redirect, render, HttpResponse
    
    
    class AuthMiddleware(MiddlewareMixin):
        """中间件"""
    
        def process_request(self, request):
            # 1.排除掉不需要登录就能访问的页面
            # request.path_info 获取当前用户请求的URL
            if request.path_info == "/login/":
                return
            # 2.读取当前访问的用户的session信息,如果能读到,说明已登陆过,就可以继续向后走
            info_dict = request.session.get("info")
            if info_dict:
                return
            # 3.没有登录过,回到登陆页面
            return redirect("/login/")
    
        def process_response(self, request, response):
            print("退出M1")
            return response
    
  • 应用中间件settings.py

    "app01.middleware.auth.AuthMiddleware",
    

13.4注销

views -> account.py

def logout(request):
    """注销"""
    # 清除当前用户的session
    request.session.clear()
    return redirect("/login/")

13.5在html中调用session

request.session中存储的字典关键字.key

layout.html

<ul class="nav navbar-nav navbar-right">
    <li class="dropdown">
      <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">{{ request.session.info.name }}<span class="caret"></span></a>
      <ul class="dropdown-menu">
        <li><a href="#">个人资料</a></li> 
        <li><a href="#">修改密码</a></li>
        <li><a href="/logout/">注销</a></li>
        <li role="separator" class="divider"></li>
        <li><a href="#">关于我们</a></li>
      </ul>
    </li>
</ul>

14.图片验证码

14.1生成图片

pip install pillow
from PIL import Image

# 模式, 大小, 颜色
img = Image.new(mode='RGB', size=(120, 30), color=(255, 255, 255))

#打开图片
with open('code.png', 'wb') as f:
    img.save(f, dormat='png')

utils -> code.py

from PIL import Image, ImageDraw, ImageFont, ImageFilter
import random

def check_code(
    font_file="project6-Django\\System_Manage_User\\app01\\fontfile\\Monaco.ttf",
    width=120,
    height=30,
    char_length=5,
    font_size=28,
):
    code = []
    img = Image.new(mode="RGB", size=(width, height), color=(255, 255, 255))
    draw = ImageDraw.Draw(img, mode="RGB")

    def rndChar():
        """
        生成随机字母
        :return:
        """
        return chr(random.randint(65, 90))

    def rndColor():
        """
        生成随机颜色
        :return:
        """
        return (
            random.randint(0, 255),
            random.randint(10, 255),
            random.randint(64, 255),
        )

    # 写文字
    font = ImageFont.truetype(font_file, font_size)
    for i in range(char_length):
        char = rndChar()
        code.append(char)
        h = random.randint(0, 4)
        draw.text([i * width / char_length, h], char, font=font, fill=rndColor())

    # 写干扰点
    for i in range(40):
        draw.point(
            [random.randint(0, width), random.randint(0, height)], fill=rndColor()
        )

    # 写干扰圆圈
    for i in range(40):
        draw.point(
            [random.randint(0, width), random.randint(0, height)], fill=rndColor()
        )
        x = random.randint(0, width)
        y = random.randint(0, height)
        draw.arc((x, y, x + 4, y + 4), 0, 90, fill=rndColor())

    # 画干扰线
    for i in range(5):
        x1 = random.randint(0, width)
        y1 = random.randint(0, height)
        x2 = random.randint(0, width)
        y2 = random.randint(0, height)

        draw.line((x1, y1, x2, y2), fill=rndColor())

    img = img.filter(ImageFilter.EDGE_ENHANCE_MORE)
    return img, "".join(code)


if __name__ == "__main__":
    # 使用手册
    # 0.创建font_file=['font地址1', 'font地址2', 'font地址3']
    # 1. 直接打开
    # img,code = check_code(font_file)
    # img.show()

    # 2. 写入文件
    # img,code = check_code(font_file)
    # with open('code.png','wb') as f:
    #     img.save(f,format='png')

    # 3. 写入内存(Python3)
    # from io import BytesIO
    # stream = BytesIO()
    # img.save(stream, 'png')
    # stream.getvalue()

    # 4. 写入内存(Python2)
    # import StringIO
    # stream = StringIO.StringIO()
    # img.save(stream, 'png')
    # stream.getvalue()

    pass

urls.py

path("image/code/", account.image_code),

views -> account.py

def image_code(request):
    # 调用含有pillow函数生成图片
    img, code_string = check_code()
    print(code_string)
    # 将图片放到内存中
    stream = BytesIO()
    img.save(stream, "png")

    return HttpResponse(stream.getvalue())

login.html

<div class="form-group">
    <label for="id_code">图片验证码</label>
    <div class="row">
        <div class="col-xs-7">
            <input type="text" name="code" class="form-control" placeholder="请输入图片验证码" required="" id="id_code">
            <span style="color:red;"></span>
        </div>
        <div class="col-xs-5">
            <img id="img_code" src="/image/code/"> 
        </div>
    </div>
    <div>
        <a href="/login/">  看不清?换一张</a>
    </div>
</div>

14.2验证验证码 add_error&Validation

LoginForm 添加code字段

# Form - 要自己动手写,且一般用作比对而不修改数据库内容
# ModelForm - 关联数据库
class LoginForm(BootStrapForm):
    username = forms.CharField(
        label="用户名", widget=forms.TextInput, required=True
    )  # required是表示该字段是否必须填写,默认为True
    password = forms.CharField(
        label="密码", widget=forms.PasswordInput(render_value=True), required=True
    )
    code = forms.CharField(label="验证码", widget=forms.TextInput)

    # 定义用于校验登录数据的钩子函数,这样调用这个类所返回的form中的密码会自动加密用于比对数据库
    def clean_password(self):
        pwd = self.cleaned_data.get("password")
        return md5(pwd)

login

user_input_code = form.cleaned_data.pop("code")
        # 校验验证码是否正确
        image_code = request.session.get("image_code", "")
        if image_code.upper() != user_input_code.upper():
            form.add_error("code", "验证码错误")
            return render(request, "login.html", {"form": form})
def login(request):
    """登录"""
    if request.method == "GET":
        form = LoginForm()
        return render(request, "login.html", {"form": form})
    form = LoginForm(data=request.POST)
    if form.is_valid():
        # 验证成功,获取到的用户名和密码 还有验证码

        # 获取用户输入的验证码 clean_data
        # form.cleaned_data['code'] 过于繁琐
        # 剔除验证码(验证码并不在数据库中) .pop()去掉并返回这个值
        user_input_code = form.cleaned_data.pop("code")
        # 校验验证码是否正确
        image_code = request.session.get("image_code", "")
        if image_code.upper() != user_input_code.upper():
            form.add_error("code", "验证码错误")
            return render(request, "login.html", {"form": form})

        # 去数据库校验数据是否正确 使用字典校验的方式
        # admin_object = models.Admin.objects.filter(
        #     username=form.cleaned_data["username"],
        #     password=form.cleaned_data["password"],
        # ).first() 过于繁琐
        admin_object = models.Admin.objects.filter(**form.cleaned_data).first()
        """
        clean_data是一个包含验证后的数据的字典,
          如果用户名密码正确,admin_object是一个admin对象,
          如果不正确,admin_object=None
        """
        if admin_object:
            # 账号密码正确
            # 网站生成一个随机字符串*Cookie*, 再写入到Session中
            # request.session["info"] = "xxx"
            # # 在Session中存储"[cookie | info":"xxx" ]
            # 在Session中以字典形式存储cookie的对应内容 - info= {id:1, name:admin}
            request.session["info"] = {
                "id": admin_object.id,
                "name": admin_object.username,
            }
            # 设置session的保存时间
            request.session.set_expiry(60 * 60 * 24 * 7)
            return redirect("/admin/list/")
        # 添加错误 .add_error("字段名", "错误信息")
        form.add_error("password", "用户名或密码错误")
        return render(request, "login.html", {"form": form})

    return render(request, "login.html", {"form": form})

add_error是将一个错误信息添加到form中,可以通过field.errors.0来调出该错误内容

ValidationError是引起一个错误,用raise ValidationError 的话后续的cleaned_data字典中将没有该错误字段的键值对

如过有复合校验的需求,例如先校验手机号是否注册,再校验手机号与验证码是否匹配的时候,若想要两个局部钩子clean函数分开单独检验,最好使用第二种方法,否则如果手机号码出错,后续的是否匹配的钩子函数是无法在cleaned_data中获取手机号的。

posted on 2024-02-16 13:24  树深时见鹿nnn  阅读(31)  评论(0编辑  收藏  举报