Django正式版
[项目源码及其相关文件](链接:https://pan.baidu.com/s/1d6qLi0uzyUnqwI-4XX1MSw?pwd=lqj1
提取码:lqj1)
提取码:lqj1
1. 请求
1.通过表单请求能通过GET、POST请求
2.通过URL方法只能通过GET请求
2. APP
2.1 创建App
python manage.py startapp app01
2.1.1 报错:
2.1.2 原因:
没有导入os模块
2.1.3 解决:
在前面导入os模块
import os
2.2 注册App
'app01.apps.App01Config'
或者
app01
3. URL与视图
3.1 编写URL与视图函数的对应关系【urls.py】
访问url:www.xxx/index/时,运行views.index页面
3.2 编写视图函数【views.py】
导入HttpResponse模块,注意函数的
默认参数为request
4. 启动Django
-
直接启动
-
命令行
python manage.py runserver
5. templates模板
通过render,会自动找到templates目录下的user_list.html,返回到浏览器
注意:当项目根目录下和App目录下同时含有templates文件夹时
1.如果配置为'DIRS': [os.path.join(BASE_DIR, 'templates')]时,
优先到项目根目录下的templates寻找。
2.如果配置为'DIRS': []时,则按App注册的顺序依次到App目录下的templates中寻找。(一般开发环境默认为此)
6. 静态文件
6.1 问题:
图片无法加载。
6.2 解决:
重启项目,实在不行,退出pycharm,再重新打开并启动项目。
注意:
错误路径:<img src="/app01/static/img/OIP-C.jpg">
正确路径:<img src="/static/img/OIP-C.jpg">
或者
(Django推荐)<img src="{% static 'img/图.jpg'%}" alt="图片加载失败">
6.3 引入静态文件
Django等Web特有的:在前面写上占位符{% load static %} ,之后的路径相当于static与其他的路径进行字符串的拼接来形成最终的完整路径
7. 模板语法
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div>{{ n1 }}</div>
<hr/>
{#列表#}
<div>
{% for item in n2 %}
<div>{{ item }}</div>
{% endfor %}
</div>
<hr/>
{#字典里面所有的键#}
<div>
{% for item in n3.keys %}
<div>{{ item }}</div>
{% endfor %}
</div>
<hr/>
{#字典里面的所有的值#}
<div>
{% for item in n3.values %}
<div>{{ item }}</div>
{% endfor %}
</div>
<hr/>
{#获取所有的键和值#}
<div>
{% for k,v in n3.items %}
<div>{{ k }}={{ v }}</div>
{% endfor %}
</div>
<hr/>
{#列表里放字典#}
<div>
{#获取字典#}
{{ n4.1 }}
{#获取字典的值#}
{{ n4.1.name }}
{{ n4.1.age }}
{{ n4.1.describe }}
</div>
{#for循环获取字典的值#}
<div>
{% for item in n4 %}
<div>{{ item.name }} {{ item.age }} {{ item.describe }}</div>
{% endfor %}
</div>
{% if n1 == "星黛露" %}
<h1>lall</h1>
{% elif n1 == "kk"%}
<h2>hahha</h2>
{% else %}
<h3>ooooo</h3>
{% endif %}
</body>
</html>
7.1 流程
8. 请求与响应
注意:redirect【重定向】 要记得引入redirect模块
9. 数据库操作
ORM框架:起一个翻译作用,将Django的代码翻译成SQL语句。
注意:ORM只能创建、修改、删除表,不能创建数据库
9.1 Django连接数据库
9.1.1 创建数据表
-
安装第三方模块mysqlclient
pip install mysqlclient
-
在settings.py文件中进行配置与修改
-
Django操作表
在【models.py】文件中进行书写。
经过orm模块翻译后自动生成SQL,如下:
注意:默认自动生成id,无论原来写没写,经过orm模块翻译后都会默认生成id
create table app01_userinfo(
id bigint auto_increment primary key,
name varchar(225),
password varchar(225),
age int
)
-
执行命令,生成SQL语句
注意:前提是已经注册了App,否则是无法成功生成数据表的
python manage.py makemigrations python manage.py migrate
另外:我们原本只创建了一个app01_userinfo的表,但这里一下子新建了很多表,这是因为django默认还提供了很多App(如下图INSTALLED—APPS所示),要实现Django内置提供的功能需要依赖数据库的一些表,所以在生成时就会创建这些表。
9.1.2 删除数据表或某一列
直接注释或者删除掉相关语句,再执行命令即可实现删除某一数据表或者某一列
删除成功
9.1.3 添加新的一列
age=models.IntegerField()
执行python manage.py makemigrations语句后会出现如下图提示(按1则输入添加的列要填充的数据 按2则取消添加列)
describe=models.IntegerField(null=True, blank=True)
添加的列数据为空(null) models.IntegerField(null=True, blank=True)不报错 models.CharField(null=True, blank=True)报错
size=models.IntegerField(default=2)
添加的列数据为空(null) size=models.IntegerField(default=2) size=models.CharField(default=2)报错
9.1.4 表中数据的增删改查
# 引用models.py
from app01 import models
def orm(request):
# 增加
# models.Userinfo.objects.create(name="星黛露", password="xdl", age=6)
# models.Userinfo.objects.create(name="玲娜贝儿", password="lnbe", age=6)
# models.Userinfo.objects.create(name="唐老鸭", password="tly", age=9)
# return HttpResponse("添加成功!")
# 删除
# 按id删除
# models.Userinfo.objects.get(id=2).delete()
# 删除表中所有数据
# models.Userinfo.objects.all().delete()
# return HttpResponse("删除成功!")
# 修改
# models.Userinfo.objects.all().update(age=7)
# models.Userinfo.objects.filter(id=7).update(age=6)
# return HttpResponse("修改成功!")
# 获取数据
# data_list=[对象,对象,对象] QuerySet类型
# data_list=models.Userinfo.objects.all()
# for obj in data_list:
# print(obj.id,obj.name,obj.password,obj.age)
# 只获取第一条数据
data_first=models.Userinfo.objects.first()
print(data_first.id,data_first.name,data_first.password,data_first.age)
return HttpResponse("获取成功")
9.1.5 案例总结
{% csrf_toke %}
作用:
是防御CSRF攻击。
如何生成?
在 HTTP 请求中以参数的形式加入一个随机产生的 token,并在服务器端建立一个拦截器来验证这个 token,如果请求中没有 token 或者 token 内容不正确,则认为可能是 CSRF 攻击而拒绝该请求。
应用:
post请求时一定要在表单中:添加隐藏字段{% csrf_toke %},来启用csrftoken验证
没在表单中添加{% csrf_toke %}会报错:
9.1.6 联表操作
部门被删除时,对用户的处理 1.级联删除: 当部门删除时,部门所对应得用户也被删除 to="要关联的部门" to_field="要关联的那一列" 写 depart 生成数据时django会自动生成depart_id
depart=models.ForeignKey(to="Department",to_field="id",on_delete=models.CASCADE)
2.将用户的depart_id置空
depart=models.ForeignKey(to="Department",to_field="id",null=True,blank=True,on_delete=models.SET_NULL)
10. 模板的继承
10.1 使用例子:
父类:
继承模板:
子类:
10.2 ModelForm(对数据库中的某个表的增删改查 推荐)
注意:可以在class MyForm(MOdelForm)中添加原本Model创建的数据库里没有的数据,比如上面的”xx“,添加之后会在数据库中自动生成。
{{form.user}}相当于生成下面的input框<input type="text" placeholder="姓名" name="user">
10.3 遇到的问题
问题:
解决:
实例化对象时输出的是地址,__str__(self)可以打印具体的属性
# Create your models here.
class Department(models.Model):
title=models.CharField(verbose_name="标题",max_length=225)
def __str__(self):
return self.title
10.4 使用ModelForm后欲添加别的样式
10.5 将英文的错误提示改为中文的
在【settings.py】中将LANGUAGE_CODE的‘en-us’改为‘zh-hans’.
10.6 ModelForm的增删改查
class UserModelForm(ModelForm):
class Meta:
model = models.Employee
fields = ["name", "password", "age", "account", "join_time", "depart", "gender"]
# 重写方法 加上别的样式
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
for name, field in self.fields.items():
# 密码不用选
# if name == "password":
# continue
field.widget.attrs = {"class": "form-control", "placeholder": field.label}
10.6.1 增加
def user_modelform_add(request):
if request.method == "GET":
# 获取数据库的相关数据
form = UserModelForm()
return render(request, 'user_modelform_add.html', {"form": form})
else:
form=UserModelForm(data=request.POST)
# 合法则保存没到数据库
if form.is_valid():
form.save()
return redirect('/usermanage/list/')
else:
print(form.errors)
return render(request, 'user_modelform_add.html', {"form": form})
10.6.2 编辑
def user_modelform_edit(request,nid):
if request.method == "GET":
# 根据ID去数据库获取要编辑的那一行数据(对象)
row_object=models.Employee.objects.filter(id=nid).first()
form = UserModelForm(instance=row_object)
return render(request, 'user_modelform_edit.html', {"form": form})
else:
# 获取当前要进行更新操作的对象
row_object=models.Employee.objects.filter(id=nid).first()
# instance=当前要进行更新操作的对象
form=UserModelForm(data=request.POST,instance=row_object)
# 合法则保存没到数据库
if form.is_valid():
form.save()
return redirect('/usermanage/list/')
else:
print(form.errors)
return render(request, 'user_modelform_edit.html', {"form": form})
10.6.3 删除
def user_modelform_delete(request,nid):
models.Employee.objects.filter(id=nid).delete()
return redirect('/usermanage/list/')
11. 靓号管理
11.1 问题1:
这个的等级和状态你不是直接显示数字,应该直接显示数字所对应的中文内容
11.2 解决:
这样可以直接获取数字所对应的中文
<td>{{ item.get_level_display}}</td>
<td>{{ item.get_status_display}}</td>
11.3 显示字段
class PrettyModelForm(ModelForm):
class Meta:
model=models.PrettyNum
# 要显示所有字段
fields="__all__"
# 自选要显示的字段
fields=["mobile","price","level","status"]
# 显示除了该字段外的其他字段
# exclude=['level']
# 重写方法 加上别的样式
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
for name, field in self.fields.items():
field.widget.attrs = {"class": "form-control", "placeholder": field.label}
11.4 添加检验字段
方法1:正则表达式
只需要在ModelForm中添加检验条件,如
mobile=forms.CharField(
label="手机号",
validators=[RegexValidator(r'^1[3-9]\d{9}$','手机号格式错误')],
)
之后在函数执行if form.is_valid():…时就会多加入上述所写的检验条件
方法2:钩子函数
采用clean_xxx函数进行检验 xxx:为要检验的字段
def clean_mobile(self):
txt_mobile=self.cleaned_data["mobile"]
if len(txt_mobile)!= 11:
raise ValidationError("格式错误")
return txt_mobile
11.5 搜索
案例涉及前端页面:【prettynum_list】【basic.html】
11.6 分页
分页使用步骤:
- 调用Pagination.py
data_list = models.Employee.objects.all()
page_object = Pagination(request, data_list,page_size=2)
context = {
"data_list": page_object.page_queryset, # 分完页的数据 queryset
"page_string": page_object.html() # 页码
}
return render(request, 'usermanage_list.html', context)
- 添加分页插件到页面
<nav aria-label="...">
<ul class="pagination">
<li class="disabled"><a href="#" aria-label="Previous"><span aria-hidden="true">«</span></a></li>
<li class="active"><a href="#">1 <span class="sr-only">(current)</span></a></li>
{{ page_string }}
<li><a href="#" aria-label="Next"><span aria-hidden="true">»</span></a></li>
</ul>
</nav>
12. 时间插件
-
引用css和js
<link rel="stylesheet" href="{% static 'plugins/bootstrap-datetimepicker-master/bootstrap-datetimepicker-master/css/bootstrap-datetimepicker.min.css'%}"> <script src="{% static 'plugins/bootstrap-datetimepicker-master/bootstrap-datetimepicker-master/js/bootstrap-datetimepicker.min.js'%}"></script> <script src="{% static 'plugins/bootstrap-datetimepicker-master/bootstrap-datetimepicker-master/js/locales/bootstrap-datetimepicker.zh-CN.js' %}"></script>
-
将id修改为对应的时间id,Django会自动生成一个required_id,要修改成的id便是此required_id
<script> $(function(){ $('#id_join_time').datetimepicker( { format:'yyyy-mm-dd', startDate:'0', language:'zh-CN', autoclose:true } ); } ) </script>
13. Bootstrap样式父类
自定义类BootStrapModelForm(该类继承了Django的ModelForm),再写要用的类UserEditModelForm(该类继承了自定义类BootStrapModelForm)
如果有需要一般可以将【views.py】拆分成其他【x.py】,如将views.py拆分成【user.py】和
【pretty.py】…
14.密码确认&密码加密
密码确认
class AdminModelform(BootSrapModelform):
# 再页面新增一个字段 不会显示在数据库中
confirm_password = forms.CharField(
label="确认密码",
# confirm_password隐藏输入密码
widget=forms.PasswordInput(render_value=True)
)
class Meta:
model = models.Administration
fields = ["username", "password", "confirm_password"]
# password隐藏输入密码 render_value=True:即使密码不正确也不会制空
widgets = {
"password": forms.PasswordInput(render_value=True)
}
# 检验再次输入的密码原密码是否一样的钩子函数
def clean_confirm_password(self):
pwd = self.cleaned_data.get("password")
confirm = self.cleaned_data.get("confirm_password")
if confirm != pwd:
raise ValidationError("密码不一致")
return confirm
# def clean_confirm_password(self):
# if self.cleaned_data.get('password') != self.cleaned_data.get('comfirm_password'):
# raise ValidationError("两次密码不一致")
# else:
# return self.cleaned_data
密码加密
加盐
现在的MD5密码数据库的数据量已经非常庞大了,大部分常用密码都可以通过MD5摘要反向查询到密码明文。为了防止内部人员(能够接触到数据库或者数据库备份文件的人员)和外部入侵者通过MD5反查密码明文,更好地保护用户的密码和个人帐户安全(一个用户可能会在多个系统中使用同样的密码,因此涉及到用户在其他网站和系统中的数据安全),需要对MD5摘要结果掺入其他信息,称之为加盐。
加盐的算法有很多,考虑到加盐的目的(防止拥有系统底层权限的人员),想做到绝对不可反查是很困难的,需要有其他软件或者硬件的协助,在很多场景下的实用性比较差。如果只是想增加反查的难度,倒是有很多方法可以选择,一种便利的方法是md5(Password+UserName),即将用户名和密码字符串相加再MD5,这样的MD5摘要基本上不可反查。但有时候用户名可能会发生变化,发生变化后密码即不可用了(验证密码实际上就是再次计算摘要的过程)。
因此我们做了一个非常简单的算法,每次保存密码到数据库时,都生成一个随机16位数字,将这16位数字和密码相加再求MD5摘要,然后在摘要中再将这16位数字按规则掺入形成一个48位的字符串。在验证密码时再从48位字符串中按规则提取16位数字,和用户输入的密码相加再MD5。按照这种方法形成的结果肯定是不可直接反查的,且同一个密码每次保存时形成的摘要也都是不同的
# md5加密
def clean_password(self):
pwd = self.cleaned_data.get('password')
return md5(pwd)
新旧密码设置不一致
# md5加密与新旧密码验证
def clean_password(self):
pwd = self.cleaned_data.get('password')
md5_pwd=md5(pwd)
# 去数据库检验当前密码和输入的密码是否一致
exists=models.Administration.objects(id=self.instance.pk,password=md5_pwd).exists()
if exists:
raise ValidationError("新密码不能与原密码一致")
return md5(pwd)
15. 用户认证
15.1 Cookie&Session
def dologin(request):
if request.method == "GET":
form = DologinForm()
return render(request, 'dologin.html', {"form": form})
else:
form = DologinForm(data=request.POST)
if form.is_valid():
# 直接存放字典进去
admin_object=models.Administration.objects.filter(**form.cleaned_data).first()
if not admin_object:
# 主动显示错误信息
form.add_error("password","用户名或密码错误")
return render(request, 'dologin.html', {"form": form})
else:
# 网站生成随机字符串,写到用户浏览器得cookie中,再写到session中
request.session["info"]={"id":admin_object.id,"name":admin_object.username}
return redirect("/admins/list/")
else:
return render(request, 'dologin.html', {"form": form})
da
当登录之后,会生成对应的cookie,并可以在浏览器中查看cookie,也可以在数据库中【sessioon】表中查看到cookie.
session_key即为session,如下图的k1
对应关系为:
15.2 中间件实现登录
流程:
15.2.1 定义中间件
注意:
如果方法中没有返回值(返回None),则继续往后走
如果有返回值HttpResponse、render、redirect则返回到对应页面
from django.utils.deprecation import MiddlewareMixin
from django.shortcuts import HttpResponse, redirect
class AuthMiddleware(MiddlewareMixin):
def process_request(self, request):
# 如果方法中没有返回值(返回None),则继续往后走
# 如果有返回值HttpResponse、render、redirect
# 排除那些不需要登陆就能访问的页面(登陆页面)
# request.path_info 获取当前用户请求的URL /dologin/
if request.path_info=="/dologin/":
return
info_dict=request.session.get("info")
if info_dict:
return
else:
return redirect("/dologin/")
15.2.2 应用中间件 settings.py
15.3 注销
def dologout(request):
"""注销"""
request.session.clear()
return redirect('/dologin/')
15.4 获取用户名
在个人资料中获取当前登录的用户名:{{ request.session.info.name }}
16. 图片验证码
16.1 生成图片
python生成随机验证码,需要使用PIL模块
1.安装pillow
pip install pillow
2.下载xxx.ttf字体
注意:一定要写对字体的路径,要不然前端加载不出来图片
python代码
import random
from PIL import ImageFilter, Image, ImageDraw, ImageFont
#D:\Django\Project1\dologin\static\font\kumo.ttf 其中dologin为App
def check_code(font_file='dologin/static/font/kumo.ttf',width=120, height=30, char_length=5,font_size=28):
code = []
img = Image.new(mode='RGB', size=(width, height), color=(255, 255, 255))
draw = ImageDraw.Draw(img, mode='RGB')
def rndChar():
"""
生成随机字母
:return:
"""
return chr(random.randint(65, 90))
def rndColor():
"""
生成随机颜色
:return:
"""
return (random.randint(0, 255), random.randint(10, 255), random.randint(64, 255))
# 写文字
font = ImageFont.truetype(font_file, font_size)
for i in range(char_length):
char = rndChar()
code.append(char)
h = random.randint(0, 4)
draw.text([i * width / char_length, h], char, font=font, fill=rndColor())
# 写干扰点
for i in range(40):
draw.point([random.randint(0, width), random.randint(0, height)], fill=rndColor())
# 写干扰圆圈
for i in range(40):
draw.point([random.randint(0, width), random.randint(0, height)], fill=rndColor())
x = random.randint(0, width)
y = random.randint(0, height)
draw.arc((x, y, x + 4, y + 4), 0, 90, fill=rndColor())
# 画干扰线
for i in range(5):
x1 = random.randint(0, width)
y1 = random.randint(0, height)
x2 = random.randint(0, width)
y2 = random.randint(0, height)
draw.line((x1, y1, x2, y2), fill=rndColor())
img = img.filter(ImageFilter.EDGE_ENHANCE_MORE)
return img, ''.join(code)
if __name__ == '__main__':
# 1. 直接打开
# img,code = check_code()
# img.show()
# 2. 写入文件
img,code = check_code()
with open('code.png','wb') as f:
img.save(f,format='png')
print(code)
# 3. 写入内存(Python3)
# from io import BytesIO
# stream = BytesIO()
# img.save(stream, 'png')
# stream.getvalue()
# 4. 写入内存(Python2)
# import StringIO
# stream = StringIO.StringIO()
# img.save(stream, 'png')
# stream.getvalue()
# pass
视图函数
def image_code(request):
"""生成图片验证码"""
# 调用check_code()函数
img, code_string = check_code()
stream = BytesIO()
img.save(stream, 'png')
# 返回的是图像,当然我们还取到了一个code_string,是图片验证码的原字符,这用来和前端输入的验证码进行比较
return HttpResponse(stream.getvalue())
html页面
{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<link rel="stylesheet" href="{% static 'plugins/bootstrap-3.4.1/bootstrap-3.4.1-dist/css/bootstrap.css' %}">
<style>
.load {
width: 450px;
height: 350px;
border: 1px solid #ffecb5;
/*text-align: center;*/
/*设置边距阴影*/
box-shadow: 5px 5px 15px #2c3034;
margin-left: auto;
margin-right: auto;
margin-top: 100px;
/* 调节内边距*/
/* 上下 左右*/
padding: 10px 40px;
background-color: #9acfea;
}
.load h1 {
text-align: center;
}
</style>
</head>
<body>
<div class="load">
<h1>用户登录</h1>
<form method="post" novalidate>
{% csrf_token %}
<div class="form-group">
<div>用户名或手机号</div>
{# <input type="text" class="form-control" placeholder="请输入用户名或手机号">#}
{{ form.username }}
<span style="color: indianred">{{ form.username.errors.0 }}</span>
</div>
<div class="form-group">
<div>密码</div>
{# <input type="text" class="form-control" placeholder="请输入密码">#}
{{ form.password }}
<span style="color: indianred">{{ form.password.errors.0 }}</span>
</div>
<div class="form-group">
<label for="id_code">图片验证码</label>
<div class="row">
<div class="col-xs-7">
<input type="text" name="code" class="form-control" placeholder="请输入图片验证码" required="" id="id_code">
<span style="color: red"></span>
</div>
<div class="col-xs-5">
{# <img id="image_code" src="{%static 'img/code.png'%}" style="width: 125px">#}
<img id="image_code" src="/image/code/" style="width: 125px">
</div>
</div>
</div>
{# <div>#}
<input type="submit" class="btn btn-primary" value="登 录">
{# </div>#}
</form>
</div>
<script src="{% static 'js/jquery-3.6.0.min.js' %}"></script>
<script src="{% static 'plugins/bootstrap-3.4.1/bootstrap-3.4.1-dist/js/bootstrap.min.js' %}"></script>
</body>
</html>
报错:OSError (一般为字体路径问题)
原因:
字体路径错误,找不到字体文件,打不开字体资源。
解决:
将字体路径font_file='kumo.ttf'改为font_file='dologin/static/font/kumo.ttf'
16.2 图片校验
html
{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<link rel="stylesheet" href="{% static 'plugins/bootstrap-3.4.1/bootstrap-3.4.1-dist/css/bootstrap.css' %}">
<style>
.load {
width: 450px;
height: 350px;
border: 1px solid #ffecb5;
/*text-align: center;*/
/*设置边距阴影*/
box-shadow: 5px 5px 15px #2c3034;
margin-left: auto;
margin-right: auto;
margin-top: 100px;
/* 调节内边距*/
/* 上下 左右*/
padding: 10px 40px;
background-color: #9acfea;
}
.load h1 {
text-align: center;
}
</style>
</head>
<body>
<div class="load">
<h1>用户登录</h1>
<form method="post" novalidate>
{% csrf_token %}
<div class="form-group">
<div>用户名或手机号</div>
{# <input type="text" class="form-control" placeholder="请输入用户名或手机号">#}
{{ form.username }}
<span style="color: indianred">{{ form.username.errors.0 }}</span>
</div>
<div class="form-group">
<div>密码</div>
{# <input type="text" class="form-control" placeholder="请输入密码">#}
{{ form.password }}
<span style="color: indianred">{{ form.password.errors.0 }}</span>
</div>
<div class="form-group">
<label for="id_code">图片验证码</label>
<div class="row">
<div class="col-xs-7">
{{ form.code }}
<span style="color: indianred">{{ form.code.errors.0 }}</span>
</div>
<div class="col-xs-5">
{# <img id="image_code" src="{%static 'img/code.png'%}" style="width: 125px">#}
<img id="image_code" src="/image/code/" style="width: 125px">
</div>
</div>
</div>
{# <div>#}
<input type="submit" class="btn btn-primary" value="登 录">
{# </div>#}
</form>
</div>
<script src="{% static 'js/jquery-3.6.0.min.js' %}"></script>
<script src="{% static 'plugins/bootstrap-3.4.1/bootstrap-3.4.1-dist/js/bootstrap.min.js' %}"></script>
</body>
</html>
视图函数
class DologinForm(BootSrapForm):
username = forms.CharField(
label="用户名",
widget=forms.TextInput,
# 不能为空
required=True
)
password = forms.CharField(
label="密码",
widget=forms.PasswordInput(render_value=True),
# 不能为空
required=True
)
code= forms.CharField(
label="验证码",
widget=forms.TextInput,
# 不能为空
required=True
)
def clean_password(self):
pwd = self.cleaned_data.get("password")
# print(md5("huan"))
return md5(pwd)
def dologin(request):
"""登录"""
if request.method == "GET":
form = DologinForm()
return render(request, 'dologin.html', {"form": form})
else:
form = DologinForm(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, 'dologin.html', {"form": form})
# 直接存放字典进去
admin_object = models.Administration.objects.filter(**form.cleaned_data).first()
if not admin_object:
# 主动显示错误信息
form.add_error("password", "用户名或密码错误")
return render(request, 'dologin.html', {"form": form})
else:
# 网站生成随机字符串,写到用户浏览器得cookie中,再写到session中
request.session["info"] = {"id": admin_object.id, "name": admin_object.username}
#session可以保存七天 也就是七天免登录
request.session.set_expiry(60*60*24*7)
return redirect("/admins/list/")
else:
return render(request, 'dologin.html', {"form": form})
def image_code(request):
"""生成图片验证码"""
# 调用check_code()函数
img, code_string = check_code()
print(code_string)
#写入到session中(以便在后续过去验证码再进行校验)
request.session['image_code']=code_string
#给session设置60s超时
request.session.set_expiry(60)
stream = BytesIO()
img.save(stream, 'png')
# 返回的是图像,当然我们还取到了一个code_string,是图片验证码的原字符,这用来和前端输入的验证码进行比较
return HttpResponse(stream.getvalue())
17. Ajax
问题:form表单发送请求时,页面会发生刷新,请求一次就肉眼可见地刷新一次。
基于Ajax向后台发送请求(偷偷的发送请求)
17.1 GET请求
发送请求内容
{% extends 'basic.html' %}
{% block content %}
<div class="container">
<h1>任务管理</h1>
<div>....</div>
#基于dom的方式绑定事件
<input type="button" class="btn btn-primary" value="点击" onclick="clickMe()"/>
</div>
{% endblock %}
{% block js %}
<script type="text/javascript">
function clickMe() {
$.ajax({
url: "/task/ajax/",
type: "get",
data: {
n1: 123,
n2: 234,
},
success: function (res) {
console.log(res);
}
})
}
</script>
{% endblock %}
在后台接收请求的内容
def task_ajax(request):
print(request.GET)
return HttpResponse("成功啦")
17.2 POST请求
发送请求
1.基于dom的方式绑定事件
{% extends 'basic.html' %}
{% block content %}
<div class="container">
<h1>任务管理</h1>
<div>....</div>
#基于dom的方式绑定事件 onclinck='clickMe()'
<input type="button" class="btn btn-primary" value="点击" onclick="clickMe()"/>
</div>
{% endblock %}
<script type="text/javascript">
function clickMe() {
$.ajax({
url: "/task/ajax/",
type: "post",
data: {
n1: 123,
n2: 234,
},
success: function (res) {
console.log(res);
}
})
}
</script>
在后台接收POST请求
def task_list(request):
return render(request,'task_list.html')
# 免除csrf_token请求
@csrf_exempt
def task_ajax(request):
print(request.GET)
print(request.POST)
return HttpResponse("成功啦")
2.基于jequry的方式绑定事件
{% extends 'basic.html' %}
{% block content %}
<div class="container">
<h1>任务管理</h1>
<div>....</div>
{# <input type="button" class="btn btn-primary" value="点击" onclick="clickMe()"/>#}
<input id="Btn1" type="button" class="btn btn-primary" value="点击" />
</div>
{% endblock %}
{% block js %}
<script type="text/javascript">
$(function () {
{#当页面框架加载完成之后代码自动执行#}
bindBtn1Event()
})
function bindBtn1Event() {
$("#Btn1").click(function () {
$.ajax({
url: "/task/ajax/",
type: "post",
data: {
n1: 123,
n2: 234,
},
success: function (res) {
console.log(res);
}
})
})
}
</script>
{% endblock %}
在后台接收POST请求
def task_list(request):
return render(request,'task_list.html')
# 免除csrf_token请求
@csrf_exempt
def task_ajax(request):
print(request.GET)
print(request.POST)
return HttpResponse("成功啦")
17.3 ajax请求的返回值
一般都会返回json格式
示例1
# 免除csrf_token请求
@csrf_exempt
def task_ajax(request):
print(request.GET)
print(request.POST)
data_dict={"status":True,'data':[11,22,33,44]}
return HttpResponse(json.dumps(data_dict))
如上所示,ajax返回json格式之后,可以在前端页面获取“status”和“data”数据,可以在前端对数据进一步的处理。
{% extends 'basic.html' %}
{% block content %}
<div class="container">
<h1>示例1</h1>
<div>....</div>
{# <input type="button" class="btn btn-primary" value="点击" onclick="clickMe()"/>#}
<input id="Btn1" type="button" class="btn btn-primary" value="点击"/>
</div>
{% endblock %}
<script type="text/javascript">
$(function () {
{#当页面框架加载完成之后代码自动执行#}
bindBtn1Event()
})
function bindBtn1Event() {
$("#Btn1").click(function () {
$.ajax({
url: "/task/ajax/",
{#type: "get",#}
type: "post",
data: {
n1: 123,
n2: 234,
},
#对json数据进行处理
dataType:"JSON",
success: function (res) {
console.log(res);
console.log(res.status);
console.log(res.data);
}
})
})
}
</script>
在前端查看
示例2
通过ajax可以直接将前端输入框的内容发到后台,后台就可以接收到数据了
{% extends 'basic.html' %}
{% block content %}
<div class="container">
<h1>示例2</h1>
<input type="text" id="textUser" placeholder="用户名">
<input type="text" id="textAge" placeholder="年龄">
<input id="Btn2" type="button" class="btn btn-primary" value="提交"/>
</div>
{% endblock %}
<script type="text/javascript">
$(function () {
{#当页面框架加载完成之后代码自动执行#}
bindBtn2Event()
})
function bindBtn2Event() {
$("#Btn2").click(function () {
$.ajax({
url: "/task/ajax/",
{#type: "get",#}
type: "post",
data: {
name: $("#textUser").val(),
age: $("#textAge").val(),
},
dataType: "JSON",
success: function (res) {
console.log(res);
console.log(res.status);
console.log(res.data);
}
})
})
}
</script>
前端输入:
后台接收:
示例3
{% extends 'basic.html' %}
{% block content %}
<div class="container">
<h2>示例3</h2>
<form id="form3">
<input type="text" name="user" placeholder="用户名">
<input type="text" name="age" placeholder="年龄">
<input type="email" name="email" placeholder="邮箱">
</form>
<input id="Btn3" type="button" class="btn btn-primary" value="提交"/>
</div>
{% endblock %}
{% block js %}
<script type="text/javascript">
$(function () {
{#当页面框架加载完成之后代码自动执行#}
bindBtn3Event()
})
function bindBtn3Event() {
$("#Btn3").click(function () {
$.ajax({
url: "/task/ajax/",
{#type: "get",#}
type: "post",
{#获取form表单的内容#}
data:$("#form3").serialize(),
dataType: "JSON",
success: function (res) {
console.log(res);
console.log(res.status);
console.log(res.data);
}
})
})
}
</script>
{% endblock %}
前端输入:
后台接收:
ajax案例
在页面展示错误信息:
【views.py】
import json
from django import forms
from django.shortcuts import render,HttpResponse
# Create your views here.
from django.views.decorators.csrf import csrf_exempt
from administration.utils.bootstrap import BootSrapModelform
from administration import models
class TaskModelForm(BootSrapModelform):
class Meta:
model=models.Task
fields="__all__"
widgets={
# "detail":forms.TextInput,
"detail":forms.Textarea,
}
def task_list(request):
form=TaskModelForm
return render(request,'task_list.html',{"form":form})
# 免除csrf_token请求
@csrf_exempt
def task_ajax(request):
print(request.GET)
print(request.POST)
#字典、列表等类型
data_dict={"status":True,'data':[11,22,33,44]}
return HttpResponse(json.dumps(data_dict))
@csrf_exempt
def task_add(request):
print(request.POST)
# 字典、列表等类型
form=TaskModelForm(data=request.POST)
if form.is_valid():
form.save()
# "status": True当前提交已经保存到数据库 "status":False当前提交未保存到数据库
data_dict = {"status": True}
return HttpResponse(json.dumps(data_dict))
else:
data_sict={"status":False,"error":form.errors}
return HttpResponse(json.dumps(data_sict,ensure_ascii=False))
【task_list.html】
{% extends 'basic.html' %}
{% block content %}
<div class="container">
<div class="panel panel-default">
<div class="panel-heading">任务管理</div>
<div class="panel-body">
<form id="addForm">
<div class="clearfix">
{% for filed in form %}
<div class="col-xs-6">
<div class="form-group" style="position:relative;margin-bottom:20px">
<label>{{ filed.label }}</label>
{{ filed }}
<span class="error-msg" style="color: red;position:absolute"></span>
</div>
</div>
{% endfor %}
<input id="BtnAdd" type="button" class="btn btn-primary" value="提交"/>
</div>
</form>
</div>
</div>
</div>
{% endblock %}
{% block js %}
<script type="text/javascript">
$(function () {
{#当页面框架加载完成之后代码自动执行#}
bindBtnAddEvent()
})
function bindBtnAddEvent() {
$("#BtnAdd").click(function () {
{#当输入不为空时,将错误信息error-msg置空,就不会不显示错误提示#}
$(".error-msg").empty();
$.ajax({
url: "/task/add/",
{#type: "get",#}
type: "post",
{#获取form表单的内容#}
data: $("#addForm").serialize(),
dataType: "JSON",
success: function (res) {
if (res.status) {
{#弹框#}
alert("添加成功!");
} else {
{#遍历获取错误信息(views.py的error)#}
$.each(res.error, function (name, data) {
{#$("#id_"+name)拼接找到id,.next()在该标题的下面显示错误#}
$("#id_" + name).next().text(data[0]);
})
}
}
})
})
}
</script>
{% endblock %}
用JS实现页面的刷新: location.reload()
<script type="text/javascript">
$(function () {
{#当页面框架加载完成之后代码自动执行#}
bindBtnAddEvent()
})
function bindBtnAddEvent() {
$("#BtnAdd").click(function () {
{#当输入不为空时,将错误信息error-msg置空,就不会不显示错误提示#}
$(".error-msg").empty();
$.ajax({
url: "/task/add/",
{#type: "get",#}
type: "post",
{#获取form表单的内容#}
data: $("#addForm").serialize(),
dataType: "JSON",
success: function (res) {
if (res.status) {
{#弹框#}
alert("添加成功!");
{#用JS实现页面的刷新#}
location.reload()
} else {
{#遍历获取错误信息(views.py的error)#}
$.each(res.error, function (name, data) {
{#$("#id_"+name)拼接找到id,.next()在该标题的下面显示错误#}
$("#id_" + name).next().text(data[0]);
})
}
}
})
})
}
</script>
新建订单 【模态对话框】
显示对话框
$("#myModal").modal('show');
function bindBtnAddEvent() {
$("#btnAdd").click(function () {
{#显示对话框#}
$("#myModal").modal('show');
})
}
获取当前登录页面的用户
form.instance.admin_id=request.session["info"]["info"]
@csrf_exempt
def order_add(request):
form=OrderModelForm(data=request.POST)
if form.is_valid():
# 因为没有在页面让用户输入oid,所以这里要自己计算随机生成oid,这样数据库的数据才完整
form.instance.oid=datetime.now().strftime("%Y%m%d%H%M%S")+str(random.randint(1000,9999))
# 管理员就是当前登录的管理员session["info"]["xx"]
form.instance.admin_id=request.session["info"]["id"]
form.save()
return JsonResponse({"status":True})
else:
return JsonResponse({"status":False,'error':form.errors})
清除错误信息
$(".error-msg").empty();
清空表单
//清空表单 $("#addForm")是jQuery对象 $("#addForm")[0]是DOM对象
$("#addForm")[0].reset();
关闭对话框
{#关闭对话框#}
$("#myModal").modal('hide');
刷新对话框
{#刷新页面#}
location.reload()
function bindBtnSaveEvent() {
$("#btnSave").click(function () {
{#清除错误信息#}
$(".error-msg").empty();
{#向后台发送请求#}
$.ajax({
url: "/order/add/",
type: "post",
data: $("#addForm").serialize(),
dataType: "JSON",
success: function (res) {
if (res.status) {
{#alert("创建成功!")#}
//清空表单 $("#addForm")是jQuery对象 $("#addForm")[0]是DOM对象
$("#addForm")[0].reset();
{#关闭对话框#}
$("#myModal").modal('hide');
{#刷新页面#}
location.reload()
} else {
$.each(res.error, function (name, errorList) {
$("#id_" + name).next().text(errorList[0]);
})
}
}
})
})
}
class="xxx"
时$(".btn-delete").clic()
<input did="{{ item.id }}" type="button" class="btn btn-danger btn-xs btn-delete"
value="删除">
function bindBtnDeleteEvent() {
$(".btn-delete").click(function () {
{#显示对话框#}
$("#deleteModal").modal('show');
DELETE_ID = $(this).attr('did')
console.log(DELETE_ID)
})
}
id="xxxx"
时$("#btnConfirm").click()
<button id="btnConfirm" type="button" class="btn btn-danger">确认</button>
<input id="btnAdd" type="button" value="新建订单2" class="btn btn-primary">
function bindConfirmEvent() {
$("#btnConfirm").click(function () {
$("#deleteModal").modal('hide');
})
}
function bindBtnAddEvent() {
$("#btnAdd").click(function () {
{#显示对话框#}
$("#myModal").modal('show');
})
}
18. 数据统计
国内工具:echarts 官网
18.1 柱状图
引入echarts.js
<script src="{% static 'js/echarts.js' %}"></script>
编写script 从官网copy示例稍作修改即可
{% block js %}
//第一步:引入echarts.js
<script src="{% static 'js/echarts.js' %}"></script>
//第二步:
<script type="text/javascript">
$(function () {
initBar();
})
function initBar() {
// 基于准备好的dom,初始化echarts实例
var myChart = echarts.init(document.getElementById('chart2'));
// 指定图表的配置项和数据
{#var option是局部变量 只有opton是全局变量#}
var option = {
title: {
{#标题#}
text: '员工销售汇总',
{#副标题#}
subtext: '广西分公司',
{#标题居中#}
textAlign: 'auto',
left: 'center'
},
tooltip: {},
legend: {
{#在后台获取#}
data: [],
{#显示在底部#}
bottom: 0,
},
//后台获取
xAxis: {
data: []
},
yAxis: {},
//后台获取
series: [],
};
$.ajax({
url: "/chart/bar/",
type: "get",
dataType: "JSON",
success: function (res) {
if (res.status){
option.legend.data=res.data.legend;
option.xAxis.data=res.data.xAxis;
option.series=res.data.series;
}
// 使用刚指定的配置项和数据显示图表。
myChart.setOption(option);
}
})
}
</script>
{% endblock %}
视图
def chart_list(request):
return render(request, 'chart_list.html')
def chart_bar(request):
"""构造柱状图的数据"""
# 数据可以去数据库中获取
legend = ["小猪佩奇", "吉吉国王"]
series = [
{
"name": "小猪佩奇",
"type": 'bar',
"data": [5, 20, 36, 10, 10, 20]
},
{
"name": "吉吉国王",
"type": 'bar',
"data": [6, 30, 26, 70, 2, 29]
}
]
xAxis = ['衬衫', '羊毛衫', '雪纺衫', '裤子', '高跟鞋', '袜子']
result = {
"status": True,
"data": {
'legend': legend,
'xAxis': xAxis,
'series': series,
}
}
return JsonResponse(result)
18.2 饼状图
html页面
{% block js %}
//第一步:引入echarts.js
<script src="{% static 'js/echarts.js' %}"></script>
//第二步:
<script type="text/javascript">
$(function () {
initPie();
})
function initPie() {
var myChart = echarts.init(document.getElementById('chart3'));
{#var option是局部变量 只有opton是全局变量#}
var option = {
title: {
text: '部门预算占比',
subtext: '广西分公司',
left: 'center'
},
tooltip: {
trigger: 'item'
},
legend: {
bottom: 0
},
series: [
{
name: '预算',
type: 'pie',
radius: '50%',
data: [
{"value": 1048, "name": 'Search Engine'},
{"value": 735, "name": 'Direct'},
{"value": 580, "name": 'Email'},
{"value": 484, "name": 'Union Ads'},
{"value": 300, "name": 'Video Ads'}
],
emphasis: {
itemStyle: {
shadowBlur: 10,
shadowOffsetX: 0,
shadowColor: 'rgba(0, 0, 0, 0.5)'
}
}
}
]
};
$.ajax({
url: "/chart/pie/",
type: "get",
dataType: "JSON",
success: function (res) {
if (res.status) {
option.series[0].data = res.data;
myChart.setOption(option);
}
}
})
}
</script>
{% endblock %}
视图
def chart_pie(request):
data_list = [
{"value": 1048, "name": 'IT部门'},
{"value": 1735, "name": '运营部门'},
{"value": 2580, "name": '新媒体'},
{"value": 12484, "name": '行政部门'}
]
result = {
"status": True,
"data": data_list,
}
return JsonResponse(result)
18.3 折线图
html页面
{% block js %}
//第一步:引入echarts.js
<script src="{% static 'js/echarts.js' %}"></script>
//第二步:
<script type="text/javascript">
$(function () {
initLine();
})
function initLine() {
var myChart = echarts.init(document.getElementById('chart1'));
var option = {
xAxis: {
type: 'category',
data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
},
yAxis: {
type: 'value'
},
series: [
{
data: [150, 230, 224, 218, 135, 147, 260],
type: 'line'
}
]
};
$.ajax({
url: "/chart/line/",
type: "get",
dataType: "JSON",
success: function (res) {
if (res.status) {
option.xAxis.data=res.xAxis;
option.series[0].data=res.series;
myChart.setOption(option);
}
}
})
}
</script>
{% endblock %}
视图
def chart_line(request):
xAxis_data=['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
series_data=[150, 230, 224, 218, 135, 147, 260]
result={
"status":True,
"xAxis":xAxis_data,
"series":series_data,
}
return JsonResponse(result)
另外国外的数据统计工具还有:heighcharts 官网
使用时不必要下载其文件,直接引入其CDN即可,heighcharts CDN:
<script src="http://cdn.highcharts.com.cn/highcharts/highcharts.js"></script>
19. 文件的上传
19.1 基本操作
html页面
注意:文件上传一定要有 enctype=“multipart/form-data”
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<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>
</body>
</html>
视图
# 文件的上传
def upload_list(request):
if request.method == "GET":
return render(request, 'upload_list.html')
else:
# 获取文件对象
file_object = request.FILES.get('avatar')
# 写入当地文件
f = open(file_object.name, mode='wb')
# 一块一块地获取文件,并写入当地
for chunk in file_object.chunks():
f.write(chunk)
# 关闭文件
f.close()
return HttpResponse("上传成功!")
19.2 批量操作数据
将excel文件内容上传到数据库
首先要安装第三方包openpyxl
此处为上传部门名称的文件 <部门名称.xlxs>
html页面
<div class="panel panel-default">
<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-xs">
</form>
</div>
</div>
视图
def depart_multi(request):
# 获取用户上传的文件对象
# "exc":html页面表示所上传文件的文件名称
file_object = request.FILES.get("exc")
# 对象传递给openpyxl,由openpyxl读取文件的内容
wb = load_workbook(file_object)
# 获取第一列
sheet = wb.worksheets[0]
# return HttpResponse("上传成功")
# 循环获取每一行
for row in sheet.iter_rows(min_row=2):
text = row[0].value
# 判断数据库是否已经存在该数据,如果存在,则不重新创建
exists = models.Department.objects.filter(title=text).exists()
if not exists:
models.Department.objects.create(title=text)
# 重定向到list页面
return redirect('/depart/list/')
19.3 案例:混合数据(基于Form)
用户输入数据+上传的文件
html页面
{% extends 'basic.html' %}
{% block head %}
{% load static %}
{% csrf_token %}
{% endblock %}
{% block content %}
<div class="container">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">{{ title }}</h3>
</div>
<div class="panel-body">
<form method="post" enctype="multipart/form-data" novalidate>
<div class="form-group">
{% csrf_token %}
{% for field in form %}
<div>
{{ field.label }}:
{{ field }}
</div>
{# 提示错误信息,接受的错误信息是一个列表[错误1,错误2,...],只获取第一个信息即可#}
<span style="color: red">{{ field.errors.0 }}</span>
{% endfor %}
</div>
<button type="submit" class="btn btn-primary">提 交</button>
</form>
</div>
</div>
</div>
{% endblock %}
<script src="{% static 'js/jquery-3.6.0.min.js' %}"></script>
<script src="{% static 'plugins/bootstrap-3.4.1/bootstrap-3.4.1-dist/js/bootstrap.min.js' %}"></script>
</body>
</html>
models.py数据库
class File(models.Model):
name=models.CharField(verbose_name="姓名",max_length=64)
age=models.IntegerField(verbose_name="年龄")
img=models.CharField(verbose_name="图片",max_length=225)
views.py视图
用户上传的文件存放在static目录中
class UpForm(BootSrapForm):
# 除了图片之外 都给其他选项加BootSrapModelform的样式
bootstrap_exclude_fields = ['img']
name = forms.CharField(label="姓名")
age = forms.IntegerField(label="年龄")
img = forms.FileField(label="头像")
# 混合数据上传
def file_list(request):
title = "混合数据上传(Form)"
if request.method == "GET":
form = UpForm()
return render(request, 'file_list.html', {"form": form, "title": title})
else:
form = UpForm(data=request.POST, files=request.FILES)
print(type(form))
if form.is_valid():
# 读取图片内容。写入到文件夹中,并获取文件夹的路径
image_object =form.cleaned_data.get("img")
db_file_path = os.path.join("static","img",image_object.name)
file_path = os.path.join("app01",db_file_path)
f = open(file_path, mode='wb')
for chunk in image_object.chunks():
f.write(chunk)
f.close()
# 将图片文件路径写入数据库
models.File.objects.create(
name=form.cleaned_data['name'],
age=form.cleaned_data['age'],
img=db_file_path
)
return HttpResponse("混合数据上传成功!(Form)")
else:
return render(request, 'file_list.html', {"form": form, "title": title})
用户上传的数据存放在media目录中
用media请先启动media,详见《19.4.启动media》(往下翻)
# 混合数据上传
def file_list(request):
title = "混合数据上传(Form)"
if request.method == "GET":
form = UpForm()
return render(request, 'file_list.html', {"form": form, "title": title})
else:
form = UpForm(data=request.POST, files=request.FILES)
print(type(form))
if form.is_valid():
# 读取图片内容。写入到文件夹中,并获取文件夹的路径
image_object =form.cleaned_data.get("img")
# 用户上传的数据存放在static目录中
# db_file_path = os.path.join("static","img",image_object.name)
# file_path = os.path.join("app01",db_file_path)
# 用户上传的数据存放在media目录中,为绝对路径
# media_path = os.path.join(settings.MEDIA_ROOT, image_object.name)
# 用户上传的数据存放在media目录中,为相对路径
media_path = os.path.join("media", image_object.name)
# file_path = os.path.join("app01", db_file_path)
# f = open(file_path, mode='wb')
f = open(media_path, mode='wb')
for chunk in image_object.chunks():
f.write(chunk)
f.close()
# 将图片文件路径写入数据库
models.File.objects.create(
name=form.cleaned_data['name'],
age=form.cleaned_data['age'],
#
img=media_path
)
return HttpResponse("混合数据上传成功!(Form)")
else:
return render(request, 'file_list.html', {"form": form, "title": title})
注意:
就目前而言,存放静态文件只能放在static目录中
static:存放静态文件的路径,包括CSS,JS,项目图片
media:用户上传的数据的目录
用media的话需要启动,在urls.py和settings.py中配置
19.4. 启动media
urls.py配置
# media配置
# ***************************************]*********
from django.urls import path,re_path
from django.views.static import serve
from django.conf import settings
# 部门管理
urlpatterns = [
re_path(r'^media/(?P<path>.*)$',serve,{'document_root':settings.MEDIA_ROOT},name='media'),
]
settings配置
import os
# media配置
MEDIA_ROOT = os.path.join(BASE_DIR, "media")
MEDIA_URL = "/media/"
在项目中创建media文件夹
在网页中查看media中的图片
http://127.0.0.1:8000/media/OIP.jpg/
19.5 案例:混合数据(基于ModelForm)!!推荐!!
1146报错:
ProgrammingError at /file/modelformlist/ (1146, "Table 'django.app01_city' doesn't exist")
原因:
没有生成数据库!!!
解决:
python manage.py makemigrations
python manage.py migrate
报错:
cannot unpack non-iterable builtin_function_or_method object
原因:
获取数据库数据时,方法写错了
解决:
def city_list(request):
"""城市列表"""
#原来错误:queryset=models.City.objects.get(all)
queryset=models.City.objects.all()
return render(request,'city_list.html',{"queryset":queryset})
city_list.html页面
显示图片:
<td>
<img src="/media/{{item.img}}" style="height:80px">
</td>
% extends 'basic.html' %}
{% block search %}
<form class="navbar-form navbar-left" method="get">
<div class="form-group">
<input type="text" name="query" class="form-control" placeholder="Search" value={{ search_data }}>
</div>
<button type="submit" class="btn btn-default">
<span class="glyphicon glyphicon-search" aria-hidden="true"></span>
</button>
</form>
{% endblock %}
{% block content %}
<div class="container">
<div style="margin-bottom: 10px">
<a class="btn btn-success" href="/city/add/">
<span class="glyphicon glyphicon-plus-sign" aria-hidden="true">
新增城市
</span>
</a>
</div>
<div class="panel panel-default">
<div class="panel-heading">
<caption>
<span class="glyphicon glyphicon-th-list" aria-hidden="true"></span>
<span class="glyphicon-class">城市列表</span>
</caption>
</div>
<div class="panel-body">
<div class="bs-example" data-example-id="bordered-table">
<table class="table table-bordered">
<thead>
<tr>
<th>ID</th>
<th>名称</th>
<th>人口</th>
<th>Logo</th>
<th>操作</th>
</tr>
</thead>
<tbody>
{% for item in queryset %}
<tr>
<th scope="row">{{ item.id }}</th>
<td>{{ item.name }}</td>
<td>{{ item.age }}</td>
<td>
<img src="/media/{{item.img}}" style="height:80px">
</td>
<td>
<a class="btn btn-primary btn-xs" href="#">编辑</a>
<a class="btn btn-danger btn-xs" href="#">删除</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
<nav aria-label="...">
<ul class="pagination">
<li class="disabled"><a href="#" aria-label="Previous"><span aria-hidden="true">«</span></a></li>
<li class="active"><a href="#">1 <span class="sr-only">(current)</span></a></li>
{{ page_string }}
<li><a href="#" aria-label="Next"><span aria-hidden="true">»</span></a></li>
</ul>
</nav>
</div>
{% endblock %}
city_add.html
{% extends 'basic.html' %}
{% block head %}
{% load static %}
{% csrf_token %}
{% endblock %}
{% block content %}
<div class="container">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">{{ title }}</h3>
</div>
<div class="panel-body">
<form method="post" enctype="multipart/form-data" novalidate>
<div class="form-group">
{% csrf_token %}
{% for field in form %}
<div>
{{ field.label }}:
{{ field }}
</div>
{# 提示错误信息,接受的错误信息是一个列表[错误1,错误2,...],只获取第一个信息即可#}
<span style="color: red">{{ field.errors.0 }}</span>
{% endfor %}
</div>
<button type="submit" class="btn btn-primary">提 交</button>
</form>
</div>
</div>
</div>
{% endblock %}
<script src="{% static 'js/jquery-3.6.0.min.js' %}"></script>
<script src="{% static 'plugins/bootstrap-3.4.1/bootstrap-3.4.1-dist/js/bootstrap.min.js' %}"></script>
</body>
</html>
视图函数
class UpModelForm(BootSrapModelform):
bootstrap_exclude_fields = ['img']
class Meta:
model = models.City
fields = "__all__"
def city_list(request):
"""城市列表"""
queryset = models.City.objects.all()
return render(request, 'city_list.html', {"queryset": queryset})
def city_add(request):
title = "混合数据上传(ModelForm)"
if request.method == "GET":
form = UpModelForm()
return render(request, 'file_list.html', {"form": form, "title": title})
else:
form = UpModelForm(data=request.POST, files=request.FILES)
if form.is_valid():
# 对于文件:自动保存文件
# 字段+上传路径写入数据库
form.save()
return redirect('/city/list/')
else:
return render(request, 'file_list.html', {"form": form, "title":title})
该笔记仅作为自己在学习Django时的学习记录。