带 SFTP 的 Django 存储:GET 请求失败
Django-Storages with SFTP: GET-requests fail
我正在尝试使用 django-storages 访问我的“Hetzner”存储盒 (https://www.hetzner.com/storage/storage-box) 使用 SFTP 应该保存媒体数据,即图像文件我网站的用户可以动态上传。
我的 settings.py
文件的相应部分如下所示:
DEFAULT_FILE_STORAGE = 'storages.backends.sftpstorage.SFTPStorage'
SFTP_STORAGE_HOST = 'username.your-storagebox.de'
SFTP_STORAGE_ROOT = '/media'
SFTP_STORAGE_PARAMS = {
'username': 'username',
'password': 'password',
'allow_agent': False,
'look_for_keys': False,
}
奇怪的是,当用户上传图片时,它被放置在存储空间 space 中,我可以使用 SFTP 确认。但是从存储盒中获取图像失败,没有图像显示。控制台摘录:
[03/Sep/2021 22:34:01] "GET /media/filename.jpg HTTP/1.1" 404 1962
我发现 Django 仍在我的 MEDIA_DIR
中寻找文件。再一次,我设置的相应部分:
MEDIA_DIR = 'media'
MEDIA_ROOT = os.path.join(BASE_DIR, MEDIA_DIR)
MEDIA_URL = '/media/'
所以简而言之:使用 SFTP 似乎可以将文件放入存储中,但以某种方式再次获取它们却失败了。
编辑:根据要求,我将提供更多代码片段:
models.py
:
class SizeRestrictedImageField(ImageField):
def __init__(self, *args, **kwargs):
self.max_upload_size = kwargs.pop('max_upload_size', 0)
super().__init__(*args, **kwargs)
def clean(self, *args, **kwargs):
data = super().clean(*args, **kwargs)
file = data.file
try:
if file.size > self.max_upload_size:
raise forms.ValidationError(_('Please keep filesize under %s. Current filesize %s'
) % (filesizeformat(self.max_upload_size),
filesizeformat(file.size)))
except AttributeError:
logger.exception('An Exception occured while checking for max size of image upload. size: `%s`'
, file.size)
pass
return data
class ImageModel(models.Model):
image = SizeRestrictedImageField(upload_to=POST_PIC_FOLDER, null=True, blank=True,
help_text="Erlaubte Dateitypen: .jpeg, .jpg, .png, .gif", max_upload_size=MAX_IMAGE_SIZE)
还有我的urls.py
:
urlpatterns = [
path('defaultsite/', defaultsite_view, name='home'),
path('help', help_view, name="help"),
path('user/', include('user.urls')),
path('sowi/', include('sowi.urls')),
path('blog/', include('blog.urls')),
path('chat/', include('chat.urls')),
path('notifications/', include('notifications.urls')),
path('cookies/', include('cookie_consent.urls')),
path('', home_view, name="home"),
path('about/', AboutUsView.as_view(), name="about-us"),
path('impressum/', impressum_view, name="imprint"),
path('privacy/', privacy_view, name='privacy'),
path('privacy/statement/', privacy_statement_view, name='privacy-statement'),
path('agb', agb_view, name="agb")
] + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) + static(settings.MEDIA_URL,
document_root=settings.MEDIA_ROOT)
我尝试从我的 url-模式中删除 +static(...)
-部分,但这似乎没有解决问题。
检查 django 存储设置
我觉得您可能忘记了在 Django 模型中迁移您的字段?
在 django-storage
documentation on Github 中,您有这些代码片段。
发件人:
photo = models.FileField(
storage=FileSystemStorage(location=settings.MEDIA_ROOT),
upload_to='photos',
)
至:
photo = models.FileField(
upload_to='photos',
)
难道是这样吗? (如评论中所述,有一些代码片段会大有帮助。
SFTP访问
Django-storage
act 有一个代理可以将您的文件保存在某个地方。我可以是一个s3 bucket,一个http cdn之类的。或者在你的情况下是 SFTP 服务器。
使用其他支持 HTTP 协议的后端,很容易取回文件。由于后端会为您提供 link 直接指向您存储的内容。
对于SFTP,这会有所不同,网页本身不支持FTP协议。因此,为了访问该文件,您必须在网页和 FTP 服务器之间创建一个代理层。
@action(methods=['get'], detail=True)
def download(self, request, pk=None):
try:
obj = ImageModel.objects.get(id=pk)
except ImageModel.DoesNotExist:
raise Http404
# with some SFTP client
# 1. check the file exist
# 2. pull the file from the server
# 3. attach it to the response with the proper header
stream = sftp_client.open(obj.file.name)
file = stream.read()
type, encoding = mimetypes.guess_type(obj.file.name)
response = HttpResponse(file, content_type=type)
response['Content-Disposition'] = u'attachment; filename="{filename}'.format(
filename=obj.file.name)
return response
raise Http404
我想完成@Paulos 的回答。您可以使用中间件创建代理。创建文件 project/middleware.py
并将其添加到 settings.py
中的 middleware
数组。
然后创建中间件:
import mimetypes
from storages.backends.sftpstorage import SFTPStorage
from django.http import HttpResponse
class SFTPMiddleware:
def __init__(self, get_response):
self.get_response = get_response
# One-time configuration and initialization.
def __call__(self, request):
# Code to be executed for each request before
# the view (and later middleware) are called.
SFS = SFTPStorage()
response = self.get_response(request)
path = request.get_full_path()
if SFS.exists(path):
file = SFS._read(path)
type, encoding = mimetypes.guess_type(path)
response = HttpResponse(file, content_type=type)
response['Content-Disposition'] = u'attachment; filename="{filename}"'.format(filename=path)
return response
编辑:
实际上没有必要在每次调用时都打开一个新连接。您应该在系统启动时在 config.py
的 ready()
挂钩中创建一个连接并使用这个连接。
我正在尝试使用 django-storages 访问我的“Hetzner”存储盒 (https://www.hetzner.com/storage/storage-box) 使用 SFTP 应该保存媒体数据,即图像文件我网站的用户可以动态上传。
我的 settings.py
文件的相应部分如下所示:
DEFAULT_FILE_STORAGE = 'storages.backends.sftpstorage.SFTPStorage'
SFTP_STORAGE_HOST = 'username.your-storagebox.de'
SFTP_STORAGE_ROOT = '/media'
SFTP_STORAGE_PARAMS = {
'username': 'username',
'password': 'password',
'allow_agent': False,
'look_for_keys': False,
}
奇怪的是,当用户上传图片时,它被放置在存储空间 space 中,我可以使用 SFTP 确认。但是从存储盒中获取图像失败,没有图像显示。控制台摘录:
[03/Sep/2021 22:34:01] "GET /media/filename.jpg HTTP/1.1" 404 1962
我发现 Django 仍在我的 MEDIA_DIR
中寻找文件。再一次,我设置的相应部分:
MEDIA_DIR = 'media'
MEDIA_ROOT = os.path.join(BASE_DIR, MEDIA_DIR)
MEDIA_URL = '/media/'
所以简而言之:使用 SFTP 似乎可以将文件放入存储中,但以某种方式再次获取它们却失败了。
编辑:根据要求,我将提供更多代码片段:
models.py
:
class SizeRestrictedImageField(ImageField):
def __init__(self, *args, **kwargs):
self.max_upload_size = kwargs.pop('max_upload_size', 0)
super().__init__(*args, **kwargs)
def clean(self, *args, **kwargs):
data = super().clean(*args, **kwargs)
file = data.file
try:
if file.size > self.max_upload_size:
raise forms.ValidationError(_('Please keep filesize under %s. Current filesize %s'
) % (filesizeformat(self.max_upload_size),
filesizeformat(file.size)))
except AttributeError:
logger.exception('An Exception occured while checking for max size of image upload. size: `%s`'
, file.size)
pass
return data
class ImageModel(models.Model):
image = SizeRestrictedImageField(upload_to=POST_PIC_FOLDER, null=True, blank=True,
help_text="Erlaubte Dateitypen: .jpeg, .jpg, .png, .gif", max_upload_size=MAX_IMAGE_SIZE)
还有我的urls.py
:
urlpatterns = [
path('defaultsite/', defaultsite_view, name='home'),
path('help', help_view, name="help"),
path('user/', include('user.urls')),
path('sowi/', include('sowi.urls')),
path('blog/', include('blog.urls')),
path('chat/', include('chat.urls')),
path('notifications/', include('notifications.urls')),
path('cookies/', include('cookie_consent.urls')),
path('', home_view, name="home"),
path('about/', AboutUsView.as_view(), name="about-us"),
path('impressum/', impressum_view, name="imprint"),
path('privacy/', privacy_view, name='privacy'),
path('privacy/statement/', privacy_statement_view, name='privacy-statement'),
path('agb', agb_view, name="agb")
] + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) + static(settings.MEDIA_URL,
document_root=settings.MEDIA_ROOT)
我尝试从我的 url-模式中删除 +static(...)
-部分,但这似乎没有解决问题。
检查 django 存储设置
我觉得您可能忘记了在 Django 模型中迁移您的字段?
在 django-storage
documentation on Github 中,您有这些代码片段。
发件人:
photo = models.FileField(
storage=FileSystemStorage(location=settings.MEDIA_ROOT),
upload_to='photos',
)
至:
photo = models.FileField(
upload_to='photos',
)
难道是这样吗? (如评论中所述,有一些代码片段会大有帮助。
SFTP访问
Django-storage
act 有一个代理可以将您的文件保存在某个地方。我可以是一个s3 bucket,一个http cdn之类的。或者在你的情况下是 SFTP 服务器。
使用其他支持 HTTP 协议的后端,很容易取回文件。由于后端会为您提供 link 直接指向您存储的内容。
对于SFTP,这会有所不同,网页本身不支持FTP协议。因此,为了访问该文件,您必须在网页和 FTP 服务器之间创建一个代理层。
@action(methods=['get'], detail=True)
def download(self, request, pk=None):
try:
obj = ImageModel.objects.get(id=pk)
except ImageModel.DoesNotExist:
raise Http404
# with some SFTP client
# 1. check the file exist
# 2. pull the file from the server
# 3. attach it to the response with the proper header
stream = sftp_client.open(obj.file.name)
file = stream.read()
type, encoding = mimetypes.guess_type(obj.file.name)
response = HttpResponse(file, content_type=type)
response['Content-Disposition'] = u'attachment; filename="{filename}'.format(
filename=obj.file.name)
return response
raise Http404
我想完成@Paulos 的回答。您可以使用中间件创建代理。创建文件 project/middleware.py
并将其添加到 settings.py
中的 middleware
数组。
然后创建中间件:
import mimetypes
from storages.backends.sftpstorage import SFTPStorage
from django.http import HttpResponse
class SFTPMiddleware:
def __init__(self, get_response):
self.get_response = get_response
# One-time configuration and initialization.
def __call__(self, request):
# Code to be executed for each request before
# the view (and later middleware) are called.
SFS = SFTPStorage()
response = self.get_response(request)
path = request.get_full_path()
if SFS.exists(path):
file = SFS._read(path)
type, encoding = mimetypes.guess_type(path)
response = HttpResponse(file, content_type=type)
response['Content-Disposition'] = u'attachment; filename="{filename}"'.format(filename=path)
return response
编辑:
实际上没有必要在每次调用时都打开一个新连接。您应该在系统启动时在 config.py
的 ready()
挂钩中创建一个连接并使用这个连接。