Skip to content

Commit

Permalink
Added MarkItUp editor and preview
Browse files Browse the repository at this point in the history
  • Loading branch information
voronind committed Dec 20, 2012
1 parent f103730 commit d7eb445
Show file tree
Hide file tree
Showing 112 changed files with 1,802 additions and 99 deletions.
7 changes: 7 additions & 0 deletions .hgignore
@@ -0,0 +1,7 @@
syntax: glob


.idea*
*.pyc

*.pyo
2 changes: 1 addition & 1 deletion markitup_field/__init__.py
@@ -1 +1 @@
__version__ = '1.1.0-dev'
__version__ = '1.0.2'
133 changes: 79 additions & 54 deletions markitup_field/fields.py
@@ -1,31 +1,29 @@
import django
from django.conf import settings
#from django.conf import settings
from django.db import models
from django.utils.safestring import mark_safe
from django.utils.html import escape
from django.utils.encoding import python_2_unicode_compatible

from markupfield import widgets
from markupfield import markup
from markitup_field import widgets
#from markitup_field import markup
from markitup_field import settings

_rendered_field_name = lambda name: '_%s_rendered' % name
_markup_type_field_name = lambda name: '%s_markup_type' % name
_markup_format_field_name = lambda name: '%s_markup_format' % name

# for fields that don't set markup_types: detected types or from settings
_MARKUP_TYPES = getattr(settings, 'MARKUP_FIELD_TYPES', markup.DEFAULT_MARKUP_TYPES)
#MARKUP_CHOICES = getattr(settings, 'MARKUP_FILTERS', markup.MARKUP_FILTERS)


@python_2_unicode_compatible
class Markup(object):

def __init__(self, instance, field_name, rendered_field_name,
markup_type_field_name):
def __init__(self, instance, field_name, rendered_field_name, markup_format_field_name):
# instead of storing actual values store a reference to the instance
# along with field names, this makes assignment possible
self.instance = instance
self.field_name = field_name
self.rendered_field_name = rendered_field_name
self.markup_type_field_name = markup_type_field_name
self.markup_format_field_name = markup_format_field_name

# raw is read/write
def _get_raw(self):
Expand All @@ -36,74 +34,91 @@ def _set_raw(self, val):

raw = property(_get_raw, _set_raw)

# markup_type is read/write
def _get_markup_type(self):
return self.instance.__dict__[self.markup_type_field_name]
# markup_format is read/write
def _get_markup_format(self):
return self.instance.__dict__[self.markup_format_field_name]

def _set_markup_type(self, val):
return setattr(self.instance, self.markup_type_field_name, val)
def _set_markup_format(self, val):
# return setattr(self.instance, self.markup_format_field_name, val)
setattr(self.instance, self.markup_format_field_name, val)

markup_type = property(_get_markup_type, _set_markup_type)
markup_format = property(_get_markup_format, _set_markup_format)

# rendered is a read only property
def _get_rendered(self):
return getattr(self.instance, self.rendered_field_name)
rendered = property(_get_rendered)

# allows display via templates to work without safe filter
def __str__(self):
def __unicode__(self):
return mark_safe(self.rendered)


class MarkupDescriptor(object):

def __init__(self, field):
self.field = field
self.rendered_field_name = _rendered_field_name(self.field.name)
self.markup_type_field_name = _markup_type_field_name(self.field.name)
self.rendered_field_name = field.rendered_field_name
self.markup_format_field_name = field.markup_format_field_name

def __get__(self, instance, owner):
if instance is None:
raise AttributeError('Can only be accessed via an instance.')
markup = instance.__dict__[self.field.name]
if markup is None:
return None
return Markup(instance, self.field.name, self.rendered_field_name,
self.markup_type_field_name)
return Markup(instance, self.field.name, self.rendered_field_name, self.markup_format_field_name)

def __set__(self, obj, value):
if isinstance(value, Markup):
obj.__dict__[self.field.name] = value.raw
setattr(obj, self.rendered_field_name, value.rendered)
setattr(obj, self.markup_type_field_name, value.markup_type)
setattr(obj, self.markup_format_field_name, value.markup_format)
else:
obj.__dict__[self.field.name] = value


class MarkupField(models.TextField):

def __init__(self, verbose_name=None, name=None, markup_type=None,
default_markup_type=None, markup_choices=_MARKUP_TYPES,
def __init__(self, verbose_name=None, name=None,
markup_format=None, default_markup_format=None, markup_choices=settings.MARKUP_CHOICES,
rendered_field_name=None,
escape_html=False, **kwargs):

if markup_type and default_markup_type:
raise ValueError('Cannot specify both markup_type and default_markup_type')
if markup_format and default_markup_format:
raise ValueError("Cannot specify both 'markup_format' and 'default_markup_format'")

self.default_markup_type = markup_type or default_markup_type
self.markup_type_editable = markup_type is None
if not default_markup_format:
default_markup_format = settings.MARKUP_DEFAULT_FORMAT

# if render_to_field and rendered_field_name:
# raise ValueError("Cannot specify both 'render_to_field' and 'rendered_field_name'")

# if render_to_field:
# render_to_field_class_is_child_of_field = False
# try:
# render_to_field_class_is_child_of_field = issubclass(render_to_field.__class__, models.Field)
# except TypeError:
# pass
#
# if not render_to_field_class_is_child_of_field:
# raise ValueError("'render_to_field' must be Field")

# self.render_to_field = render_to_field
# print(render_to_field.)
self.rendered_field_name = rendered_field_name

self.default_markup_format = markup_format or default_markup_format
self.markup_format_editable = markup_format is None
self.escape_html = escape_html

# pre-1.0 markup_choices might have been a dict
if isinstance(markup_choices, dict):
raise ValueError('passing a dict as markup_choices removed in 1.1')
else:
self.markup_choices_list = [mc[0] for mc in markup_choices]
self.markup_choices_dict = dict(markup_choices)
self.markup_choices = markup_choices
self.markup_choices_list = [mc[0] for mc in markup_choices]
# self.markup_choices_dict = dict(markup_choices)

if (self.default_markup_type and
self.default_markup_type not in self.markup_choices_list):
raise ValueError("Invalid default_markup_type for field '%s', allowed values: %s" %
(name, ', '.join(self.markup_choices_list)))
# default_markup_format in markup_choices ?
if (self.default_markup_format and (self.default_markup_format not in self.markup_choices_list)):
raise ValueError("Invalid 'default_markup_format' for field '{}', allowed values: {}".format(name, ', '.join(self.markup_choices_list)))

# for South FakeORM compatibility: the frozen version of a
# MarkupField can't try to add a _rendered field, because the
Expand All @@ -114,32 +129,42 @@ def __init__(self, verbose_name=None, name=None, markup_type=None,
super(MarkupField, self).__init__(verbose_name, name, **kwargs)

def contribute_to_class(self, cls, name):
self.rendered_field_name = self.rendered_field_name or _rendered_field_name(name)
self.markup_format_field_name = _markup_format_field_name(name)

if not cls._meta.abstract:
choices = zip(self.markup_choices_list, self.markup_choices_list)
markup_type_field = models.CharField(max_length=30,
choices=choices, default=self.default_markup_type,
editable=self.markup_type_editable, blank=self.blank)
# choices = zip(self.markup_choices_list, self.markup_choices_list)

markup_format_field = models.CharField(max_length=30, choices=self.markup_choices, default=self.default_markup_format, editable=self.markup_format_editable, blank=self.blank)
markup_format_field.creation_counter = self.creation_counter - 1
cls.add_to_class(_markup_format_field_name(name), markup_format_field)

# self.creation_counter += 1

# if not self.render_to_field:
rendered_field = models.TextField(editable=False)
markup_type_field.creation_counter = self.creation_counter+1
rendered_field.creation_counter = self.creation_counter+2
cls.add_to_class(_markup_type_field_name(name), markup_type_field)
cls.add_to_class(_rendered_field_name(name), rendered_field)
super(MarkupField, self).contribute_to_class(cls, name)
# rendered_field = models.TextField(editable=True)
rendered_field.creation_counter = self.creation_counter + 1

cls.add_to_class(self.rendered_field_name, rendered_field)

super(MarkupField, self).contribute_to_class(cls, name)
setattr(cls, self.name, MarkupDescriptor(self))

def pre_save(self, model_instance, add):
value = super(MarkupField, self).pre_save(model_instance, add)
if value.markup_type not in self.markup_choices_list:
raise ValueError('Invalid markup type (%s), allowed values: %s' %
(value.markup_type,
if value.markup_format not in self.markup_choices_list:
raise ValueError('Invalid markup format (%s), allowed values: %s' %
(value.markup_format,
', '.join(self.markup_choices_list)))
if self.escape_html:
raw = escape(value.raw)
else:
raw = value.raw
rendered = self.markup_choices_dict[value.markup_type](raw)
setattr(model_instance, _rendered_field_name(self.attname), rendered)
# rendered = self.markup_choices_dict[value.markup_format](raw)
rendered = settings.MARKUP_FILTERS[value.markup_format](raw)
# setattr(model_instance, _rendered_field_name(self.attname), rendered)
setattr(model_instance, self.rendered_field_name, rendered)
return value.raw

def get_prep_value(self, value):
Expand All @@ -163,7 +188,7 @@ def formfield(self, **kwargs):

# register MarkupField to use the custom widget in the Admin
from django.contrib.admin.options import FORMFIELD_FOR_DBFIELD_DEFAULTS
FORMFIELD_FOR_DBFIELD_DEFAULTS[MarkupField] = {'widget': widgets.AdminMarkupTextareaWidget}
FORMFIELD_FOR_DBFIELD_DEFAULTS[MarkupField] = {'widget': widgets.AdminMarkupWidget}

# allow South to handle MarkupField smoothly
try:
Expand All @@ -173,6 +198,6 @@ def formfield(self, **kwargs):
# True in a frozen MarkupField, which is what we want.
add_introspection_rules(rules=[
( (MarkupField,), [], { 'rendered_field': ['rendered_field', {}], })
], patterns=['markupfield\.fields\.MarkupField'])
], patterns=['markitup_field\.fields\.MarkupField'])
except ImportError:
pass
1 change: 1 addition & 0 deletions markitup_field/management/__init__.py
@@ -0,0 +1 @@
__author__ = 'dimka'
1 change: 1 addition & 0 deletions markitup_field/management/commands/__init__.py
@@ -0,0 +1 @@
__author__ = 'dimka'
39 changes: 39 additions & 0 deletions markitup_field/management/commands/dice_markup_build_css.py
@@ -0,0 +1,39 @@

from django.core.management.base import BaseCommand, CommandError

import os

from scss import Scss
css = Scss()

class Command(BaseCommand):
# args = '<arg_x arg_y ...>'
help = "Builds 'style.css' of markups in one css-file using pySCSS"

def handle(self, *args, **options):
my_path = os.path.dirname(__file__)
sets_path = os.path.join(my_path, '../../static/markitup/sets/')
sets_path = os.path.abspath(sets_path)

style_scss = ''
# sets list
sets = [set_dir for set_dir in os.listdir(sets_path) if os.path.isdir(os.path.join(sets_path, set_dir))]
for set_dir in sets:
set_path = os.path.join(sets_path, set_dir, 'style.css')

set_css = open(set_path, 'r').read()
set_css = set_css.replace('url(images/', 'url({}/images/'.format(set_dir))

set_scss = '.{set_dir} {{\n{set_css}\n}}\n'.format(set_dir=set_dir, set_css=set_css)

style_scss += set_scss

style_css = css.compile(style_scss)

with open(os.path.join(sets_path, 'style.css'), 'w') as style_css_file:
style_css_file.write(style_css)

# style_css_file = open(os.path.join(sets_path, 'style.css'), 'w')
# style_css_file.write(style_css)
# style_css_file.close()

28 changes: 17 additions & 11 deletions markitup_field/markup.py
@@ -1,15 +1,15 @@
from __future__ import unicode_literals

from django.utils.html import linebreaks, urlize
from django.utils.functional import curry
from django.conf import settings

# build DEFAULT_MARKUP_TYPES
DEFAULT_MARKUP_TYPES = [
('html', lambda markup: markup),
('plain', lambda markup: urlize(linebreaks(markup))),
]
# build MARKUP_FILTERS
MARKUP_FILTERS = {
'text': lambda markup: urlize(linebreaks(markup)),
'html': lambda markup: markup,
}


# Pygments
try:
import pygments
PYGMENTS_INSTALLED = True
Expand All @@ -34,7 +34,7 @@ def pygments_directive(name, arguments, options, content, lineno,
# no lexer found - use the text one instead of an exception
lexer = TextLexer()
formatter = options and VARIANTS[options.keys()[0]] or DEFAULT
parsed = highlight('\n'.join(content), lexer, formatter)
parsed = highlight(u'\n'.join(content), lexer, formatter)
return [nodes.raw('', parsed, format='html')]
pygments_directive.arguments = (1, 0, 1)
pygments_directive.content = 1
Expand All @@ -43,6 +43,8 @@ def pygments_directive(name, arguments, options, content, lineno,
except ImportError:
PYGMENTS_INSTALLED = False


# Markdown
try:
import markdown

Expand All @@ -57,11 +59,13 @@ def pygments_directive(name, arguments, options, content, lineno,
pass

# whichever markdown_filter was available
DEFAULT_MARKUP_TYPES.append(('markdown', md_filter))
MARKUP_FILTERS['markdown'] = md_filter

except ImportError:
pass


# ReStructured Text
try:
from docutils.core import publish_parts

Expand All @@ -74,14 +78,16 @@ def render_rest(markup):
settings_overrides=overrides)
return parts["fragment"]

DEFAULT_MARKUP_TYPES.append(('restructuredtext', render_rest))
MARKUP_FILTERS['restructuredtext'] = render_rest
except ImportError:
pass


# Textile
try:
import textile
textile_filter =curry(textile.textile, encoding='utf-8', output='utf-8')
DEFAULT_MARKUP_TYPES.append(('textile', textile_filter))
MARKUP_FILTERS['textile'] = textile_filter
except ImportError:
pass

28 changes: 28 additions & 0 deletions markitup_field/settings.py
@@ -0,0 +1,28 @@

from django.conf import settings

from markitup_field.markup import MARKUP_FILTERS


MARKUP_FILTERS.update(getattr(settings, 'MARKUP_FILTERS', {}))
MARKUP_CHOICES = getattr(settings, 'MARKUP_CHOICES', None)

if not MARKUP_CHOICES:
MARKUP_CHOICES = (
('text', 'Text'),
('html', 'HTML'),
('markdown', 'Markdown'),
('textile', 'Textile'),
('restructuredtext', 'ReStructured Text'),
)

MARKUP_CHOICES = [markup for markup in MARKUP_CHOICES if markup[0] in MARKUP_FILTERS.keys()]

MARKUP_DEFAULT_FORMAT = getattr(settings, 'MARKUP_DEFAULT_FORMAT', 'default')

MARKUP_AUTO_PREVIEW = getattr(settings, 'MARKUP_AUTO_PREVIEW', False)

MARKUP_SKIN = getattr(settings, 'MARKUP_SKIN', 'markitup/skins/simple')

JQUERY_URL = getattr(settings, 'JQUERY_URL', 'http://ajax.googleapis.com/ajax/libs/jquery/1.6/jquery.min.js')

0 comments on commit d7eb445

Please sign in to comment.