我应该在Django中将图片上传到静态目录吗?
我有一个模型,里面有一个图片字段。
from django.db import models
from django.contrib.auth.models import User
class Customer(models.Model):
user = models.ForeignKey(User)
name = models.CharField(max_length=127)
logo = models.ImageField(upload_to='customer/logo', null=True, blank=True)
def __str__(self):
return self.name
在我的视图中,我从指定的链接下载图片,并把它存储到这个图片字段里。为了测试,我使用一个测试用户作为外键。
import json
import urllib.request
from django.core.files.base import ContentFile
from django.http import HttpResponse
from django.contrib.auth.models import User
from customer.models import Customer
def create(request):
values = json.loads(request.body.decode('utf-8'))
values['user'] = User.objects.get(id=1)
values['logo'] = ContentFile(urllib.request.urlopen(values['logo']).read(),
'test.png')
model = Customer.objects.create(**values)
return HttpResponse('Created customer with ' + str(values))
图片成功上传到了 customer/logo/test.png
,这正是我预期的结果。现在,我该如何在前端显示这些图片呢?我可以把它们保存到静态文件目录里,但只有相关的用户应该能访问这些图片。
(顺便提一下,Django的管理界面显示已经为 Customer
对象上传了一个文件。但是它链接到 http://localhost:8000/admin/customer/customer/20/customer/logo/test.png
,这个位置是错误的,导致找不到页面。)
1 个回答
对于 FileField
和 ImageField
上传的文件,它们是相对于 settings.MEDIA_ROOT
来上传的,并且应该通过在 settings.MEDIA_URL
后面加上相同的相对文件名来访问。这就是为什么你的管理界面指向了错误的 URL。出于安全考虑,这些设置应该和 STATIC_ROOT
以及 STATIC_URL
不同,否则 Django 会报错 ImproperlyConfiguredError
。
不过,这样做并不能阻止用户访问他们不应该看到的文件,如果他们知道或者能猜到 URL。为了防止这种情况,你需要通过 Django 来提供这些私有文件,而不是通过你选择的网络服务器。简单来说,你需要在网站根目录下指定一个私有目录,并且只有在用户有权限查看文件时,才能加载这些文件。例如:
from django.core.files.storage import FileSystemStorage
PRIVATE_DIR = os.path.join(ROOT_DIR, 'web-private')
fs = FileSystemStorage(location=PRIVATE_DIR)
class Customer(models.Model):
logo = models.ImageField(upload_to='customer/logo', storage=fs, null=True, blank=True)
在你的视图中,你需要提供这个文件。我当前项目中的一个自定义应用使用以下函数来发送静态文件:
import mimetypes
from django.http import HttpResponse # StreamingHttpResponse
from django.core.servers.basehttp import FileWrapper
def send_file(file):
"""
Send a file through Django without loading the whole file into
memory at once. The FileWrapper will turn the file object into an
iterator for chunks of 8KB.
"""
filename = file.name
if settings.PRIVATE_MEDIA_USE_XSENDFILE:
# X-sendfile
response = HttpResponse()
response['X-Accel-Redirect'] = filename # Nginx
response['X-Sendfile'] = filename # Apache 2, mod-xsendfile
# Nginx doesn't overwrite headers, but does add the missing headers.
del response['Content-Type']
else:
# Can use django.views.static.serve() method (which supports if-modified-since),
# but this also does the job well, as it's mainly for debugging.
mimetype, encoding = mimetypes.guess_type(filename)
response = HttpResponse(FileWrapper(file), content_type=mimetype)
response['Content-Length'] = os.path.getsize(filename)
return response
然后在你的视图中使用 send_file(customer.logo)
。
如果你使用的是 Django 版本大于等于 1.5,应该使用新的 StreamingHttpResponse
,而不是 HttpResponse
。