上传
upload_to参数可以指定文件被上传到的文件夹,如果不存在会被自动创建. 支持time-format.
upload_to参数的根目录是settings文件中的MEDIA_ROOT所指向的目录
MEDIA_ROOT = "/opt/project_data"
MEDIA_URL = "/files/"
class Question(models.Model):
image = models.FileField(blank=True, null=True, upload_to='os_fans_images/%Y/%m/%d', verbose_name='问题图片')
class Meta:
db_table = 'question'
进行上述配置之后上传的图片就会被自动上传到/opt/project_data/os_fans_images/2022/06/08/目录下了.
注意上传的时候的contentType需要为multipart-formdata或者formdata.
此时数据库中image字段在数据库中的值为os_fans_images/2022/05/09/17.png
返回
drf 的FileField的to_representation方法源码如下.
class FileField(Field):
...
def to_representation(self, value):
if not value:
return None
use_url = getattr(self, 'use_url', api_settings.UPLOADED_FILES_USE_URL)
if use_url:
try:
url = value.url
except AttributeError:
return None
request = self.context.get('request', None)
if request is not None:
return request.build_absolute_uri(url)
return url
return value.name
value的值其实是一个django/db/models/fields/fields.py中的FieldFile类的实例.
class FieldFile(File):
...
@property
def url(self):
self._require_file()
return self.storage.url(self.name)
self.storage
的值时django中一个配置项,DEFAULT_FILE_STORAGE.默认值为FileSystemStorage的实例.
class FileSystemStorage(Storage):
...
@cached_property
def base_url(self):
if self._base_url is not None and not self._base_url.endswith('/'):
self._base_url += '/'
return self._value_or_setting(self._base_url, settings.MEDIA_URL)
def url(self, name):
if self.base_url is None:
raise ValueError("This file is not accessible via a URL.")
url = filepath_to_uri(name)
if url is not None:
url = url.lstrip('/')
return urljoin(self.base_url, url)
可以看到在没有指定参数的情况下会将settings.MEDIA_URL拼接到数据库数据的前面
因此在to_representation方法的第10行.将os_fans_images/2022/05/09/17.png
就变为了/files/os_fans_images/2022/05/09/17.png
然后,to_representation方法的第15行,build_absolute_uri会根据request的domain来将绝对路径拼接完成./files/os_fans_images/2022/05/09/17.png
就变为了http://10.150.80.80/files/os_fans_images/2022/05/09/17.png
由于,通常django会跑在nginx的反向代理后面,所以build_absolute_uri方法会传入错误的协议或者domain.可以考虑重写这个序列化器字段.
class BaseFileField(FileField):
def to_representation(self, value):
if not value:
return None
use_url = getattr(self, 'use_url', api_settings.UPLOADED_FILES_USE_URL)
if use_url:
try:
url = value.url
except AttributeError:
return None
# 20220218:相比父类移除了build_absolute_uri的调用以规避域名问题
# request = self.context.get('request', None)
# if request is not None:
# return request.build_absolute_uri(url)
return url
return value.name
那么为什么不直接将user_url指定为false呢?
因为MEDIA_ROOT通常我们会用nginx代理.这样可以通过nginx来下载用户上传的文件.
location /files/ {
autoindex on;
autoindex_exact_size off;
autoindex_localtime on;
charset utf-8,gbk;
alias /opt/project_data/;
}
这样前端收到的值就是/files/os_fans_images/2022/05/09/17.png
一个相对路径了.会比较方便.