0%

django学习

安装

1
pip install django

安装完之后在环境目录D:\Users\11200\anaconda3\envs\web下会有:

- python.exe
- Scripts
    - pip.exe
    - django-admin.exe
- Lib
    - 内置模块
    - site-packages
        - 第三方模块 
        - django

创建项目

终端

  1. 进入终端
  2. 进入想把项目放在的目录下
  3. 执行命令django-admin startproject 项目名
    django-admin要进入这个环境当中。

pycharm创建

选好解释器,删除templates文件夹,将57行改为'DIRS': [],,为了和终端创建的保持一致。

项目结构

└─test01
    │  manage.py        【项目管理,启动项目、创建app、数据库管理】
    │
    └─test01            【与项目同名】
            asgi.py     【接受网络请求】【异步】
            settings.py 【配置文件】
            urls.py     【URL与函数的对应关系】
            wsgi.py     【接受网络请求】【同步】
            __init__.py

APP

app可以理解成一个大项目里的一些小模块功能,每个app可以有自己独立的数据库、函数、HTML模板等。

创建app

在终端中,当前环境下,项目目录下执行命令:
python manage.py startapp app01(app名)

app目录结构

E:.
│  manage.py
│
├─app01
│  │  admin.py         【固定,不用动】django默认提供了后台admin的功能。
│  │  apps.py          【固定,不用动】app启动类
│  │  models.py        对数据库进行操作。
│  │  tests.py         【固定,不用动】单元测试用
│  │  views.py         函数。
│  │  __init__.py
│  │
│  └─migrations       【固定,不用动】对数据库字段进行修改做记录的
│          __init__.py
│
└─test01
    │ ...
    │
    └─__pycache__
            settings.cpython-310.pyc
            __init__.cpython-310.pyc

快速上手

注册app

settings.py中的INSTALLED_APPS里加入app01.apps.App01Config,这是一个类名,在app01(要注册的app名字)里的apps.py里。

编写URL和视图函数之间的关系(urls..py)

注释掉默认的,写上自己的。

1
path('index/', views.index),

地址名 -> 函数名
需要导入views
1
from app01 import views

编写视图函数(views.py)

1
2
3
4
from django.shortcuts import render,HttpResponse

def index(request):
return HttpResponse("hello world 欢迎使用")
  • 选哟一个默认参数request

启动项目

  • 终端
    命令:python manage.py runserver
  • PyCharm
    本质也是知名上面的命令。
    结果:
    django-helloworld

templates模板

在函数里返回HTML文件:

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

  • 参数1:传入的request;
  • 需要返回的HTML文件;

如何寻找HTML文件?

  1. 如果按照pycharm默认生成的格式,会有限到项目的根目录下去找;
  2. 将根目录下的templates删了,按照app注册的顺序,一个个去app目录下的templates目录下去找。

静态文件

在开发过程中,一般将:

  • 图片
  • CSS
  • js
    作为静态文件。
    需要在app目录下创建static目录,在这个目录下,再创建一些目录:
    • css
    • img
    • js
    • plugins

例子:
user_list.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>用户列表</title>
</head>
<body>

<h1>用户列表555</h1>
<img src="{% static "img/cat1.jpg" %}" alt="mao">
<img src="/static/img/1.png" alt="没有图片">

</body>
</html>

  • 在最开始的地方加入{% load static %},这样就可以用第一种写法,也hi是推荐的写法。
  • 如果用第二种写法,就要将settings.py里的STATIC_URL = 'static/'改为STATIC_URL = '/static/',不然static目录得要放在项目目录下了而不是app目录下。
  • 注意单引号和双引号。花括号外面没有双引号虽然也可以,的那还是加上好。

模板语法

最常用的

视图函数

1
2
3
4
5
6
7
8
9
10
11
12
def tpl(request):
name = "韩超"
roles = ["管理员", "CEO", "保安"]
user_info = {'name': "一号", "salary": 10000, "role": "CTO"}

data_list = [
{'name': "一号", "salary": 10000, "role": "CTO"},
{'name': "二号", "salary": 20000, "role": "CEO"},
{'name': "三号", "salary": 30000, "role": "COO"}
]
return render(request, "tpl.html",
{"n1": name, "n2": roles, "n3": user_info, "n4": data_list})

页面代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>


<h1>模板语法的学习</h1>
<div>{{ n1 }}</div>

{# 列表#}
<div>{{ n2 }}</div>
<div>{{ n2.0 }}</div>
<div>{{ n2.1 }}</div>
<div>{{ n2.2 }}</div>

<!--for循环-->
<div>
{% for item in n2 %}
<span>{{ item }}</span>
{% endfor %}
</div>

<hr/>
{#字典#}
{{ n3 }}
{{ n3.name }}
{{ n3.salary }}
{{ n3.role }}

<ul>
{% for item in n3.keys %}
<li>{{ item }}</li>
{% endfor %}

{% for item in n3.values %}
<li>{{ item }}</li>
{% endfor %}

{% for k,v in n3.items %}
<li>{{ k }}:{{ v }}</li>
{% endfor %}
</ul>

<hr/>
{#列表里面套字典#}
{{ n4.0 }}
{{ n4.q.name }}
{% for item in n4 %}
<div>{{ item.name }} {{ item.salary }}</div>
{% endfor %}

{#if语句#}
{% if n1 == "韩超" %}
<div>aaaaa</div>
{#elif#}
{% else %}
<div>bbbbb</div>
{% endif %}

</body>
</html>

结果:
django-模板语法学习效果
注意:

  1. if语句里的 ==前后得要有空格;
  2. {{ }}两个花括号,取出其中的内容,{% %}花括号加百分号,是类似python的语法。

模板的流程

django-模板流程
模板的语法是django开发的,用户浏览器发起请求后,django会经过urls.pyviews.py找到对应的视图函数以及返回的html文件,将htnl的内容以字符串的方式都进来,然后替换掉其中的模板语法,再泛函给用户浏览器。

案例

视频教程用的是中国联通新闻中心的数据,但是我爬不下来,所以用了疫情的数据。

视图函数
1
2
3
4
5
6
def epidemic(request):
import requests
res = requests.get("https://api.inews.qq.com/newsqa/v1/query/pubished/daily/list?province=%E6%B5%99%E6%B1%9F")
data_json = res.json()
# print(data_json)
return render(request, "epidemic.html", {"data": data_json['data']})
HTML代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
{% load myfilter %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>疫情数据</title>
</head>
<body>
<h1>疫情数据</h1>

<table border="solid">
<tr>
<th>省份</th>
<th>新增确证</th>
<th>新增无症状</th>
<th>新增治愈</th>
<th>新增死亡</th>
<th></th>
<th></th>
<th></th>
</tr>
{% for item in data %}
<tr>
<td>{{ item.province }}</td>
<td>{{ item.newConfirm }}</td>
<td>{{ item.wzz }}</td>
<td>{{ item.newHeal }}</td>
<td>{{ item.newDead }}</td>
<td>{{ item.year }}</td>

{% with item.date|split:"." as details %}
{% for p in details %}
<td>{{ p }}</td>
{% endfor %}
{% endwith %}

</tr>
{% endfor %}
</table>

</body>
</html>
字符串分割

在app目录下创建templatetags文件夹,在该文件夹下创建__init__.pymyfilter.py两个文件,myfilter名字当然随便。其中myfilter.py的内容为:

1
2
3
4
5
6
7
8
9
10
11
12
from django.template import Library

register = Library()


@register.filter(name="split")
def split(value, key):
"""
Returns the value turned into a list.
"""

return value.split(key)

在HTML的开始{% load myfilter %},这样在HTML中就可以使用split方法了,用法建上面代码。

结果

django-疫情数据

在手机(其他主机)上查看

  1. settings.py中将ALLOWED_HOSTS改为['*'],表示任何主机都可以访问,否则会拒绝访问;
  2. 在终端执行命令python manage.py runserver 0.0.0.0:8000,得是这个ip;
  3. 关闭系统防火墙,或者允许程序访问;
  4. 在手机(其他主机)上通过地址访问192.168.10.9:8000/epidemic
  5. 在本机上查看依然是用127.0.0.1:8000/
    django-手机查看

请求和响应

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
def something(request):
# request是一个对象,封装了用户请求发来的数据
# 1.请求方式 GET/POST
print(request.method)

# 2.URL传递参数
print(request.GET)

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

# 4.HttpResponse("something"),返回一个字符串给请求者
# return HttpResponse("something")

# 5.读取HTML文件 + 渲染 -> 字符串返还给用户
# return render(request,'something.html',{"title":"AAA"})

# 6.重定向
return redirect("https://www.baidu.com")

三个请求:method,GET,POST
三个响应:字符串,页面,重定向

案例:用户登录

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 用户登录
def login(request):
if request.method == "GET":
return render(request, "login.html")
# print(request.POST)
username = request.POST.get("user")
password = request.POST.get("pwd")
print(username, password)
if username == "root" and password == "123":
# return HttpResponse("登录成功!")
return redirect("https://www.baidu.com")
else:
# return HttpResponse("你是坏人!")
return render(request, "login.html", {"error_msg": "用户名或密码错误"})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>登录</title>
</head>
<body>

<h1>登录</h1>
<form method="post" action="/login/">
{% csrf_token %}
<input type="text" name="user" placeholder="用户名">
<input type="password" name="pwd" placeholder="密码">
<input type="submit" value="提交">
<span style="color: red;">{{ error_msg }}</span>
</form>

</body>
</html>
  1. 表单提交数据后依然返回到login这个函数,但是请求方式变为了POST,所以可以通过请求方式的不同来做出不同的处理,GET就返回登陆页面,Post可以获得填写的数据;
  2. 在表单里需要写上{% csrf_token %},,可以理解为django会做一个token的判断,如果不对,django会阻拦。

数据库操作

当然可以用pymysql这样的库,但django用了ORM的框架。
ORM的框架:
django-ORM框架

安装第三方模块

pip install mysqlclient

ORM

ORM可以做两件事:

  • 创建、修改和删除数据库中的表,但不能创建数据库。
  • 操作表中的数据。
  1. 创建数据库
    在命令行连接Mysql,创建数据库django_test02
    MySQL数据库的版本要在5.6以上
  2. djangoo连接数据库
    settings.py中,
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    DATABASES = {
    'default': {
    'ENGINE': 'django.db.backends.mysql',
    'NAME': 'django_test02', # 数据库的名字
    'USER': 'root',
    'PASSWORD': '123456',
    'HOST': '127.0.0.1',
    'PORT': 3306,
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    3. django操作表(创建表)
    在`models.py`中
    ​~~~ python
    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()
    django底层会执行这样的sql语句,创建的表名是app名加下划线加类名的小写,还会自动生成一个id字段。
    1
    2
    3
    4
    5
    6
    create table app01_userinfo(
    id bigint auto_increment primary key,
    name varchar(32),
    password varchar(64),
    age int
    )
    执行命令:
    python manage.py makemigrations
    python manage.py migrate

django-数据库操作命令

django-数据库建表结果
在生成的时候,会把django默认给的app里的models.py都生成。

  1. django操作表(删除表或字段)
    直接将不要了的表或字段注释或去掉,然后重新执行命令。
  2. django操作表(新增字段)
    在执行命令的时候,
  • 给一个值;
  • 退出,在字段中加入default=
  • 允许空值numm=True,blank=True
    1
    2
    3
    4
    size = models.IntegerField()
    age2 = models.IntegerField(default=18)
    data = models.IntegerField(null=True, blank=True)

  1. django操作表中的数据
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    # 数据库操作
    from app01.models import *


    def orm(request):
    # ######## 1. 新增数据 ########
    # Department.objects.create(title="运维部")
    # Department.objects.create(title="IT部")
    # Department.objects.create(title="编辑部")
    # UserInfo.objects.create(name="盛日辉",password='123',age=18)
    # UserInfo.objects.create(name="特朗普",password='666',age=76)
    # UserInfo.objects.create(name="拜登",password='555',age=80)
    # UserInfo.objects.create(name="小艺",password='1233')

    # ######## 2. 删除数据 ########
    # Department.objects.filter(id=4).delete() # 删除id=3的一条
    # Department.objects.filter(id__gt=3).delete() # 删除id>3的数据,双下划线
    # Department.objects.all().delete() # 删除所有

    # ######## 3. 查询数据 ########
    # QuerySet类型的数据
    # 可以理解成数据列表,每一项都是一行
    # data_list = UserInfo.objects.all()
    # print(data_list)
    # <QuerySet [<UserInfo: UserInfo object (1)>, <UserInfo: UserInfo object (2)>, <UserInfo: UserInfo object (3)>, <UserInfo: UserInfo object (4)>]>
    # 打印每一行的信息
    # for obj in data_list:
    # print(obj.id, obj.name,obj.password,obj.age)

    # obj_list = UserInfo.objects.filter(id=1) # 返回的虽然只有一个,但也是QuerySet
    # obj = UserInfo.objects.filter(id=1).first() # 只取第一个
    # print(obj.id, obj.name, obj.password, obj.age)

    # ######## 4. 更新数据 ########
    # 先找到数据,再update
    # UserInfo.objects.all().update(age=0)
    UserInfo.objects.filter(name="特朗普").update(age=100)

    return HttpResponse("成功")

案例:用户管理

  1. 用户信息展示
    视图函数:
    1
    2
    3
    4
    5
    # 案例:用户管理
    # 用户列表
    def info_list(request):
    data_list = UserInfo.objects.all()
    return render(request, 'info_list.html', {'data_list': data_list})
    前端:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <title>用户管理</title>
    </head>
    <body>

    <h1>INFO列表</h1>
    <a href="/info/add/">添加</a>
    <table border="1">
    <thead>
    <tr>
    <th>ID</th>
    <th>姓名</th>
    <th>密码</th>
    <th>年龄</th>
    </tr>
    </thead>

    <tbody>
    {% for obj in data_list %}
    <tr>
    <td>{{ obj.id }}</td>
    <td>{{ obj.name }}</td>
    <td>{{ obj.password }}</td>
    <td>{{ obj.age }}</td>
    </tr>
    {% endfor %}
    </tbody>

    </table>

    </body>
    </html>
  2. 添加用户
    视图函数:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    # 添加用户
    def info_add(request):
    if request.method == "GET":
    return render(request, 'info_add.html')
    # 获取提交的数据
    user = request.POST.get('user')
    pwd = request.POST.get('pwd')
    age = request.POST.get('age')

    # 写入数据库
    UserInfo.objects.create(name=user, password=pwd, age=age)
    # return render(request, "info_list.html") # 结果是没有数据,模板部分没有获取到数据
    # info_list(request) # 报大错
    return redirect("/info/list/") # 必须还得是重定向
    前端:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <title>添加用户</title>
    </head>
    <body>

    <h1>添加用户</h1>
    <form method="post">{#向这个页面发请求,可以不屑action#}
    {% csrf_token %}
    <input type="text" name="user" placeholder="姓名">
    <input type="text" name="pwd" placeholder="密码">
    <input type="text" name="age" placeholder="年龄">
    <input type="submit" value="提交">
    </form>

    </body>
    </html>
  3. 删除用户
    想法是通过地址发起GET请求http://127.0.0.1:8000/info/delete/?nid=1,获得nid,然后在视图函数中进行数据库删除操作。
    进一步,在表格中增加一列,“删除”的a标签,其中的href就是发起的GET请求的地址。执行delete的函数后,重定向到用户列表。
    视图函数:

    1
    2
    3
    4
    5
    # 删除用户
    def info_delete(request):
    nid = request.GET.get('nid')
    UserInfo.objects.filter(id=nid).delete()
    return redirect("/info/list/")

    前端页面修改:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    <table border="1">
    <thead>
    <tr>
    <th>ID</th>
    <th>姓名</th>
    <th>密码</th>
    <th>年龄</th>
    <th>操作</th>
    </tr>
    </thead>

    <tbody>
    {% for obj in data_list %}
    <tr>
    <td>{{ obj.id }}</td>
    <td>{{ obj.name }}</td>
    <td>{{ obj.password }}</td>
    <td>{{ obj.age }}</td>
    <td>
    <a href="/info/delete/?nid={{ obj.id }}">删除</a>
    </td>
    </tr>
    {% endfor %}
    </tbody>
    </table>
  4. 效果
    用户列表页面
    django-用户管理案例用户列表页面
    添加用户页面
    django-用户管理案例添加用户页面

用户管理部门管理案例

表设计

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
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) # CharField必须要有max_length,是varchar
password = models.CharField(verbose_name="密码", max_length=64)
age = models.IntegerField(verbose_name="年龄")

# 位数总共10,小数点后面2,默认0
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,生成表是depart_id,django会自动生成
# 3. 删除用户的时候
# ### 3.1 级联删除:部门删除,用户也删除
depart = models.ForeignKey(to="Department", to_field="id", on_delete=models.CASCADE)
# ### 3.2 置空
# depart=models.ForeignKey(to="Department",to_field="id",null=True,blank=True,on_delete=models.SET_NULL)

# 在django中做的约束
gender_choices = (
(1, '男'),
(2, '女')
)
gender = models.SmallIntegerField(verbose_name="性别", choices=gender_choices)

通过url传值

1
path('depart/<int:nid>/edit/', views.depart_edit),

<>里一个正则表达式,可以传一个值。

1
2
3
4
5
6
7
8
def depart_edit(request, nid):
""" 编辑部门 """
if request.method == "GET":
row_object = models.Department.objects.filter(id=nid).first()
return render(request, "depart_edit.html", {"row_object": row_object})
title = request.POST.get("title")
models.Department.objects.filter(id=nid).update(title=title)
return redirect("/depart/list")

模板继承

layout.html里:

1
2
3
<div>
{% block content %}{% endblock %}
</div>

其他html:
1
2
3
4
5
6
{% extends 'layout.html' %}

{% block content %}
...
...
{% endblock %}

  • content名字可以随便。
  • 可以有好几个block,有需要就继承。

一些小语法

  • 时间展示
    1
    <td>{{ obj.create_time|date:"Y-m-d" }}</td>
  • 性别,有django的choices约束
    obj.xxx - > 1 /2
    obj.get_xxx_display() -> 男/女
    1
    <td>{{ obj.get_gender_display }}</td>
  • 多表
    obj.xxx_id,然后再根据这个id去另一个表filter找数据
    obj.xxx.yyy,obj.xxx直接拿到xxx表里对应的那一行
    1
    <td>{{ obj.depart.title }}</td>
  • 排序
    1
    2
    # select * from 表 order by level desc;
    queryset = models.PrettyNum.objects.all().order_by("-level")
    -就是倒序,不带-就是正序。

用 Form 和 ModelForm 之前

添加用户原始方法(表单,POST请求。。)的问题:

  • 用户提交数据没有校验。
  • 出现错误,页面上没有提示。
  • 页面上每一个字段的html都要写一遍。
  • 关联的数据(本案例中的部门、性别)需要手动添加。

    Form

1. views.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class MyForm(Form):
user = forms.CharField(widget=forms.Input)
pwd = form.CharFiled(widget=forms.Input)
email = form.CharFiled(widget=forms.Input)
account = form.CharFiled(widget=forms.Input)
create_time = form.CharFiled(widget=forms.Input)
depart = form.CharFiled(widget=forms.Input)
gender = form.CharFiled(widget=forms.Input)


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

2.user_add.html

1
2
3
4
5
6
<form method="post">
{% for field in form%}
{{ field }}
{% endfor %}
<!-- <input type="text" placeholder="姓名" name="user" /> -->
</form>
1
2
3
4
5
6
<form method="post">
{{ form.user }}
{{ form.pwd }}
{{ form.email }}
<!-- <input type="text" placeholder="姓名" name="user" /> -->
</form>

ModelForm

Form还需要自己写好多字段。
针对数据库中的某个表用,其他的用Form。

0. models.py

1
2
3
4
5
6
7
8
9
10
11
12
13
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

定义MyForm继承·froms.ModelForm

  • fields需要哪些字段就写那些,全都要可以写__all__,还可以exclude=[...,...]不包括某些字段。
  • 两种添加样式的方法,__init__方法更简单。
  • 1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    from django import forms
    class UserModelForm(forms.ModelForm):
    # password = forms.CharField(widget=forms.PasswordInput())
    class Meta():
    model = models.UserInfo
    # fields = ["name", "password", "age", "account", "create_time", "gender", "depart"]
    fields = '__all__'
    # 给每一个 fields 都加上class,可以应用css
    # widgets = {
    # "name": forms.TextInput(attrs={"class": "form-control"}),
    # "password": forms.PasswordInput(attrs={"class": "form-control"})
    # }

    def __init__(self, *args, **kwargs):
    super(UserModelForm, self).__init__(*args, **kwargs)
    for name, field in self.fields.items():
    # 获取定义了的要在页面上显示的字段
    # name:字段名字,field:字段对象
    # if name == "password":
    # field.widget = forms.PasswordInput()
    field.widget.attrs = {"class": "form-control", "placeholder": field.label}

  • 视图函数里要先实例化类。
  • 传入前端,就可以在前端循环的方式让前面类中定义的字段们显示了。
  • 数据校验及错误信息显示。具体椒盐规则在后面。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19

    def user_model_form_add(request):
    """ 添加用户 ModelForm版本 """
    if request.method == "GET":
    form = UserModelForm() # 实例化
    return render(request, "user_model_form_add.html", {"form": form})

    # 数据校验
    # request.POST里包含用户的提交的数据
    # 传进去后每一个字段进行校验
    form = UserModelForm(data=request.POST)
    if form.is_valid():
    # print(form.cleaned_data)
    form.save() # 保存到数据库
    return redirect("/user/list")
    else:
    # print(form.errors)
    return render(request, "user_model_form_add.html", {"form": form})

2.user_model_form_add.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<form method="post" novalidate><!-- novalidate 不要浏览器的检查  -->

{% csrf_token %}

{% for field in form %}
<div class="form-group">
{# field.label 是从定义类的时候里的verbose_name #}
<label>{{ field.label }}</label>
{{ field }}
{# 错误提示信息,会有好多,取第一个 #}
<span style="color: red;">{{ field.errors.0 }}</span>
</div>
{% endfor %}

<button type="submit" class="btn btn-primary">提 交</button>
</form>

数据校验

方式一:正则表达式校验
1
2
3
4
5
6
7
8
9
10
11
12
13
14
from django.core.validators import RegexValidator
class PrettyModelForm(forms.ModelForm):
# 校验方式一
mobile = forms.CharField(
label="手机号",
# 正则表达式校验手机号是不是1开头
validators=[RegexValidator(r'^1[2-9]\d{9}$', "手机号格式不正确")]
)

class Meta:
model = models.PrettyNum
fields = '__all__'

...
方式二:clean_xxx方法校验
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from django.core.exceptions import ValidationError
class PrettyModelForm(forms.ModelForm):


def clean_mobile(self):
# 获取用户输入的数据
txt_mobile = self.cleaned_data['mobile']
# 这个数据是否存在
exist = models.PrettyNum.objects.filter(mobile=txt_mobile).exists()
if exist:
raise ValidationError("手机号已经存在")
if len(txt_mobile) != 11:
# 校验不通过
raise ValidationError("手机号格式错误")
return txt_mobile
添加和更新数据时判断数据是否已经存在的区别

添加的时候校验数据库中没有重复的就如上面的,的那在更新的时候,得要检查除了本条数据之外的是否有重复的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class PrettyEditModelForm(forms.ModelForm):

...

def clean_mobile(self):
txt_mobile = self.cleaned_data['mobile']
# 这个对象的id
pk = self.instance.pk
# 除了这一个之外的exclude
exist = models.PrettyNum.objects.exclude(id=pk).filter(mobile=txt_mobile).exists()
if exist:
raise ValidationError("手机号已经存在")
if len(txt_mobile) != 11:
# 校验不通过
raise ValidationError("手机号格式错误")
return txt_mobile

搜索

1
2
3
4
5
6
7
8
9
10
11
12
13
""" 搜索
从GET中获取到q,做包含q内容的的filter条件,并以字典的形式传入filter。
如果q是空的,就传入空字典。
HTML中搜索框发起GET请求
"""
data_dict = {}
search_data = request.GET.get('q')
# 如果输入为空
if search_data:
data_dict = {"mobile__contains": search_data}
queryset_all = models.PrettyNum.objects.filter(**data_dict).order_by("-level")
# # select * from 表 order by level desc;
# queryset = models.PrettyNum.objects.all().order_by("-level")
1
2
3
4
5
6
7
8
9
10
11
12
13
<div style="float: right;width: 300px;">
<form method="get">
<div class="input-group">
<input type="text" name="q" class="form-control" placeholder="搜搜手机号"
value="{{ search_data }}">
<span class="input-group-btn">
<button class="btn btn-default" type="submit">
<span class="glyphicon glyphicon-search" aria-hidden="true"></span>
</button>
</span>
</div>
</form>
</div>

value="{{ search_data }}是为了搜索结果出了之后,搜索框中依然显示搜索的内容。

分页

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
query_dict = copy.deepcopy(request.GET)

""" 分页
1.获取GET请求中的page参数
2.计算需要展示的数据范围,并从数据库中查询
2.1 count方法获得数据总数
2.2 计算一共有多少页
2.3 根据总页数调整page(当前页)的值,并计算数据范围,获取数据
3.生成页码的html代码
3.1 计算显示的开始页码和结束页码
3.2 拼接html代码,可以加上首页、上一页、下一页、尾页、输入跳转等
"""

# 1.获取GET请求中的page参数,默认是1
# 并将page变为int
try:
page = int(request.GET.get("page", "1"))
except:
page = 1

# 2.计算需要展示的数据范围,并从数据库中查询
# 2.1 count方法获得数据总数
total_count = queryset_all.count()
# 2.2 计算一共有多少页
page_size = 10 # 每一页的数据数量
total_page_count, div = divmod(total_count, page_size) # 返回(整除结果,余数)
if div:
total_page_count += 1
# 输入页数/page参数比总页数还要大
if page > total_page_count:
page = total_page_count
# 没有数据
if total_page_count == 0:
page = 1
# 2.3 根据总页数和输入的数据类型调整page(当前页)的值,并计算数据范围,获取数据
start = (page - 1) * page_size
end = page * page_size

# 3.生成页码的html代码
# '<li><a href="/pretty/list/?page=3">3</a></li>'
# 3.1 计算显示的开始页码和结束页码
page_plus = 5 # 规定一共显示 page_plus*2+1 个页码
# 总数比较少的时候
if total_page_count < 2 * page_plus + 1:
start_page = 1
end_page = total_page_count
else:
# 总数比较多
# 当前页表较小,固定开始页
if page <= page_plus:
start_page = 1
end_page = 2 * page_plus + 1
# 当前页靠近最大页的时候,固定最后一页
elif total_page_count - page_plus <= page <= total_page_count:
start_page = total_page_count - 2 * page_plus
end_page = total_page_count
else:
start_page = page - page_plus
end_page = page + page_plus

# 3.2 拼接html代码,可以加上首页、上一页、下一页、尾页、输入跳转等
page_string_list = []
# 首页、上一页
query_dict.setlist("page", [1])
page_string_list.append(f'<li><a href="/pretty/list/?page=1">首页</a></li>')
if page > 1:
query_dict.setlist("page", [page - 1])
page_string_list.append(f'<li><a href="/pretty/list/?{query_dict.urlencode()}">上一页</a></li>')
else:
query_dict.setlist("page", [1])
page_string_list.append(f'<li><a href="/pretty/list/?{query_dict.urlencode()}">上一页</a></li>')

for i in range(start_page, end_page + 1):
query_dict.setlist("page", [i])
if i == page:
page_string_list.append(f'<li class="active"><a href="/pretty/list/?{query_dict.urlencode()}">{i}</a></li>')
else:
page_string_list.append(f'<li><a href="/pretty/list/?{query_dict.urlencode()}">{i}</a></li>')
# 下一页、尾页
if page < total_page_count:
query_dict.setlist("page", [page + 1])
page_string_list.append(f'<li><a href="/pretty/list/?{query_dict.urlencode()}">下一页</a></li>')
else:
query_dict.setlist("page", [total_page_count])
page_string_list.append(f'<li><a href="/pretty/list/?{query_dict.urlencode()}">下一页</a></li>')
query_dict.setlist("page", [total_page_count])
page_string_list.append(f'<li><a href="/pretty/list/?{query_dict.urlencode()}">尾页</a></li>')
search_string = """
<li>
<form style="float: left;margin-left: -1px" method="get">
<input name="page"
style="position: relative;float:left;display: inline-block;width: 80px;border-radius: 0;"
type="text" class="form-control" placeholder="页码">
<button style="border-radius: 0" class="btn btn-default" type="submit">跳转</button>
</form>
</li>
"""

page_string_list.append(search_string)
page_string_list.append(f'<span>共{total_page_count}页</span>')

page_string = mark_safe("".join(page_string_list))

context = {
"queryset": queryset_all[start:end],
"search_data": search_data,
"page_string": page_string,
"range": range(start_page, end_page + 1),
"page": page,
"total_page_count": total_page_count
}

return render(request, "pretty_list.html", context=context)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
<div class="clearfix">
<ul class="pagination">
{{ page_string }}
</ul>
</div>

{#前端直接生成#}
<div class="clearfix">
<ul class="pagination">
<li><a href="/pretty/list/?page=i">首页</a></li>

{% if page > 1 %}
<li><a href="/pretty/list/?page={{ page|add:-1 }}">上一页</a></li>
{% else %}
<li><a href="/pretty/list/?page=1">上一页</a></li>
{% endif %}
{% for i in range %}
{% if i == page %}
<li class="active"><a href="/pretty/list/?page={{ i }}">{{ i }}</a></li>
{% else %}
<li><a href="/pretty/list/?page={{ i }}">{{ i }}</a></li>
{% endif %}
{% endfor %}
{% if page < total_page_count %}
<li><a href="/pretty/list/?page={{ page|add:1 }}">下一页</a></li>
{% else %}
<li><a href="/pretty/list/?page={{ total_page_count }}">下一页</a></li>
{% endif %}
<li><a href="/pretty/list/?page={{ total_page_count }}">尾页</a></li>

<li>
<form style="float: left;margin-left: -1px" method="get">
<input name="page"
style="position: relative;float:left;display: inline-block;width: 80px;border-radius: 0;"
type="text" class="form-control" placeholder="页码">
<button style="border-radius: 0" class="btn btn-default" type="submit">跳转</button>
</form>
</li>
<span>共{{ total_page_count }}页</span>

</ul>
</div>

这里有两种方法,一种是视频当中的在后台生成html然后传入到模板,一种是直接在HTML中写。
并且我这个解决了一小bug,做了一些小优化:

  • 删除操作后依然停留在当前页面;
  • 最后一页删除完了,到倒数第二页(也就是新的最后一页);
  • 没有数据的时候显示在第一页;
    但是,搜索+分页的时候,去了别的页搜索框里的内容就没了的小bug在直接生成HTMl的方法里没有解决。

按照教程写成了组件的形式:
存留的问题是在跳转页面的时候,不能继续博阿留搜索内容。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
class Pagination(object):

def __init__(self, request, queryset, page_size=10, page_param="page", plus=5):
"""
:param request: 请求的对象
:param queryset: 符合条件的数据(根据这个数据给他进行分页处理)
:param page_size: 每页显示多少条数据
:param page_param: 在URL中传递的获取分页的参数,例如:/pretty/list/?page=12
:param plus: 显示当前页的 前或后几页(页码)
"""

query_dict = copy.deepcopy(request.GET)
# query_dict._mutable = True
self.query_dict = query_dict

self.page_param = page_param

# 1.获取GET请求中的page参数,默认是1
# 并将page变为int
try:
page = int(request.GET.get(self.page_param, "1"))
except:
page = 1

# 2.计算需要展示的数据范围,并从数据库中查询
# 2.1 count方法获得数据总数
total_count = queryset.count()
# 2.2 计算一共有多少页
self.page_size = page_size # 每一页的数据数量
total_page_count, div = divmod(total_count, self.page_size) # 返回(整除结果,余数)
if div:
total_page_count += 1
# 输入页数/page参数比总页数还要大
if page > total_page_count:
page = total_page_count
# 没有数据
if total_page_count == 0:
page = 1
# 2.3 根据总页数和输入的数据类型调整page(当前页)的值,并计算数据范围,获取数据
self.start = (page - 1) * page_size
self.end = page * page_size
self.queryset = queryset[self.start:self.end]
self.page = page
self.total_page_count = total_page_count
self.page_plus = plus

def html(self):
# 3.生成页码的html代码
# '<li><a href="/pretty/list/?page=3">3</a></li>'
# 3.1 计算显示的开始页码和结束页码
# 总数比较少的时候
if self.total_page_count < 2 * self.page_plus + 1:
start_page = 1
end_page = self.total_page_count
else:
# 总数比较多
# 当前页表较小,固定开始页
if self.page <= self.page_plus:
start_page = 1
end_page = 2 * self.page_plus + 1
# 当前页靠近最大页的时候,固定最后一页
elif self.total_page_count - self.page_plus <= self.page <= self.total_page_count:
start_page = self.total_page_count - 2 * self.page_plus
end_page = self.total_page_count
else:
start_page = self.page - self.page_plus
end_page = self.page + self.page_plus

# 3.2 拼接html代码,可以加上首页、上一页、下一页、尾页、输入跳转等
page_string_list = []
# 首页、上一页
self.query_dict.setlist("page", [1])
page_string_list.append(f'<li><a href="/pretty/list/?{self.query_dict.urlencode()}">首页</a></li>')
if self.page > 1:
self.query_dict.setlist("page", [self.page - 1])
page_string_list.append(f'<li><a href="/pretty/list/?{self.query_dict.urlencode()}">上一页</a></li>')
else:
self.query_dict.setlist("page", [1])
page_string_list.append(f'<li><a href="/pretty/list/?{self.query_dict.urlencode()}">上一页</a></li>')

for i in range(start_page, end_page + 1):
self.query_dict.setlist("page", [i])
if i == self.page:
page_string_list.append(
f'<li class="active"><a href="/pretty/list/?{self.query_dict.urlencode()}">{i}</a></li>')
else:
page_string_list.append(f'<li><a href="/pretty/list/?{self.query_dict.urlencode()}">{i}</a></li>')
# 下一页、尾页
if self.page < self.total_page_count:
self.query_dict.setlist("page", [self.page + 1])
page_string_list.append(f'<li><a href="/pretty/list/?{self.query_dict.urlencode()}">下一页</a></li>')
else:
self.query_dict.setlist("page", [self.total_page_count])
page_string_list.append(f'<li><a href="/pretty/list/?{self.query_dict.urlencode()}">下一页</a></li>')
self.query_dict.setlist("page", [self.total_page_count])
page_string_list.append(f'<li><a href="/pretty/list/?{self.query_dict.urlencode()}">尾页</a></li>')
search_string = """
<li>
<form style="float: left;margin-left: -1px" method="get">
<input name="page"
style="position: relative;float:left;display: inline-block;width: 80px;border-radius: 0;"
type="text" class="form-control" placeholder="页码">
<button style="border-radius: 0" class="btn btn-default" type="submit">跳转</button>
</form>
</li>
"""

page_string_list.append(search_string)
page_string_list.append(f'<span>共{self.total_page_count}页</span>')

page_string = mark_safe("".join(page_string_list))

return page_string

时间插件

ModelForm和BootStrap

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

    1
    2
    3
    4
    5
    6
    class UserModelForm(forms.ModelForm):
    class Meta:
    model = models.UserInfo
    fields = ["name", "password",]

    form = UserModelForm()
    1
    2
    {{form.name}}      普通的input框
    {{form.password}} 普通的input框
  • 定义插件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    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"}),
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11

    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"]
    1
    2
    {{form.name}}      BootStrap的input框
    {{form.password}} BootStrap的input框
  • 重新定义的init方法,批量设置

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    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
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    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
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    class UserEditModelForm(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
    }
  • 自定义类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    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
    }
    1
    2
    3
    4
    class UserEditModelForm(BootStrapModelForm):
    class Meta:
    model = models.UserInfo
    fields = ["name", "password", "age",]

管理员操作

登录

django-http短链接无状态

http或者https连接有短链接和无状态的特点:

  • 短链接:浏览器向服务器发起一次请求,服务器做出一次响应,然后就会断开连接
  • 无状态:浏览器每次发起请求,服务器都会认为是“新人”,没有之前的状态。

因此需要cookie和session。

浏览器向服务器发起一个请求,服务器会返还响应体(就是我们能看到的页面)和响应头,其中cookie就包含在响应头当中。cookie可以认为是一个键值对,其中的值是服务器生成的随机字符串。浏览器下一次发起请求的时候就会带上这个cookie。

在服务器上,会有一个地方叫session,里面放着字符串和对应的一些信息,当浏览器带着cookie发来请求后,服务器找到有这么个cookie就可以认为他已经是登陆了的,并且可以返还该cookie对应的一些信息。

django的cookie、session信息保存在django_session表中。

cookie和session

登录的代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
from django.shortcuts import render, redirect
from django import forms

from app01 import models
from app01.utils.bootstrap import BootStrapForm
from app01.utils.encrypt import md5


class LoginForm(BootStrapForm):
username = forms.CharField(
label="用户名",
widget=forms.TextInput,
# required=True, # 必填项,这一个不能为空,默认的
)
password = forms.CharField(
label="密码",
widget=forms.PasswordInput(render_value=True)
# required=True, # 必填项,这一个不能为空,默认的
)

def clean_password(self):
pwd = self.cleaned_data.get('password')
return md5(pwd)


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():
row_object = models.Admin.objects.filter(**form.cleaned_data).first()
if not row_object:
# 用户名或密码不正确
form.add_error("password", "用户名或密码不正确")
return render(request, "login.html", {"form": form})

# 用户名密码正确
request.session['info'] = {"id": row_object.id, "name": row_object.username}
return redirect("/admin/list")

# 用户名或密码验证不通过
return render(request, "login.html", {"form": form})

重点:

1
2
# 生成cookie给浏览器
request.session['info'] = {"id": row_object.id, "name": row_object.username}

bootStrap中创建了BootStrap类,BootStrapFormBootStrapModelForm同时继承BootStrap和各自的FormModelForm

中间件

有些页面需要登陆后才能被看到,因此:

1
2
3
info = request.session.get('info')
if not info:
return redirect('/login')

在需要的页面加上这样的检查,因为如果没有登陆,info的值是空,就说明他没有登陆,于是重订向到登陆的页面。

但页面有好多,不能每个试图函数都要写,于是就有中间件。

django中间件

中间件就是类。请求会按次序经过一些中间件,全都通过后才会到视图函数,试图函数的响应也会按来的相反顺序往回走。如果在某个中间件没有通过,就立刻返回。

中间件实现登录校验

  1. app01中创建目录middleware,再创建auth.py

  2. 编写中间件:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    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 in ["/login/", "/image/code/","/depart/list/","/user/list/","/pretty/list/"]:
    return

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

    # 2.没有登录过,重新回到登录页面
    return redirect('/login/')

  3. settings.py中的MIDDLEWARE加上这个中间件:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    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',
    ]

说明:

  • 还可以有process_response函数,但可以只有process_request函数;

  • 函数没有返回值,就可以继续走下去。

  • ~~~ python

    request.path_info 获取当前用户请求的URL /login/

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15

    + `"/login/", "/image/code/","/depart/list/","/user/list/","/pretty/list/"`首尾都要有`/`

    ```python
    if request.path_info in ["/login/", "/image/code/","/depart/list/","/user/list/","/pretty/list/"]:
    ```


    #### 注销

    ~~~ Python
    def logout(request):
    """ 注销 """
    request.session.clear()
    return redirect("/login")

session信息清除掉。

当前用户

只需要在模板当中{{ request.session.info.name }}

因为我是列表信息页面是不需要登陆就可以的,所以我的模板代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{% if not request.session.info.name %}
<li><a href="/login">登录</a></li>
{% else %}
<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 role="separator" class="divider"></li>
<li><a href="/logout">注销</a></li>
</ul>
</li>
{% endif %}

验证码

Ajax请求

图表

文件上传

  1. 基本

    视图函数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    def upload_list(request):
    if request.method == "GET":
    return render(request, "upload_list.html")

    # # <QueryDict: {'csrfmiddlewaretoken': ['VkXX3whWxQKJwGeQFIROMQvCGqcSlmRP45HY5mjjVrWJyGPOMlr2w2uclrGz8gLe'], 'username': ['22']}>
    # print(request.POST)
    # # < MultiValueDict: {'avatar': [ < InMemoryUploadedFile: 分公司业绩图.png(image / png) >]} >
    # print(request.FILES)
    # # 分公司业绩图.png
    # print(request.FILES.get('avatar'))

    file_object = request.FILES.get('avatar')

    f = open('bbb.png', 'wb')
    for chunk in file_object.chunks():
    f.write(chunk)
    f.close()
    return HttpResponse("...")

    前端HTML:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    {% extends 'layout.html' %}

    {% block content %}
    <div class="container">
    <form method="post" enctype="multipart/form-data">
    {% csrf_token %}
    <input type="text" name="username">
    <input type="file" name="avatar">
    <input type="submit" value="提交">
    </form>

    </div>
    {% endblock %}

    注意:

    1. <form method="post" enctype="multipart/form-data">为了能得到文件对象而不仅仅是文件名,需要加上enctype="multipart/form-data"
    2. 表单的method必须是POST.
  2. Excel案例

    以批量上传新增部门为例,上传一个文件。

    depart_list.html中做修改:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    {% extends 'layout.html' %}

    {% block content %}
    <div class="container">
    <div class="panel panel-default">
    <!-- Default panel contents -->
    <div class="panel-heading">
    <span class="glyphicon glyphicon-th-list" aria-hidden="true"></span>
    批量上传
    </div>
    <div class="panel-body">
    <form method="post" enctype="multipart/form-data" action="/depart/multi/">
    {% csrf_token %}
    <div class="form-group">
    <input type="file" name="exc">
    </div>
    <input type="submit" value="上传" class="btn btn-info btn-sm">
    </form>
    </div>
    </div>
    <div style="margin-bottom: 10px">
    <a class="btn btn-success" href="/depart/add/">
    <span class="glyphicon glyphicon-plus-sign" aria-hidden="true"></span>
    新建部门
    </a>
    </div>
    <div class="panel panel-default">
    <!-- Default panel contents -->
    <div class="panel-heading">
    <span class="glyphicon glyphicon-th-list" aria-hidden="true"></span>
    部门列表
    </div>

    <!-- Table -->
    <table class="table table-bordered">
    <thead>
    <tr>
    <th>ID</th>
    <th>名称</th>
    <th>操作</th>
    </tr>
    </thead>
    <tbody>
    {% for obj in queryset %}
    <tr>
    <th>{{ obj.id }}</th>
    <td>{{ obj.title }}</td>
    <td>
    <a class="btn btn-primary btn-xs" href="/depart/{{ obj.id }}/edit/">编辑</a>
    <a class="btn btn-danger btn-xs" href="/depart/delete/?nid={{ obj.id }}">删除</a>
    </td>
    </tr>
    {% endfor %}
    </tbody>
    </table>
    </div>
    </div>
    {% endblock %}

    视图函数:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    def multi(request):
    """ 批量上传(Excel文件) """
    # 1.获取用户上传的文件对象
    file_object = request.FILES.get('exc')
    # print(type(file_object)) # <class 'django.core.files.uploadedfile.InMemoryUploadedFile'>
    if not file_object: # 没有上传文件
    return redirect('/depart/list/')
    # 2.直接打开Excel读取数据
    from openpyxl import load_workbook
    wb = load_workbook(file_object) # 直接放文件对象
    sheet = wb.worksheets[0]

    # cell = sheet.cell(1, 1)
    # print(cell.value)
    # 3.循环获取每一行
    for row in sheet.iter_rows(min_row=2): # 从第二行开始
    text = row[0].value
    # print(text)
    if not models.Department.objects.filter(title=text).exists():
    models.Department.objects.create(title=text)
    return redirect('/depart/list/')
  3. media

    settings.py加入

    1
    2
    3
    4
    import os

    MEDIA_ROOT = os.path.join(BASE_DIR, "media")
    MEDIA_URL = "/media/"

    urls.py加上re_path,并且将urlpatterns改为列表[].

    1
    2
    3
    4
    5
    6
    7
    8
    from django.conf import settings
    from django.urls import path, re_path
    from django.views.static import serve

    urlpatterns = [
    re_path(r'^media/(?P<path>.*)$', serve, {'document_root': settings.MEDIA_ROOT}, name='media'),
    ...
    ]
  1. Form案例

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    from app01.utils.bootstrap import BootStrapForm


    class UpForm(BootStrapForm):
    bootstrap_exclude_fields = ['img']
    name = forms.CharField(label="姓名")
    age = forms.IntegerField(label="年龄")
    img = forms.FileField(label="头像")

    def upload_form(request):
    title = "Form上传"
    if request.method == "GET":
    form = UpForm()
    return render(request, "upload_form.html", {"title": title, "form": form})

    form = UpForm(data=request.POST, files=request.FILES)
    if form.is_valid():
    # print(form.cleaned_data)
    # 1.读取图片内容,写入到文件夹中并获取文件的路径。
    image_object = request.FILES.get('img')

    # media_path = os.path.join(settings.MEDIA_ROOT, image_object.name)
    media_path = os.path.join('media/', image_object.name)

    f = open(media_path, "wb")
    for chunk in image_object.chunks():
    f.write(chunk)
    f.close()

    # 2.将图片文件路径写入到数据库
    models.Boss.objects.create(
    name=form.cleaned_data['name'],
    age=form.cleaned_data['age'],
    img=media_path,
    )
    # return HttpResponse("....")
    return redirect("/upload/form/")
    return render(request, "upload_form.html", {"title": title, "form": form})

    • 在类Bootstrap中加上类属性bootstrap_exclude_fields = [],这样如果有什么字段不想设置bootstrap样式就写进去。
    • Boss表已经出感慨呢。
  2. ModelForm案例

    models.py

1
2
3
4
5
6
7
8
class City(models.Model):
""" 城市 """
name = models.CharField(verbose_name="名称", max_length=32)
count = models.IntegerField(verbose_name="人口")

# 本质上数据库也是CharField,自动保存数据。
img = models.FileField(verbose_name="Logo", max_length=128, upload_to='city/')

视图函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class UpModelForm(BootStrapModelForm):
class Meta:
model = models.City
fields = '__all__'


def upload_modal_form(request):
""" 上传问阿和数据(ModelForm) """
title = "ModelForm上传"
if request.method == "GET":
form = UpModelForm()
return render(request, "upload_form.html", {"form": form})
form = UpModelForm(data=request.POST, files=request.FILES)
if form.is_valid():
form.save()
return HttpResponse("上传成功")
return render(request, "upload_form.html", {"form": form})

  • models.FileFieldupload_to放在media下的那个文件夹。
  • 直接save