我希望 Django 只为登录用户提供一些媒体文件(例如用户上传的文件)。由于我的网站流量很低,我想我会保持简单,不要使用
django-sendfile
告诉 Nginx 何时提供文件。相反,我会让 Django/Gunicorn 来完成这项工作。对我来说,这似乎更简单,对于低流量站点,这可能更安全。
但是组织文件存储位置的最佳方式是什么?媒体文件都存储在
MEDIA_ROOT
下面,这个目录由 Nginx 在生产中提供。如果我将文件上传到MEDIA_ROOT/protected/
,我必须告诉Nginx不要提供子目录中的文件protected
.
但这是个好主意吗?让 Nginx 访问
/media/
然后保护子目录 /media/protected/
对我来说似乎有点冒险。不使用 MEDIA_ROOT
的子目录来存储受保护的文件不是更好吗?
但是如果我在我的模型中尝试这种快速而肮脏的东西:
upload_to='../protected/documents/%Y/%m/'
Django 抱怨:
SuspiciousFileOperation at /admin/core/document/add/
The joined path (/home/me/projects/project/protected/documents/2016/09/test.file) is located outside of the base path component (/home/me/projects/project/media)
所以我认为“离开”
MEDIA_ROOT
. 不是好的做法
存储和提供受保护媒体文件的最佳解决方案是什么?
直接从视图中提供媒体文件(可能是大文件)并不好。您可以使用 nginx 服务器中提供的
sendfile
扩展;示例 nginx 配置如下所示。
location /projects/project/media/{
# this path is not public
internal;
# absolute path
alias /projects/project/media/;
}
改变你的看法
@login_required
def serve_protected_document(request, file):
document = get_object_or_404(ProtectedDocument, file="protected/documents/" + file)
# Split the elements of the path
path, file_name = os.path.split(file)
response = HttpResponse()
response["Content-Disposition"] = "attachment; filename=" + file_name
# nginx uses this path to serve the file
response["X-Accel-Redirect"] = document.name # path to file
return response
链接:关于在 nginx 上配置 sendfile 扩展的更多细节是这里
我现在想出了以下解决方案:
我的 Django 设置中有这个:
MEDIA_ROOT = "/projects/project/media/"
MEDIA_URL = "/media/
在我的模型中,我要么:
document = models.FileField(upload_to="public/documents")
或
document = models.FileField(upload_to="protected/documents")
这样,我的媒体文件目录中现在有两个子目录“public”和“protected”。
Nginx 或 Djangos 开发服务器仅提供“公共”子目录中的文件。
对于 Djangos 开发服务器:
if os.environ["ENVIRONMENT_TYPE"] == 'development':
urlpatterns += static(settings.MEDIA_URL + "public/", document_root=settings.MEDIA_ROOT + "public/")
对于 Nginx(用于生产):
location /media/public/ {
alias /projects/project/media/public/;
}
当我想提供受保护的文档时,我会执行以下操作:
在 urls.py 中:
url(r'^media/protected/documents/(?P<file>.*)$', core.views.serve_protected_document, name='serve_protected_document'),
在 views.py 中:
@login_required()
def serve_protected_document(request, file):
document = get_object_or_404(ProtectedDocument, file="protected/documents/" + file)
# Split the elements of the path
path, file_name = os.path.split(file)
response = FileResponse(document.file,)
response["Content-Disposition"] = "attachment; filename=" + file_name
return response
如有任何意见,我将不胜感激!有没有更好的方法来实现这个?
from django.conf import settings
from django.conf.urls.static import static
from django.contrib import admin
from django.urls import include, path
from .views import media_protect
urlpatterns = [
...
path('media/protect/<str:app>/<int:author>/<str:file>', media_protect)
...
]
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
from django.contrib.auth.decorators import login_required
from django.http import FileResponse
from project import settings
@login_required
def media_protect(request, app, author, file):
media_root = os.path.join(settings.MEDIA_ROOT)
if ...:
response = FileResponse(
open(f'{media_root}/protect/{app}/{author}/{file}', 'rb'))
return response
else ...:
retun ...