Skip to content

Commit

Permalink
Merge branch 'master' of ssh://git.netcentrum.cz/projects/content/GIT…
Browse files Browse the repository at this point in the history
…/ella
  • Loading branch information
honzakral committed Apr 19, 2009
2 parents edeff7e + e5d60cd commit e7ef719
Show file tree
Hide file tree
Showing 24 changed files with 169 additions and 136 deletions.
16 changes: 16 additions & 0 deletions INSTALL
@@ -0,0 +1,16 @@
Requirements
============

* Python 2.5+
* Django 1.1 (with patch from #3400)
* python-markdown2 (or python-markdown)
* PIL
* python-cjson (optional)
* django-markup (optional)

For testing:
------------

* python-nose
* djangosanetesting

104 changes: 0 additions & 104 deletions ella/newman/fields.py
Expand Up @@ -8,121 +8,17 @@

from django.forms import fields
from django.forms.util import ValidationError
from django.utils.encoding import smart_unicode
from django.utils.translation import ugettext_lazy as _
from django.forms.models import ModelChoiceField
from django.db.models.fields.related import ManyToManyField
from django.db.models import signals
from django.contrib.admin.widgets import AdminFileWidget
from django.contrib.contenttypes.models import ContentType
from django.conf import settings

from ella.core.templatetags.core import render_str
from ella.newman import widgets, utils
from ella.newman.permission import get_permission, permission_filtered_model_qs, has_category_permission
from ella.newman import config

log = logging.getLogger('ella.newman')
MARKUP_APP_INSTALLED = False # used by RichTextField

class NotFoundError(Exception):
pass

def listener_post_save(sender, signal, created, **kwargs):
log.debug('Listener activated by %s, sig=%s, created=%s' % (sender, signal, created))
log.debug('Listener kwargs=%s' % kwargs)
if not hasattr(listener_post_save, 'src_text'):
return
src_text = listener_post_save.src_text
if ContentType.objects.get_for_model(kwargs['instance']) == src_text.ct:
delattr(listener_post_save, 'src_text')
signals.post_save.disconnect(receiver=listener_post_save)
log.debug('Signal listener disconnected')
src_text.obj_id = kwargs['instance'].pk
src_text.save()

class RichTextField(fields.Field):
widget = widgets.RichTextAreaWidget
default_error_messages = {
'syntax_error': _('Bad syntax in markdown formatting or template tags.'),
'url_error': _('Some links are invalid: %s.'),
'link_error': _('Some links are broken: %s.'),
}

def __init__(self, *args, **kwargs):
# TODO: inform widget about selected processor (JS editor..)
self.field_name = kwargs.pop('field_name')
self.instance = kwargs.pop('instance')
self.model = kwargs.pop('model')
if self.instance:
self.ct = ContentType.objects.get_for_model(self.instance)
else:
self.ct = ContentType.objects.get_for_model(self.model)
super(RichTextField, self).__init__(*args, **kwargs)
setattr(self.widget, '_field', self)

def is_markup(self):
return self.instance and MARKUP_APP_INSTALLED

def get_source(self):
if not self.is_markup():
return
# find SourceText associated with instance
from ella.newman.markup.models import SourceText
try:
src_text = SourceText.objects.get(ct=self.ct, obj_id=self.instance.pk, field=self.field_name)
except SourceText.DoesNotExist:
log.warning('SourceText.DoesNotExist for ct=%d obj_id=%d field=%s' % (self.ct.pk, self.instance.pk, self.field_name))
#raise NotFoundError(u'No SourceText defined for object [%s] , field [%s] ' % ( self.instance.__unicode__(), self.field_name))
return SourceText()
return src_text

def get_source_text(self):
if not self.is_markup():
return
return self.get_source().content

def get_rendered_text(self):
if not self.is_markup():
return
return self.get_source().render()

def clean(self, value):
super_value = super(RichTextField, self).clean(value)
if not self.is_markup():
return super_value
if value in fields.EMPTY_VALUES:
return u''
text = smart_unicode(value)
if not MARKUP_APP_INSTALLED:
return text
# TODO save value to SourceText, return rendered. post_save signal !
from ella.newman.markup.models import SourceText, TextProcessor
if self.instance:
src_text, created = SourceText.objects.get_or_create(ct=self.ct, obj_id=self.instance.pk, field=self.field_name)
src_text.content = text
try:
rendered = src_text.render()
except:
raise ValidationError(self.error_messages['syntax_error'])
src_text.save()
else:
# in case of adding new model, instance is not set
default_proc = TextProcessor.objects.get(name=config.NEWMAN_MARKUP_DEFAULT)
src_text = SourceText(
ct=self.ct,
field=self.field_name,
content=text,
processor=default_proc
)
try:
rendered = src_text.render()
except:
raise ValidationError(self.error_messages['syntax_error'])
listener_post_save.src_text = src_text
signals.post_save.connect(listener_post_save)
return rendered

class AdminSuggestField(fields.Field):
"""
Admin field with AJAX suggested values.
Expand Down
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
11 changes: 11 additions & 0 deletions ella/newman/media/js/markitup/sets/markdown/readme.txt
@@ -0,0 +1,11 @@
Markup language:
Markdown

Description:
A basic Markdown markup set with Headings, Bold, Italic, Picture, Link, List, Quotes, Code, Preview button.

Install:
- Download the zip file
- Unzip it in your markItUp! sets folder
- Modify your JS link to point at this set.js
- Modify your CSS link to point at this style.css
52 changes: 52 additions & 0 deletions ella/newman/media/js/markitup/sets/markdown/set.js
@@ -0,0 +1,52 @@
// -------------------------------------------------------------------
// markItUp!
// -------------------------------------------------------------------
// Copyright (C) 2008 Jay Salvat
// http://markitup.jaysalvat.com/
// -------------------------------------------------------------------
// MarkDown tags example
// http://en.wikipedia.org/wiki/Markdown
// http://daringfireball.net/projects/markdown/
// -------------------------------------------------------------------
// Feel free to add more tags
// -------------------------------------------------------------------
mySettings = {
previewParserPath: '',
onShiftEnter: {keepDefault:false, openWith:'\n\n'},
markupSet: [
{name:'First Level Heading', key:'1', placeHolder:'Your title here...', closeWith:function(markItUp) { return miu.markdownTitle(markItUp, '=') } },
{name:'Second Level Heading', key:'2', placeHolder:'Your title here...', closeWith:function(markItUp) { return miu.markdownTitle(markItUp, '-') } },
{name:'Heading 3', key:'3', openWith:'### ', placeHolder:'Your title here...' },
{name:'Heading 4', key:'4', openWith:'#### ', placeHolder:'Your title here...' },
{name:'Heading 5', key:'5', openWith:'##### ', placeHolder:'Your title here...' },
{name:'Heading 6', key:'6', openWith:'###### ', placeHolder:'Your title here...' },
{separator:'---------------' },
{name:'Bold', key:'B', openWith:'**', closeWith:'**'},
{name:'Italic', key:'I', openWith:'_', closeWith:'_'},
{separator:'---------------' },
{name:'Bulleted List', openWith:'- ' },
{name:'Numeric List', openWith:function(markItUp) {
return markItUp.line+'. ';
}},
{separator:'---------------' },
{name:'Picture', key:'P', replaceWith:'![[![Alternative text]!]]([![Url:!:http://]!] "[![Title]!]")'},
{name:'Link', key:'L', openWith:'[', closeWith:']([![Url:!:http://]!] "[![Title]!]")', placeHolder:'Your text to link here...' },
{separator:'---------------'},
{name:'Quotes', openWith:'> '},
{name:'Code Block / Code', openWith:'(!(\t|!|`)!)', closeWith:'(!(`)!)'},
{separator:'---------------'},
{name:'Preview', call:'preview', className:"preview"}
]
}

// mIu nameSpace to avoid conflict.
miu = {
markdownTitle: function(markItUp, char) {
heading = '';
n = $.trim(markItUp.selection||markItUp.placeHolder).length;
for(i = 0; i < n; i++) {
heading += char;
}
return '\n'+heading;
}
}
54 changes: 54 additions & 0 deletions ella/newman/media/js/markitup/sets/markdown/style.css
@@ -0,0 +1,54 @@
/* -------------------------------------------------------------------
// markItUp!
// By Jay Salvat - http://markitup.jaysalvat.com/
// ------------------------------------------------------------------*/
.markItUp .markItUpButton1 a {
background-image:url(images/h1.png);
}
.markItUp .markItUpButton2 a {
background-image:url(images/h2.png);
}
.markItUp .markItUpButton3 a {
background-image:url(images/h3.png);
}
.markItUp .markItUpButton4 a {
background-image:url(images/h4.png);
}
.markItUp .markItUpButton5 a {
background-image:url(images/h5.png);
}
.markItUp .markItUpButton6 a {
background-image:url(images/h6.png);
}

.markItUp .markItUpButton7 a {
background-image:url(images/bold.png);
}
.markItUp .markItUpButton8 a {
background-image:url(images/italic.png);
}

.markItUp .markItUpButton9 a {
background-image:url(images/list-bullet.png);
}
.markItUp .markItUpButton10 a {
background-image:url(images/list-numeric.png);
}

.markItUp .markItUpButton11 a {
background-image:url(images/picture.png);
}
.markItUp .markItUpButton12 a {
background-image:url(images/link.png);
}

.markItUp .markItUpButton13 a {
background-image:url(images/quotes.png);
}
.markItUp .markItUpButton14 a {
background-image:url(images/code.png);
}

.markItUp .preview a {
background-image:url(images/preview.png);
}
5 changes: 4 additions & 1 deletion ella/newman/options.py
Expand Up @@ -28,6 +28,8 @@
from ella.newman.xoptions import XModelAdmin
from ella.newman.config import STATUS_OK, STATUS_FORM_ERROR, STATUS_VAR_MISSING, STATUS_OBJECT_NOT_FOUND, AUTOSAVE_MAX_AMOUNT

from djangomarkup.fields import RichTextField

DEFAULT_LIST_PER_PAGE = getattr(settings, 'NEWMAN_LIST_PER_PAGE', 25)

log = logging.getLogger('ella.newman')
Expand All @@ -54,8 +56,9 @@ def formfield_for_dbfield_factory(cls, db_field, **kwargs):
'field_name': db_field.name,
'instance': custom_params.get('instance', None),
'model': custom_params.get('model'),
'widget': widgets.NewmanRichTextAreaWidget
})
rich_text_field = fields.RichTextField(**kwargs)
rich_text_field = RichTextField(**kwargs)
if css_class:
rich_text_field.widget.attrs['class'] += ' %s' % css_class
return rich_text_field
Expand Down
59 changes: 28 additions & 31 deletions ella/newman/widgets.py
Expand Up @@ -5,13 +5,17 @@
from django.contrib.admin import widgets
from django.utils.text import truncate_words
from ella.ellaadmin.utils import admin_url
from djangomarkup.widgets import RichTextAreaWidget

MARKITUP_SET = getattr(settings, 'DEFAULT_MARKUP', 'default')
MEDIA_PREFIX = getattr(settings, 'NEWMAN_MEDIA_PREFIX', settings.ADMIN_MEDIA_PREFIX)

# Rich text editor
JS_MARKITUP = 'js/markitup/jquery.markitup.js'
JS_MARKITUP_SETTINGS = 'js/markitup/sets/default/set.js'
JS_MARKITUP_SET = 'js/markitup/sets/%s/set.js' % MARKITUP_SET
JS_EDITOR = 'js/editor.js'
CSS_MARKITUP = 'js/markitup/skins/markitup/style.css'
CSS_MARKITUP_TOOLBAR = 'js/markitup/sets/default/style.css'
CSS_MARKITUP_SET = 'js/markitup/sets/%s/style.css' % MARKITUP_SET
CLASS_RICHTEXTAREA = 'rich_text_area'

# Generic suggester media files
Expand All @@ -31,6 +35,28 @@
JS_FLASH_IMAGE_INPUT = 'js/flash_image.js'
SWF_FLASH_IMAGE_INPUT = 'swf/PhotoUploader.swf'

class NewmanRichTextAreaWidget(RichTextAreaWidget):
"""
Newman's implementation of markup, based on markitup editor.
"""
class Media:
js = (
MEDIA_PREFIX + JS_MARKITUP,
MEDIA_PREFIX + JS_MARKITUP_SET,
MEDIA_PREFIX + JS_EDITOR,
)
css = {
'screen': (
MEDIA_PREFIX + CSS_MARKITUP,
MEDIA_PREFIX + CSS_MARKITUP_SET,
),
}

def __init__(self, attrs={}):
css_class = CLASS_RICHTEXTAREA
super(RichTextAreaWidget, self).__init__(attrs={'class': css_class})


class FlashImageWidget(widgets.AdminFileWidget):
class Media:
js = (
Expand Down Expand Up @@ -74,35 +100,6 @@ def label_for_value(self, value):
return '&nbsp;<a href="%s">%s</a>' % (adm, label)


class RichTextAreaWidget(forms.Textarea):
'Widget representing the RichTextEditor.'
class Media:
js = (
settings.NEWMAN_MEDIA_PREFIX + JS_MARKITUP,
settings.NEWMAN_MEDIA_PREFIX + JS_MARKITUP_SETTINGS,
settings.NEWMAN_MEDIA_PREFIX + JS_EDITOR,
)
css = {
'screen': (
settings.NEWMAN_MEDIA_PREFIX + CSS_MARKITUP,
settings.NEWMAN_MEDIA_PREFIX + CSS_MARKITUP_TOOLBAR,
),
}

def __init__(self, height=None, attrs={}):
css_class = CLASS_RICHTEXTAREA
if height:
css_class += ' %s' % height
super(RichTextAreaWidget, self).__init__(attrs={'class': css_class})

def render(self, name, value, attrs=None):
final_attrs = self.build_attrs(attrs, name=name)
if value and self._field.is_markup():
src_text = self._field.get_source_text()
else:
src_text = value
return super(RichTextAreaWidget, self).render(name, src_text, attrs)

class AdminSuggestWidget(forms.TextInput):
class Media:
js = (settings.NEWMAN_MEDIA_PREFIX + JS_JQUERY_UI, settings.NEWMAN_MEDIA_PREFIX + JS_GENERIC_SUGGEST,)
Expand Down
1 change: 1 addition & 0 deletions tests/example_project/settings/base.py
Expand Up @@ -73,6 +73,7 @@
'ella.newman',
'ella.newman.licenses',
'django.contrib.admin',
'djangomarkup',
)

VERSION = 1
3 changes: 3 additions & 0 deletions tests/example_project/settings/config.py
Expand Up @@ -62,3 +62,6 @@
ADMIN_MEDIA_PREFIX = '/static/admin_media/'
NEWMAN_MEDIA_PREFIX = '/static/newman_media/'

# markup settings
DEFAULT_MARKUP = 'markdown'

0 comments on commit e7ef719

Please sign in to comment.