最近寫 Side-Projcet 花了很多時間卡在這種鬼地方,於官方的文件,還是一些線上的參考資料中,都沒有獲得有用的資訊,最後還直接看 Django-Storages 的 Source Code 才找到解法,其問題是覺得該套件沒有做好管線處理,導致許多功能都只能依賴它的方法,並且不易變化。
Preface
幾個禮拜前才寫了一篇 Python 相關的文章 「[Python 3] 檢查檔案及目錄是否存在 ,自動更名,避免複寫。」而已,最近又因為跳坑,開始寫 WebApp,主要是使用 Python 與 Django 框架開發的一個簡易上傳功能的網站,並搭載縮網址功能,可惜沒有打算 Open Source ((笑
首先參考了不少網路上的教學資源,也翻過 Buffer Overflow ,完全無功而返,查看完Django-Storages的 Source Code 之後才發現事情好像沒這麼簡單,功能幾乎都是寫死的,由於自身功力不足,基本上就是透過 Class 的方式去複寫Django-Storages上的函式庫,雖然也覺得是個笨方法,但也沒更好的解藥了,至於為什麼網路上的教學沒有用,我想可能是版本差異,所以方法不可行。
Environment
- Dgango 3.6+
- Django 3.0+
- Django-Storages 1.9.1
Preceding Operation
基本上就是建置好你的 Virtual Environment 與安裝好 Django 、 Django-Storages,並設置好 Settings.py 。
AWS_ACCESS_KEY_ID = ""
AWS_SECRET_ACCESS_KEY = ""
AWS_STORAGE_BUCKET_NAME = ""
AWS_S3_CUSTOM_DOMAIN = f'{AWS_STORAGE_BUCKET_NAME}.s3.amazonaws.com'
AWS_STATIC_LOCATION = 'static'
STATICFILES_STORAGE = 'webapp.storage_backends.StaticStorage'
STATIC_URL = f"https://{AWS_S3_CUSTOM_DOMAIN}/{AWS_STATIC_LOCATION}/"
AWS_PRIVATE_MEDIA_LOCATION = 'media/private'
PRIVATE_FILE_STORAGE = 'webapp.storage_backends.PrivateMediaStorage'
AWS_DEFAULT_ACL = None
然後設置 Models ,關於 path_from_date 的功能就是設置日期路徑,針對上傳的時間建立樹狀日期目錄。
class File_Info(models.Model):
def path_from_date(instance, filename):
date = datetime.today()
if date:
year = str(date.year)
month = str(date.month).zfill(2)
day = str(date.day).zfill(2)
return f'{year}/{month}/{day}/{filename}'
id = models.AutoField(db_column='ID', primary_key=True)
media_storage = PrivateMediaStorage()
file_name = models.FileField(upload_to=path_from_date,
storage= media_storage
)
# file_name = models.ImageField(storage=PrivateMediaStorage())
content_type = models.ForeignKey(db_column='Content_type', null=True)
file_url = models.URLField(db_column='File_URL', max_length=1600)
class Meta:
managed = True
db_table = 'File_Info'
建立 storage_backends.py ,基本上就是把整個 Source 整個貼過來,然後加上 #Add This 那兩行,如果要新增其他元素也可以從這邊加上,但比較麻煩的就是遞值。
from django.conf import settings
from storages.backends.s3boto3 import S3Boto3Storage
from os import SEEK_SET
class PrivateMediaStorage(S3Boto3Storage):
location = settings.AWS_PRIVATE_MEDIA_LOCATION
default_acl = 'private'
file_overwrite = False
custom_domain = False
def _save(self, name, content):
cleaned_name = self._clean_name(name)
name = self._normalize_name(cleaned_name)
params = self._get_write_parameters(name, content)
if (self.gzip and
params['ContentType'] in self.gzip_content_types and
'ContentEncoding' not in params):
content = self._compress_content(content)
params['ContentEncoding'] = 'gzip'
# Add This ...
filename = name.split('/')[-1]
params['ContentDisposition'] = f'attachment; filename="{filename}"'
# end
encoded_name = self._encode_name(name)
obj = self.bucket.Object(encoded_name)
if self.preload_metadata:
self._entries[encoded_name] = obj
content.seek(0, SEEK_SET)
obj.upload_fileobj(content, ExtraArgs=params)
return cleaned_name
Upload File to S3
然後這邊就是 views 的部分,透過 urls.py 設置路由到 save_file 完成上傳的動作。
def save_file(request, file):
file_info = VideoUploadForm(request.POST, request.FILES)
file = file_info.cleaned_data["file"]
file_bin = Filedata(
file_name= file,
content_type= Contenttype.objects.get(name= file.content_type),
)
file_bin.save()
Result
可以看見上傳至 S3 之後的檔案路徑,除了一開始設置的根路徑為 location = settings.AWS_PRIVATE_MEDIA_LOCATION 也就是 media/private 以外,後方的路徑就依照日期年、月、日來建立資料夾,並存放檔案。
Reference
https: / / s t a c k o v e r f l o w . c o m / q u e s t i o n s / 4 3 2 0 8 4 0 1 / a d d – d y n a m i c – c o n t e n t – d i s p o s i t i o n – f o r – f i l e – n a m e s a m a z o n – s 3 – i n – p y t h o n
h t t p s : / / d j a n g o – s t o r a g e s . r e a d t h e d o c s . i o / e n / l a t e s t / b a c k e n d s / a m a z o n – S 3 . h t m l
h t t p s : / /g i t h u b . c o m / e t i a n e n / d j a n g o – s 3 – s t o r a g e