因爲博客用的富文本編輯器, 文章博客圖片想通過第三方存儲, 這樣可以通過cdn獲取以加速網站打開速度, 搜索部分資料發現這個篇文章不錯
介紹
最近正在使用Django開發一個項目,有一個需求是需要在後臺,使用富文本編輯器,去自定義一些內容。會涉及到圖片的上傳。我準備把上傳的內容存儲到七牛裏面,不想放在服務器上面。碰到一些問題,總結一下。
分析
我使用的django-ckeditor版本爲5.6.1。前面操作都是跟官網說的一樣。
- 在setting.py裏面的INSTALLED_APPS增加:
'ckeditor', # 富文本編輯器
'ckeditor_uploader' # 富文本編輯器上傳圖片模塊
增加upload_path
CKEDITOR_UPLOAD_PATH = "uploads/"
- 在model裏面對相應的字段,使用ckeditor_uploader。
from ckeditor_uploader.fields import RichTextUploadingField
content = RichTextUploadingField(verbose_name='內容')
- 重點來了,那我怎麼拿到我上傳的文件或圖片呢,比如我要傳到七牛。通過查看ckeditor_uploader的源碼,我們發現有一個路由配置文件ckeditor_uploader/urls.py。
很好奇的查看upload做了什麼事
大概的閱讀了一下,發現這個地方就是處理用戶上傳文件的時候,其中保存文件是在self._save_file(request, uploaded_file)這個方法裏面操作的。繼續往下面看
看到這個storage就有點熟悉了,django也有提供,難道是一個東西?
到這邊就已經很明白了,如果我們不在setting裏面定義storage的話,就默認用的django的storage。
方案
分析完代碼之後,我大概理了一下思路,我想了想一共有兩種方法,可以去解決:
- 在setting裏面配置CKEDITOR_STORAGE_BACKEND
這個是比較簡單的,也是官方推薦我們用的方法,我們只需要處理上傳的地方就可以了。只需要寫個class把django.core.files.storage封裝一下就可以。
from django.core.files.storage import Storage
class StorageObject(Storage):
def __init__(self):
self.now = datetime.datetime.now()
self.file = None
def _new_name(self, name):
new_name = "file/{0}/{1}.{2}".format(self.now.strftime("%Y/%m/%d"), str(uuid.uuid4()).replace('-', ''),
name.split(".").pop())
return new_name
def _open(self, name, mode):
return self.file
def _save(self, name, content):
"""
上傳文件到七牛
"""
# 構建鑑權對象
q = Auth(access_key, secret_key)
token = q.upload_token(bucket_name)
self.file = content
file_data = content.file
ret, info = put_data(token, self._new_name(name), file_data.read())
if info.status_code == 200:
base_url = 'http://%s/%s' % (QINIU_URL_DOMAIN, ret.get("key"))
# 表示上傳成功, 返回文件名
return base_url
else:
# 上傳失敗
raise Exception("上傳七牛失敗")
def exists(self, name):
# 驗證文件是否存在,因爲我這邊會去生成一個新的名字去存儲到七牛,所以沒有必要驗證
return False
def url(self, name):
# 上傳完之後,已經返回的是全路徑了
return name
然後在setting裏面配置
CKEDITOR_STORAGE_BACKEND = 'common.storage.StorageObject'
就可以了。
2. 第二個方法就比較抽象了,在寫url的時候,直接把ckeditor/upload用我們自己的方法來代替。
urlpatterns = [
path('ckeditor/upload/', staff_member_required(csrf_exempt(CkeditorView.as_view())), name='ckeditor_upload'),
]
然後寫一個view
class CkeditorView(generic.View):
permission_classes = [IsAuthenticated]
http_method_names = ['post']
def post(self, request, **kwargs):
uploaded_file = request.FILES['upload']
backend = image_processing.get_backend()
ck_func_num = request.GET.get('CKEditorFuncNum')
if ck_func_num:
ck_func_num = escape(ck_func_num)
# Throws an error when an non-image file are uploaded.
if not getattr(settings, 'CKEDITOR_ALLOW_NONIMAGE_FILES', True):
try:
backend.image_verify(uploaded_file)
except utils.NotAnImageException:
return HttpResponse("""
<script type='text/javascript'>
window.parent.CKEDITOR.tools.callFunction({0}, '', 'Invalid file type.');
</script>""".format(ck_func_num))
# 存儲文件
url = storage(uploaded_file)
if ck_func_num:
# Respond with Javascript sending ckeditor upload url.
return HttpResponse("""
<script type='text/javascript'>
window.parent.CKEDITOR.tools.callFunction({0}, '{1}');
</script>""".format(ck_func_num, url))
else:
retdata = {'url': url, 'uploaded': '1',
'fileName': uploaded_file.name}
return JsonResponse(retdata)
這樣的話,就可以了。
這個方法不太建議,一般我們沒有必要去修改整個post的過程,只需要動到upload file這個過程就可以了。
結論
就上面兩個方案,我建議使用第一個,簡單方便。
秀一下成果