1-Django - 静态文件

before

django提供了两种静态文件:

  • static,这类静态文件用于django项目所需要的静态文件,如js、css、img等等这些静态文件。
  • media,这类静态文件就是用户相关的静态文件了,比如存放用户上传的图像、音视频文件等。

static

来看怎么引入的。

首先,项目结构:

D:\TMP\DEMO\
├─app01   # app,内部结构略
|  └─static
|      └─index.css
├─demo
|  └─settings.py
├─static
|  ├─jquery-2.2.3.min.js
|  ├─a.jpg
│  └─bootstrap
│      ├─css
       |  └─bootstrap.css
│      └─js
|	  └─bootstrap.js
└─templates
   ├─books.html
   └─form.html

然后在settings.py中配置相关路径:

import os

INSTALLED_APPS = [
    # 这个app必须写,不写前端访问404
    'django.contrib.staticfiles'
]

BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
STATIC_URL = '/static/'  # 通过别名指向STATICFILES_DIRS目录,当然,别名也可以修改
STATICFILES_DIRS = [  # 列表或者元组都行
    os.path.join(BASE_DIR, 'static'),
    os.path.join(BASE_DIR, 'app01', 'static')   # 你也可以配置多个静态文件目录,只需拼上路径就好了
]

我们把静态文件CSSJSimg等都可以存放在static目录。当然,这里面可以有多个静态文件目录。但每个目录必须存在,才能后续引用。
在你的html文件中可以这么用:

<!DOCTYPE html>
<html lang="en">
<head>
    {% load static %}   <!-- 使用load声明引用静态文件 -->
	{% static 'a.jpg' as a_obj %}  <!-- 某个文件被多次使用可以赋值给变量 -->
    <meta charset="UTF-8">
    <meta http-equiv="x-ua-compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>看中医,学医术,尽在中医智库</title>
    <link rel="stylesheet" href="{% static 'index.css' %}">   <!-- 这个引入是app01下的static中的静态文件 -->
    <link rel="stylesheet" href="{% static 'base.css' %}"> <!-- 使用static别名进行引用静态文件 -->
    <link rel="stylesheet" href="{% static 'base.css' %}">
    <link rel="stylesheet" href="{% static 'bootstrap/css/bootstrap.min.css' %}">
</head>
<body>
<img src="{% static 'a.jpg' %}" alt="">
<img src="{{ a_obj }}"></img>
</body>
<script src="{% static 'jquery-2.2.3.min.js' %}"></script>
<script src="{% static 'bootstrap/js/bootstrap.min.js' %}"></script>
</html>

推荐使用方式2。

get_static_prefix

get_static_prefix用的较少,它是获取settings中STATIC_URL指向的别名,在模板中渲染时是这样的:

<!DOCTYPE html>
<html lang="en">
<head>
    {% load static %}   <!-- 必须load static -->
    <meta charset="UTF-8">
    <title>Title</title>
    <link rel="stylesheet" href="{% static 'bootstrap/css/bootstrap.js' %}">
    <link rel="stylesheet" href="{% get_static_prefix %}bootstrap/css/bootstrap.js">
</head>
<body>
{% get_static_prefix %}   <!--渲染成: /static/ -->

</body>
<script src="{% static 'bootstrap/js/bootstrap.js' %}"></script>
<script src="{% get_static_prefix %}bootstrap/js/bootstrap.js"></script> 
</html>

get_static_prefix的用法如上示例所示,它跟static的区别是,get_static_prefix需要手动拼接路径;而static是Django会自动拼接路径,推荐使用static

media

media通常就是跟用户打交道了,所以涉及到表中的两个字段:

  • FileField:一个专门用于文件上传的字段。它的常用属性有:
    • FieldFile.name,上传文件的文件名。
    • FieldFile.size,获取文件大小。
    • FieldFile.path,只读属性,通过调用底层的 path() 方法,访问文件的本地文件系统路径,用的较少。
  • ImageField:继承 FileField 的所有属性和方法,但也验证上传的对象是有效的图像。除了 FileField 的特殊属性外, ImageField 也有两个常用属性:
    • ImageField.height_field,模型字段的名称,每次保存模型实例时将自动填充图像的高度。
    • ImageField.width_field,模型字段的名称,每次保存模型实例时将自动填充图像的宽度。

除此之外,我们还可以在settings.py中,配置两个media参数:

  • MEDIA_ROOT:默认值为''(空字符串)。用于保存用户上传的文件的绝对路径。另外,MEDIA_ROOT 和 STATIC_ROOT 必须有不同的值。
  • MEDIA_URL: 默认值为''(空字符串),如果设置为非空值,则必须以斜线结束。结合路由中的配置,可以对外提供访问。

来看看简单的用法,如果你的settings.py中,没有配置MEDIA_ROOT和MEDIA_URL,即两个参数默认为空字符串,然后models.py中的字段设置如下:

from django.db import models

class Asset(models.Model):
    user = models.CharField(max_length=32, verbose_name='用户名')
    avatar = models.FileField(upload_to='avatars/', verbose_name='用户头像', default='avatars/default.png')
    def __str__(self):
        return self.user

那么,如果生成一条用户记录时,图片会被django默认保存到项目根目录下的avatars目录下,如果avatars目录不存在,会先创建。而Asset表的avatar字段存的数据长这样avatars/20190701134630.jpg,很明显存的是路径而不是文件内容。

但一切都是默认的,肯定不符合我们的需求,我们需要自己定义这些用户文件应该保存到项目的哪个目录下,当然通常这些文件将会保存到对应的文件服务器中。
来看看怎么自定义吧。
首先先来看配置文件settings.py

# 上传的文件将会保存到项目根目录下的media目录下
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')  
MEDIA_URL = "/media/"   # 必须反斜杠结尾。

然后models.py不变:

from django.db import models

class Asset(models.Model):
    user = models.CharField(max_length=32, verbose_name='用户名')
    avatar = models.FileField(upload_to='avatars/', verbose_name='用户头像', default='avatars/default.png')
    def __str__(self):
        return self.user

另外,urls.py还要进行特殊的配置,注意,如果是多app环境,必须要在主路由(也就是项目目录下的urls.py)中配置才能生效

from django.contrib import admin
from django.urls import path, re_path
from django.views.static import serve
from django.conf import settings
from app01 import views
urlpatterns = [
    path('admin/', admin.site.urls),
    # 关于media配置的固定写法
    re_path('media/(?P<path>.*)$', serve, {"document_root": settings.MEDIA_ROOT})
]

经过这么一配置啊,我们浏览器就可以通过访问mediaurl,访问到项目根目录下的静态文件了,如http://127.0.0.1:8000/media/avatars/20190701134630.jpg
来通过一个示例进行演示,包括文件上传、删除、下载的示例。
settings.py

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

urls.py,注意,必须是在总路由文件配置关于media的配置:

from django.contrib import admin
from django.urls import path, re_path
from django.views.static import serve
from django.conf import settings
from app01 import views
urlpatterns = [
    path('admin/', admin.site.urls),
    # 关于media配置的固定写法
    re_path('media/(?P<path>.*)$', serve, {"document_root": settings.MEDIA_ROOT}),
    path('add/', views.add, name='add'),
    path('user_list/', views.user_list, name='user_list'),
    re_path('del_user/(?P<pk>.*?)/', views.del_user, name='del_user'),
    re_path('download/(?P<pk>.*?)/', views.download, name='download'),
]

views.py

import os
from django.shortcuts import render, HttpResponse, redirect
from django.http import FileResponse
# 解决图片名称不能是中文的问题,导入这个家伙
from django.utils.encoding import escape_uri_path
from app01 import models



def add(request):
    """ 添加用户信息 """
    if request.method == "GET":
        return render(request, 'add_user.html')
    user = request.POST.get('user')
    avatar = request.FILES.get('avatar')

    models.Asset.objects.create(user=user, avatar=avatar)
    return redirect('/user_list/')


def user_list(request):
    """ 用户列表页 """
    if request.method == "GET":
        users = models.Asset.objects.all()
        return render(request, 'user_list.html', {"users": users})


def del_user(request, pk):
    # 如果直接使用delete删除记录,那么表中的记录是会被删除,但是avatar对应的文件不会被删除
    # models.Asset.objects.filter(pk=pk).delete()
    # 所以,想要连文件一块删除,则需要手动删除
    obj = models.Asset.objects.filter(pk=pk).first()
    obj.avatar.delete(save=True)  # 删除对应的文件
    obj.delete()  # 删除表中记录

    return redirect('/user_list/')


def download(request, pk):
    # 下面示例演示从django项目的本地下载图片
    # 如果是文件存储在远程,则可以通过requests模块获取文件内容,在封装到FileResponse中
    file_obj = models.Asset.objects.filter(pk=pk).first()
    file_path = file_obj.avatar.path
    # print(file_path)
    f = open(file_path, 'rb')
    response = FileResponse(f)
    response['Content-Type'] = 'application/octet-stream'
    response['Content-Disposition'] = 'attachment;filename="{}"'.format(escape_uri_path(file_path.rsplit(os.sep, 1)[-1]))
    # 这里不能关闭文件
    # f.close()
    return response

models.py

from django.db import models


class Asset(models.Model):
    user = models.CharField(max_length=32, verbose_name='用户名')
    avatar = models.FileField(upload_to='avatars/', verbose_name='用户头像', default='avatars/')
    def __str__(self):
        return self.user

demo\templates\add_user.html:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

<h3>添加用户信息</h3>
<form action="" method="post" enctype="multipart/form-data">
    {% csrf_token %}
    <input type="text" name="user" value="zhangkai">
    <input type="file" name="avatar">
    <input type="submit" value="提交">
</form>
</body>
</html>

demo\templates\user_list.html:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <link href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="container">
    <div class="row">
        <div class="col-md-10">
        <h3>用户列表页</h3>
            <a href="/add/"><button>添加用户</button></a>
            <table class="table table-hover table-striped">
                <thead>
                <tr>
                    <th>用户名</th>
                    <th>图片</th>
                    <th>大小(kb)</th>
                    <th>操作</th>
                </tr>
                </thead>
                <tbody>
                {% for row in users %}
                    <tr>
                        <td>{{ row.user }}</td>
                        <td>{{ row.avatar.name }}</td>
                        <td>{{ row.avatar.size }}</td>
                        <td>
                            <a href="{% url 'del_user' row.pk %}" class="btn btn-default">删除</a>
                            <a href="{% url 'download' row.pk %}" class="btn btn-primary">下载</a>
                        </td>
                    </tr>
                {% endfor %}
                </tbody>
            </table>
        </div>
    </div>
</div>
</body>
</html>

关于自定义media的路径

这部分就是说,我们在上传文件或者图片时,可能会遇到重名的问题,就有了自定义的路径的需求,django提供了自定义上传文件名的方法,只需要修改models.py文件即可:

import os
import uuid  # # 通过uuid或者其他方式搞个随机的字符串解决重名
from django.db import models


def custom_file_path(instance, filename):
    print(333, uuid.uuid4().hex, type(uuid.uuid4().hex), filename, type(filename))
    # 通过uuid搞出来一个子目录,如 media\avatars\f49c652c622640bdad2abffa8a29471e\abc.jpg
    # 但有个操蛋的问题,删除时,只会删除图片,而那个uuid的子目录就保留了,需要我们手动处理,这.....我懒得处理,故排除这个写法
    # return os.path.join('avatars', uuid.uuid4().hex, filename)

    # 上面的懒得搞,就折衷把文件名搞一下解决处理,这样删除的时候就可以了,也不用管那个子目录的事儿了
    return os.path.join('avatars', '{}-{}'.format(uuid.uuid4().hex, filename))


class Asset(models.Model):
    user = models.CharField(max_length=32, verbose_name='用户名')
    avatar = models.FileField(upload_to=custom_file_path, verbose_name='用户头像', default='avatars/default.png')

    def __str__(self):
        return self.user

修改了之后,别忘了重新执行数据库迁移。
另外,你要说这种程度的自定义不满足我的需求,其实在开发中,一般都有静态文件的服务器,比如七牛云、腾讯云来配合处理,那个时候,就不用咱们这么处理了。


that's all, see also:

https://www.cnblogs.com/yuanchenqi/articles/7614921.html

posted @ 2019-06-01 18:39  听雨危楼  阅读(877)  评论(0编辑  收藏  举报