Luke Plant avatar Luke Plant committed b1c7fe1

Implemented CssClass - template association.

Requires a migration.

Comments (0)

Files changed (9)

semanticeditor/cms_plugins.py

     name = _("Text/layout")
     admin_preview = False
 
-    def get_editor_widget(self, request, plugins):
-        return SemanticEditor(installed_plugins=plugins)
+    # A lot of duplication from TextPlugin because get_form needs to find out
+    # what page/template we are using, and pass that on to get_editor_widget
+
+    def get_editor_widget(self, request, plugins, page):
+        return SemanticEditor(installed_plugins=plugins,
+                              page=page)
+
+    def get_form_class(self, request, plugins, page):
+        """
+        Returns a subclass of Form to be used by this plugin
+        """
+        # We avoid mutating the Form declared above by subclassing
+        class TextPluginForm(self.form):
+            pass
+
+        widget = self.get_editor_widget(request, plugins, page)
+        TextPluginForm.declared_fields["body"] = CharField(widget=widget, required=False)
+        return TextPluginForm
+
+    def get_form(self, request, obj=None, **kwargs):
+        page = None
+        if obj:
+            page = obj.page
+        plugins = plugin_pool.get_text_enabled_plugins(self.placeholder, page)
+        form = self.get_form_class(request, plugins, page)
+        kwargs['form'] = form # override standard form
+        return super(TextPlugin, self).get_form(request, obj, **kwargs)
 
 plugin_pool.register_plugin(SemanticTextPlugin)

semanticeditor/fields.py

+from django import forms
+from django.db import models
+from django.utils.text import capfirst
+
+# Thanks to danielroseman from djangosnippets.org for this code!
+
+class MultiSelectFormField(forms.MultipleChoiceField):
+    widget = forms.SelectMultiple
+
+    def __init__(self, *args, **kwargs):
+        self.max_choices = kwargs.pop('max_choices', 0)
+        super(MultiSelectFormField, self).__init__(*args, **kwargs)
+
+    def clean(self, value):
+        if not value and self.required:
+            raise forms.ValidationError(self.error_messages['required'])
+        if value and self.max_choices and len(value) > self.max_choices:
+            raise forms.ValidationError('You must select a maximum of %s choice%s.'
+                    % (apnumber(self.max_choices), pluralize(self.max_choices)))
+        return value
+
+    def widget_attrs(self, widget):
+        return {'class':'checkboxlist'}
+
+class MultiSelectField(models.Field):
+    __metaclass__ = models.SubfieldBase
+
+    def get_internal_type(self):
+        return "TextField"
+
+    def get_choices_default(self):
+        return self.get_choices(include_blank=False)
+
+    def _get_FIELD_display(self, field):
+        value = getattr(self, field.attname)
+        choicedict = dict(field.choices)
+
+    def formfield(self, **kwargs):
+        # don't call super, as that overrides default widget if it has choices
+        defaults = {'required': not self.blank, 'label': capfirst(self.verbose_name), 
+                    'help_text': self.help_text, 'choices':self.choices}
+        if self.has_default():
+            defaults['initial'] = self.get_default()
+        defaults.update(kwargs)
+        return MultiSelectFormField(**defaults)
+
+    def get_db_prep_value(self, value):
+        if isinstance(value, basestring):
+            return value
+        elif isinstance(value, list):
+            return ",".join(value)
+
+    def to_python(self, value):
+        if isinstance(value, list):
+            return value
+        elif value==None:
+            return ''
+        return value.split(",")
+
+    def contribute_to_class(self, cls, name):
+        super(MultiSelectField, self).contribute_to_class(cls, name)
+        if self.choices:
+            func = lambda self, fieldname = name, choicedict = dict(self.choices):",".join([choicedict.get(value,value) for value in getattr(self,fieldname)])
+            setattr(cls, 'get_%s_display' % self.name, func)
+
+    def value_to_string(self, obj):
+        value = self._get_val_from_obj(obj)
+        return self.get_db_prep_value(value)
+

semanticeditor/media/semanticeditor/javascript/jquery.query-2.1.7.js

+/**
+ * jQuery.query - Query String Modification and Creation for jQuery
+ * Written by Blair Mitchelmore (blair DOT mitchelmore AT gmail DOT com)
+ * Licensed under the WTFPL (http://sam.zoy.org/wtfpl/).
+ * Date: 2009/8/13
+ *
+ * @author Blair Mitchelmore
+ * @version 2.1.7
+ *
+ **/
+new function(settings) { 
+  // Various Settings
+  var $separator = settings.separator || '&';
+  var $spaces = settings.spaces === false ? false : true;
+  var $suffix = settings.suffix === false ? '' : '[]';
+  var $prefix = settings.prefix === false ? false : true;
+  var $hash = $prefix ? settings.hash === true ? "#" : "?" : "";
+  var $numbers = settings.numbers === false ? false : true;
+  
+  jQuery.query = new function() {
+    var is = function(o, t) {
+      return o != undefined && o !== null && (!!t ? o.constructor == t : true);
+    };
+    var parse = function(path) {
+      var m, rx = /\[([^[]*)\]/g, match = /^([^[]+)(\[.*\])?$/.exec(path), base = match[1], tokens = [];
+      while (m = rx.exec(match[2])) tokens.push(m[1]);
+      return [base, tokens];
+    };
+    var set = function(target, tokens, value) {
+      var o, token = tokens.shift();
+      if (typeof target != 'object') target = null;
+      if (token === "") {
+        if (!target) target = [];
+        if (is(target, Array)) {
+          target.push(tokens.length == 0 ? value : set(null, tokens.slice(0), value));
+        } else if (is(target, Object)) {
+          var i = 0;
+          while (target[i++] != null);
+          target[--i] = tokens.length == 0 ? value : set(target[i], tokens.slice(0), value);
+        } else {
+          target = [];
+          target.push(tokens.length == 0 ? value : set(null, tokens.slice(0), value));
+        }
+      } else if (token && token.match(/^\s*[0-9]+\s*$/)) {
+        var index = parseInt(token, 10);
+        if (!target) target = [];
+        target[index] = tokens.length == 0 ? value : set(target[index], tokens.slice(0), value);
+      } else if (token) {
+        var index = token.replace(/^\s*|\s*$/g, "");
+        if (!target) target = {};
+        if (is(target, Array)) {
+          var temp = {};
+          for (var i = 0; i < target.length; ++i) {
+            temp[i] = target[i];
+          }
+          target = temp;
+        }
+        target[index] = tokens.length == 0 ? value : set(target[index], tokens.slice(0), value);
+      } else {
+        return value;
+      }
+      return target;
+    };
+    
+    var queryObject = function(a) {
+      var self = this;
+      self.keys = {};
+      
+      if (a.queryObject) {
+        jQuery.each(a.get(), function(key, val) {
+          self.SET(key, val);
+        });
+      } else {
+        jQuery.each(arguments, function() {
+          var q = "" + this;
+          q = q.replace(/^[?#]/,''); // remove any leading ? || #
+          q = q.replace(/[;&]$/,''); // remove any trailing & || ;
+          if ($spaces) q = q.replace(/[+]/g,' '); // replace +'s with spaces
+          
+          jQuery.each(q.split(/[&;]/), function(){
+            var key = decodeURIComponent(this.split('=')[0] || "");
+            var val = decodeURIComponent(this.split('=')[1] || "");
+            
+            if (!key) return;
+            
+            if ($numbers) {
+              if (/^[+-]?[0-9]+\.[0-9]*$/.test(val)) // simple float regex
+                val = parseFloat(val);
+              else if (/^[+-]?[0-9]+$/.test(val)) // simple int regex
+                val = parseInt(val, 10);
+            }
+            
+            val = (!val && val !== 0) ? true : val;
+            
+            if (val !== false && val !== true && typeof val != 'number')
+              val = val;
+            
+            self.SET(key, val);
+          });
+        });
+      }
+      return self;
+    };
+    
+    queryObject.prototype = {
+      queryObject: true,
+      has: function(key, type) {
+        var value = this.get(key);
+        return is(value, type);
+      },
+      GET: function(key) {
+        if (!is(key)) return this.keys;
+        var parsed = parse(key), base = parsed[0], tokens = parsed[1];
+        var target = this.keys[base];
+        while (target != null && tokens.length != 0) {
+          target = target[tokens.shift()];
+        }
+        return typeof target == 'number' ? target : target || "";
+      },
+      get: function(key) {
+        var target = this.GET(key);
+        if (is(target, Object))
+          return jQuery.extend(true, {}, target);
+        else if (is(target, Array))
+          return target.slice(0);
+        return target;
+      },
+      SET: function(key, val) {
+        var value = !is(val) ? null : val;
+        var parsed = parse(key), base = parsed[0], tokens = parsed[1];
+        var target = this.keys[base];
+        this.keys[base] = set(target, tokens.slice(0), value);
+        return this;
+      },
+      set: function(key, val) {
+        return this.copy().SET(key, val);
+      },
+      REMOVE: function(key) {
+        return this.SET(key, null).COMPACT();
+      },
+      remove: function(key) {
+        return this.copy().REMOVE(key);
+      },
+      EMPTY: function() {
+        var self = this;
+        jQuery.each(self.keys, function(key, value) {
+          delete self.keys[key];
+        });
+        return self;
+      },
+      load: function(url) {
+        var hash = url.replace(/^.*?[#](.+?)(?:\?.+)?$/, "$1");
+        var search = url.replace(/^.*?[?](.+?)(?:#.+)?$/, "$1");
+        return new queryObject(url.length == search.length ? '' : search, url.length == hash.length ? '' : hash);
+      },
+      empty: function() {
+        return this.copy().EMPTY();
+      },
+      copy: function() {
+        return new queryObject(this);
+      },
+      COMPACT: function() {
+        function build(orig) {
+          var obj = typeof orig == "object" ? is(orig, Array) ? [] : {} : orig;
+          if (typeof orig == 'object') {
+            function add(o, key, value) {
+              if (is(o, Array))
+                o.push(value);
+              else
+                o[key] = value;
+            }
+            jQuery.each(orig, function(key, value) {
+              if (!is(value)) return true;
+              add(obj, key, build(value));
+            });
+          }
+          return obj;
+        }
+        this.keys = build(this.keys);
+        return this;
+      },
+      compact: function() {
+        return this.copy().COMPACT();
+      },
+      toString: function() {
+        var i = 0, queryString = [], chunks = [], self = this;
+        var encode = function(str) {
+          str = str + "";
+          if ($spaces) str = str.replace(/ /g, "+");
+          return encodeURIComponent(str);
+        };
+        var addFields = function(arr, key, value) {
+          if (!is(value) || value === false) return;
+          var o = [encode(key)];
+          if (value !== true) {
+            o.push("=");
+            o.push(encode(value));
+          }
+          arr.push(o.join(""));
+        };
+        var build = function(obj, base) {
+          var newKey = function(key) {
+            return !base || base == "" ? [key].join("") : [base, "[", key, "]"].join("");
+          };
+          jQuery.each(obj, function(key, value) {
+            if (typeof value == 'object') 
+              build(value, newKey(key));
+            else
+              addFields(chunks, newKey(key), value);
+          });
+        };
+        
+        build(this.keys);
+        
+        if (chunks.length > 0) queryString.push($hash);
+        queryString.push(chunks.join($separator));
+        
+        return queryString.join("");
+      }
+    };
+    
+    return new queryObject(location.search, location.hash);
+  };
+}(jQuery.query || {}); // Pass in jQuery.query as settings object

semanticeditor/media/semanticeditor/javascript/wymeditor/plugins/semantic/wymeditor.semantic.js

 PresentationControls.prototype.retrieve_styles = function() {
     // Retrieve via AJAX
     var self = this;
-    jQuery.getJSON(this.opts.retrieve_styles_url, {},
+    jQuery.getJSON(this.opts.retrieve_styles_url, {'template':this.opts.template,
+                                                   'page_id':this.opts.page_id
+                                                  },
 		   function (data) {
 		       self.with_good_data(data, function(value) {
 			   self.available_styles = data.value;

semanticeditor/migrations/0002_cssclass_template.py

+
+from south.db import db
+from django.db import models
+from semanticeditor.models import *
+
+class Migration:
+    
+    def forwards(self, orm):
+        
+        # Adding field 'CssClass.templates'
+        db.add_column('semanticeditor_cssclass', 'templates', orm['semanticeditor.cssclass:templates'])
+        
+    
+    
+    def backwards(self, orm):
+        
+        # Deleting field 'CssClass.templates'
+        db.delete_column('semanticeditor_cssclass', 'templates')
+        
+    
+    
+    models = {
+        'semanticeditor.cssclass': {
+            'allowed_elements': ('django.db.models.fields.CharField', [], {'default': "'h1 h2 h3 h4 h5 h6 p blockquote ul li row column'", 'max_length': '255'}),
+            'column_equiv': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
+            'description': ('django.db.models.fields.TextField', [], {'max_length': '255', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'unique': 'True'}),
+            'templates': ('MultiSelectField', ['"Templates"'], {'default': "''", 'blank': 'True'}),
+            'verbose_name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'unique': 'True'})
+        }
+    }
+    
+    complete_apps = ['semanticeditor']

semanticeditor/models.py

 from django.db import models
+from semanticeditor.fields import MultiSelectField
+from django.conf import settings
+
+template_list = [(f,n) for (f,n) in settings.CMS_TEMPLATES if f != settings.CMS_TEMPLATE_INHERITANCE_MAGIC]
 
 class CssClass(models.Model):
     name = models.CharField("CSS class name", max_length=255, unique=True,
                                    "when the user hovers over the name in the "
                                    "list of styles.  The data will be interpreted "
                                    "as raw HTML, so it can include an example.")
+    templates = MultiSelectField("Templates", choices=template_list, blank=True,
+                                 default="")
+
     allowed_elements = models.CharField("Allowed HTML elements", max_length=255,
                                         help_text="A space separated list of HTML "
                                         "element names.  Use 'row' or 'column' to indicate "

semanticeditor/templates/semanticeditor/editorwidget.html

             .append(html);
 
             // Enable the 'semantic' plugin
+            // 'template' attribute can be overridden by a '?template' query
+            // string parameter on parent frame
+            var qs = jQuery.query.load(window.parent.location.search);
+            var template = qs.get('template');
+            if (template == "") {
+                template = "{{ page.template }}";
+            }
+
             symanticopts = {
                 extract_structure_url: "{% url semantic.extract_structure %}",
                 retrieve_styles_url: "{% url semantic.retrieve_styles %}",
                 separate_presentation_url: "{% url semantic.separate_presentation %}",
                 combine_presentation_url: "{% url semantic.combine_presentation %}",
                 clean_html_url: "{% url semantic.clean_html %}",
-                preview_url: "{% url semantic.preview %}"
+                preview_url: "{% url semantic.preview %}",
+                template: template,
+                page_id: "{{ page.id }}"
             };
             wym.semantic(symanticopts);
 

semanticeditor/views.py

+from cms.models import Page
 from django.http import HttpResponse
 from django.utils import simplejson
 from django.core.mail import mail_admins
+from django.conf import settings
 from django.utils.translation import ugettext as _
 from semanticeditor.utils import extract_structure, extract_presentation, format_html, preview_html, AllUserErrors, NEWROW, NEWCOL, PresentationInfo, PresentationClass, clean_html
 from semanticeditor.models import CssClass
 
 @json_view
 def retrieve_styles(request):
+    template = request.GET['template']
+    page_id = request.GET['page_id']
+    if template == settings.CMS_TEMPLATE_INHERITANCE_MAGIC:
+        # Need to look up page to find out what template to use
+        p = Page.objects.get(pk=page_id)
+        template = p.get_template()
+    classes = CssClass.objects.all().order_by('verbose_name')
+    # Can't do filter in DB easily, because 'templates' is actually
+    # a comma separated list in DB.
+    classes = filter(lambda c: template in c.templates, classes)
     retval = map(css_class_to_presentation_class,
-                 CssClass.objects.all().order_by('verbose_name'))
+                 classes)
     return success(map(PI_to_dict,retval))
 
 @json_view

semanticeditor/widgets.py

               ('javascript/wymeditor/plugins/semantic/wymeditor.semantic.js',
                'javascript/json2.js',
                'javascript/orbitaltooltip.js',
+               'javascript/jquery.query-2.1.7.js',
                )]
 
+    def __init__(self, attrs=None, installed_plugins=None, page=None):
+        self.page = page
+        super(SemanticEditor, self).__init__(attrs)
+
     def render_additions(self, name, value, attrs=None):
         language = get_language()
         context = {
             'WYM_CONTAINERS': mark_safe(text_settings.WYM_CONTAINERS),
             'WYM_CLASSES': mark_safe(text_settings.WYM_CLASSES),
             'installed_plugins': self.installed_plugins,
+            'page': self.page,
         }
 
         return mark_safe(render_to_string(
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.