tehfink avatar tehfink committed bd01601

initial commit

Comments (0)

Files changed (19)

+PRE-ALPHA
+
+Name: cmsplugin-faq
+Description: duplicate of django-cms2's Text plugin, with a 'topic' field and link anchors in templates; CMSFaqEntryPlugin creates FAQ entries (questions & answers), CMSFaqListPlugin creates <a> anchor list of FAQ entries, on the same page
+Download: http://bitbucket.org/tehfink/cmsplugin-faq/
+
+Requirements:
+- django-cms-2.0: rev 28911b424c64330ba66e599028031bd22b5b3355
+- django: 1.1
+
+Last tested with:
+- django-cms-2.0: rev 28911b424c64330ba66e599028031bd22b5b3355
+- django: rev 11600
+
+Setup
+- make sure requirements are installed and properly working
+- add cmsplugin_faq to python path
+- add 'cmsplugin_faq' to INSTALLED_APPS
+- run 'python manage.py syncdb'
+- add plugins to pages
+
+Optional
+- define CMSPLUGIN_FAQLIST_CSS_CHOICES in settings
+- copy cmsplugin_faq/templates/plugins/cmsplugin_faq/ to your project directory
+
+Todo:
+- allow CMSFaqListPlugin plugin to be on a different page than CMSFaqEntryPlugin
+- add ability to set css for faq entries: CMSPLUGIN_FAQENTRY_CSS_CHOICES
+- test with TinyMCE (should work)
+- subclass Text plugin when this is possible
+
+
+Examples:
+
+CMSPLUGIN_FAQLIST_CSS_CHOICES = (('0', ''),('1', 'faq-list-entry'),('2', 'faq-list-entry-small'),)
+- adds an optional css class to the entry in the faq list in the plugin template
+
+
+Note:
+This is not great code, but it works. Please tell me how to make it better!
Add a comment to this file

cmsplugin_faq/__init__.py

Empty file added.

cmsplugin_faq/cms_plugins.py

+from cms.plugin_pool import plugin_pool
+from cms.plugin_base import CMSPluginBase
+from cms.models import CMSPlugin
+from django.utils.translation import ugettext_lazy as _
+from models import FaqEntry, FaqList
+from forms import FaqEntryForm
+from widgets.wymeditor_widget import WYMEditor
+from utils import plugin_tags_to_user_html
+from django.forms.fields import CharField, BooleanField
+from django.forms import TextInput
+from settings import USE_TINYMCE
+from django.conf import settings
+
+
+class CMSFaqEntryPlugin(CMSPluginBase):
+    """Copy of Text plugin, includes 'topic' Charfield"""
+
+    model = FaqEntry
+    name = _("FAQ Entry")
+    form = FaqEntryForm
+    render_template = "plugins/cmsplugin_faq/faq_entry.html"
+    change_form_template = "cms/plugins/text_plugin_change_form.html"
+
+    def get_editor_widget(self, request, plugins):
+        """
+        Returns the Django form Widget to be used for
+        the text area
+        """
+        if USE_TINYMCE and "tinymce" in settings.INSTALLED_APPS:
+            from cms.plugins.text.widgets.tinymce_widget import TinyMCEEditor
+            return TinyMCEEditor(installed_plugins=plugins)
+        else:
+            return WYMEditor(installed_plugins=plugins)
+
+    def get_form_class(self, request, plugins):
+        """
+        Returns a subclass of Form to be used by this plugin
+        """
+        # We avoid mutating the Form declared above by subclassing
+        class FaqEntryPluginForm(self.form):
+            pass
+
+        widget = self.get_editor_widget(request, plugins)
+        FaqEntryPluginForm.declared_fields["body"] = CharField(widget=widget, required=False)
+        return FaqEntryPluginForm
+
+    def get_form(self, request, obj=None, **kwargs):
+        plugins = plugin_pool.get_text_enabled_plugins(self.placeholder)
+        form = self.get_form_class(request, plugins)
+        kwargs['form'] = form # override standard form
+        return super(CMSFaqEntryPlugin, self).get_form(request, obj, **kwargs)
+
+    def render(self, context, instance, placeholder):
+        context.update({
+            'body':plugin_tags_to_user_html(instance.body, context, placeholder),
+            'topic':instance.topic,
+            'placeholder':placeholder,
+            'object':instance
+        })
+        return context
+
+plugin_pool.register_plugin(CMSFaqEntryPlugin)
+
+
+class CMSFaqListPlugin(CMSPluginBase):
+    """Lists all FaqEntry plugins on the same page as this plugin"""
+
+    model = FaqList
+    name = _("FAQ List")
+    render_template = "plugins/cmsplugin_faq/faq_list.html"
+    
+    def render(self, context, instance, placeholder):
+        import pprint
+
+#        pprint.pprint(dir(instance))
+#        pprint.pprint(dir(instance.page.cmsplugin_set))
+#        plugins = instance.page.cmsplugin_set.all()
+#        pprint.pprint(plugins)
+
+        #get all FaqEntryPlugin on this page
+        plugins = instance.page.cmsplugin_set.filter(plugin_type='CMSFaqEntryPlugin')
+#        pprint.pprint(plugins)
+        
+        faqentry_plugins = []
+
+        #make a list of the faqentry plugin objects
+        for plugin in plugins:
+            faqentry_plugins.append(plugin.faqentry)
+#            pprint.pprint(plugin.faqentry.topic)
+#            pprint.pprint(dir(plugin.faqentry.topic))
+
+        context.update({'faq_list':faqentry_plugins, 'placeholder':placeholder})
+        context.update({'css' : instance.get_css_display()})
+        return context
+
+plugin_pool.register_plugin(CMSFaqListPlugin)

cmsplugin_faq/forms.py

+from django.forms.models import ModelForm
+#from cms.plugins.text.models import Text
+from models import FaqEntry, FaqList
+from django import forms
+
+
+class FaqEntryForm(ModelForm):
+    body = forms.CharField()
+    
+    class Meta:
+        model = FaqEntry
+        exclude = ('page', 'position', 'placeholder', 'language', 'plugin_type')
+        
+#not needed because we're not allowing listing from other pages yet        
+#class FaqListForm(ModelForm):
+#    def __init__(self, *args, **kwargs):
+#        super(FaqListForm, self).__init__(*args, **kwargs)
+#        if self.instance:
+#            self.fields['cmspage'].queryset = FaqList.objects.filter(publisher_is_draft=True)

cmsplugin_faq/managers.py

+from django.db import models
+from cms import settings
+
+class ContentManager(models.Manager):
+
+    def sanitize(self, content):
+        """
+        Sanitize the content to avoid XSS and so
+        """
+        import html5lib
+        from html5lib import sanitizer
+        p = html5lib.HTMLParser(tokenizer=sanitizer.HTMLSanitizer)
+        # we need to remove <html><head/><body>...</body></html>
+        return p.parse(content).toxml()[19:-14]
+
+    def set_or_create_content(self, page, language, cnttype, body):
+        """
+        set or create a content for a particular page and language
+        """
+        if settings.CMS_SANITIZE_USER_INPUT:
+            body = self.sanitize(body)
+        try:
+            content = self.filter(page=page, language=language,
+                                  type=cnttype).latest('creation_date')
+            content.body = body
+        except self.model.DoesNotExist:
+            content = self.model(page=page, language=language, body=body,
+                                 type=cnttype)
+        content.save()
+        return content
+
+
+    # TODO: probably not used anymore after django-revision integration
+    def create_content_if_changed(self, page, language, cnttype, body):
+        """
+        set or create a content for a particular page and language
+        """
+        if settings.CMS_SANITIZE_USER_INPUT:
+            body = self.sanitize(body)
+        try:
+            content = self.filter(page=page, language=language,
+                                  type=cnttype).latest('creation_date')
+            if content.body == body:
+                return content
+        except self.model.DoesNotExist:
+            pass
+        content = self.create(page=page, language=language, body=body, type=cnttype)
+
+    def get_content(self, page, language, cnttype, language_fallback=False,
+            latest_by='creation_date'):
+        """
+        Gets the latest content for a particular page and language. Falls back
+        to another language if wanted.
+        """
+        try:
+            content = self.filter(language=language, page=page,
+                                        type=cnttype).latest(latest_by)
+            return content.body
+        except self.model.DoesNotExist:
+            pass
+        if language_fallback:
+            try:
+                content = self.filter(page=page, type=cnttype).latest(latest_by)
+                return content.body
+            except self.model.DoesNotExist:
+                pass
+        return None

cmsplugin_faq/models.py

+from django.db import models
+from django.utils.translation import ugettext_lazy as _
+from cms.models import CMSPlugin, Page
+from django.conf import settings
+from django.utils.html import strip_tags
+from django.utils.text import truncate_words
+from cms.plugins.text.utils import plugin_admin_html_to_tags, plugin_tags_to_admin_html
+from django.conf import settings
+
+class FaqEntry(CMSPlugin):
+    """Copy of Text plugin model, plus additional 'topic' Charfield"""
+    topic = models.CharField(_("Topic"),max_length=500, help_text=_('FAQ entry topic'))
+    body = models.TextField(_("body"))
+   
+    def _set_body_admin(self, text):
+        self.body = plugin_admin_html_to_tags(text)
+
+    def _get_body_admin(self):
+        return plugin_tags_to_admin_html(self.body)
+
+    body_for_admin = property(_get_body_admin, _set_body_admin, None,
+                              """
+                              body attribute, but with transformations
+                              applied to allow editing in the
+                              admin. Read/write.
+                              """)
+    
+    def __unicode__(self):
+#        return u"%s" % (truncate_words(strip_tags(self.body), 3)[:30]+"...")
+        return u"%s" % (truncate_words(self.topic, 5)[:30]+"...")
+
+
+#get custom css from settings or use default
+CMSPLUGIN_FAQLIST_CSS_CHOICES = getattr(settings,"CMSPLUGIN_FAQLIST_CSS_CHOICES", (('0', ''),('1', 'faq-list-entry'),('2', 'faq-list-entry-small'),) )
+
+class FaqList(CMSPlugin):
+    """Model to give FaqList plugin various options"""
+
+    css = models.CharField(_('CSS class'), max_length=1, choices=CMSPLUGIN_FAQLIST_CSS_CHOICES, default='0', help_text=_('Additional CSS class to apply'))
+
+    def __unicode__(self):
+        return u"%s" % (self.page.get_page_title())

cmsplugin_faq/settings.py

+from django.conf import settings
+
+# Uses TinyMCE as editor (no inline plugins). Requires django-tinymce app. 
+# If false, then WYMEditor is used. 
+USE_TINYMCE = getattr(settings, 'CMS_USE_TINYMCE', "tinymce" in settings.INSTALLED_APPS)
+
+WYM_TOOLS = ",\n".join([
+    "{'name': 'Bold', 'title': 'Strong', 'css': 'wym_tools_strong'}",
+    "{'name': 'Italic', 'title': 'Emphasis', 'css': 'wym_tools_emphasis'}",
+    "{'name': 'Superscript', 'title': 'Superscript', 'css': 'wym_tools_superscript'}",
+    "{'name': 'Subscript', 'title': 'Subscript', 'css': 'wym_tools_subscript'}",
+    "{'name': 'InsertOrderedList', 'title': 'Ordered_List', 'css': 'wym_tools_ordered_list'}",
+    "{'name': 'InsertUnorderedList', 'title': 'Unordered_List', 'css': 'wym_tools_unordered_list'}",
+    "{'name': 'Indent', 'title': 'Indent', 'css': 'wym_tools_indent'}",
+    "{'name': 'Outdent', 'title': 'Outdent', 'css': 'wym_tools_outdent'}",
+    "{'name': 'Undo', 'title': 'Undo', 'css': 'wym_tools_undo'}",
+    "{'name': 'Redo', 'title': 'Redo', 'css': 'wym_tools_redo'}",
+    "{'name': 'Paste', 'title': 'Paste_From_Word', 'css': 'wym_tools_paste'}",
+    "{'name': 'ToggleHtml', 'title': 'HTML', 'css': 'wym_tools_html'}",
+    #"{'name': 'CreateLink', 'title': 'Link', 'css': 'wym_tools_link'}",
+    #"{'name': 'Unlink', 'title': 'Unlink', 'css': 'wym_tools_unlink'}",
+    #"{'name': 'InsertImage', 'title': 'Image', 'css': 'wym_tools_image'}",
+    #"{'name': 'InsertTable', 'title': 'Table', 'css': 'wym_tools_table'}",
+    #"{'name': 'Preview', 'title': 'Preview', 'css': 'wym_tools_preview'}",
+])
+
+WYM_TOOLS = getattr(settings, "WYM_TOOLS", WYM_TOOLS)
+
+WYM_CONTAINERS = ",\n".join([
+    "{'name': 'P', 'title': 'Paragraph', 'css': 'wym_containers_p'}",
+    "{'name': 'H1', 'title': 'Heading_1', 'css': 'wym_containers_h1'}",
+    "{'name': 'H2', 'title': 'Heading_2', 'css': 'wym_containers_h2'}",
+    "{'name': 'H3', 'title': 'Heading_3', 'css': 'wym_containers_h3'}",
+    "{'name': 'H4', 'title': 'Heading_4', 'css': 'wym_containers_h4'}",
+    "{'name': 'H5', 'title': 'Heading_5', 'css': 'wym_containers_h5'}",
+    "{'name': 'H6', 'title': 'Heading_6', 'css': 'wym_containers_h6'}",
+    "{'name': 'PRE', 'title': 'Preformatted', 'css': 'wym_containers_pre'}",
+    "{'name': 'BLOCKQUOTE', 'title': 'Blockquote', 'css': 'wym_containers_blockquote'}",
+    "{'name': 'TH', 'title': 'Table_Header', 'css': 'wym_containers_th'}",
+])
+    
+WYM_CONTAINERS = getattr(settings, "WYM_CONTAINERS", WYM_CONTAINERS)
+
+WYM_CLASSES = ",\n".join([
+    "{'name': 'date', 'title': 'PARA: Date', 'expr': 'p'}",
+    "{'name': 'hidden-note', 'title': 'PARA: Hidden note', 'expr': 'p[@class!=\"important\"]'}",
+])
+    
+WYM_STYLES = ",\n".join([
+    "{'name': '.hidden-note', 'css': 'color: #999; border: 2px solid #ccc;'}",
+    "{'name': '.date', 'css': 'background-color: #ff9; border: 2px solid #ee9;'}",
+])
+
+WYM_CLASSES = getattr(settings, "WYM_CLASSES", WYM_CLASSES)
+WYM_STYLES = getattr(settings, "WYM_STYLES", WYM_STYLES)
+
+#Advantageously replaces WYM_CLASSES and WYM_STYLES
+##Prepare url for wymeditor.css
+CMS_MEDIA_PATH = getattr(settings, 'CMS_MEDIA_PATH', 'cms/')
+WYM_STYLESHEET_PATH = getattr(settings, 'CMS_MEDIA_URL', ''.join((settings.MEDIA_URL, CMS_MEDIA_PATH)) )
+WYM_STYLESHEET = getattr(settings, "WYM_STYLESHEET",  '"%scss/wymeditor.css"' % WYM_STYLESHEET_PATH  )
+##If you don't want to use WYM_STYLESHEET enable the line below. And disable 3 lines above.
+#WYM_STYLESHEET = getattr(settings, "WYM_STYLESHEET", '""' )

cmsplugin_faq/templates/plugins/cmsplugin_faq/faq_entry.html

+
+<div class="faq-entry">
+    <div><a name="{{ topic|safe }}">{{ topic|safe }}</a></div>
+    <div>
+        {{ body|safe }}
+    </div>
+</div>

cmsplugin_faq/templates/plugins/cmsplugin_faq/faq_list.html

+
+<div class="faq-list">
+    <ul>
+{% for faq_entry in faq_list %}
+        <li{% if css %} class="{{ css }}"{% endif %}><a href="#{{ faq_entry.topic|safe }}">{{ faq_entry.topic|safe }}</a>
+        <p>{{ faq_entry.body|safe|truncatewords_html:10 }}
+        <hr>
+{% endfor %}
+    </ul>
+</div>

cmsplugin_faq/templates/plugins/cmsplugin_faq/text.html

+{{ body|safe }}

cmsplugin_faq/templates/plugins/cmsplugin_faq/text_plugin_change_form.html

+{% extends "admin/cms/page/plugin_change_form.html" %}
+
+{% block fieldsets %}
+{% for fieldset in adminform %}
+  {% include "cms/plugins/text_plugin_fieldset.html" %}
+{% endfor %}
+{% endblock %}

cmsplugin_faq/templates/plugins/cmsplugin_faq/text_plugin_fieldset.html

+<fieldset class="module aligned {{ fieldset.classes }}">
+  {% if fieldset.name %}<h2>{{ fieldset.name }}</h2>{% endif %}
+  {% if fieldset.description %}<div class="description">{{ fieldset.description|safe }}</div>{% endif %}
+  {% for line in fieldset %}
+      <div class="form-row{% if line.errors %} errors{% endif %} {% for field in line %}{{ field.field.name }} {% endfor %} ">
+      {{ line.errors }}
+      {% for field in line %}{% if not field.field.is_hidden %}
+      <div{% if not line.fields|length_is:"1" %} class="field-box"{% endif %}>{% endif %}
+          {% if field.is_checkbox %}
+              {{ field.field }}{{ field.label_tag }}
+          {% else %}
+		  		{% if field.field.is_hidden %}
+					{{ field.field }}
+				{% else %}
+              		{{ field.field }}
+			  	{% endif %}
+          {% endif %}
+		  {% if not field.field.is_hidden %}
+          {% if field.field.field.help_text %}<p class="help">{{ field.field.field.help_text|safe }}</p>{% endif %}
+      </div>{% endif %}
+      {% endfor %}
+      </div>
+  {% endfor %}
+</fieldset>

cmsplugin_faq/templates/plugins/cmsplugin_faq/widgets/tinymce.html

+{% load i18n %}
+
+// Global var, for storing callbacks, see below.
+var editPluginPopupCallbacks = {};
+
+{% include "cms/plugins/widgets/widget_lib.js" %}
+
+var texteditor = null;
+
+// Creates a new plugin class
+tinymce.create('tinymce.plugins.CMSPluginEditor', {
+	init : function(ed, url) {
+        // Register an example button
+        var tiny = ed;
+        
+    	var c = new TinyMCEPlaceholderBridge(tiny);
+    	PlaceholderEditorRegistry.registerEditor("{{name}}", c);
+    	
+    	ed.onNodeChange.add(function(ed, cm, n, co) {
+    		var is_plugin = n.nodeName == 'IMG' && n.id.split("plugin_obj_").length > 1; 
+			cm.setDisabled('editplugins', !is_plugin);
+			cm.setActive('editplugins', is_plugin);
+		});
+        
+    },
+    createControl: function(n, cm) {
+    	switch (n) {
+            case 'cmsplugins':        
+		        var mlb = cm.createListBox('mylistbox', {
+		             title : '{% filter escapejs %}{% trans "Plugins" %}{% endfilter %}',
+		             onselect : function(v) {
+		             	var pluginvalue = v;
+						var splits = window.location.href.split("?")[0].split("/");
+						var parent_id = Number(splits[splits.length - 2]);
+						var language = $('#id_language').attr('value');
+						
+						if (pluginvalue == "") {
+							alert("{% filter escapejs %}{% trans "Please select a plugin type." %}{% endfilter %}");
+							return;
+						}
+						texteditor = get_editor("{{ name }}");
+						
+						// First create db instance using AJAX post back
+						add_plugin(pluginvalue, parent_id, language)
+		             }
+		        });
+				{% for p in installed_plugins %}
+				mlb.add('{{ p.name }}', '{{ p.value }}');{% endfor %}
+	     		
+		      	// Return the new listbox instance
+                return mlb;
+        	case 'cmspluginsedit':
+        		var bt = cm.createButton('editplugins', {
+            		title : '{% filter escapejs %}{% trans "Edit selected plugin" %}{% endfilter %}',
+            		onclick : function() {
+                 		var texteditor = get_editor("{{ name }}");
+						if (texteditor == null || texteditor.selectedObject == null) {
+							alert("{% filter escapejs %}{% trans "Text editor does not support editing objects." %}{% endfilter %}");
+							return;
+						}
+						var imgobj = texteditor.selectedObject();
+						if (imgobj == null) {
+							alert("{% filter escapejs %}{% trans "No object selected." %}{% endfilter %}");
+							return;
+						}
+						
+						
+						var plugin_id = imgobj.split("plugin_obj_")[1].split('"')[0];
+						edit_plugin(plugin_id);
+            		},
+            		'class' : 'plugin_edit' // Use the bold icon from the theme
+        		});
+        		cm.createSeparator(); 
+        		return bt;
+        }
+
+        return null;
+    }
+});
+
+
+
+// Register plugin with a short name
+tinymce.PluginManager.add('cmsplugins', tinymce.plugins.CMSPluginEditor);
+
+function init_plugin_editor(placeholder){
+	var toolbar = get_plugin_html()
+	var html = '<table class="mceToolbar mceToolbarRow3 Enabled"><tbody>'
+	html += '<td class="mceToolbarStart mceToolbarStartListBox mceFirst">'
+	html += '<span></span>'
+	html += '</td><td>' + toolbar + '</td></tbody></table>'
+	$("td.mceToolbar").append(html);
+	init_buttons(placeholder);
+}
+
+
+function init_buttons(placeholder){
+	$('span.insert-object').click(function(){
+		var select = $(this).parent().children("select");
+		var pluginvalue = select.attr('value');
+		var splits = window.location.href.split("?")[0].split("/");
+		var parent_id = Number(splits[splits.length - 2]);
+		var language = $('#id_language').attr('value');
+		
+		if (pluginvalue == "") {
+			alert("{% filter escapejs %}{% trans "Please select a plugin type." %}{% endfilter %}");
+			return;
+		}
+		
+		var texteditor = get_editor(placeholder);
+		if (texteditor == null || texteditor.insertText == null) {
+			alert("{% filter escapejs %}{% trans "Text editor does not support inserting objects." %}{% endfilter %}");
+			return;
+		}
+		// First create db instance using AJAX post back
+		add_plugin(pluginvalue, parent_id, language)
+		
+	}).css("cursor", "pointer").css("margin", "5px");
+	
+	/* onclick for 'Edit selected object' */
+	$('span.edit-object').click(function(){
+		var texteditor = get_editor(placeholder);
+		if (texteditor == null || texteditor.selectedObject == null) {
+			alert("{% filter escapejs %}{% trans "Text editor does not support editing objects." %}{% endfilter %}");
+			return;
+		}
+		var imgobj = texteditor.selectedObject();
+		if (imgobj == null) {
+			alert("{% filter escapejs %}{% trans "No object selected." %}{% endfilter %}");
+			return;
+		}
+		if (imgobj.id == null || imgobj.id.indexOf("plugin_obj_") != 0) {
+			alert("{% filter escapejs %}{% trans "Not a plugin object" %}{% endfilter %}");
+			return;
+		}
+		var plugin_id = imgobj.id.substr("plugin_obj_".length);
+		edit_plugin(plugin_id);
+	}).css("cursor", "pointer").css("margin","5px");
+}

cmsplugin_faq/templates/plugins/cmsplugin_faq/widgets/widget_lib.js

+{% load i18n %}
+
+function escapeHtml(html) {
+    return html.replace(/&/g,"&amp;")
+        .replace(/\"/g,"&quot;")
+        .replace(/</g,"&lt;")
+        .replace(/>/g,"&gt;");
+}
+
+
+function add_plugin(type, parent_id, language){
+	$.post("add-plugin/", {
+		parent_id: parent_id,
+		plugin_type: type
+	}, function(data) {
+		if ('error' != data) {
+			// Successfully created, data is pk of object, but object
+			// is 'blank'. We now want to show the edit popup.
+			
+			// We only want to insert the text if the user actually
+			// *saved* the object.  We don't have modal dialogs, so we
+			// register a callback against the id number.  This callback
+			// is called by dismissEditPluginPopup().
+			var plugin_id = data;
+			editPluginPopupCallbacks[data] = function(plugin_id, icon_src, icon_alt){
+                texteditor = get_editor("{{ name }}");
+				texteditor.insertText(plugin_admin_html(plugin_id, icon_src, icon_alt));
+				editPluginPopupCallbacks[data] = null; // Unbind callback
+			};
+			
+			// Show popup for editing
+			edit_plugin(plugin_id);
+		}
+	}, "html");
+}
+
+function edit_plugin(obj_id) {
+    editPluginPopupCallbacks[obj_id] = function(plugin_id, icon_src, icon_alt){
+        var texteditor = get_editor("{{ name }}");
+		var rExp = new RegExp('<img src="[^>]*" alt="[^>]*" id="plugin_obj_' + obj_id + '"[^>]*>', "g");
+		try {
+			texteditor.replaceContent(rExp, plugin_admin_html(plugin_id, icon_src, icon_alt));
+		} catch (e) {}
+		editPluginPopupCallbacks[obj_id] = null; // Unbind callback
+	};
+	
+	
+	// Pop up window for editing object.
+    window.open("edit-plugin/" + obj_id + "/?_popup=1",
+                "Edit plugin object",
+                "menubar=no,titlebar=no,toolbar=no,resizable=yes"
+                  + ",width=800,height=300,top=0,left=0,scrollbars=yes"
+                  + ",location=no"
+               );
+}
+
+function plugin_admin_html(plugin_id, icon_src, icon_alt) {
+    return '<img src="' + escapeHtml(icon_src) + '" ' +
+        'alt="'+ escapeHtml(icon_alt) + '" ' +
+        'id="plugin_obj_' + plugin_id + '"/>';
+}
+
+
+// Global function, needed for popup window to call the parent via opener.dismissEditPluginPopup
+function dismissEditPluginPopup(win, plugin_id, icon_src, icon_alt) {
+    // This is called after user presses 'Save' in popup.
+    win.close();
+	
+    var callback = editPluginPopupCallbacks[plugin_id];
+    if (callback != null) {
+        callback(plugin_id, icon_src, icon_alt);
+    }
+}
+
+/* General functions */
+function get_editor(placeholder) {
+    // Find the placeholder text editor widget
+    if (typeof(PlaceholderEditorRegistry) == "undefined") {
+        // This could occur if javascript defining PlaceholderEditorRegistry
+        // has not been loaded for some reason.
+        alert("{% filter escapejs %}{% trans "A programming error occurred - cannot find text editor widgets." %}{% endfilter %}");
+        return null;
+    }
+    return PlaceholderEditorRegistry.retrieveEditor(placeholder);
+}
+

cmsplugin_faq/templates/plugins/cmsplugin_faq/widgets/wymeditor.html

+{% load i18n %}
+<script type="text/javascript">
+
+// Global var, for storing callbacks, see below.
+var editPluginPopupCallbacks = {};
+
+{% include "cms/plugins/widgets/widget_lib.js" %}
+
+
+
+$(document).ready(function(){
+    $('#id_{{ name }}').wymeditor({
+        lang: '{{ language }}',
+        skin: 'django',
+        skinPath: "{{ CMS_MEDIA_URL }}js/wymeditor/skins/django/",
+        updateSelector: 'input[type=submit],',
+        updateEvent: 'click',
+		logoHtml: '',
+		toolsItems: [
+			    {{ WYM_TOOLS }}
+			],
+		containersItems: [
+		        {{ WYM_CONTAINERS }}
+		    ],
+		classesItems: [
+			    {{ WYM_CLASSES }}
+			],
+		editorStyles: [
+			{{ WYM_STYLES }}
+			],
+		{% if WYM_STYLESHEET %}
+		stylesheet:
+			{{ WYM_STYLESHEET }}
+		,
+		{% endif %}
+        postInit: function(wym) {
+			 //wym.resizable({handles: "s", maxHeight: 600});
+            //construct the insertLinkButton html
+            html = get_plugin_html()
+            //add the button to the tools box
+            jQuery(wym._box)
+            .find(wym._options.toolsSelector + wym._options.toolsListSelector)
+            .append(html);
+            // Enable the placeholderbridge plugin, to allow
+            // the placeholder controls to talk to editor
+            wym.placeholderbridge({'name': '{{ name }}'});
+            init_buttons("{{ name }}");
+        },
+        //handle click event on dialog's submit button
+        postInitDialog: function( wym, wdw ) {
+     
+        }
+    });
+	
+
+   
+
+    /* onclick for 'Insert object' */
+	
+	function init_buttons(placeholder){
+		$('span.insert-object').click(function(){
+			var select = $(this).parent().children("select");
+			var pluginvalue = select.attr('value');
+			var splits = window.location.href.split("?")[0].split("/");
+			var parent_id = Number(splits[splits.length - 2]);
+			var language = $('#id_language').attr('value');
+			
+			if (pluginvalue == "") {
+				alert("{% filter escapejs %}{% trans "Please select a plugin type." %}{% endfilter %}");
+				return;
+			}
+			
+			var texteditor = get_editor(placeholder);
+			if (texteditor == null || texteditor.insertText == null) {
+				alert("{% filter escapejs %}{% trans "Text editor does not support inserting objects." %}{% endfilter %}");
+				return;
+			}
+			// First create db instance using AJAX post back
+			add_plugin(pluginvalue, parent_id, language)
+			
+		}).css("cursor", "pointer").css("margin", "5px");
+		
+		/* onclick for 'Edit selected object' */
+		$('span.edit-object').click(function(){
+			var texteditor = get_editor(placeholder);
+			if (texteditor == null || texteditor.selectedObject == null) {
+				alert("{% filter escapejs %}{% trans "Text editor does not support editing objects." %}{% endfilter %}");
+				return;
+			}
+			var imgobj = texteditor.selectedObject();
+			if (imgobj == null) {
+				alert("{% filter escapejs %}{% trans "No object selected." %}{% endfilter %}");
+				return;
+			}
+			if (imgobj.id == null || imgobj.id.indexOf("plugin_obj_") != 0) {
+				alert("{% filter escapejs %}{% trans "Not a plugin object" %}{% endfilter %}");
+				return;
+			}
+			var plugin_id = imgobj.id.substr("plugin_obj_".length);
+			edit_plugin(plugin_id);
+		}).css("cursor", "pointer").css("margin","5px");
+	}
+});
+
+
+
+function get_plugin_html(){
+	html = '<div class="plugin-select-holder">'
+		 + '<select name="plugins">'
+		 + '<option value="" selected="selected">{% filter escapejs %}{% trans "Available Plugins" %}{% endfilter %}</option>'{% for p in installed_plugins %}
+	     + '<option value="{{ p.value }}">{{ p.name }}</option>'{% endfor %}
+		 + '</select>'
+		 + '<span class="insert-object addlink">{% filter escapejs %}{% trans "Insert plugin" %}{% endfilter %}</span>'
+		 + '<span class="edit-object changelink">{% filter escapejs %}{% trans "Edit selected plugin" %}{% endfilter %}</span>'
+		 + '</div>';
+	return html;
+}
+    </script>

cmsplugin_faq/utils.py

+from cms.models import CMSPlugin
+import re
+
+OBJ_TAG_RE = re.compile(u"\{\{ plugin_object (\d+) \}\}")
+OBJ_ADMIN_RE = re.compile(ur'<img [^>]*\bid="plugin_obj_(\d+)"[^>]*/?>')
+
+def plugin_tags_to_admin_html(text):
+    """
+    Convert plugin object 'tags' into the form used to represent
+    them in the admin text editor.
+    """
+    def _tag_to_admin(m):
+        plugin_id = int(m.groups()[0])
+        try:
+            obj = CMSPlugin.objects.get(pk=plugin_id)
+        except CMSPlugin.DoesNotExist:
+            # Object must have been deleted.  It cannot be rendered to
+            # end user, or edited, so just remove it from the HTML
+            # altogether
+            return u''
+        return u'<img src="%(icon_src)s" alt="%(icon_alt)s" id="plugin_obj_%(id)d" />' % \
+               dict(id=plugin_id,
+                    icon_src=force_escape(obj.get_instance_icon_src()),
+                    icon_alt=force_escape(obj.get_instance_icon_alt()),
+                    )
+
+    return OBJ_TAG_RE.sub(_tag_to_admin, text)
+
+def plugin_tags_to_user_html(text, context, placeholder):
+    """
+    Convert plugin object 'tags' into the form for public site.
+
+    context is the template context to use, placeholder is the placeholder name
+    """
+    def _render_tag(m):
+        plugin_id = int(m.groups()[0])
+        try:
+            obj = CMSPlugin.objects.get(pk=plugin_id)
+        except CMSPlugin.DoesNotExist:
+            # Object must have been deleted.  It cannot be rendered to
+            # end user so just remove it from the HTML altogether
+            return u''
+        return obj.render_plugin(context, placeholder)
+    return OBJ_ADMIN_RE.sub(_render_tag, text)
+
+
+
+def plugin_admin_html_to_tags(text):
+    """
+    Convert the HTML used in admin editor to represent plugin objects
+    into the 'tag' form used in the database
+    """
+    return OBJ_ADMIN_RE.sub(lambda m: u"{{ plugin_object %s }}"  % m.groups()[0], text)
+
Add a comment to this file

cmsplugin_faq/widgets/__init__.py

Empty file added.

cmsplugin_faq/widgets/tinymce_widget.py

+from tinymce.widgets import TinyMCE, get_language_config
+from cms.settings import CMS_MEDIA_URL
+from django.utils.translation import get_language
+from django.template.loader import render_to_string
+from django.utils.safestring import mark_safe
+from os.path import join
+from django.utils.encoding import smart_unicode
+import tinymce.settings
+from django.utils import simplejson
+from django.template.defaultfilters import escape
+from django.forms.widgets import flatatt
+
+class TinyMCEEditor(TinyMCE):
+    
+    def __init__(self, installed_plugins=None,  **kwargs):
+        super(TinyMCEEditor, self).__init__(**kwargs)
+        self.installed_plugins = installed_plugins
+        
+    def render_additions(self, name, value, attrs=None):
+        language = get_language()
+        context = {
+            'name': name,
+            'language': language,
+            'CMS_MEDIA_URL': CMS_MEDIA_URL,
+            'installed_plugins': self.installed_plugins,
+        }
+        return mark_safe(render_to_string(
+            'cms/plugins/widgets/tinymce.html', context))
+        
+    def _media(self):
+        media = super(TinyMCEEditor, self)._media()
+        media.add_js([join(CMS_MEDIA_URL, path) for path in (
+                      'js/tinymce.placeholdereditor.js',
+                      'js/lib/ui.core.js',
+                      'js/placeholder_editor_registry.js',
+                      )])
+        media.add_css({"all":[join(CMS_MEDIA_URL, path) for path in ('css/jquery/cupertino/jquery-ui.css',
+                                                                     'css/tinymce_toolbar.css')]})
+        
+        return media
+    
+    
+    media = property(_media)
+    
+    def render(self, name, value, attrs=None):
+        if value is None: value = ''
+        value = smart_unicode(value)
+        final_attrs = self.build_attrs(attrs)
+        final_attrs['name'] = name
+        assert 'id' in final_attrs, "TinyMCE widget attributes must contain 'id'"
+        mce_config = tinymce.settings.DEFAULT_CONFIG.copy()
+        mce_config.update(get_language_config(self.content_language))
+        if tinymce.settings.USE_FILEBROWSER:
+            mce_config['file_browser_callback'] = "djangoFileBrowser"
+        mce_config.update(self.mce_attrs)
+        mce_config['mode'] = 'exact'
+        mce_config['elements'] = final_attrs['id']
+        mce_config['strict_loading_mode'] = 1
+        plugins = mce_config.get("plugins", "")
+        if len(plugins):
+            plugins += ","
+        plugins += "-cmsplugins"
+        mce_config['plugins'] = plugins
+        adv2 = mce_config.get('theme_advanced_buttons1', "")
+        if len(adv2):
+            adv2 = "," + adv2
+        adv2 = "cmsplugins,cmspluginsedit" + adv2
+        mce_config['theme_advanced_buttons1'] = adv2
+        json = simplejson.dumps(mce_config)
+        
+        html = [u'<textarea%s>%s</textarea>' % (flatatt(final_attrs), escape(value))]
+        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,
+            }
+            c_json = simplejson.dumps(compressor_config)
+            html.append(u'<script type="text/javascript">tinyMCE_GZ.init(%s);</script>' % (c_json))
+        html.append(u'<script type="text/javascript">%s;\ntinyMCE.init(%s);</script>' % (self.render_additions(name, value, attrs), json))
+        return mark_safe(u'\n'.join(html))
+    
+    
+    

cmsplugin_faq/widgets/wymeditor_widget.py

+from os.path import join
+from django.conf import settings
+from django.forms import Textarea
+from django.utils.safestring import mark_safe
+from django.template.loader import render_to_string
+
+from cms.settings import CMS_MEDIA_URL
+from cms.plugins.text import settings as text_settings
+from django.utils.translation.trans_real import get_language
+
+
+class WYMEditor(Textarea):
+    class Media:
+        js = [join(CMS_MEDIA_URL, path) for path in (
+            'wymeditor/jquery.wymeditor.js',
+            'wymeditor/plugins/resizable/jquery.wymeditor.resizable.js',
+            'js/wymeditor.placeholdereditor.js',
+            'js/lib/ui.core.js',
+            'js/placeholder_editor_registry.js',
+        )]
+        css = {
+            'all': [join(CMS_MEDIA_URL, path) for path in (
+                        'css/jquery/cupertino/jquery-ui.css',
+                    )],
+        }
+
+    def __init__(self, attrs=None, installed_plugins=None):
+        """
+        Create a widget for editing text + plugins.
+
+        installed_plugins is a list of plugins to display that are text_enabled
+        """
+        self.attrs = {'class': 'wymeditor'}
+        if attrs:
+            self.attrs.update(attrs)
+        super(WYMEditor, self).__init__(attrs)
+        self.installed_plugins = installed_plugins
+
+    def render_textarea(self, name, value, attrs=None):
+        return super(WYMEditor, self).render(name, value, attrs)
+
+    def render_additions(self, name, value, attrs=None):
+        language = get_language()
+        context = {
+            'name': name,
+            'language': language,
+            'CMS_MEDIA_URL': CMS_MEDIA_URL,
+            'WYM_TOOLS': mark_safe(text_settings.WYM_TOOLS),
+            'WYM_CONTAINERS': mark_safe(text_settings.WYM_CONTAINERS),
+            'WYM_CLASSES': mark_safe(text_settings.WYM_CLASSES),
+            'WYM_STYLES': mark_safe(text_settings.WYM_STYLES),
+            'WYM_STYLESHEET': mark_safe(text_settings.WYM_STYLESHEET),
+            'installed_plugins': self.installed_plugins,
+        }
+        return mark_safe(render_to_string(
+            'cms/plugins/widgets/wymeditor.html', context))
+
+    def render(self, name, value, attrs=None):
+        return self.render_textarea(name, value, attrs) + \
+            self.render_additions(name, value, attrs)
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.