Bootstrap

Django正式版

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

  1. 直接启动

  2. 命令行

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 分页

在这里插入图片描述

分页使用步骤:

  1. 调用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)

在这里插入图片描述

  1. 添加分页插件到页面
  <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. 时间插件

  1. 引用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>
    
  2. 将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时的学习记录。

;