python 全栈开发,Day86(上传文件,上传头像,CBV,python读写Excel,虚拟环境virtualenv)

一、上传文件

上传一个图片

使用input type="file",来上传一个文件。注意:form表单必须添加属性enctype="multipart/form-data"

在views.py视图函数中,获取文件对象,必须使用request.FILES.get

新建项目upload_file,在项目中新建static文件夹,在文件夹里面分别创建js和upload目录。

在js目录中,放入文件jquery.min.js,下载地址为:https://cdn.bootcdn.net/ajax/libs/jquery/3.1.0/jquery.min.js

此时,目录结构如下:

./
├── app01
│   ├── admin.py
│   ├── apps.py
│   ├── __init__.py
│   ├── migrations
│   │   └── __init__.py
│   ├── models.py
│   ├── tests.py
│   └── views.py
├── manage.py
├── static
│   ├── js
│   │   └── jquery.min.js
│   └── upload
├── templates
└── untitled1
    ├── __init__.py
    ├── settings.py
    ├── urls.py
    └── wsgi.py

请仔细对比文件结构,不要缺少文件。

 

修改settings.py,定义static路径

STATIC_URL = '/static/'
STATICFILES_DIRS = (
    os.path.join(BASE_DIR,'static'),
)
View Code

修改urls.py,增加路径

from app01 import views
urlpatterns = [
    path('admin/', admin.site.urls),
    path('index/', views.index),
]
View Code

修改views.py,增加视图函数

from django.shortcuts import render,HttpResponse
import os
from upload_file import settings  # 导入settings。注意:upload_file为项目名

# Create your views here.
def index(request):
    if request.method == "GET":
        return render(request,'index.html')
    user = request.POST.get('user')
    # 获取文件对象,必须使用request.FILES.get
    avatar = request.FILES.get('avatar')
    print(user)
    print(avatar,type(avatar))
    print(avatar.__dict__)  # 查看对象所有属性
    # 文件存储的绝对路径
    path = os.path.join(settings.BASE_DIR, "static", "upload", avatar.name)
    with open(path,'wb') as f:
        # 将大文件分割成若干小文件处理,处理完每个小文件后释放该部分内存
        for line in avatar.chunks():
            f.write(line)  # 写入文件

    return HttpResponse('上传成功')
View Code

新建index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
{#上传文件必须指定enctype="multipart/form-data"#}
<form method="post" enctype="multipart/form-data">
    {% csrf_token %}
    <lable>上传图片</lable>
    <input type="file" name="avatar"><br>
    <lable>用户名</lable>
    <input type="text" name="user">
    <input type="submit" value="提交">

</form>
</body>
</html>
View Code

启动项目,访问url: http://127.0.0.1:8000/index/

选择一个图片

提示上传成功

 在upload目录,就会多出一个图片  QQ图片20180327153648.jpg

 

上传按钮美化

所有好看的上传按钮,都是经过修饰的。那么它是如何做到的呢!原理如下:

修饰的样式一般是用html做的,将前面的input上传控件变成透明。

那么用户看到的是一个经过修饰的按钮,实际点击的是input上传控件!

注意:上传控件的尺寸和后面的html控件,大小是一样的,比如div

举例:

 

修改index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
{#上传文件必须指定enctype="multipart/form-data"#}
<form method="post" enctype="multipart/form-data">
    {% csrf_token %}
    <div style="position: relative;display: inline-block;height: 50px;min-width: 300px;overflow: hidden;">
        <div style="position: absolute;top: 0;left: 0;right: 0;bottom: 0;z-index: 1000;border: 1px dotted #9d9d9d;color: #9d9d9d;line-height: 50px;padding-left: 15px;">
            <i class="fa fa-cloud-upload" aria-hidden="true"></i>
            <span>点击上传文件</span>
        </div>
        {#opacity表示设置透明度,0表示完全透明#}
        <input name="avatar" type="file" id="avatarImg"
               style="position: absolute;top: 0;left: 0;right: 0;bottom: 0;background-color: #333333;z-index: 1001;opacity: 0;filter:alpha(opacity=0);">
    </div>
    <div>
        <lable>用户名: </lable><input type="text" name="user">
        <input type="submit" value="提交">
    </div>
</form>
<script src="/static/js/jquery.min.js"></script>
<script>
    $(function () {
        {#当元素的值发生改变时,会发生 change 事件#}
        $('#avatarImg').change(function (e) {
            {#event.currentTarget 属性是在事件冒泡阶段内的当前 DOM 元素,通常等于 this#}
            {#:file 选择器选取带有 type=file 的 input 元素#}
            {#0表示获取第一个元素,name表示获取文件名#}
            var fileName = e.currentTarget.files[0].name;
            {#prev() 获得匹配元素集合中每个元素紧邻的前一个同胞元素,通过选择器进行筛选是可选的#}
            {#$(this)表示上传控件,那么它前一个同胞元素为div style="position:...#}
            {#find() 方法获得当前元素集合中每个元素的后代,通过选择器、jQuery 对象或元素来筛选。#}
            {#text() 方法方法设置或返回被选元素的文本内容#}
            $(this).prev().find('span').text(fileName);
        })
    })
</script>
</body>
</html>
View Code

 

重新访问网页,效果如下:

重新上传一个图片,效果如下:

点击提交,提示上传成功!

这个是重点,考试必考!

 

二、上传头像

 一般做上传头像功能,会有一个预览效果。总共有4种方法:

  1. createObjectURL
  2. FileReader
  3. FormData
  4. iframe

 

前2种,是在浏览器端可以做图片预览,没有上传图片到服务器!

后2种,图片需要上传到服务器。

兼容性效果对比: iframe > FormData > FileReader -> createObjectURL

iframe的兼容性是最好的,即使ie6也支持。FormData对浏览器,有一定要求

参考兼容性:

 

createObjectURL

举例:

修改urls.py,增加路径

urlpatterns = [
    path('admin/', admin.site.urls),
    path('index/', views.index),
    path('upload_file/', views.upload_file),
]
View Code

 

修改views.py,增加视图函数

def upload_file(request):
    if request.method == "GET":
        print(11)
        return render(request,'upload_file.html')

    user = request.POST.get('user')
    avatar = request.FILES.get('avatar')
    # 文件存储的绝对路径
    path = os.path.join(settings.BASE_DIR, "static", "upload", avatar.name)
    with open(path,'wb') as f:
        for line in avatar.chunks():
            f.write(line)
    return HttpResponse('上传成功')
View Code

 

在static目录下创建images文件夹,然后存放一个图片default.png,大家自己网上随便下载一个图片即可。

 

创建upload_file.html,注意:不是所有浏览器都有createObjectURL

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<form method="post" enctype="multipart/form-data">
    {% csrf_token %}
    <div style="height: 100px;width: 100px;padding: 2px;border: 1px solid #dddddd;position: relative;">
        <img style="height: 100%;width: 100%;border: 0;overflow: hidden;border-radius: 50%;" id="previewImg"
             src="/static/images/default.png">
        <input style="top: 0;left: 0;right: 0;bottom: 0;opacity: 0;position: absolute;z-index: 102;" id="avatarImg"
               name="avatar" type="file" class="img-file"/>
    </div>
    <div>点击图片更换(<a href="#">撤销</a>)</div>

    <lable>用户名:</lable>
    <input type="text" name="user">
    <input type="submit" value="提交">
</form>
<script src="/static/js/jquery.min.js"></script>
<script>
    {#浏览器加载资源完成后#}
    $(function () {
        {#执行函数#}
        bindChangeAvatar1();
    });

    function bindChangeAvatar1() {
        {#绑定change事件#}
        $('#avatarImg').change(function () {
            {#$(this)表示input上传控件,0表示第一个元素#}
            {#files[0] 获取file input中的File对象#}
            var file_obj = $(this)[0].files[0];
            console.log(file_obj);
            {#通过createObjectURL读取对象,生成url#}
            var blob = window.URL.createObjectURL(file_obj);
            {#修改src的值#}
            document.getElementById('previewImg').src = blob;
            {#load() 当资源加载完成之后才会执行#}
            $('#previewImg').on('load', function() {
                {#revokeObjectURL释放对象#}
                window.URL.revokeObjectURL(blob);
            })
        })
    }
</script>
</body>
</html>
View Code

 

访问url: http://127.0.0.1:8000/upload_file/

点击图片更改,可以发现图片会立即更换。注意:这个更换仅仅是前端js完成的,刷新页面后,还是会还原的。

 

FileReader

举例:

修改upload_file.html里面的js代码即可,readAsDataURL也有兼容问题

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<form method="post" enctype="multipart/form-data">
    {% csrf_token %}
    <div style="height: 100px;width: 100px;padding: 2px;border: 1px solid #dddddd;position: relative;">
        <img style="height: 100%;width: 100%;border: 0;overflow: hidden;border-radius: 50%;" id="previewImg"
             src="/static/images/default.png">
        <input style="top: 0;left: 0;right: 0;bottom: 0;opacity: 0;position: absolute;z-index: 102;" id="avatarImg"
               name="avatar" type="file" class="img-file"/>
    </div>
    <div>点击图片更换(<a href="#">撤销</a>)</div>

    <lable>用户名:</lable>
    <input type="text" name="user">
    <input type="submit" value="提交">
</form>
<script src="/static/js/jquery.min.js"></script>
<script>
    $(function () {
        bindChangeAvatar2();
    });

    function bindChangeAvatar2() {
        $('#avatarImg').change(function () {
            var file_obj = $(this)[0].files[0];
            //使用fileReader对文件对象进行操作
            var reader = new FileReader();
            //readAsDataURL 将文件读取为 DataURL
            reader.readAsDataURL(file_obj);
            //onload 事件会在页面或图像加载完成后立即发生
            reader.onload = function (e) {
                // 修改src属性
                $('#previewImg')[0].src = this.result;
            };
        })
    }
</script>
</body>
</html>
View Code

点击图片更改,可以发现图片会立即更换。

 

 

FormData

上面的保存图片方式有问题,因为用户上传的图片,可能会重名。为了解决这个问题,需要使用uuid模块。

uuid

UUID是128位的全局唯一标识符,通常由32字节的字符串表示。
它可以保证时间和空间的唯一性,也称为GUID,全称为:
UUID —— Universally Unique IDentifier。Python 中叫 UUID

它通过MAC地址、时间戳、命名空间、随机数、伪随机数来保证生成ID的唯一性。

UUID主要有五个算法,也就是五种方法来实现:

1、uuid1()——基于时间戳

       由MAC地址、当前时间戳、随机数生成。可以保证全球范围内的唯一性,
       但MAC的使用同时带来安全性问题,局域网中可以使用IP来代替MAC。

2、uuid2()——基于分布式计算环境DCE(Python中没有这个函数)

        算法与uuid1相同,不同的是把时间戳的前4位置换为POSIX的UID。
        实际中很少用到该方法。

3、uuid3()——基于名字的MD5散列值

        通过计算名字和命名空间的MD5散列值得到,保证了同一命名空间中不同名字的唯一性,
        和不同命名空间的唯一性,但同一命名空间的同一名字生成相同的uuid。    

4、uuid4()——基于随机数

        由伪随机数得到,有一定的重复概率,该概率可以计算出来。

5、uuid5()——基于名字的SHA-1散列值

        算法与uuid3相同,不同的是使用 Secure Hash Algorithm 1 算法
View Code

使用方面:

首先,Python中没有基于DCE的,所以uuid2可以忽略;
其次,uuid4存在概率性重复,由无映射性,最好不用;
再次,若在Global的分布式计算环境下,最好用uuid1;
最后,若有名字的唯一性要求,最好用uuid3或uuid5。
View Code

编码方法:

# -*- coding: utf-8 -*-
import uuid

name = "test_name"
namespace = "test_namespace"

print(uuid.uuid1())  # 带参的方法参见Python Doc
print(uuid.uuid3(namespace, name))
print(uuid.uuid4())
print(uuid.uuid5(namespace, name))
View Code

修改urls.py,增加路径form_data_upload,用来和ajax交互的

urlpatterns = [
    path('admin/', admin.site.urls),
    path('index/', views.index),
    path('upload_file/', views.upload_file),
    path('form_data_upload/', views.form_data_upload),
]
View Code

修改upload_file视图函数,使用uuid,完整代码如下:

import os
import json
import uuid
from django.shortcuts import render,HttpResponse
from upload_file import settings  # 导入settings。注意:upload_file为项目名


# Create your views here.
def index(request):
    if request.method == "GET":
        return render(request,'index.html')
    user = request.POST.get('user')
    # 获取文件对象,必须使用request.FILES.get
    avatar = request.FILES.get('avatar')
    print(user)
    print(avatar,type(avatar))
    print(avatar.__dict__)  # 查看对象所有属性
    # 文件存储的绝对路径
    path = os.path.join(settings.BASE_DIR, "static", "upload", avatar.name)
    with open(path,'wb') as f:
        # 将大文件分割成若干小文件处理,处理完每个小文件后释放该部分内存
        for line in avatar.chunks():
            f.write(line)  # 写入文件

    return HttpResponse('上传成功')

def upload_file(request):
    if request.method == "GET":
        return render(request, 'upload_file.html')

    # 直接获取图片地址即可,因为ajax已经将图片上传了
    avatar = request.POST.get('avatar')
    user = request.POST.get('user')

    print(avatar,user)
    return HttpResponse("上传成功")

def form_data_upload(request):
    """
    ajax上传文件
    :param request:
    :return:
    """
    img_upload = request.FILES.get('img_upload')  # 获取文件对象
    # 生成随机文件名
    file_name = str(uuid.uuid4()) + "." + img_upload.name.rsplit('.', maxsplit=1)[1]
    # 文件保存到static下的images目录
    img_file_path = os.path.join('static', 'images', file_name)
    with open(img_file_path, 'wb') as f:  # 写入问题
        for line in img_upload.chunks():
            f.write(line)
    # 因为ajax发送的图片路径的static前面没有带/,所以这里要拼接一下
    return HttpResponse(os.path.join("/",img_file_path))
View Code

修改upload_file.html,要全部修改

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
{% csrf_token %}
<div style="height: 100px;width: 100px;padding: 2px;border: 1px solid #dddddd;position: relative;">
    <img style="height: 100%;width: 100%;border: 0;overflow: hidden;border-radius: 50%;"
         id="previewImg"
         src="/static/images/default.png">
    <input style="top: 0;left: 0;right: 0;bottom: 0;opacity: 0;position: absolute;z-index: 102;" id="avatarImg"
           name="avatar_img" type="file" class="img-file"/>
</div>
<div>点击图片更换(<a href="#">撤销</a>)</div>

<form method="post" enctype="multipart/form-data">
    {% csrf_token %}
    <div>
        <input type="hidden" name="avatar" id="avatar">
        <input type="text" name="user">
        <input type="submit" value="提交">
    </div>
</form>
<script src="/static/js/jquery.min.js"></script>
    <script>
        $(function () {
            bindChangeAvatar3();
        });

        function bindChangeAvatar3() {
            $('#avatarImg').change(function () {
                var csrf = $("[name=csrfmiddlewaretoken]").val();
                var file_obj = $(this)[0].files[0];
                var formdata = new FormData();
                formdata.append('img_upload', file_obj);
                formdata.append("csrfmiddlewaretoken",csrf);
                $.ajax({
                    url: '/form_data_upload/',
                    type:'POST',
                    data: formdata,
                    processData: false,  // tell jQuery not to process the data
                    contentType: false,  // tell jQuery not to set contentType
                    success: function (arg) {
                        // 给img标签设置src属性,预览
                        console.log(arg);
                        $('#previewImg').attr('src',arg);
                        console.log(arg);
                        var avatar = $('#avatar').val(arg);
                        console.log(avatar);  //打印头像地址,用于后续表单提交
                    }
                })
            })
        }
    </script>
</body>
</html>
View Code

访问网页,上传一张图片

点击提交,提示上传成功

查看upload目录,会多一个文件 d405ecde-59bc-40f0-a2eb-c8dfe8c8645f.jpg

注意:form_data_upload和upload_file虽然都处理了POST数据。但是它们接收的数据是不一样的!

用户点击上传时,走form_data_upload视图函数,将图片上传到服务器

用户点击提交时,将服务器的图片url地址和用户名提交给upload_file视图函数。

为什么呢?因为用户点击上传时,已经将图片上传到服务器了。所以点击提交按钮时,需要再将图片上传一次。

只需要将图片的url地址传给服务器即可!

 

iframe

所有浏览器都支持 <iframe> 标签,它是兼容性最好的一种方式

iframe 元素会创建包含另外一个文档的内联框架(即行内框架)

举例:内嵌汽车之家

创建文件iframe.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<iframe style="width: 960px;height: 800px;" src="https://www.autohome.com.cn/beijing/" frameborder="0"></iframe>
</body>
</html>
View Code

直接使用谷歌浏览器打开,效果如下:

它直接内嵌了一个网页,如果width和height设置合理的话,打开网页,将会和汽车直接,是一摸一样的。

 

 举例2:输入什么地址,就跳转什么地址

修改iframe.html,增加一个输入框,加入一段js代码。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<input type="text" id="addr">
<input type="submit" onclick="changeUrl()" value="访问">
<iframe id="ifr" style="width: 960px;height: 800px;" src="https://www.autohome.com.cn/beijing/" frameborder="0"></iframe>
<script>
    function changeUrl() {
        //获取输入框的值
        var addr = document.getElementById("addr").value;
        //修改iframe的src的值
        document.getElementById("ifr").src = addr;
    }
</script>
</body>
</html>
View Code

访问页面,效果如下:

注意:整体页面并没有刷新,只是iframe里面刷新了!

它有2个应用场景:

1. iframe标签
可以修改src,且页面不刷新
2. form表单
提交表单数据,但刷新数据

 

修改urls.py,增加路径

urlpatterns = [
    path('admin/', admin.site.urls),
    path('index/', views.index),
    path('upload_file/', views.upload_file),
    path('form_data_upload/', views.form_data_upload),
    path('iframe_upload_img/', views.iframe_upload_img),
    path('upload_iframe/', views.upload_iframe),
]
View Code

修改views.py,增加视图函数

def iframe_upload_img(request):
    if request.method == "GET":
        return render(request,'iframe_upload_img.html')
    
    USER_LIST = []  # 空列表
    user = request.POST.get('user')
    pwd = request.POST.get('pwd')
    avatar = request.POST.get('avatar')
    # 最加到列表中
    USER_LIST.append(
        {
            'user':user,
            'pwd':pwd,
            'avatar':avatar
        }
    )
    return HttpResponse("上传成功")

def upload_iframe(request):  # iframe post提交
    ret = {'status':True,'data':None}
    try:
        avatar = request.FILES.get('avatar')
        file_name = str(uuid.uuid4()) + "." + avatar.name.rsplit('.', maxsplit=1)[1]
        img_file_path = os.path.join('static', 'upload', file_name)
        with open(img_file_path, 'wb') as f:
            for line in avatar.chunks():
                f.write(line)
        ret['data'] = os.path.join("/",img_file_path)
        
    except Exception as e:
        ret['status'] = False
        ret['error'] = '上传失败'
        
    return HttpResponse(json.dumps(ret))
View Code

增加iframe_upload_img.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
     <div style="height: 100px;width: 100px;padding: 2px;border: 1px solid #dddddd;position: relative;">
            <iframe style="display: none;" id="ifr" name="fffff"></iframe>
            <form method="POST" action="/upload_iframe/" enctype="multipart/form-data" target="fffff">
                {% csrf_token %}
                <img style="height: 100px;width: 100px;border: 0;overflow: hidden;border-radius: 50%;" id="prevImg"
                     src="/static/images/default.png">
                <input style="top: 0;left: 0;right: 0;bottom: 0;opacity: 0;position: absolute;z-index: 102;"
                       id="avatar"
                       name="avatar" type="file" class="img-file"/>
            </form>
        </div>

     <form method="post" action="/iframe_upload_img/">
         {% csrf_token %}
         <input type="text" name="avatar" id="formAvatar" style="display: none">
         <input type="text" name="user" placeholder="请输入用户名">
         <input type="submit" value="提交">
     </form>

    <script src="/static/js/jquery.min.js"></script>
    <script>
        $(function () {
            bindChangeAvatar4();
        });

         function bindChangeAvatar4() {
            $('#avatar').change(function () {
                //parent该变量指的是包含当前分割窗口的父窗口
                $(this).parent().submit();
                //onload 事件会在页面或图像加载完成后立即发生
                $('#ifr')[0].onload = function (){
                    //获取post返回值,比如{"status": true, "data": "/static\\upload\\bc72823e-b274-4a76-8ec2-af844a738959.jpg"}
                    var iframeContents = $('#ifr')[0].contentWindow.document.body.innerText;
                    console.log(iframeContents);
                    //反向序列化数据
                    iframeContents = JSON.parse(iframeContents);
                    if (iframeContents.status) {
                        //修改图片的src属性
                        $('#prevImg').attr('src', iframeContents.data);
                        //修改隐藏输入框的值
                        $('#formAvatar').val(iframeContents.data);
                    }
                }

            })
        }

    </script>
</body>
</html>
View Code

访问url:  http://127.0.0.1:8000/iframe_upload_img/

上传一个图片

点击提交,提示上传成功

 

查看upload目录,会多一个文件 de83205e-2e8b-4839-a1c4-19656df9c49f.jpg

 

总结:

1. 在浏览器端可以做图片预览
    - createObjectURL
    - FileReader
    
2. 使用Ajax上传文件:
    - FormData对象

3. 伪ajax上传文件:
    - iframe 
    - form 
    
4. 图片预览 
    本质就是修改img的src数据
    
5. 使用场景:
    a. 文件上传 
        - FormData对象
        - iframe 
        
    b. 图片预览
        - createObjectURL、FileReader
        - iframe

文件上传场景:

假如是政府,或者传统企业,使用最后一种

假如是互联网企业,使用第3种!

 

三、CBV

CBV(class base views) 就是在视图里使用类处理请求

之前我们写的视图函数,都是FBV(function base views)

路径url的配置

cbv 顾名知义就是通过类的方法来调用,我们在url中配置为如下路径

path('user/', views.UserView.as_view()),

这里的UserView是一个class 类,View不是必须的。一般约定成俗,会加一个View!

要想使用此方法,这个路径后面还得必须有一个as_view()这个是必须的固定格式

views里面函数的格式

在views里面配置类,需要导入一个模块View

from django.views import View
#这里必须要继承View这个类,只有继承了这个url那里的as_view()才会有这个方法
class UserView(View):
    def get(self, request):
        return HttpResponse('cbv-get')

    def post(self, request):
        return HttpResponse('cbv-post')
View Code

注意:get和post的方法名,必须是小写。因为在源码中,使用了request.method.lower(),通过反射来找到类中的方法!

详情请参考下面的内容

 

get方式访问

post方式访问

login登陆页面

修改views.py

from django.views import View
#这里必须要继承View这个类,只有继承了这个url那里的as_view()才会有这个方法
class UserView(View):
    def get(self, request):
        # return HttpResponse('cbv-get')
        return render(request, 'login.html')  # 发送到login.html

    def post(self, request):
        return HttpResponse('cbv-post')
View Code

新建login.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<form action="" method="post">
    {% csrf_token %}
    <input type="text" name="username">
    <input type="submit" value="提交">
</form>
</body>
</html>
View Code

访问页面,点击提交

输出:

这里通过查看View的源码,可以看到里面会有很多种提交方法
http_method_names = ['get', 'post', 'put', 'patch', 'delete', 'head', 'options', 'trace']
使用ajax的时候这些方法都是可以使用的。

cbv匹配原理

这种根据url来匹配方法的是通过反射方法(getattr)来做的。请求过来后先走dispatch这个方法,这个方法存在View类中。

def dispatch(self, request, *args, **kwargs):
    # Try to dispatch to the right method; if a method doesn't exist,
    # defer to the error handler. Also defer to the error handler if the
    # request method isn't on the approved list.
    if request.method.lower() in self.http_method_names:
        handler = getattr(self, request.method.lower(), self.http_method_not_allowed)
    else:
        handler = self.http_method_not_allowed
    return handler(request, *args, **kwargs)

request.method.lower() 将请求方式变成小写。通过反射来找到类中对应的方法!

所有的框架,本质都是通过请求方式,反射不同的函数!

所以CBV的本质,实际还是用的FBV,只不过了类封装而已!

 

定制dispatch

如果需要批量对方法,例如get,post等方法做一些操作的时候,这里我们可以手动写一个dispatch,这个dispatch就和装饰器的效果一样。因为请求来的时候总是先走的dispatch。

from django.views import View
#这里必须要继承View这个类,只有继承了这个url那里的as_view()才会有这个方法
class UserView(View):
    def dispatch(self, request, *args, **kwargs):
        print('操作前的代码...')
        obj = super(UserView, self).dispatch(request, *args, **kwargs)
        print('操作后的代码...')
        return obj

    def get(self, request):
        # return HttpResponse('cbv-get')
        return render(request, 'login1.html')  # 发送到login.html

    def post(self, request):
        return HttpResponse('cbv-post')
View Code

这次我们再通过浏览器访问的时候,发现不管get或者post方法,都会走print代码

 

四、python读写Excel

python操作excel主要用到xlrd和xlwt这两个库,即xlrd是读excel,xlwt是写excel的库。

需要安装2个模块

pip3 install xlrd
pip3 install xlwt

读excel-->xlrd

新建一个excel,文件名为fruits.xlsx,内容如下:

抛开django框架,新建一个test.py文件

import xlrd
#打开excel文件,创建一个workbook对象,book对象也就是fruits.xlsx文件,表含有sheet名
rbook=xlrd.open_workbook(r'C:\Users\xiao\Desktop\fruits.xlsx')
#sheets方法返回对象列表,[<xlrd.sheet.Sheet object at 0x103f147f0>]
rbook.sheets()
# xls默认有3个工作簿,Sheet1,Sheet2,Sheet3
rsheet=rbook.sheet_by_index(0)  # 取第一个工作簿
#获取总行数
rows=rsheet.nrows
#获取总列数
cols=rsheet.ncols
print('行数:',rows,'列数',cols)
#通过cell的位置坐标取得cell值
cell=rsheet.cell(0,0)
print('0,0坐标的值是:',cell.value)
print('3,1坐标的值是:',rsheet.cell(3,1).value)
#取得第二行的值,参数是(行数,起点,终点)
row1=rsheet.row_values(1)
print('第一行的值是:',row1)
View Code

 

执行输出:

行数: 4 列数 2
0,0坐标的值是: 名称
3,1坐标的值是: 0.5
第一行的值是: ['西瓜', 0.3]

 

注意:a1单元格的坐标为0,0。在xlrd模块里面,坐标都是数字,所以不能用a1表示。

坐标如下:

A,B,C纵坐标分别为0,1,2

所以3,1坐标的值是最后一行的价格,也就是0.5

 

循环工作簿的所有行和列

在末尾增加以下代码

# 循环工作簿的所有行
for row in rsheet.get_rows():
    # 循环一行的所有列
    for col in row:
        # 获取一个单元格中的值
        print(col.value)
View Code

执行输出:

名称
价格/斤
西瓜
0.3
脐橙
3.5
黄金梨
0.5

 

写excel-->xltw

import xlwt
f = xlwt.Workbook()
sheet1 = f.add_sheet('学生',cell_overwrite_ok=True)
row0 = ["姓名","年龄","出生日期","爱好"]
colum0 = ["张三","李四","王五"]
#写第一行
for i in range(0,len(row0)):
    sheet1.write(0,i,row0[i])

#写第一列
for i in range(0,len(colum0)):
    sheet1.write(i+1,0,colum0[i])

# 写入一行数据
sheet1.write(1,1,"23")
sheet1.write(1,2,"1990")
sheet1.write(1,3,"")

f.save('test.xls')
View Code

执行程序,查看excel文件

 

OpenPyXL

由于xlrd不能对已存在的xlsx文件,进行修改!所以必须使用OpenPyXL

OpenPyXL:较好的支持对xlsx文件的修改,功能比较强大,适用于需要处理XLSX文件,需要修改XLSX文件中的值,最后生成xlsx。openpyxl(可读写excel表)专门处理Excel2007及以上版本产生的xlsx文件,xls和xlsx之间转换容易

注意:如果文字编码是“gb2312” 读取后就会显示乱码,请先转成Unicode。

官网上最推荐的是openpyxl:

综上,所以选择使用OpenPyX来做一个修改excel的小程序。

OpenPyXL的官网参考:
https://openpyxl.readthedocs.io/en/latest/usage.html
https://openpyxl.readthedocs.io/en/stable/

1、OpenPyXL模块的安装

pip3 install openpyxl

2、快速实现xlsx文件的单元格修改

举例:增加一列地区,并增加相应的值

from openpyxl import load_workbook
#excel文件绝对路径
file_home = r'C:\Users\xiao\Desktop\fruits.xlsx'

wb = load_workbook(filename= file_home)  # 打开excel文件
sheet_ranges = wb['Sheet1']
print(sheet_ranges['A1'].value)  # 打印A1单元格的值
ws = wb['Sheet1'] #根据Sheet1这个sheet名字来获取该sheet
ws["C1"] = '地区'   #修改C1的值为LJK5679842
ws['C2'] = '湖北'
ws['C3'] = '云南'
ws['C4'] = '四川'
wb.save(file_home)    #保存修改后的excel
View Code

执行代码,查看excel文件

 

五、虚拟环境virtualenv

windows 安装

安装模块virtualenv

pip3 install virtualenv

安装纯净环境

–no-site-packages表示不包括系统全局的Python安装包,这样会更令环境更干净

E:\python_script>virtualenv --no-site-packages pure_venv
Using base prefix 'c:\\python35'
New python executable in E:\python_script\pure_venv\Scripts\python.exe
Installing setuptools, pip, wheel...done.

E:\python_script>cd pure_venv

E:\python_script\pure_venv>cd Scripts

激活虚拟环境

E:\python_script\pure_venv\Scripts>activate.bat

查看当前模块列表

(pure_venv) E:\python_script\pure_venv\Scripts>pip3 list
Package    Version
---------- -------
pip        10.0.1
setuptools 40.0.0
wheel      0.31.1

安装指定版本的django

(pure_venv) E:\python_script\pure_venv\Scripts>pip3 install django==1.11

打开Pcharm,新建一个django项目

点击...

 选择虚拟环境,点击...

 选择虚拟目录的python.exe

 

 选择刚才添加的虚拟环境

 创建项目之后,发现url就是老版本的了!

 

pip安装包临时指定 从国内的清华pip源下载:

pip install django==1.11.11 -i https://pypi.tuna.tsinghua.edu.cn/simple

将虚拟环境的依赖包关系导出到requirements.txt

pip freeze > requirements.txt

注意:约定成熟使用requirements.txt,一些开源的django项目,里面一般使用这个名字!

查看requirements.txt文件,内容如下:

Django==1.11.11
pytz==2018.5

 

如果需要按照 requirements.txt 安装项目依赖的第三方包,使用命令:

pip install -r requirements.txt

新建一个虚拟环境,测试一下,就可以了!

 

linux安装

# 安装virtualenv
/usr/local/python3.5/bin/pip3 install virtualenv
# 创建虚拟目录
mkdir /virtualenvs
cd /virtualenvs
# 创建虚拟环境
# -p 指定python版本
# –no-site-packages表示不包括系统全局的Python安装包,这样会更令环境更干净
/usr/local/python3.5/bin/virtualenv -p /usr/local/python3.5/bin/python3.5 --no-site-packages venv

 

指定豆瓣源

#创建目录
mkdir ~/.pip
# 编辑配置文件
vi ~/.pip/pip.conf

内容如下:

[global]
index-url = http://pypi.douban.com/simple
trusted-host = pypi.douban.com

安装相关模块

# django指定版本为1.11
/virtualenvs/venv/bin/pip3 install django==1.11
# rest-framework
/virtualenvs/venv/bin/pip3 install djangorestframework
# 监控内存模块
 /virtualenvs/venv/bin/pip3 install memory_profiler

 

作业:

1.客户表,放到excel文件中。使用excel上传,将数据导入到数据中!

2.增加登录页面

答案:

1.excel文件上传

新建一个excel文件,比如客户表.xlsx,内容如下:

新建一个项目excel_upload

操作excel,需要安装2个模块。xlrd用来读取,xlwt用来写入!

pip3 install xlrd
pip3 install xlwt

 

修改settings.py,增加static路径

STATIC_URL = '/static/'
STATICFILES_DIRS = (
    os.path.join(BASE_DIR,'static'),
)
View Code

新建目录static,在里面创建2个目录,js和file。file用来存放上传的excel文件

将jquery.min.js放到js目录中!

修改models.py,增加客户表模型

from django.db import models

# Create your models here.
class Customer(models.Model):
    name = models.CharField(max_length=32,verbose_name="姓名")
    age = models.IntegerField(verbose_name="年龄")
    email = models.CharField(max_length=32,verbose_name="邮箱")
    company = models.CharField(max_length=32,verbose_name="公司")
View Code

使用2个命令,生成表

python manage.py makemigrations
python manage.py migrate

修改urls.py,增加2个路径

from app01 import views
urlpatterns = [
    path('admin/', admin.site.urls),
    path('upload_file/', views.upload_file),
    path('index/', views.index),
]
View Code

修改views.py,增加视图函数

import os
import uuid
import xlrd
from app01 import models
from excel_upload import settings
from django.shortcuts import render, HttpResponse, redirect
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger

# Create your views here.

def upload_file(request):  # 上传文件
    if request.method == "GET":
        return render(request, 'upload_file.html')

    user = request.POST.get('user')
    file_upload = request.FILES.get('customer_excel')  # 获取excel文件对象
    # 判断上传的文件后缀
    if file_upload.name.rsplit('.', maxsplit=1)[1] not in ['xls','xlsx']:
        return HttpResponse('上传失败,只能上传xls格式')
    # 生成唯一的文件名
    file_name = str(uuid.uuid4()) + '.' + file_upload.name.rsplit('.', maxsplit=1)[1]
    # 拼接路径
    img_file_path = os.path.join('static', 'files', file_name)
    print(img_file_path)
    
    with open(img_file_path, 'wb') as f:  # 写入文件
        for line in file_upload.chunks():
            f.write(line)
    
    # 拼接excel文件的绝对路径
    file_path = os.path.join(settings.BASE_DIR, img_file_path)
    print(file_path)
    # 打开excel表
    data = xlrd.open_workbook(file_path)
    table = data.sheet_by_index(0)  # 读取第一个sheet
    nrows = table.nrows  # 获得总行数
    date_list = []  # 定义空列表,用来批量插入
    try:
        for i in range(1, nrows):  # 读取每一行数据
            rows = table.row_values(i)  # 行的数据放在数组里
            # 生成对象
            obj_list = models.Customer(name=rows[0],
                                       age=rows[1],
                                       email=rows[2],
                                       company=rows[3],

                                       )
            # 追加到列表中
            date_list.append(obj_list)
            
        # 使用bulk_create批量插入
        models.Customer.objects.bulk_create(date_list)

    except Exception as e:
        return HttpResponse('批量添加失败{}'.format(e))

    return redirect('/index/')  # 跳转首页


def index(request):  # 首页展示数据
    customer_list = models.Customer.objects.all()  # 读取表中的所有数据
    # print()
    paginator = Paginator(customer_list, 20)  # 每页显示2条

    # 异常判断
    try:
        # 当前页码,如果取不到page参数,默认为1
        current_num = int(request.GET.get("page", 1))  # 当前页码
        customer_list = paginator.page(current_num)  # 获取当前页码的数据
    except EmptyPage:  # 页码不存在时,报EmptyPage错误
        customer_list = paginator.page(1)  # 强制更新为第一页

    #  如果页数十分多时,换另外一种显示方式
    if paginator.num_pages > 9:  # 一般网页展示11页,左5页,右5页,加上当前页,共11页
        if current_num - 4 < 1:  # 如果前5页小于1时
            pageRange = range(1, 9)  # 页码的列表:范围是初始状态
        elif current_num + 4 > paginator.num_pages:  # 如果后5页大于总页数时
            # 页码的列表:范围是(当前页-5,总页数+1)。因为range顾头不顾尾,需要加1
            pageRange = range(current_num - 4, paginator.num_pages + 1)
        else:
            # 页码的列表:后5页正常时,页码范围是(当前页-5,当前页+6)。注意不是+5,因为range顾头不顾尾!
            pageRange = range(current_num - 4, current_num + 5)
    else:
        pageRange = paginator.page_range  # 页码的列表

    data = {"customer_list": customer_list, "paginator": paginator, "current_num": current_num, "pageRange": pageRange}
    return render(request, "index.html", data)
View Code

创建文件index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <link rel="stylesheet" href="https://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css">
</head>
<body>
{#数据展示#}
<div class="container">
    <div class="row">
        <div class="col-md-8">
            <table class="table table-bordered">
                <thead>
                <tr>
                    <th>编号</th>
                    <th>姓名</th>
                    <th>年龄</th>
                    <th>邮箱</th>
                    <th>公司</th>
                </tr>
                </thead>
                <tbody>
                {% for customer in customer_list %}
                    <tr>
                        <th scope="row">{{ forloop.counter }}</th>
                        <td>{{ customer.name }}</td>
                        <td>{{ customer.age }}</td>
                        <td>{{ customer.email }}</td>
                        <td>{{ customer.company }}</td>
                    </tr>
                {% endfor %}
                </tbody>
            </table>
            {#分页展示#}
            <nav aria-label="Page navigation">
                <ul class="pagination">
                    <li><a href="?page=1" aria-label="Previous"><span aria-hidden="true">首页</span></a></li>

                    {#has_previous 判断是否有上一页#}
                    {% if customer_list.has_previous %}
                        {#previous_page_number 上一页的页码#}
                        <li><a href="?page={{ customer_list.previous_page_number }}" aria-label="Previous"><span
                                aria-hidden="true">上一页</span></a></li>
                    {% else %}
                        {#class="disabled" 禁止用户点击#}
                        <li class="disabled"><a href="" aria-label="Previous"><span aria-hidden="true">上一页</span></a>
                        </li>
                    {% endif %}

                    {#遍历页码的列表#}
                    {% for i in pageRange %}
                        {#判断当前页码数等于底部页码时#}
                        {% if current_num == i %}
                            {#增加class,加深按钮#}
                            <li class="active"><a href="?page={{ i }}">{{ i }}</a></li>
                        {% else %}
                            {#href参数为简写,它会自动获取当前路径,并拼接参数#}
                            <li><a href="?page={{ i }}">{{ i }}</a></li>
                        {% endif %}

                    {% endfor %}

                    {#has_next 判断是否有下一页#}
                    {% if customer_list.has_next %}
                        {#next_page_number 下一页的页码#}
                        <li><a href="?page={{ customer_list.next_page_number }}" aria-label="Next"><span
                                aria-hidden="true">下一页</span></a></li>
                    {% else %}
                        {#class="disabled" 禁止用户点击#}
                        <li class="disabled"><a href="" aria-label="Next"><span aria-hidden="true">下一页</span></a></li>
                    {% endif %}

                    <li><a href="?page={{ paginator.num_pages }}" aria-label="Next"><span aria-hidden="true">最后一页</span></a>
                    </li>

                </ul>
            </nav>
        </div>

    </div>
</div>


</body>
</html>
View Code

创建文件upload_file.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <form method="post" enctype="multipart/form-data">
        {% csrf_token %}
       <div style="position: relative;display: inline-block;height: 50px;min-width: 300px;overflow: hidden;">
            <div style="position: absolute;top: 0;left: 0;right: 0;bottom: 0;z-index: 1000;border: 1px dotted #9d9d9d;color: #9d9d9d;line-height: 50px;padding-left: 15px;">
                <i class="fa fa-cloud-upload" aria-hidden="true"></i>
                <span>点击上传文件</span>
            </div>
            <input name="customer_excel" type="file" id="excelFile"
                   style="position: absolute;top: 0;left: 0;right: 0;bottom: 0;background-color: #333333;z-index: 1001;opacity: 0;filter:alpha(opacity=0);">
        </div>
        <div>
            <input type="text" name="user">
            <input type="submit" value="提交">
        </div>
    </form>
    <script src="/static/js/1jquery.min.js"></script>
<script>
    !window.jQuery && alert('jQuery未导入!请确认路径是否正确');
    $(function () {
        $('#excelFile').change(function (e) {
            var fileName = e.currentTarget.files[0].name;
            $(this).prev().find('span').text(fileName);
        })
    })
</script>
</body>
</html>
View Code

启动django项目,访问页面

上传一个excel文件

成功后,会跳转首页

 2.登录页面

创建超级用户

python manage.py createsuperuser

完整代码在github:

https://github.com/987334176/excel_upload

 

登录效果如下:

 

 

posted @ 2018-07-18 17:09  肖祥  阅读(2875)  评论(1编辑  收藏  举报