Source code for tinymce.widgets

# coding: utf-8
# License: MIT, see LICENSE.txt
"""
TinyMCE 4 forms widget

This TinyMCE widget was copied and extended from this code by John D'Agostino:
http://code.djangoproject.com/wiki/CustomWidgetsTinyMCE
"""

from __future__ import unicode_literals
from __future__ import absolute_import
import json
import logging
import os

from django.conf import settings
from django.contrib.staticfiles import finders
from django.forms import Textarea, Media
from django.forms.utils import flatatt
from django.utils.encoding import smart_text
from django.utils.safestring import mark_safe
from django.utils.html import escape
from django.utils.translation import get_language, get_language_bidi
from django.template.loader import render_to_string
from django.core.urlresolvers import reverse
from django.contrib.admin import widgets as admin_widgets
import tinymce.settings as mce_settings

__all__ = ['TinyMCE', 'render_tinymce_init_js']

logging.basicConfig(format='[%(asctime)s] %(module)s: %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)
logger.setLevel(20)


def language_file_exists(language_code):
    """
    Check if TinyMCE has a language file for the specified lang code

    :param language_code: language code
    :type language_code: str
    :return: check result
    :rtype: bool
    """
    filename = '{0}.js'.format(language_code)
    path = os.path.join('tinymce', 'js', 'tinymce', 'langs', filename)
    return finders.find(path) is not None


def get_language_config():
    """
    Creates a language configuration for TinyMCE4 based on Django project settings

    :return: language- and locale-related parameters for TinyMCE 4
    :rtype: dict
    """
    language_code = convert_language_code(get_language() or settings.LANGUAGE_CODE)
    if not language_file_exists(language_code):
        language_code = language_code[:2]
        if not language_file_exists(language_code):
            # Fall back to English if Tiny MCE 4 does not have required translation
            language_code = 'en'
    config = {'language': language_code}
    if get_language_bidi():
        config['directionality'] = 'rtl'
    else:
        config['directionality'] = 'ltr'
    if mce_settings.USE_SPELLCHECKER:
        from enchant import list_languages
        enchant_languages = list_languages()
        if settings.DEBUG:
            logger.info('Enchant languages: {0}'.format(enchant_languages))
        lang_names = []
        for lang, name in settings.LANGUAGES:
            lang = convert_language_code(lang)
            if lang not in enchant_languages:
                lang = lang[:2]
            if lang not in enchant_languages:
                logger.error('Missing {0} spellchecker dictionary!'.format(lang))
                continue
            if config.get('spellchecker_language') is None:
                config['spellchecker_language'] = lang
            lang_names.append('{0}={1}'.format(name, lang))
        config['spellchecker_languages'] = ','.join(lang_names)
    return config


def convert_language_code(django_lang):
    """
    Converts Django language codes "ll-cc" into ISO codes "ll_CC" or "ll"

    :param django_lang: Django language code as ll-cc
    :type django_lang: str
    :return: ISO language code as ll_CC
    :rtype: str
    """
    lang_and_country = django_lang.split('-')
    try:
        return '_'.join((lang_and_country[0], lang_and_country[1].upper()))
    except IndexError:
        return lang_and_country[0]


def render_tinymce_init_js(mce_config, callbacks, id_=''):
    """
    Renders TinyMCE.init() JavaScript code

    :param mce_config: TinyMCE 4 configuration
    :type mce_config: dict
    :param callbacks: TinyMCE callbacks
    :type callbacks: dict
    :param id_: HTML element's ID to which TinyMCE is attached.
    :type id_: str
    :return: TinyMCE.init() code
    :rtype: str
    """
    if mce_settings.USE_FILEBROWSER and 'file_browser_callback' not in callbacks:
        callbacks['file_browser_callback'] = 'djangoFileBrowser'
    if mce_settings.USE_SPELLCHECKER and 'spellchecker_callback' not in callbacks:
        callbacks['spellchecker_callback'] = render_to_string('tinymce/spellchecker.js')
    if id_:
        mce_config['selector'] = mce_config.get('selector', 'textarea') + '#{0}'.format(id_)
    mce_json = json.dumps(mce_config, indent=2)
    return render_to_string('tinymce/tinymce_init.js', context={'callbacks': callbacks,
                                                                'tinymce_config': mce_json[1:-1]})


[docs]class TinyMCE(Textarea): """ TinyMCE 4 widget It replaces a textarea form widget with a rich-text WYSIWYG `TinyMCE 4`_ editor widget. :param attrs: General Django widget attributes. :type attrs: dict :param mce_attrs: Additional configuration parameters for TinyMCE 4. They *amend* the existing configuration. :type mce_attrs: dict :param profile: TinyMCE 4 configuration parameters. They *replace* the existing configuration. :type profile: dict .. _TinyMCE 4: https://www.tinymce.com/ """ def __init__(self, attrs=None, mce_attrs=None, profile=None): super(TinyMCE, self).__init__(attrs) self.mce_attrs = mce_attrs or {} self.profile = get_language_config() default_profile = profile or mce_settings.CONFIG.copy() self.profile.update(default_profile) def render(self, name, value, attrs=None, renderer=None): if value is None: value = '' value = smart_text(value) final_attrs = self.build_attrs(attrs) final_attrs['name'] = name mce_config = self.profile.copy() mce_config.update(self.mce_attrs) if mce_config.get('inline', False): html = '<div{0}>{1}</div>\n'.format(flatatt(final_attrs), escape(value)) else: html = '<textarea{0}>{1}</textarea>\n'.format(flatatt(final_attrs), escape(value)) html += '<script type="text/javascript">{0}</script>'.format( render_tinymce_init_js(mce_config, mce_settings.CALLBACKS.copy(), final_attrs['id']) ) return mark_safe(html) @property def media(self): js = [mce_settings.JS_URL] if mce_settings.USE_FILEBROWSER: js.append(reverse('tinymce-filebrowser')) if mce_settings.ADDIONAL_JS_URLS: js += mce_settings.ADDIONAL_JS_URLS css = {'all': [reverse('tinymce-css')]} if mce_settings.CSS_URL: css['all'].append(mce_settings.CSS_URL) return Media(js=js, css=css)
[docs]class AdminTinyMCE(TinyMCE, admin_widgets.AdminTextareaWidget): """TinyMCE 4 widget for Django Admin interface""" pass