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
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
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') # 你也可以配置多個靜態文件目錄,只需拼上路徑就好了
]
我們把靜態文件CSS
、JS
、img
等都可以存放在static
目錄。當然,這里面可以有多個靜態文件目錄。但每個目錄必須存在,才能后續引用。
引入方式1:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<link rel="stylesheet" href="/static/jquery-2.2.3.min.js">
<link rel="stylesheet" href="/static/bootstrap/css/bootstrap.js">
</head>
<body>
</body>
<script src="/static/bootstrap/js/bootstrap.js"></script>
</html>
但是,我不推薦上面這種寫死了的形式,如果靜態文件位置改變,那就所有的頁面都無法引用靜態文件了。
所以,推薦下面這種形式。通過settings
中的static
別名,我們如果更改了具體的靜態文件路徑,只需要在setttings
中更改別名的路徑就可以了,相當靈活。
引入方式2:
<!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
還要進行特殊的配置:
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})
]
經過這么一配置啊,我們瀏覽器就可以通過訪問media
url,訪問到項目根目錄下的靜態文件了,如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
:
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>