我应该在Django中将图片上传到静态目录吗?

2 投票
1 回答
8086 浏览
提问于 2025-04-18 05:19

我有一个模型,里面有一个图片字段。

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 个回答

10

对于 FileFieldImageField 上传的文件,它们是相对于 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

撰写回答