+from datetime import datetime
+
+from django.conf import settings
+from django.core.files.storage import default_storage
+
+from django.http import JsonResponse
+
+from django.views import generic
+from django.views.decorators.csrf import csrf_exempt
+
+from . import utils, image_processing
+
+
+def get_upload_filename(upload_name):
+ # Generate date based path to put uploaded file.
+ date_path = datetime.now().strftime('%Y/%m/%d')
+
+ # Complete upload path (upload_path + date_path).
+ upload_path = os.path.join(settings.SIMDITOR_UPLOAD_PATH, date_path)
+
+ if getattr(settings, 'SIMDITOR_UPLOAD_SLUGIFY_FILENAME', True):
+ upload_name = utils.slugify_filename(upload_name)
+
+ return default_storage.get_available_name(os.path.join(upload_path, upload_name))
+
+
+def upload_handler(request):
+ files = request.FILES
+
+ upload_config = settings.SIMDITOR_CONFIGS.get(
+ 'upload', {'fileKey': 'upload'})
+ filekey = upload_config.get('fileKey', 'upload')
+
+ uploaded_file = files.get(filekey)
+
+ if not uploaded_file:
+ retdata = {'file_path': '', 'success': False,
+ 'msg': '图片上传失败,无法获取到图片对象!'}
+ return JsonResponse(retdata)
+
+ image_size = upload_config.get('image_size')
+ if image_size and uploaded_file.size > image_size:
+ retdata = {'file_path': '', 'success': False,
+ 'msg': '上传失败,已超出图片最大限制!'}
+ return JsonResponse(retdata)
+
+ backend = image_processing.get_backend()
+
+ if not getattr(settings, 'SIMDITOR_ALLOW_NONIMAGE_FILES', True):
+ try:
+ backend.image_verify(uploaded_file)
+ except utils.NotAnImageException:
+ retdata = {'file_path': '', 'success': False,
+ 'msg': '图片格式错误!'}
+ return JsonResponse(retdata)
+
+ filename = get_upload_filename(uploaded_file.name)
+ saved_path = default_storage.save(filename, uploaded_file)
+
+ url = utils.get_media_url(saved_path)
+
+ is_api = settings.SIMDITOR_CONFIGS.get('is_api', False)
+ url = request.META.get('HTTP_ORIGIN') + url if is_api else url
+
+ retdata = {'file_path': url, 'success': True, 'msg': '上传成功!'}
+
+ return JsonResponse(retdata)
+
+
+class ImageUploadView(generic.View):
+ """ImageUploadView."""
+
+ http_method_names = ['post']
+
+ def post(self, request, **kwargs):
+ """Post."""
+ return upload_handler(request)
+
+
+UPLOAD = csrf_exempt(ImageUploadView.as_view())
@@ -0,0 +1,170 @@ |
||
1 |
+"""simditor widgets.""" |
|
2 |
+from __future__ import absolute_import |
|
3 |
+ |
|
4 |
+from django import forms |
|
5 |
+from django.conf import settings |
|
6 |
+from django.core.exceptions import ImproperlyConfigured |
|
7 |
+from django.core.serializers.json import DjangoJSONEncoder |
|
8 |
+ |
|
9 |
+from django.template.loader import render_to_string |
|
10 |
+from django.utils.encoding import force_text |
|
11 |
+from django.utils.safestring import mark_safe |
|
12 |
+from django.utils.html import conditional_escape |
|
13 |
+from django.utils.functional import Promise |
|
14 |
+ |
|
15 |
+try: |
|
16 |
+ # Django >=2.1 |
|
17 |
+ from django.forms.widgets import get_default_renderer |
|
18 |
+ IS_NEW_WIDGET = True |
|
19 |
+except ImportError: |
|
20 |
+ IS_NEW_WIDGET = False |
|
21 |
+ |
|
22 |
+try: |
|
23 |
+ # Django >=1.7 |
|
24 |
+ from django.forms.utils import flatatt |
|
25 |
+except ImportError: |
|
26 |
+ # Django <1.7 |
|
27 |
+ from django.forms.util import flatatt # pylint disable=E0611, E0401 |
|
28 |
+ |
|
29 |
+ |
|
30 |
+class LazyEncoder(DjangoJSONEncoder): |
|
31 |
+ """LazyEncoder.""" |
|
32 |
+ |
|
33 |
+ # pylint disable=E0202 |
|
34 |
+ def default(self, obj): |
|
35 |
+ if isinstance(obj, Promise): |
|
36 |
+ return force_text(obj) |
|
37 |
+ return super(LazyEncoder, self).default(obj) |
|
38 |
+ |
|
39 |
+ |
|
40 |
+JSON_ENCODE = LazyEncoder().encode |
|
41 |
+ |
|
42 |
+ |
|
43 |
+FULL_TOOLBAR = [ |
|
44 |
+ 'title', 'bold', 'italic', 'underline', 'strikethrough', 'fontScale', |
|
45 |
+ 'color', '|', 'ol', 'ul', 'blockquote', 'code', 'table', '|', 'link', |
|
46 |
+ 'image', 'hr', '|', 'indent', 'outdent', 'alignment', 'checklist', |
|
47 |
+ 'markdown', 'fullscreen' |
|
48 |
+] |
|
49 |
+ |
|
50 |
+DEFAULT_TOOLBAR = [ |
|
51 |
+ 'title', 'bold', 'italic', 'underline', 'strikethrough', 'fontScale', |
|
52 |
+ 'color', '|', 'ol', 'ul', 'blockquote', 'table', '|', 'link', |
|
53 |
+ 'image', 'hr', '|', 'indent', 'outdent', 'alignment' |
|
54 |
+] |
|
55 |
+ |
|
56 |
+DEFAULT_CONFIG = { |
|
57 |
+ 'toolbar': DEFAULT_TOOLBAR, |
|
58 |
+ 'cleanPaste': True, |
|
59 |
+ 'tabIndent': True, |
|
60 |
+ 'pasteImage': True, |
|
61 |
+ 'upload': { |
|
62 |
+ 'url': '/', |
|
63 |
+ 'fileKey': 'file' |
|
64 |
+ } |
|
65 |
+} |
|
66 |
+ |
|
67 |
+ |
|
68 |
+class SimditorWidget(forms.Textarea): |
|
69 |
+ """ |
|
70 |
+ Widget providing Simditor for Rich Text Editing.abs |
|
71 |
+ Supports direct image uploads and embed. |
|
72 |
+ """ |
|
73 |
+ class Media: |
|
74 |
+ """Media.""" |
|
75 |
+ |
|
76 |
+ css_list = [ |
|
77 |
+ 'simditor/styles/simditor.min.css' |
|
78 |
+ ] |
|
79 |
+ |
|
80 |
+ if 'emoji' in settings.SIMDITOR_TOOLBAR: |
|
81 |
+ css_list.append('simditor/styles/simditor-emoji.css') |
|
82 |
+ |
|
83 |
+ if 'fullscreen' in settings.SIMDITOR_TOOLBAR: |
|
84 |
+ css_list.append('simditor/styles/simditor-fullscreen.min.css') |
|
85 |
+ |
|
86 |
+ if 'checklist' in settings.SIMDITOR_TOOLBAR: |
|
87 |
+ css_list.append('simditor/styles/simditor-checklist.min.css') |
|
88 |
+ |
|
89 |
+ if 'markdown' in settings.SIMDITOR_TOOLBAR: |
|
90 |
+ css_list.append('simditor/styles/simditor-markdown.min.css') |
|
91 |
+ |
|
92 |
+ css = {'all': tuple(settings.STATIC_URL + url for url in css_list)} |
|
93 |
+ |
|
94 |
+ jquery_list = ['simditor/scripts/jquery.min.js', |
|
95 |
+ 'simditor/scripts/module.min.js', |
|
96 |
+ 'simditor/scripts/hotkeys.min.js', |
|
97 |
+ 'simditor/scripts/uploader.min.js', |
|
98 |
+ 'simditor/scripts/simditor.min.js'] |
|
99 |
+ |
|
100 |
+ if 'fullscreen' in settings.SIMDITOR_TOOLBAR: |
|
101 |
+ jquery_list.append('simditor/scripts/simditor-fullscreen.min.js') |
|
102 |
+ |
|
103 |
+ if 'checklist' in settings.SIMDITOR_TOOLBAR: |
|
104 |
+ jquery_list.append('simditor/scripts/simditor-checklist.min.js') |
|
105 |
+ |
|
106 |
+ if 'markdown' in settings.SIMDITOR_TOOLBAR: |
|
107 |
+ jquery_list.append('simditor/scripts/marked.min.js') |
|
108 |
+ jquery_list.append('simditor/scripts/to-markdown.min.js') |
|
109 |
+ jquery_list.append('simditor/scripts/simditor-markdown.min.js') |
|
110 |
+ |
|
111 |
+ if 'image' in settings.SIMDITOR_TOOLBAR: |
|
112 |
+ jquery_list.append('simditor/scripts/simditor-dropzone.min.js') |
|
113 |
+ |
|
114 |
+ if 'emoji' in settings.SIMDITOR_TOOLBAR: |
|
115 |
+ jquery_list.append('simditor/scripts/simditor-emoji.js') |
|
116 |
+ |
|
117 |
+ js = tuple(settings.STATIC_URL + url for url in jquery_list) |
|
118 |
+ |
|
119 |
+ try: |
|
120 |
+ |
|
121 |
+ js += (settings.STATIC_URL + 'simditor/simditor-init.js',) |
|
122 |
+ except AttributeError: |
|
123 |
+ raise ImproperlyConfigured("django-simditor requires \ |
|
124 |
+ SIMDITOR_MEDIA_PREFIX setting. This setting specifies a \ |
|
125 |
+ URL prefix to the ckeditor JS and CSS media (not \ |
|
126 |
+ uploaded media). Make sure to use a trailing slash: \ |
|
127 |
+ SIMDITOR_MEDIA_PREFIX = '/media/simditor/'") |
|
128 |
+ |
|
129 |
+ def __init__(self, *args, **kwargs): |
|
130 |
+ super(SimditorWidget, self).__init__(*args, **kwargs) |
|
131 |
+ # Setup config from defaults. |
|
132 |
+ self.config = DEFAULT_CONFIG.copy() |
|
133 |
+ |
|
134 |
+ # Try to get valid config from settings. |
|
135 |
+ configs = getattr(settings, 'SIMDITOR_CONFIGS', None) |
|
136 |
+ if configs: |
|
137 |
+ if isinstance(configs, dict): |
|
138 |
+ self.config.update(configs) |
|
139 |
+ else: |
|
140 |
+ raise ImproperlyConfigured( |
|
141 |
+ 'SIMDITOR_CONFIGS setting must be a dictionary type.') |
|
142 |
+ |
|
143 |
+ def build_attrs(self, base_attrs, extra_attrs=None, **kwargs): |
|
144 |
+ """ |
|
145 |
+ Helper function for building an attribute dictionary. |
|
146 |
+ This is combination of the same method from Django<=1.10 and Django1.11 |
|
147 |
+ """ |
|
148 |
+ attrs = dict(base_attrs, **kwargs) |
|
149 |
+ if extra_attrs: |
|
150 |
+ attrs.update(extra_attrs) |
|
151 |
+ return attrs |
|
152 |
+ |
|
153 |
+ def render(self, name, value, attrs=None, renderer=None): |
|
154 |
+ if value is None: |
|
155 |
+ value = '' |
|
156 |
+ final_attrs = self.build_attrs(self.attrs, attrs, name=name) |
|
157 |
+ |
|
158 |
+ params = ('simditor/widget.html', { |
|
159 |
+ 'final_attrs': flatatt(final_attrs), |
|
160 |
+ 'value': conditional_escape(force_text(value)), |
|
161 |
+ 'id': final_attrs['id'], |
|
162 |
+ 'config': JSON_ENCODE(self.config) |
|
163 |
+ }) |
|
164 |
+ |
|
165 |
+ if renderer is None and IS_NEW_WIDGET: |
|
166 |
+ renderer = get_default_renderer() |
|
167 |
+ |
|
168 |
+ data = renderer.render(*params) if IS_NEW_WIDGET else render_to_string(*params) |
|
169 |
+ |
|
170 |
+ return mark_safe(data) |