0%

Django

Django

  • Python知识点:函数、面向对象。
  • 前端开发:HTML、CSS、JavaScript、jQuery、BootStrap。
  • MySQL数据库。
  • Python的Web框架:
    • Flask,自身短小精悍 + 第三方组件。
    • Django,内部已集成了很多组件 + 第三方组件。【主要】

安装django

pip install django

#使用清华大学的镜像
pip install some_package --index https://pypi.mirrors.ustc.edu.cn/simple/
pip install django --index https://pypi.tuna.tsinghua.edu.cn/simple

# 检查django版本
python -m django --version
# 4.1
c:\python39
- python.exe
- Scripts
- pip.exe
- django-admin.exe 【工具,创建django项目中的文件和文件夹】
- Lib
- 内置模块
- site-packages
- openpyxl
- python-docx
- flask
- django 【框架的源码】

# /Users/jiangangkong/miniconda3/lib/python3.9

创建项目

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

在终端
  • 打开终端

  • 进入某个目录

    cd /Users/jiangangkong/workSpace/pycharmWorkSpace/
  • 执行命令创建项目

    /Users/jiangangkong/miniconda3/bin/django-admin startproject 项目名
    # 如果 /Users/jiangangkong/miniconda3/bin/ 已加入环境系统环境变量。

    django-admin startproject 项目名称
image-20220819015241441 image-20220819015324722
在Pycharm
image-20220819131906745

特殊说明:

  • 命令行,创建的项目是标准的。

  • pycharm,在标准的基础上默认给咱们加了点东西。【现在删除,以后不一定要删。是关于模版的设置】

    • 创建了一个templates目录【删除】
    • settings.py中【删除划线处】
      image-20211124091443354

项目目录结构

image-20220819133037783

mysite2
├── manage.py 【项目的管理,启动项目、创建app、数据管理】【不要动】【***常常引用***】
└── mysite2
├── __init__.py
├── settings.py 【项目配置】 【***常常修改***】
├── urls.py 【URL和处理函数的对应关系】【***常常修改***】
├── asgi.py 【接收网络请求(django3异步式)】【不要动】
└── wsgi.py 【接收网络请求(同步式的)】【不要动】
  • 要写的只有settings.pyurls.py 加之引用manage.py

APP

  • 此APP非彼APP,主要实现功能的划分
- 项目
- app,用户管理【独立的数据库表结构、函数、前端模板】
- app,订单管理【表结构、函数、HTML模板、CSS】
- app,后台管理【表结构、函数、HTML模板、CSS】
- app,网站 【表结构、函数、HTML模板、CSS】
- app,API 【表结构、函数、HTML模板、CSS】
..

注意:我们开发比较简洁,用不到多app,一般情况下,项目下创建1个app即可。
创建app
  • 点击项目根目录,调出终端,输入:
python3.9 manage.py startapp app01
  • image-20220819140621114
app目录结构

image-20220819141005961

├── app01
│   ├── __init__.py
│   ├── admin.py 【不用动】django默认提供了admin后台管理。
│   ├── apps.py 【不用动】app启动类
│   ├── migrations 【不用动】数据库变更记录
│   │   └── __init__.py
│   ├── models.py 【**重要**】,对数据库操作。
│   ├── tests.py 【前期不用动】单元测试
│   └── views.py 【**重要**】,就是urls中指定的函数。
├── manage.py
└── mysite2
├── __init__.py
├── asgi.py
├── settings.py
├── urls.py 【URL->函数】
└── wsgi.py
  • 所以对app的操作只在models.pyviews.py中进行

开发流程

  • 在项目settings.py中注册创建的app【settings.py】
image-20220819143640297
  • 编写url与视图函数的对应关系【urls.py中】
image-20220819143456426
  • 编写视图函数【在对应app的 views.py中编写】
image-20220819144404087

开发流程【补】

在app根目录中添加 templates文件夹以及static文件夹

引用静态文件【用之前的写法也可以】

image-20220819153922108

模版语法【重要】

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

image-20220819155958331

  • views.py中要传键值对,html中要用两对花括号接收
  • 获取列表元素不用[],而用.

image-20220819160733066

也可以这么写

image-20220819161001747

通过.获取属性

image-20220819161153749

案例:伪联通新闻中心

  • 这里的requests是第三方模块,是python专门用来发送请求的
  • views.py中的request是django的参数。为了不混淆可以写为req

image-20211124115145293

image-20211124115155394

image-20211124115209067

image-20211124115218937

请求和响应【重要】

  • request是浏览器向后台发的【请求】
  • 使用redirect需要导入redirect包

image-20220819163251655

  • 关于重定向【是2号路线】
    • 后台让浏览器自己去重定向的网站寻找资源

image-20211124142033257

案例:用户登录(无数据库版)

  • 流程梳理
  1. 首先明确所有发送到/login的页面都会被送到后台views.login处理
  2. 用户第一次请求访问login页面时,是GET请求,所以后台要返回login.html
  3. 若用户在login.html中提交form表单则向后台发送POST请求,则验证用户名与密码
  4. {% csrf_token %}是django用来保证访问的合法性的,若不带,则浏览器会阻止该次请求。(也可以加在html最头部)

image-20211124151119553

image-20211124151127364

image-20211124151135563

数据库操作

  • 原始版采用:MySQL数据库 + pymysql

    import pymysql

    # 1.连接MySQL
    conn = pymysql.connect(host="127.0.0.1", port=3306, user='root', passwd="00000000", charset='utf8', db='库名')
    cursor = conn.cursor(cursor=pymysql.cursors.DictCursor)

    # 2.发送指令
    cursor.execute("insert into 表名(属性1,属性2,属性3) values('值1','值2','值3')")
    conn.commit()

    # 3.关闭
    cursor.close()
    conn.close()

    Django开发操作数据库更简单,内部提供了ORM框架。(将django简洁的代码翻译成sql语句)
    image-20211124151748712

安装第三方模块
  • 在pycharm项目终端输入pip3.9 install mysqlclient
ORM

ORM可以:

  • 创建、修改、删除数据库中的表(不用写SQL语句)。 【无法创建数据库】

  • 操作表中的数据(不用写SQL语句)。

1. 自己建库

image-20220819173816571

2. django连接数据库
  • settings.py中修改DATABASE字段
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'djangomysql', # 数据库名字
'USER': 'root',
'PASSWORD': '00000000',
'HOST': '127.0.0.1', # 那台机器安装了MySQL
'PORT': 3306,
}
}
3. django操作表
  • 创建表
  1. 创建表:在models.py文件中

image-20211124154658774

  1. 打开pycharm项目终端执行命令:
# 这里我出了bug,原因是找不到libmysqlclient.21.dylib
# 解决方法 找到系统里的libmysqlclient.21.dylib,将它与报错找不到的地方软链接起来
sudo ln -s /usr/local/mysql-8.0.28-macos11-arm64/lib/libmysqlclient.21.dylib /Users/jiangangkong/miniconda3/lib/libmysqlclient.21.dylib
python manage.py makemigrations
python manage.py migrate
# ORM相当于执行了以下代码 表名为 app名_类名小写
create table app01_userinfo(
id bigint auto_increment primary key,
name varchar(32),
password varchar(64),
age int
)

其余的表是django内置的app生成的表【暂时不用管它们】

image-20220819203327273

  • 删除表

删掉models.py中的代码并且执行以下命令

python manage.py makemigrations
python manage.py migrate
  • 修改表

在表中新增列时,由于已存在列中可能已有数据,所以新增列必须要指定新增列对应的数据:

终端会提醒你两个解决方法

  1. 设置默认值
age = models.IntegerField(default=2)
  1. 允许为空
data = models.IntegerField(null=True, blank=True)
4. 增删改查
  • create
  • delete
  • update(xx = xx)
  • all
  • filter(xx = xx)
# #### 1.新增表项 ####
# Department.objects.create(title="销售部")
# Department.objects.create(title="IT部")
# Department.objects.create(title="运营部")
# UserInfo.objects.create(name="武沛齐", password="123", age=19)
# UserInfo.objects.create(name="朱虎飞", password="666", age=29)
# UserInfo.objects.create(name="吴阳军", password="666")

# #### 2.删除 ####
# UserInfo.objects.filter(id=3).delete()
# Department.objects.all().delete()

# #### 3.获取数据 ####
# 3.1 获取符合条件的所有数据
# data_list = [对象,对象,对象,...] QuerySet类型
# data_list = UserInfo.objects.all()
# 根据id升序
# data_list = UserInfo.objects.all().order_by("id")
# 根据id降序
# data_list = UserInfo.objects.all().order_by("-id")
# for obj in data_list:
# print(obj.id, obj.name, obj.password, obj.age)

# data_list = UserInfo.objects.filter(id=1)
# print(data_list)
# 3.1 获取第一条数据【对象】
# row_obj = UserInfo.objects.filter(id=1).first()
# print(row_obj.id, row_obj.name, row_obj.password, row_obj.age)


# #### 4.更新数据 ####
# UserInfo.objects.all().update(password=999)
# UserInfo.objects.filter(id=2).update(age=999)
# UserInfo.objects.filter(name="朱虎飞").update(age=999)
搜索[查]
models.PrettyNum.objects.filter(mobile="19999999991",id=12)
# 等价于
data_dict = {"mobile":"19999999991","id":123}
models.PrettyNum.objects.filter(**data_dict)
# 数字
models.PrettyNum.objects.filter(id=12) # 等于12
models.PrettyNum.objects.filter(id__gt=12) # 大于12
models.PrettyNum.objects.filter(id__gte=12) # 大于等于12
models.PrettyNum.objects.filter(id__lt=12) # 小于12
models.PrettyNum.objects.filter(id__lte=12) # 小于等于12

data_dict = {"id__lte":12}
models.PrettyNum.objects.filter(**data_dict)
# 字符串
models.PrettyNum.objects.filter(mobile="999") # 等于
models.PrettyNum.objects.filter(mobile__startswith="1999") # 筛选出以1999开头
models.PrettyNum.objects.filter(mobile__endswith="999") # 筛选出以999结尾
models.PrettyNum.objects.filter(mobile__contains="999") # 筛选出包含999

data_dict = {"mobile__contains":"999"}
models.PrettyNum.objects.filter(**data_dict)
分页-原理[查]
queryset = models.PrettyNum.objects.all()

queryset = models.PrettyNum.objects.filter(id=1)[0:10]


# 第1页
queryset = models.PrettyNum.objects.all()[0:10]

# 第2页
queryset = models.PrettyNum.objects.all()[10:20]

# 第3页
queryset = models.PrettyNum.objects.all()[20:30]
data = models.PrettyNum.objects.all().count()
data = models.PrettyNum.objects.filter(id=1).count()
  • 分页的逻辑和处理规则

  • 封装分页类

    • 从头到尾开发
    • 写项目用【pagination.py】公共组件。
  • 小Bug,搜索 + 分页情况下。

    分页时候,保留原来的搜索条件

    http://127.0.0.1:8000/pretty/list/?q=888
    http://127.0.0.1:8000/pretty/list/?page=1

    http://127.0.0.1:8000/pretty/list/?q=888&page=23
5.表之间的关联关系
from django.db import models


class Department(models.Model):
""" 部门表 """
title = models.CharField(verbose_name='标题', max_length=32)


class UserInfo(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="入职时间")

# 无约束
# depart_id = models.BigIntegerField(verbose_name="部门ID")
# 1.有约束
# - to,与那张表关联
# - to_field,表中的那一列关联
# 2.django自动
# - 写的depart
# - django内部自动生成数据列 depart_id

# 3.当部门表被删除时,两种选择
# ### 1 级联删除
depart = models.ForeignKey(to="Department", to_field="id", on_delete=models.CASCADE)
# ### 2 置空
# depart = models.ForeignKey(to="Department", to_field="id", null=True, blank=True, on_delete=models.SET_NULL)

# 在django中做的约束(数据库里这列存的是1,2。然后再去django中查找对应的值)
gender_choices = (
(1, "男"),
(2, "女"),
)
gender = models.SmallIntegerField(verbose_name="性别", choices=gender_choices)

模板的继承

定义模板:layout.html

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<link rel="stylesheet" href="{% static 'plugin...min.css' %}">
{% block css %}{% endblock %}
</head>
<body>
<h1>标题</h1>
<div>
{% block content %}{% endblock %}
</div>
<h1>底部</h1>

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

继承模板:

{% extends 'layout.html' %}

{% block css %}
<link rel="stylesheet" href="{% static 'pluxxx.css' %}">
<style>
...
</style>
{% endblock %}


{% block content %}
<h1>首页</h1>
{% endblock %}


{% block js %}
<script src="{% static 'js/jqxxxin.js' %}"></script>
{% endblock %}

取关联或特殊数据

<td>{{ item.create_time|date:"Y-m-d" }}</td> <!-- 2022-8-21 -->
<td>{{ item.get_gender_display}}</td> <!-- “男”,“女” -->
<td>{{ item.depart.title }}</td> <!-- “采购部”,“研发部” -->

ModelForm【牛逼】

  • 解决数据校验和错误提示
  • 避免views.py和html页面中有太多冗余代码
  1. models.py
from django.db import models

# Create your models here.
class Department(models.Model):
"""部门表"""
title = models.CharField(verbose_name='部门名',max_length=32)
def __str__(self):
return self.title


class UserInfo(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="入职时间")
depart = models.ForeignKey(to="Department", to_field="id", on_delete=models.CASCADE)
gender_choices = (
(1, "男"),
(2, "女"),
)
gender = models.SmallIntegerField(verbose_name="性别", choices=gender_choices)
  1. views.py
class MyForm(forms.ModelForm):
class Meta:
model = models.UserInfo
fields = ["name","password","age","gender","depart","create_time"]
# widgets = {
# "name": forms.TextInput(attrs={"class":"form-control"}),
# "password": forms.PasswordInput(attrs={"class": "form-control"}),
# "age": forms.TextInput(attrs={"class": "form-control"}),
# "gender": forms.TextInput(attrs={"class": "form-control"}),
# "depart": forms.TextInput(attrs={"class": "form-control"})
# }
def __init__(self,*args,**kwargs):
super().__init__(*args,**kwargs)
for name,field in self.fields.items():
field.widget.attrs={"class": "form-control"}

def user_add(request):
if request.method=="GET":
form = MyForm()
return render(request,'useradd.html',{"form": form})

# POST方式提交数据,需要数据校验
form = MyForm(data=request.POST)
if form.is_valid():
form.save()
return redirect('/user/list/')
else:
return render(request, 'useradd.html', {"form": form})

def user_edit(request,nid):
if request.method == "GET":
obj=models.UserInfo.objects.filter(id=nid).first()
form = MyForm(instance=obj) # form 就只为obj这一行
return render(request,'useredit.html',{'form':form})

obj = models.UserInfo.objects.filter(id=nid).first()
form=MyForm(data=request.POST,instance=obj) # form 就只为obj这一行
if form.is_valid():
form.save()
return redirect('/user/list/')
else:
return render(request, 'useredit.html', {"form": form})

def user_delete(request,nid):
models.UserInfo.objects.filter(id=nid).delete()
return redirect('/user/list/')
  1. useradd.html和useredit.html【useredit.html中post不用填action,填了会出错,bug未知】
{% extends 'layout.html' %}
{% block content %}
<div class="container">
<form action="/user/add/" method="post" novalidate>
{% csrf_token %}
{% for field in form %}
<div class="form-group">
{{ field.label }}:{{ field }}
<span style="color:red">{{ field.errors.0 }}</span>
</div>
{% endfor %}
<button type="submit" class="btn btn-primary" >提交</button>
</form>
</div>
{% endblock %}
{% extends 'layout.html' %}
{% block content %}
<div class="container">
<form method="post" novalidate>
{% csrf_token %}
{% for field in form %}
<div class="form-group">
{{ field.label }}:{{ field }}
<span style="color:red">{{ field.errors.0 }}</span>
</div>
{% endfor %}
<button type="submit" class="btn btn-primary">提交</button>
</form>
</div>
{% endblock %}
ModelForm数据校验
class PrettyModelForm(forms.ModelForm):

# 验证方法1:
mobile = forms.CharField(
label="手机号",
validators=[RegexValidator(r'^1[3-9]\d{9}$', "手机号格式错误")]
)

class Meta:
model = models.PrettyNum
fields = ["id", "mobile", "price", "level", "status"]

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
for name, field in self.fields.items():
field.widget.attrs = {"class": "form-control"}

# 验证方法2 :钩子方法
def clean_mobile(self):
txt_mobile = self.cleaned_data["mobile"]
exists = models.PrettyNum.objects.filter(mobile=txt_mobile).exists()
if exists:
raise ValidationError("手机号已存在")

return txt_mobile

image-20220822212707657

分页

  • app01/utils/pagination.py
"""
自定义的分页组件
"""
from django.utils.safestring import mark_safe


class Pagination(object):

# page_param="page"意思是获取页面里的 page 字段的值
def __init__(self, request, queryset, page_size=10, page_param="page", plus=5):

# 解决搜索后分页 url拼接参数
import copy
from django.http.request import QueryDict
query_dict = copy.deepcopy(request.GET)
query_dict._multable = True
# self.query_dict 包含了原来所有的参数
self.query_dict = query_dict
self.page_param = page_param

# 拿到 页数 page 和 页面大小 page_size
page = request.GET.get(page_param, "1")
if page.isdecimal(): # 如果page是十进制的数
page = int(page)
else:
page = 1
self.page = page
self.page_size = page_size

# 每一页的起始索引和结束索引
self.start = (page - 1) * page_size
self.end = page * page_size

# 每一页的queryset
self.page_queryset = queryset[self.start: self.end]

# 数据总条数
self.total_count = queryset.count()

# 总页码
total_page_count, div = divmod(self.total_count, page_size)
if div:
total_page_count += 1
self.total_page_count = total_page_count

# 显示前后 plus 页
self.plus = plus

def html(self):
# 显示当前页的前五页,后五页
if self.total_page_count <= 2 * self.plus + 1:
# 数据库中的数据比较少,未达到11页
if self.total_count == 0:
start_page = 1
end_page = self.total_page_count + 1
else:
start_page = 1
end_page = self.total_page_count
# 数据库中的数据大于11页
else:
if self.page <= self.plus:
start_page = 1
end_page = 2 * self.plus + 1
else:
if(self.page + self.plus) > self.total_page_count:
start_page = self.total_page_count - 2 * self.plus
end_page = self.total_page_count
else:
start_page = self.page - self.plus
end_page = self.page + self.plus

# 每一页的html标签
page_str_list = []

# 首页
self.query_dict.setlist(self.page_param, [1])
page_str_list.append('<li><a href="?{}">首页</a></li>'.format(self.query_dict.urlencode()))

# 上一页
if self.page > 1:
self.query_dict.setlist(self.page_param, [self.page - 1])
prev = '<li><a class="active" href="?{}">上一页</a></li>'.format(self.query_dict.urlencode())
else:
self.query_dict.setlist(self.page_param, [1])
prev = '<li><a class="active" href="?{}">上一页</a></li>'.format(self.query_dict.urlencode())
page_str_list.append(prev)


for i in range(start_page, end_page + 1):
self.query_dict.setlist(self.page_param, [i])
if i == self.page:
ele = '<li class="active"><a href="?{}">{}</a></li>'.format(self.query_dict.urlencode(), i)
else:
ele = '<li><a href="?{}">{}</a></li>'.format(self.query_dict.urlencode(), i)
page_str_list.append(ele)

# 下一页
if self.page < self.total_page_count:
self.query_dict.setlist(self.page_param, [self.page + 1])
next = '<li><a href="?{}">下一页</a></li>'.format(self.query_dict.urlencode())
else:
self.query_dict.setlist(self.page_param, [self.total_page_count])
next = '<li><a href="?{}">下一页</a></li>'.format(self.query_dict.urlencode())
page_str_list.append(next)

page_string = mark_safe(" ".join(page_str_list))
return page_string
  • views.py中使用
from app01.utils.pagination import Pagination


def pretty_list(request):
data_list = {}
value = request.GET.get("q", "") # " "为默认值

if value:
data_list["mobile__contains"] = value
queryset = models.PrettyNum.objects.filter(**data_list).order_by("id")

page_object = Pagination(request, queryset)

return render(request, 'pretty_list.html', {"value": value, "queryset": page_object.page_queryset, "page_string": page_object.html()})
  • html
<div style="margin-top: 10px " >
<nav aria-label="Page navigation">
<ul class="pagination">
<li>
<a href="?page=1" aria-label="head">
<span aria-hidden="true">&laquo;</span>
</a>
</li>
{{ page_string }}
</ul>
</nav>
</div>

时间选择插件

  • 引入bootstrap-datepicker
  • 在应用ModelForm的网页中 id要变化, 具体id要在网页上检查元素
<head>	
<link rel="stylesheet" href="static/plugins/bootstrap-3.4.1/css/bootstrap.css">
<link rel="stylesheet" href="static/plugins/bootstrap-datepicker/css/bootstrap-datepicker.css">
</head>

<input type="text" id="dt" class="form-control" placeholder="入职日期">



<script src="static/js/jquery-3.6.0.min.js"></script>
<script src="static/plugins/bootstrap-3.4.1/js/bootstrap.js"></script>
<script src="static/plugins/bootstrap-datepicker/js/bootstrap-datepicker.js"></script>
<script src="static/plugins/bootstrap-datepicker/locales/bootstrap-datepicker.zh-CN.min.js"></script>


<script>
$(function () {
$('#dt').datepicker({
format: 'yyyy-mm-dd',
startDate: '0',
language: "zh-CN",
autoclose: true
});

})
</script>

ModelForm和BootStrap

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

    class UserModelForm(forms.ModelForm):
    class Meta:
    model = models.UserInfo
    fields = ["name", "password",]

    form = UserModelForm()
    {{form.name}}      普通的input框
    {{form.password}} 普通的input框
  • 定义插件[太繁琐]

    class UserModelForm(forms.ModelForm):
    class Meta:
    model = models.UserInfo
    fields = ["name", "password",]
    widgets = {
    "name": forms.TextInput(attrs={"class": "form-control"}),
    "password": forms.PasswordInput(attrs={"class": "form-control"}),
    "age": forms.TextInput(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", "age"]
    {{form.name}}      BootStrap的input框
    {{form.password}} BootStrap的input框
  • 重新定义的init方法[批量设置,较便捷]

    class UserModelForm(forms.ModelForm):
    class Meta:
    model = models.UserInfo
    fields = ["name", "password", "age",]

    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):
    class Meta:
    model = models.UserInfo
    fields = ["name", "password", "age",]

    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 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",]

让不同的页面输入框使用不同的 class

  • 提取公共的类

    image-20211126175803303
    image-20211126175826579

  • ModelForm拆分出来
    image-20211126175852716

  • 视图函数的归类
    image-20211126175927378

    image-20211126175946996

md5密码加密

  • utils/encrypt.py
from django.conf import  settings
import hashlib


def md5(data_string):
obj = hashlib.md5(settings.SECRET_KEY.encode('utf-8'))
obj.update(data_string.encode('utf-8'))
return obj.hexdigest()
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("请输入相同密码")
return confirm
http://127.0.0.1:8000/admin/list/
# or
https://127.0.0.1:8000/admin/list/
  • http或者https都是无状态的短链接

image-20220824223956247

  • 解决方案

image-20211127111106780

  1. 响应体即是你能看见的内容
  2. k1是网站发给浏览器某个请求的凭证
使用
def account_login(request):
""" 登录 """
if request.method == "GET":
form = AccountModelForm()
return render(request, 'login.html', {"form": form})

form = AccountModelForm(data=request.POST)
if form.is_valid():
if models.Admin.objects.filter(adminName=form.cleaned_data.get("adminName")).exists():
obj = models.Admin.objects.filter(adminName=form.cleaned_data.get("adminName")).first()
if obj.password == form.cleaned_data.get("password"):

# 用户名和密码正确
# 网站生成随机字符串; 写到用户浏览器的cookie中;在写入到session中;
request.session["info"] = {'id': obj.id, 'name': obj.adminName}
# session可以保存7天
request.session.set_expiry(60 * 60 * 24 * 7)

return redirect('/pretty/list/')

form.add_error("password", "用户名或密码错误")
form.add_error("password", "用户名或密码错误")

return render(request, 'login.html', {"form": form})
info = request.session.get("info")
if not info:
return redirect('/login/')

中间件

image-20211127142838372

  • app01/Middleware/auth.py中定义中间件

    from django.utils.deprecation import MiddlewareMixin
    from django.shortcuts import HttpResponse

    class M1(MiddlewareMixin):
    """ 中间件1 """

    def process_request(self, request):

    # 如果方法中没有返回值(返回None),继续向后走
    # 如果有返回值 HttpResponse、render 、redirect
    print("M1.process_request")
    return HttpResponse("无权访问")

    def process_response(self, request, response):
    print("M1.process_response")
    return response


    class M2(MiddlewareMixin):
    """ 中间件2 """

    def process_request(self, request):
    print("M2.process_request")

    def process_response(self, request, response):
    print("M2.process_response")
    return response
  • 应用中间件 setings.py

    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',
    ]
  • 在中间件的process_request方法

    # 如果方法中没有返回值(返回None),继续向后走
    # 如果有返回值 HttpResponse、render 、redirect,则不再继续向后执行。
中间件实现登录校验
  • 编写中间件

    from django.utils.deprecation import MiddlewareMixin
    from django.shortcuts import HttpResponse, redirect


    class AuthMiddleware(MiddlewareMixin):

    def process_request(self, request):
    # 0.排除那些不需要登录就能访问的页面
    # request.path_info 获取当前用户请求的URL /login/
    if request.path_info == "/login/":
    return

    # 1.读取当前访问的用户的session信息,如果能读到,说明已登陆过,就可以继续向后走。
    info_dict = request.session.get("info")
    print(info_dict)
    if info_dict:
    return

    # 2.没有登录过,重新回到登录页面
    return redirect('/login/')
  • 应用中间件(要在settings.py中注册)

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

注销

def logout(request):
""" 注销 """

request.session.clear()

return redirect('/login/')

html中获取已登录用户信息

<li><a href="#">id: {{ request.session.info.id }}</a></li>

图片验证码

image-20211127152344329
  • 在项目根目录下导入Monaco.ttf
  • app01/utils/code.py中写入下方文件
import random
from PIL import Image, ImageDraw, ImageFont, ImageFilter


def check_code(width=120, height=30, char_length=5, font_file='Monaco.ttf', 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__':

# code_str 即是写入图片的文字
img, code_str = check_code()
print(code_str)

with open('code.png', 'wb') as f:
img.save(f, format='png')

  • login.html
<div class="form-group">
<label for="id_code">图片验证码</label>
<div class="row">
<div class="col-xs-7">
{{ form.code }}
<div>
<span style="color: red">{{ form.code.errors.0 }}</span>
</div>
</div>
<div class="col-xs-5">
<img id="image_code" src="/image/code/">
</div>
</div>
</div>
  • urls.py
path('image/code/', account.image_code)
  • app01/views/account.py
def account_login(request):
""" 登录 """
if request.method == "GET":
form = AccountModelForm()
return render(request, 'login.html', {"form": form})

form = AccountModelForm(data=request.POST)
if form.is_valid():

# 验证码的校验
user_input_code = form.cleaned_data.pop('code')
code = request.session.get('image_code', "")
if code.upper() != user_input_code.upper():
form.add_error("code", "验证码错误")
return render(request, 'login.html', {'form': form})

if models.Admin.objects.filter(adminName=form.cleaned_data.get("adminName")).exists():
obj = models.Admin.objects.filter(adminName=form.cleaned_data.get("adminName")).first()
if obj.password == form.cleaned_data.get("password"):

# 用户名和密码正确
# 网站生成随机字符串; 写到用户浏览器的cookie中;在写入到session中;
request.session["info"] = {'id': obj.id, 'name': obj.adminName}
# session可以保存7天
request.session.set_expiry(60 * 60 * 24 * 7)

return redirect('/pretty/list/')

form.add_error("password", "用户名或密码错误")
form.add_error("password", "用户名或密码错误")

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


def image_code(request):
""" 生成图片验证码 """

# 调用pillow函数,生成图片
img, code_string = check_code()

# 写入到自己的session中(以便于后续获取验证码再进行校验)
request.session['image_code'] = code_string
# 给Session设置60s超时
request.session.set_expiry(60)

# 写入内存
stream = BytesIO()
img.save(stream, 'png')
return HttpResponse(stream.getvalue())
  • 并且在AccountModelForm中新加入code字段