上传
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 Noneuse_url = getattr(self, 'use_url', api_settings.UPLOADED_FILES_USE_URL)if use_url:try:url = value.urlexcept AttributeError:return Nonerequest = self.context.get('request', None)if request is not None:return request.build_absolute_uri(url)return urlreturn value.name
value的值其实是一个django/db/models/fields/fields.py中的FieldFile类的实例.
class FieldFile(File):...@propertydef url(self):self._require_file()return self.storage.url(self.name)
self.storage的值时django中一个配置项,DEFAULT_FILE_STORAGE.默认值为FileSystemStorage的实例.
class FileSystemStorage(Storage):...@cached_propertydef 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 Noneuse_url = getattr(self, 'use_url', api_settings.UPLOADED_FILES_USE_URL)if use_url:try:url = value.urlexcept 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 urlreturn 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一个相对路径了.会比较方便.
