puchar/tinymce/widgets.py
2022-12-05 04:13:11 +01:00

179 lines
6.5 KiB
Python

# Copyright (c) 2008 Joost Cassee
# Licensed under the terms of the MIT License (see LICENSE.txt)
"""
This TinyMCE widget was copied and extended from this code by John D'Agostino:
http://code.djangoproject.com/wiki/CustomWidgetsTinyMCE
"""
from collections import OrderedDict
import json
from pathlib import Path
import warnings
from django import forms
from django.conf import settings
from django.contrib.admin import widgets as admin_widgets
from django.forms.utils import flatatt
from django.urls import reverse
from django.utils.html import escape
from django.utils.safestring import mark_safe
from django.utils.translation import get_language, gettext as _, to_locale
import tinymce.settings
class TinyMCE(forms.Textarea):
"""
TinyMCE widget. Set settings.TINYMCE_JS_URL to set the location of the
javascript file. Default is "STATIC_URL + 'tinymce/tinymce.min.js'".
You can customize the configuration with the mce_attrs argument to the
constructor.
In addition to the standard configuration you can set the
'content_language' parameter. It takes the value of the 'language'
parameter by default.
In addition to the default settings from settings.TINYMCE_DEFAULT_CONFIG,
this widget sets the 'language', 'directionality' and
'spellchecker_languages' parameters by default. The first is derived from
the current Django language, the others from the 'content_language'
parameter.
"""
def __init__(self, content_language=None, attrs=None, mce_attrs=None):
super().__init__(attrs)
mce_attrs = mce_attrs or {}
self.mce_attrs = mce_attrs
if "mode" not in self.mce_attrs:
self.mce_attrs["mode"] = "exact"
self.mce_attrs["strict_loading_mode"] = 1
self.content_language = content_language
def use_required_attribute(self, *args):
# The html required attribute may disturb client-side browser validation.
return False
def get_mce_config(self, attrs):
mce_config = tinymce.settings.DEFAULT_CONFIG.copy()
if "language" not in mce_config:
mce_config["language"] = get_language_from_django()
if mce_config["language"] == "en_US":
del mce_config["language"]
else:
mce_config["language"] = match_language_with_tinymce(mce_config["language"])
mce_config.update(
get_language_config(self.content_language or mce_config.get("language", "en_US"))
)
if tinymce.settings.USE_FILEBROWSER:
mce_config["file_picker_callback"] = "djangoFileBrowser"
mce_config.update(self.mce_attrs)
if mce_config["mode"] == "exact":
mce_config["elements"] = attrs["id"]
return mce_config
def render(self, name, value, attrs=None, renderer=None):
if value is None:
value = ""
final_attrs = self.build_attrs(self.attrs, attrs)
final_attrs["name"] = name
if final_attrs.get("class", None) is None:
final_attrs["class"] = "tinymce"
else:
final_attrs["class"] = " ".join(final_attrs["class"].split(" ") + ["tinymce"])
assert "id" in final_attrs, "TinyMCE widget attributes must contain 'id'"
mce_config = self.get_mce_config(final_attrs)
mce_json = json.dumps(mce_config)
if tinymce.settings.USE_COMPRESSOR:
compressor_config = {
"plugins": mce_config.get("plugins", ""),
"themes": mce_config.get("theme", "advanced"),
"languages": mce_config.get("language", ""),
"diskcache": True,
"debug": False,
}
final_attrs["data-mce-gz-conf"] = json.dumps(compressor_config)
final_attrs["data-mce-conf"] = mce_json
html = [f"<textarea{flatatt(final_attrs)}>{escape(value)}</textarea>"]
return mark_safe("\n".join(html))
def _media(self):
css = None
if tinymce.settings.USE_COMPRESSOR:
js = [reverse("tinymce-compressor")]
else:
js = [tinymce.settings.JS_URL]
if tinymce.settings.USE_FILEBROWSER:
js.append(reverse("tinymce-filebrowser"))
if tinymce.settings.USE_EXTRA_MEDIA:
if "js" in tinymce.settings.USE_EXTRA_MEDIA:
js += tinymce.settings.USE_EXTRA_MEDIA["js"]
if "css" in tinymce.settings.USE_EXTRA_MEDIA:
css = tinymce.settings.USE_EXTRA_MEDIA["css"]
js.append("django_tinymce/init_tinymce.js")
return forms.Media(css=css, js=js)
media = property(_media)
class AdminTinyMCE(TinyMCE, admin_widgets.AdminTextareaWidget):
pass
def get_language_from_django():
language = get_language()
language = to_locale(language) if language is not None else "en_US"
return language
def match_language_with_tinymce(lang):
"""
Language codes in TinyMCE are inconsistent. E.g. Hebrew is he_IL.js, while
Danish is da.js. So we apply some heuristic to find a language code
with an existing TinyMCE translation file.
"""
if lang.startswith("en"):
return lang
# Read tinymce langs from tinymce/static/tinymce/langs/
tiny_lang_dir = Path(__file__).parent / "static" / "tinymce" / "langs"
tiny_langs = [file_.stem for file_ in tiny_lang_dir.iterdir() if file_.suffix == ".js"]
if lang in tiny_langs:
return lang
if lang[:2] in tiny_langs:
return lang[:2]
two_letter_map = {lg[:2]: lg for lg in tiny_langs}
if lang[:2] in two_letter_map:
return two_letter_map[lang[:2]]
warnings.warn(f"No TinyMCE language found for '{lang}', defaulting to 'en_US'", RuntimeWarning)
return "en_US"
def get_language_config(content_language):
content_language = content_language[:2]
config = {}
lang_names = OrderedDict()
for lang, name in settings.LANGUAGES:
if lang[:2] not in lang_names:
lang_names[lang[:2]] = []
lang_names[lang[:2]].append(_(name))
sp_langs = []
for lang, names in lang_names.items():
if lang == content_language:
default = "+"
else:
default = ""
sp_langs.append(f'{default}{" / ".join(names)}={lang}')
config["spellchecker_languages"] = ",".join(sp_langs)
if content_language in settings.LANGUAGES_BIDI:
config["directionality"] = "rtl"
else:
config["directionality"] = "ltr"
if tinymce.settings.USE_SPELLCHECKER:
config["spellchecker_rpc_url"] = reverse("tinymce-spellcheck")
return config