media
有别于static的静态文件,media通常用于存储图片、音、视频、压缩文件等各种跟用户相关的文件,所以配置和使用上也不一样。
Django也提供了一套默认的文件存储系统,你可以通过下面的官档快速了解下:
- 管理文件:https://docs.djangoproject.com/zh-hans/5.0/topics/files/#managing-files
- 文件存储api:https://docs.djangoproject.com/zh-hans/5.0/ref/files/storage/#module-django.core.files.storage
- 重要request.FILES对象:https://docs.djangoproject.com/zh-hans/5.0/ref/request-response/#django.http.HttpRequest.FILES
跟模型类结合使用时,主要涉及到两个字段:
FileField:一个专门用于文件上传的字段,官档:https://docs.djangoproject.com/zh-hans/5.0/ref/models/fields/#filefield。它的常用属性有:
- FieldFile.name,上传文件的文件名。
- FieldFile.size,获取文件大小。
- FieldFile.path,只读属性,通过调用底层的 path() 方法,访问文件的本地文件系统路径,用的较少。
- FileField.upload_to,指定上传目录和文件名的方式。
ImageField:继承 FileField 的所有属性和方法,但也验证上传的对象是有效的图像。官档:https://docs.djangoproject.com/zh-hans/5.0/ref/models/fields/#imagefield。除了包含FileField 的特殊属性外, ImageField 也有两个常用属性:
ImageField.height_field,模型字段的名称,每次保存模型实例时将自动填充图像的高度。
ImageField.width_field,模型字段的名称,每次保存模型实例时将自动填充图像的宽度。
注意,ImageField内部依赖pillow模块,所以,你要提前安装该模块
pip install Pillow
。否则会提醒:bash(dj5py312) D:\code\dj5>python manage.py makemigrations SystemCheckError: System check identified some issues: ERRORS: store.UserInfo.avatar: (fields.E210) Cannot use ImageField because Pillow is not installed. HINT: Get Pillow at https://pypi.org/project/Pillow/ or run command "python -m pip install Pillow".
想要使用Django提供的默认的文件存储系统,一些必不可少的配置需要做。
示例
我们来做一个完整的用户信息管理功能来演示相关的配置和用法。
"""
注意要启用静态文件相关的app
避不可少的配置
MEDIA_ROOT:默认值为''(空字符串)。用于保存用户上传的文件的绝对路径。另外,MEDIA_ROOT 和 STATIC_ROOT 必须有不同的值。
MEDIA_URL:默认值为''(空字符串),如果设置为非空值,则必须以斜线结束。结合路由中的配置,可以对外提供访问。
"""
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles', # 这个app要启用
'apps.ctts.apps.CttsConfig',
'apps.drf.apps.DrfConfig',
'apps.api.apps.ApiConfig',
'apps.store.apps.StoreConfig',
'rest_framework',
]
STATIC_URL = 'static/'
STATICFILES_DIRS = (
os.path.join(BASE_DIR, 'static'),
)
if not os.path.exists(MEDIA_ROOT):
os.makedirs(MEDIA_ROOT)
MEDIA_URL = "/media/"
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
"""
避不可少的配置,注意,如果是多app环境,必须要在主路由(也就是settings.py同级目录下的urls.py)中配置才能生效
"""
from django.contrib import admin
from django.urls import path, include, re_path
from django.contrib.staticfiles.views import serve
from django.views.static import serve
from django.conf import settings
from apps import drf, api, store
urlpatterns = [
path('admin/', admin.site.urls),
path('drf/', include(('apps.drf.urls', 'apps.drf'), namespace='drf')),
path('api/', include(('apps.api.urls', 'apps.api'), namespace='api')),
path('store/', include(('apps.store.urls', 'apps.store'), namespace='store')),
re_path('media/(?P<path>.*)$', serve, {"document_root": settings.MEDIA_ROOT}), # 这个配置不能少
path('favicon.ico', serve, {'path': 'favicon.ico', 'document_root': '/static/'})
]
from django.db import models
def custom_upload_to(instance, filename):
return 'certs/{filename}'.format(filename=filename)
class UserInfo(models.Model):
username = models.CharField(max_length=32, verbose_name='用户名')
avatar = models.ImageField(upload_to='avatars/', verbose_name='用户头像', default='avatars/default.png')
cert = models.FileField(upload_to='cert/', verbose_name='用户证书', default='certs/default.png')
def __str__(self):
return self.username
import os
from django.shortcuts import render, redirect
from django.http.response import HttpResponse
from django.views.generic import View
from django.views.generic import ListView, CreateView, UpdateView, DeleteView
from django.urls import reverse_lazy
from django.forms import ModelForm, Form
from apps.store.models import UserInfo
class UserInfoForm(ModelForm):
class Meta:
model = UserInfo
fields = ['username', 'avatar', 'cert']
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# 为所有的字段添加相同的属性
for field in self.fields.values():
field.widget.attrs.update({"class": "form-control"})
field.error_messages.update({"required": "该字段不能为空"})
def clean_cert(self):
# 校验就省略了
cert = self.cleaned_data['cert']
return cert
def clean_avatar(self):
# 校验就省略了
avatar = self.cleaned_data['avatar']
return avatar
class UserInfoView(ListView):
model = UserInfo
template_name = 'user/user_list.html'
context_object_name = 'user_list'
class UserInfoAddView(CreateView):
model = UserInfo
template_name = 'user/user_add.html'
form_class = UserInfoForm
success_url = reverse_lazy("store:userlist")
def form_valid(self, form):
# 可以在这里进行手动保存,做一些额外的操作
# print(111, self.request.FILES)
form.save()
return redirect(self.success_url)
class UserInfoDelView(DeleteView):
model = UserInfo
success_url = reverse_lazy("store:userlist")
def get(self, request, *args, **kwargs):
user = UserInfo.objects.get(id=request.GET.get("id"))
if not user.avatar.name.endswith("default.png"):
user.avatar.delete()
if not user.cert.name.endswith("default.png"):
user.cert.delete()
user.delete()
return redirect(self.success_url)
class UserInfoUpdateView(UpdateView):
model = UserInfo
success_url = reverse_lazy("store:userlist")
template_name = 'user/user_add.html'
context_object_name = 'user_detail'
form_class = UserInfoForm
def form_valid(self, form):
# 可以在这里进行手动保存,做一些额外的操作
# print(111, self.object.avatar.name, )
if os.path.exists(self.object.avatar.path):
os.remove(self.object.avatar.path)
if os.path.exists(self.object.cert.path):
os.remove(self.object.cert.path)
form.save()
return redirect(self.success_url)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>用户信息处理</title>
<link href="https://cdn.bootcss.com/twitter-bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdn.bootcss.com/twitter-bootstrap/3.3.7/css/bootstrap-theme.min.css" rel="stylesheet">
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.1/jquery.js"></script>
<script src="http://cdn.bootcss.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
<style>
.errorMsg {
color: red;
margin-left: 10px;
}
</style>
</head>
<body>
<div class="container">
<div class="row">
<div class="col-md-8 col-md-offset-2">
{% block content %}
{% endblock %}
</div>
</div>
</div>
</body>
</html>
{% extends 'user/base.html' %}
{% block content %}
<h3>用户列表</h3>
<a class="btn btn-success" href="{% url 'store:useradd' %}">添加</a>
<table class="table table-hover table-striped">
<thead>
<tr>
<th>姓名</th>
<th>头像</th>
<th>认证文件</th>
<th>操作</th>
</tr>
</thead>
<tbody>
{% for user_info in user_list %}
<tr>
<td>{{ user_info.username }}</td>
<td>{{ user_info.avatar }}</td>
<td>{{ user_info.cert }}</td>
<td>
<a href="{% url 'store:userdetail' user_info.id %}?id={{ user_info.id }}" class="btn bg-info">编辑</a>
<a href="{% url 'store:userdel' %}?id={{ user_info.id }}" class="btn bg-warning">删除</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endblock %}
{% extends 'user/base.html' %}
{% block content %}
<h3>添加用户</h3>
<form action="" method="post" novalidate enctype="multipart/form-data">
{% csrf_token %}
{% for field in form %}
<div class="form-group">
<label for="">{{ field.label }}</label>
<!-- 渲染input标签,并在有错误提示时渲染该错误信息 -->
{{ field }} <span class="errorMsg">{{ field.errors.0 }}</span>
</div>
{% endfor %}
<p><input type="submit" class="btn btn-success pull-left"></p>
</form>
{% endblock %}
关于自定义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
常见问题
django静态文件配置没问题,浏览器请求静态文件成功,但页面无法渲染静态文件中的样式
win10 + python3.9 + django4.2
这个问题非常难碰到啊!大概情况是这样的,django4.2的项目,在正确引入静态文件后,浏览器访问Django项目成功,访问其css文件成功,但页面中无法应用上css文件中的样式。
排查:
- django关于静态文件的配置,OK的。
- django开启了debug=True,OK的。
- 配置中INSTALLED_APPS也有
django.contrib.staticfiles
。 - 项目html文件中,load static也没问题,相关引入静态文件的写法也都正确。
- 浏览器访问通过network栏发现请求到了css文件,也是200,看响应也拿到了css文件内容。
- 浏览器的console栏中也没有报错或者相关提示信息输出,当然有的会提示
Resource interpreted as Stylesheet but transferred with MIME type application/x-css
错误信息。
一切迹象表明,咱这静态文件引入是没问题的,但,就是页面不加载css样式。
继续浏览器的network栏观察,静态文件的请求,发现请求头和响应头是否有问题,有一行响应头引起了注意,那就是:
上面这个截图真是要素过多啊!!!!这个content-type就是这里就是突破口,经查,来自这个帖子:https://www.cnblogs.com/lance2088/p/4173868.html解释,我们套到Django中,其原因应该就是django在返回时,设置mimetypes时,如果是Windows环境下,就会读取本地的注册表,而此时如果注册表中的相关设置偶问题,这样就造成浏览器不能正常显示页面,css完全不起作用。
而我复现问题时,也发现的确是win10系统复现出来了问题,但在win11中没有复现出来,无所谓了,顺这个思路知道咋解决就好了。
来看解决流程吧!
win + R键打开运行,输入regedit
打开注册表,然后按照下图操作就完了。
完事之后,重启电脑,再浏览器尝试访问,应该就没问题了。
再不行,就看下注册表中这个选项是否也需要修改:计算机\HKEY_LOCAL_MACHINE\SOFTWARE\Classes\.css
。
参考: