安装
1 | pip install django |
安装完之后在环境目录D:\Users\11200\anaconda3\envs\web
下会有:
- python.exe
- Scripts
- pip.exe
- django-admin.exe
- Lib
- 内置模块
- site-packages
- 第三方模块
- django
创建项目
终端
- 进入终端
- 进入想把项目放在的目录下
- 执行命令
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 | from django.shortcuts import render,HttpResponse |
- 选哟一个默认参数
request
启动项目
- 终端
命令:python manage.py runserver
- PyCharm
本质也是知名上面的命令。
结果:
templates模板
在函数里返回HTML文件:1
2def user_list(request):
return render(request,"user_list.html")
- 参数1:传入的request;
- 需要返回的HTML文件;
如何寻找HTML文件?
- 如果按照pycharm默认生成的格式,会有限到项目的根目录下去找;
- 将根目录下的templates删了,按照app注册的顺序,一个个去app目录下的templates目录下去找。
静态文件
在开发过程中,一般将:
- 图片
- CSS
- js
作为静态文件。
需要在app目录下创建static
目录,在这个目录下,再创建一些目录:- css
- img
- js
- plugins
例子:
user_list.html1
2
3
4
5
6
7
8
9
10
11
12
13
14
15{% load static %}
<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
12def 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
<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>
结果:
注意:
- if语句里的
==
前后得要有空格; {{ }}
两个花括号,取出其中的内容,{% %}
花括号加百分号,是类似python的语法。
模板的流程
模板的语法是django开发的,用户浏览器发起请求后,django会经过urls.py
和views.py
找到对应的视图函数以及返回的html文件,将htnl的内容以字符串的方式都进来,然后替换掉其中的模板语法,再泛函给用户浏览器。
案例
视频教程用的是中国联通新闻中心的数据,但是我爬不下来,所以用了疫情的数据。
视图函数
1 | def epidemic(request): |
HTML代码
1 | {% load myfilter %} |
字符串分割
在app目录下创建templatetags
文件夹,在该文件夹下创建__init__.py
和myfilter.py
两个文件,myfilter
名字当然随便。其中myfilter.py
的内容为:1
2
3
4
5
6
7
8
9
10
11
12from django.template import Library
register = Library()
def split(value, key):
"""
Returns the value turned into a list.
"""
return value.split(key)
在HTML的开始{% load myfilter %}
,这样在HTML中就可以使用split
方法了,用法建上面代码。
结果
在手机(其他主机)上查看
- 在
settings.py
中将ALLOWED_HOSTS
改为['*']
,表示任何主机都可以访问,否则会拒绝访问; - 在终端执行命令
python manage.py runserver 0.0.0.0:8000
,得是这个ip; - 关闭系统防火墙,或者允许程序访问;
- 在手机(其他主机)上通过地址访问
192.168.10.9:8000/epidemic
。 - 在本机上查看依然是用
127.0.0.1:8000/
请求和响应
1 | def something(request): |
三个请求:method,GET,POST
三个响应:字符串,页面,重定向
案例:用户登录
1 | # 用户登录 |
1 |
|
- 表单提交数据后依然返回到
login
这个函数,但是请求方式变为了POST
,所以可以通过请求方式的不同来做出不同的处理,GET
就返回登陆页面,Post
可以获得填写的数据; - 在表单里需要写上
{% csrf_token %}
,,可以理解为django会做一个token的判断,如果不对,django会阻拦。
数据库操作
当然可以用pymysql
这样的库,但django用了ORM的框架。
ORM的框架:
安装第三方模块
pip install mysqlclient
ORM
ORM可以做两件事:
- 创建、修改和删除数据库中的表,但不能创建数据库。
- 操作表中的数据。
- 创建数据库
在命令行连接Mysql,创建数据库django_test02
MySQL数据库的版本要在5.6以上 - djangoo连接数据库
在settings.py
中,1
2
3
4
5
6
7
8
9
10DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'django_test02', # 数据库的名字
'USER': 'root',
'PASSWORD': '123456',
'HOST': '127.0.0.1',
'PORT': 3306,
}
}django底层会执行这样的sql语句,创建的表名是app名加下划线加类名的小写,还会自动生成一个id字段。1
2
3
4
5
6
7
8
9
10
11
123. 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()执行命令:1
2
3
4
5
6create 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默认给的app里的models.py都生成。
- django操作表(删除表或字段)
直接将不要了的表或字段注释或去掉,然后重新执行命令。 - django操作表(新增字段)
在执行命令的时候,
- 给一个值;
- 退出,在字段中加入
default=
; - 允许空值
numm=True,blank=True
。1
2
3
4size = models.IntegerField()
age2 = models.IntegerField(default=18)
data = models.IntegerField(null=True, blank=True)
- 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
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
<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> - 添加用户
视图函数:前端: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
<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> 删除用户
想法是通过地址发起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>效果
用户列表页面
添加用户页面
用户管理部门管理案例
表设计
1 | from django.db import models |
通过url传值
1 | path('depart/<int:nid>/edit/', views.depart_edit), |
<>
里一个正则表达式,可以传一个值。1
2
3
4
5
6
7
8def 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 /2obj.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请求。。)的问题:
1. views.py
1 | class MyForm(Form): |
2.user_add.html
1 | <form method="post"> |
1 | <form method="post"> |
ModelForm
Form还需要自己写好多字段。
针对数据库中的某个表用,其他的用Form。
0. models.py
1 | class UserInfo(models.Model): |
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
22from 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 | <form method="post" novalidate><!-- novalidate 不要浏览器的检查 --> |
数据校验
方式一:正则表达式校验
1 | from django.core.validators import RegexValidator |
方式二:clean_xxx方法校验
1 | from django.core.exceptions import ValidationError |
添加和更新数据时判断数据是否已经存在的区别
添加的时候校验数据库中没有重复的就如上面的,的那在更新的时候,得要检查除了本条数据之外的是否有重复的。
1 | class PrettyEditModelForm(forms.ModelForm): |
搜索
1 | """ 搜索 |
1 | <div style="float: right;width: 300px;"> |
value="{{ search_data }}
是为了搜索结果出了之后,搜索框中依然显示搜索的内容。
分页
1 | query_dict = copy.deepcopy(request.GET) |
1 | <div class="clearfix"> |
这里有两种方法,一种是视频当中的在后台生成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
114class 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
6class 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
9class 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
14class 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
19class 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
19class 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
14class 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
4class UserEditModelForm(BootStrapModelForm):
class Meta:
model = models.UserInfo
fields = ["name", "password", "age",]
管理员操作
略
登录
Http,cookie,session
http或者https连接有短链接和无状态的特点:
- 短链接:浏览器向服务器发起一次请求,服务器做出一次响应,然后就会断开连接
- 无状态:浏览器每次发起请求,服务器都会认为是“新人”,没有之前的状态。
因此需要cookie和session。
浏览器向服务器发起一个请求,服务器会返还响应体(就是我们能看到的页面)和响应头,其中cookie就包含在响应头当中。cookie可以认为是一个键值对,其中的值是服务器生成的随机字符串。浏览器下一次发起请求的时候就会带上这个cookie。
在服务器上,会有一个地方叫session,里面放着字符串和对应的一些信息,当浏览器带着cookie发来请求后,服务器找到有这么个cookie就可以认为他已经是登陆了的,并且可以返还该cookie对应的一些信息。
django的cookie、session信息保存在django_session表中。
登录的代码实现
1 | from django.shortcuts import render, redirect |
重点:
1 | # 生成cookie给浏览器 |
在bootStrap
中创建了BootStrap
类,BootStrapForm
和BootStrapModelForm
同时继承BootStrap
和各自的Form
和ModelForm
。
中间件
有些页面需要登陆后才能被看到,因此:
1 | info = request.session.get('info') |
在需要的页面加上这样的检查,因为如果没有登陆,info
的值是空,就说明他没有登陆,于是重订向到登陆的页面。
但页面有好多,不能每个试图函数都要写,于是就有中间件。
中间件就是类。请求会按次序经过一些中间件,全都通过后才会到视图函数,试图函数的响应也会按来的相反顺序往回走。如果在某个中间件没有通过,就立刻返回。
中间件实现登录校验
在
app01
中创建目录middleware
,再创建auth.py
;编写中间件:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21from 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/')在
settings.py
中的MIDDLEWARE
加上这个中间件:1
2
3
4
5
6
7
8
9
10MIDDLEWARE = [
'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 | {% if not request.session.info.name %} |
验证码
Ajax请求
图表
文件上传
基本
视图函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19def 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 %}注意:
<form method="post" enctype="multipart/form-data">
为了能得到文件对象而不仅仅是文件名,需要加上enctype="multipart/form-data"
- 表单的
method
必须是POST
.
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
21def 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/')media
settings.py
加入1
2
3
4import os
MEDIA_ROOT = os.path.join(BASE_DIR, "media")
MEDIA_URL = "/media/"urls.py
加上re_path
,并且将urlpatterns
改为列表[]
.1
2
3
4
5
6
7
8from 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'),
...
]
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
39from 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表已经出感慨呢。
- 在类
ModelForm案例
models.py
1 | class City(models.Model): |
视图函数:
1 | class UpModelForm(BootStrapModelForm): |
models.FileField
,upload_to
放在media
下的那个文件夹。- 直接
save