Browse Source

Initial import.

master
Erik Stein 7 years ago
commit
26333fe090
  1. 62
      .gitignore
  2. 8
      LICENSE
  3. 4
      MANIFEST.in
  4. 3
      README.md
  5. 57
      setup.py
  6. 1
      shared/__init__.py
  7. 1
      shared/markup/__init__.py
  8. 39
      shared/markup/content.py
  9. 82
      shared/markup/markdown_utils.py
  10. 1
      shared/markup/markupfield/__init__.py
  11. 130
      shared/markup/markupfield/fields.py
  12. 15
      shared/markup/markupfield/markup/__init__.py
  13. 73
      shared/markup/markupfield/markup/base
  14. 56
      shared/markup/markupfield/markup/base.py
  15. 16
      shared/markup/markupfield/markup/html.py
  16. 29
      shared/markup/markupfield/markup/markdown.py
  17. 9
      shared/markup/markupfield/markup/pygments.py
  18. 101
      shared/markup/markupfield/markup/rst/__init__.py
  19. 17
      shared/markup/markupfield/markup/rst/helpers.py
  20. 37
      shared/markup/markupfield/settings.py
  21. 0
      shared/markup/markupfield/tests/__init__.py
  22. 41
      shared/markup/markupfield/tests/models.py
  23. 1
      shared/markup/markupfield/tests/run_tests.sh
  24. 24
      shared/markup/markupfield/tests/settings.py
  25. 129
      shared/markup/markupfield/tests/tests.py
  26. 16
      shared/markup/markupfield/widgets.py
  27. 0
      shared/markup/templatetags/__init__.py
  28. 57
      shared/markup/templatetags/markup_tags.py

62
.gitignore vendored

@ -0,0 +1,62 @@
# ---> Python
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
env/
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
*.egg-info/
.installed.cfg
*.egg
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*,cover
# Translations
*.mo
*.pot
# Django stuff:
*.log
# Sphinx documentation
docs/_build/
# PyBuilder
target/
./links/

8
LICENSE

@ -0,0 +1,8 @@
MIT License
Copyright (c) 2008-2017 Erik Stein <erik@classlibrary.net>
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

4
MANIFEST.in

@ -0,0 +1,4 @@
include AUTHORS
include LICENSE
include README.md
recursive-include shared/markup/templates *

3
README.md

@ -0,0 +1,3 @@
# django-shared-markup
Mix of Python and Django utility functions, classed etc. for handling of marked up text.

57
setup.py

@ -0,0 +1,57 @@
#!/usr/bin/env python
from io import open
import os
from setuptools import setup, find_packages
def get_version(prefix):
import re
with open(os.path.join(prefix, '__init__.py')) as fd:
metadata = dict(re.findall("__([a-z]+)__ = '([^']+)'", fd.read()))
return metadata['version']
def read(filename):
path = os.path.join(os.path.dirname(__file__), filename)
with open(path, encoding='utf-8') as handle:
return handle.read()
setup(
name='django-shared-markup',
version=get_version('shared/markup'),
description=' Mix of Python and Django utility functions, classed etc. for handling marked up text.',
long_description=read('README.md'),
author='Erik Stein',
author_email='erik@classlibrary.net',
url='https://projects.c--y.net/erik/django-shared-markup/',
license='MIT License',
platforms=['OS Independent'],
packages=find_packages(
exclude=['tests', 'testapp'],
),
namespace_packages=['shared'],
include_package_data=True,
install_requires=[
# 'django<2', commented out to make `pip install -U` easier
'beautifulsoup4',
'lxml',
],
classifiers=[
'Development Status :: 4 - Beta',
'Environment :: Web Environment',
'Framework :: Django',
'Intended Audience :: Developers',
'License :: OSI Approved :: BSD License',
'Operating System :: OS Independent',
'Programming Language :: Python :: 3',
'Topic :: Utilities',
],
zip_safe=False,
tests_require=[
'Django',
# 'coverage',
],
# test_suite='testapp.runtests.runtests',
)

1
shared/__init__.py

@ -0,0 +1 @@
__import__('pkg_resources').declare_namespace(__name__)

1
shared/markup/__init__.py

@ -0,0 +1 @@
__version__ = '0.5'

39
shared/markup/content.py

@ -0,0 +1,39 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models
from django.utils.translation import ugettext_lazy as _
from django.utils.safestring import mark_safe
from django.utils.html import conditional_escape, linebreaks
from .utils import markdown_to_html
class MarkupContent(models.Model):
PLAIN_TEXT = 'text/plain'
MARKDOWN = 'text/x-markdown'
HTML = 'text/html'
MARKUP_FORMATS = (
(MARKDOWN, _("Markdown")),
(PLAIN_TEXT, _("Reiner Text")),
(HTML, _("HTML")),
)
markup_format = models.CharField(max_length=20, choices=MARKUP_FORMATS, default=MARKDOWN)
text = models.TextField()
class Meta:
abstract = True
def render(self, inline=False, **kwargs):
# TODO Use request?
if self.markup_format == self.MARKDOWN:
# Marked safe by the markdown converter
return markdown_to_html(self.text, inline=inline)
elif self.markup_format == self.HTML:
return mark_safe(self.text)
else:
# TODO Use linebreaks filter
return linebreaks(conditional_escape(self.text))

82
shared/markup/markdown_utils.py

@ -0,0 +1,82 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
# Erik Stein <code@classlibrary.net>, 2012-2016
import markdown as markdown_module
from django.utils.html import strip_tags
from django.utils.safestring import mark_safe
from shared.utils.text import html_entities_to_unicode
class PseudoParagraphProcessor(markdown_module.blockprocessors.ParagraphProcessor):
"""
Process paragraph blocks without producing HTML paragraphs.
"""
def run(self, parent, blocks):
block = blocks.pop(0)
if block.strip():
# Create element without enclosing tags
p = markdown_module.util.etree.SubElement(parent, None)
p.text = block.lstrip()
config = dict(
output_format='html5',
extensions=[
'markdown.extensions.extra', # Includes footnotes
'markdown.extensions.nl2br',
'markdown.extensions.sane_lists',
'markdown.extensions.admonition',
'markdown.extensions.smarty',
]
)
extensionConfigs = {
'smarty': {
'substitutions': {
'left-single-quote': '&sbquo;',
'right-single-quote': '&lsquo;',
'left-double-quote': '&bdquo;',
'right-double-quote': '&ldquo;'
}
}
}
markdown_processor = markdown_module.Markdown(**config)
# Replace ParagraphProcessor
inline_markdown_processor = markdown_module.Markdown(**config)
inline_markdown_processor.parser.blockprocessors['paragraph'] = \
PseudoParagraphProcessor(inline_markdown_processor.parser)
def markdown_to_inline_html(text, **kwargs):
kwargs['inline'] = True
return markdown_to_html(text, **kwargs)
# TODO Decprecated API
inline_markdown = markdown_to_inline_html
def markdown_to_html(text, inline=False, **kwargs):
if inline:
processor = inline_markdown_processor
else:
processor = markdown_processor
processor.reset()
html = processor.convert(text, **kwargs)
return mark_safe(html)
# TODO Decprecated API
markdown = markdown_to_html
def markdown_to_text(text, **kwargs):
"""
Converts a string from markdown to HTML, then removes all
HTML markup (tags and entities).
"""
html = markdown_to_html(text, **kwargs)
return strip_tags(html_entities_to_unicode(html))

1
shared/markup/markupfield/__init__.py

@ -0,0 +1 @@
__version__ = '1.0b2-erik'

130
shared/markup/markupfield/fields.py

@ -0,0 +1,130 @@
# -*- coding: UTF-8 -*-
# Erik Stein <code@classlibrary.net>, 10/2010
from django.db import models
from django.utils.translation import ugettext_lazy as _
from .markup import DEFAULT_MARKUP_TYPES, Markup
from .widgets import MarkupTextarea, AdminMarkupTextareaWidget
_get_markup_type_field_name = lambda name: '%s_markup_type' % name
def _get_rendered_field_name(name):
field_name = '%s_rendered' % name
# Make the field internal
if not field_name[0] == '_':
field_name = '_%s' % field_name
return field_name
class MarkupDescriptor(object):
def __init__(self, field):
self.field = field
self.rendered_field_name = _get_rendered_field_name(self.field.name)
self.markup_type_field_name = _get_markup_type_field_name(self.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]
markup_type = instance.__dict__[self.markup_type_field_name]
if markup_type is None:
return None
if hasattr(self.field.markup_choices_dict[markup_type], 'render'):
markup_class = self.field.markup_choices_dict[markup_type]
else:
# Just a plain filter function, use default Markup class
if markup is None:
return None
markup_class = Markup
return markup_class(instance, self.field.name, self.rendered_field_name,
self.markup_type_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)
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=DEFAULT_MARKUP_TYPES,
**kwargs):
if markup_type and default_markup_type:
raise ValueError("Cannot specify both markup_type and default_markup_type.")
# if markup_choices and not default_markup_type:
# raise ValueError('No default_markup_type specified.')
self.default_markup_type = markup_type or default_markup_type
self.markup_type_editable = markup_type is None
# pre 1.0 markup_choices might have been a dict
if isinstance(markup_choices, dict):
# raise DeprecationWarning('passing a dictionary as markup_choices is deprecated')
self.markup_choices_dict = markup_choices
self.markup_choices_list = markup_choices.keys()
else:
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 '%s' for field '%s', allowed values: %s" %
(self.default_markup_type, name, ', '.join(self.markup_choices_list)))
super(MarkupField, self).__init__(verbose_name, name, **kwargs)
def contribute_to_class(self, cls, name):
if not cls._meta.abstract:
column_name = self.db_column or name
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,
db_column=_get_markup_type_field_name(column_name))
rendered_field = models.TextField(editable=False,
db_column=_get_rendered_field_name(column_name))
markup_type_field.creation_counter = self.creation_counter+1
rendered_field.creation_counter = self.creation_counter+2
cls.add_to_class(_get_markup_type_field_name(name), markup_type_field)
cls.add_to_class(_get_rendered_field_name(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,
', '.join(self.markup_choices_list)))
if hasattr(self.markup_choices_dict[value.markup_type], 'render'):
rendered = value.render()
else:
rendered = self.markup_choices_dict[value.markup_type](value.raw)
setattr(model_instance, _get_rendered_field_name(self.attname), rendered)
return value.raw
def get_db_prep_value(self, value):
# for Django 1.2+ rename this to get_prep_value
if isinstance(value, Markup):
return value.raw
else:
return value
def value_to_string(self, obj):
value = self._get_val_from_obj(obj)
return value.raw
def formfield(self, **kwargs):
defaults = {'widget': MarkupTextarea}
defaults.update(kwargs)
return super(MarkupField, self).formfield(**defaults)
# 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': AdminMarkupTextareaWidget}

15
shared/markup/markupfield/markup/__init__.py

@ -0,0 +1,15 @@
# -*- coding: UTF-8 -*-
# Erik Stein <code@classlibrary.net>, 09/2010
from .base import Markup, PLAINTEXT_MARKUP_DESCRIPTION
from .html import HTMLMarkup, MARKUP_DESCRIPTION as HTML_MARKUP_DESCRIPTION
from .markdown import MarkdownMarkup, MARKUP_DESCRIPTION as MARKDOWN_MARKUP_DESCRIPTION
from .rst import RestructuredtextMarkup, MARKUP_DESCRIPTION as RST_MARKUP_DESCRIPTION
DEFAULT_MARKUP_TYPES = [
PLAINTEXT_MARKUP_DESCRIPTION,
HTML_MARKUP_DESCRIPTION,
MARKDOWN_MARKUP_DESCRIPTION,
RST_MARKUP_DESCRIPTION,
]

73
shared/markup/markupfield/markup/base

@ -0,0 +1,73 @@
# -*- coding: UTF-8 -*-
# Erik Stein <code@classlibrary.net>, 09/2010
from django.utils.translation import ugettext_lazy as _
from django.utils.html import linebreaks, urlize
class Markup(object):
def __init__(self, instance, field_name, rendered_field_name,
markup_type_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
def __unicode__(self):
# Allows display via templates to work without safe filter
return mark_safe(self.rendered)
def __nonzero__(self):
"""
Returns truth depending on the 'raw' members truth.
"""
return bool(self.raw)
__bool__ = __nonzero__ # Python 3.x compatibility
def __len__(self):
"""
Returns the length of the raw value.
"""
# TODO Decide if it's better to return the length of the rendered value.
return len(self.raw)
def _get_raw(self):
return self.instance.__dict__[self.field_name]
def _set_raw(self, value):
setattr(self.instance, self.field_name, value)
raw = property(_get_raw, _set_raw)
def _get_markup_type(self):
return self.instance.__dict__[self.markup_type_field_name]
def _set_markup_type(self, value):
return setattr(self.instance, self.markup_type_field_name, value)
markup_type = property(_get_markup_type, _set_markup_type)
def _get_rendered(self):
# The rendered value is stored in a field of the model instance and is
# maintained by the MarkupField's pre_save method.
# The render() method must be called explicitely if the raw value
# is modified.
return getattr(self.instance, self.rendered_field_name)
rendered = property(_get_rendered)
def render(self, val):
# Must be implemented by subclasses and return the value for rendered self.raw
raise NotImplementedError
render.is_safe = True
class PlaintextMarkup(object):
def render(self, value):
return urlize(linebreaks(markup))
render.is_safe = True
PLAINTEXT_MARKUP_DESCRIPTION = ('text', PlaintextMarkup)

56
shared/markup/markupfield/markup/base.py

@ -0,0 +1,56 @@
# -*- coding: UTF-8 -*-
# Erik Stein <code@classlibrary.net>, 09/2010
from django.utils.translation import ugettext_lazy as _
from django.utils.html import linebreaks, urlize
class Markup(object):
def __init__(self, instance, field_name, rendered_field_name,
markup_type_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
# raw is read/write
def _get_raw(self):
return self.instance.__dict__[self.field_name]
def _set_raw(self, value):
setattr(self.instance, self.field_name, value)
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]
def _set_markup_type(self, value):
return setattr(self.instance, self.markup_type_field_name, value)
markup_type = property(_get_markup_type, _set_markup_type)
# 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 __unicode__(self):
return self.rendered
__unicode__.is_safe = True
def render(self, value):
"""
Must be implemented by subclasses and return the rendered self.raw
"""
raise NotImplementedError
render.is_safe = True
class PlaintextMarkup(object):
def render(self, value):
return urlize(linebreaks(markup))
render.is_safe = True
PLAINTEXT_MARKUP_DESCRIPTION = ('text', PlaintextMarkup)

16
shared/markup/markupfield/markup/html.py

@ -0,0 +1,16 @@
# -*- coding: UTF-8 -*-
# Erik Stein <code@classlibrary.net>, 06/2010
from django.utils.translation import ugettext_lazy as _
from .base import Markup
class HTMLMarkup(Markup):
def render(self):
# HTML of course doesn't need to be converted to HTML
return self.raw or u"" # Make sure that the return value is text
render.is_safe = True
MARKUP_DESCRIPTION = ('text/html', HTMLMarkup)

29
shared/markup/markupfield/markup/markdown.py

@ -0,0 +1,29 @@
# -*- coding: UTF-8 -*-
# Erik Stein <code@classlibrary.net>, 09/2010
import markdown
from django.utils.functional import curry
from django.utils.translation import ugettext_lazy as _
from .base import Markup
from .pygments import PYGMENTS_INSTALLED
md_filter = markdown.markdown
# try and replace if pygments & codehilite are available
if PYGMENTS_INSTALLED:
try:
from markdown.extensions.codehilite import makeExtension
md_filter = curry(markdown.markdown, extensions=['codehilite(css_class=highlight)'])
except ImportError:
pass
class MarkdownMarkup(Markup):
def render(self):
return md_filter(self.raw)
render.is_safe = True
MARKUP_DESCRIPTION = ('text/x-markdown', MarkdownMarkup)

9
shared/markup/markupfield/markup/pygments.py

@ -0,0 +1,9 @@
# -*- coding: UTF-8 -*-
# Erik Stein <code@classlibrary.net>, 09/2010
try:
import pygments
PYGMENTS_INSTALLED = True
except ImportError:
PYGMENTS_INSTALLED = False

101
shared/markup/markupfield/markup/rst/__init__.py

@ -0,0 +1,101 @@
# -*- coding: utf-8 -*-
# Erik Stein <code@classlibrary.net>, 09/2010
import docutils
import docutils.parsers.rst
from django.conf import settings
from django.utils.translation import ugettext_lazy as _
from ..base import Markup
from ...settings import (RST_INITIAL_HEADER_LEVEL, RST_WRITER_NAME,
RST_DEFAULT_LANGUAGE_CODE, RST_DOCTITLE_XFORM,
RST_INPUT_ENCODING, RST_DEBUG_LEVEL, RST_FILTER_SETTINGS)
from ..pygments import PYGMENTS_INSTALLED
# Let's you conveniently import the parser
parser = docutils.parsers.rst
try:
if PYGMENTS_INSTALLED:
# Register "code" directive for pygments formatting
from pygments import highlight
from pygments.lexers import get_lexer_by_name, TextLexer
from pygments.formatters import HtmlFormatter
DEFAULT = HtmlFormatter()
VARIANTS = {
'linenos': HtmlFormatter(linenos=True),
}
def pygments_directive(name, arguments, options, content, lineno,
content_offset, block_text, state, state_machine):
try:
lexer = get_lexer_by_name(arguments[0])
except ValueError:
# no lexer found - use the text one instead of an exception
lexer = TextLexer()
formatter = options and VARIANTS[options.keys()[0]] or DEFAULT
parsed = highlight(u'\n'.join(content), lexer, formatter)
return [docutils.nodes.raw('', parsed, format='html')]
pygments_directive.arguments = (1, 0, 1)
pygments_directive.content = 1
parser.directives.register_directive('code', pygments_directive)
except ImportError:
pass
class RestructuredtextMarkup(Markup):
docutils_settings = {
'language_code': RST_DEFAULT_LANGUAGE_CODE,
'doctitle_xform': RST_DOCTITLE_XFORM,
'input_encoding': RST_INPUT_ENCODING,
'initial_header_level': RST_INITIAL_HEADER_LEVEL,
'report_level': RST_DEBUG_LEVEL,
}
docutils_settings.update(RST_FILTER_SETTINGS)
def render(self, initial_header_level=RST_INITIAL_HEADER_LEVEL, **kwargs):
"""
Returns the rendered html fragment, i.e. without any html header part.
"""
settings = self.docutils_settings.copy()
settings['initial_header_level'] = initial_header_level
parts = docutils.core.publish_parts(
source=self.raw,
writer_name=WRITER_NAME,
settings_overrides=settings
)
return parts['fragment']
render.is_safe = True
def doctree(self, **kwargs):
"""
Returns the docutils doctree.
"""
return docutils.core.publish_doctree(self.raw, settings_overrides=self.docutils_settings)
def title(self, **kwargs):
"""
Returns the first found title node of a docutils doctree.
"""
# TODO Why don't we use the 'title' part?
document = self.doctree()
matches = document.traverse(condition=lambda node: isinstance(node, docutils.nodes.title))
if len(matches):
return matches[0].astext()
else:
return None
def plaintext(self, **kwargs):
return self.doctree().astext()
# Convenience function
def restructuredtext(text, **kwargs):
rst = RestructuredtextMarkup()
rst.raw = text
return rst.render(**kwargs)
MARKUP_DESCRIPTION = ('text/x-rst', RestructuredtextMarkup)

17
shared/markup/markupfield/markup/rst/helpers.py

@ -0,0 +1,17 @@
# -*- coding: utf-8 -*-
# 09/2009 Erik Stein <code@classlibrary.net>
def image_references(image_qs):
"""
Returns a ReStructured source fragment containing image references
for the given Image queryset.
"""
def make_image_reference(image_obj):
markup = u".. |%s| image:: %s" % (image_obj.slug, image_obj.imagefile.url)
cl = image_obj.caption_line()
if cl:
markup += u"\n :alt: %s"
return markup
return u"\n".join([make_image_reference(img) for img in image_qs])

37
shared/markup/markupfield/settings.py

@ -0,0 +1,37 @@
# -*- coding: UTF-8 -*-
# Erik Stein <code@classlibrary.net>, 09/2010
"""
Use PREFIX + variable name in your settings file.
Example::
MARKUP_MARKUP_TYPES
"""
import sys
from django.utils.translation import ugettext as _
from django.conf import settings as project_settings
_PREFIX = 'MARKUP_'
defaults = {
'RST_DEFAULT_LANGUAGE_CODE': getattr(project_settings, 'LANGUAGE_CODE', 'en').split('-')[0],
'RST_WRITER_NAME': 'html', # 'html4css1'
'RST_INITIAL_HEADER_LEVEL': 3,
'RST_DOCTITLE_XFORM': False, # Don't use first section title as document title
'RST_INPUT_ENCODING': 'utf-8',
'RST_DEBUG_LEVEL': getattr(project_settings, 'RST_DEBUG_LEVEL', project_settings.DEBUG and 1 or 5),
'RST_FILTER_SETTINGS': {},
}
__all__ = [defaults]
# Setting up module constants
module = sys.modules[__name__]
for setting_name, default_value in defaults.iteritems():
setattr(module, setting_name, getattr(project_settings, _PREFIX + setting_name, default_value))
__all__.append(getattr(module, setting_name))

0
shared/markup/markupfield/tests/__init__.py

41
shared/markup/markupfield/tests/models.py

@ -0,0 +1,41 @@
from django.db import models
from markupfield.fields import MarkupField
from markupfield.markup import markdown, rst
CUSTOM_MARKUP_TYPES = (
('markdown', markdown.MarkdownMarkup),
rst.MARKUP_DESCRIPTION,
)
class Post(models.Model):
title = models.CharField(max_length=50)
body = MarkupField('body of post')
def __unicode__(self):
return self.title
class Article(models.Model):
normal_field = MarkupField()
markup_choices_field = MarkupField(markup_choices=(('pandamarkup', lambda x: 'panda'),
('nomarkup', lambda x: x)))
default_field = MarkupField(default_markup_type='text/x-markdown')
markdown_field = MarkupField(markup_type='text/x-markdown')
class Abstract(models.Model):
content = MarkupField()
class Meta:
abstract = True
class Concrete(Abstract):
pass
class CustomArticle(models.Model):
text = MarkupField(markup_choices=CUSTOM_MARKUP_TYPES, default_markup_type='text/x-rst')

1
shared/markup/markupfield/tests/run_tests.sh

@ -0,0 +1 @@
django-admin.py test --settings=markupfield.tests.settings --pythonpath=../..

24
shared/markup/markupfield/tests/settings.py

@ -0,0 +1,24 @@
DATABASE_ENGINE = 'sqlite3'
DATABASE_NAME = 'markuptest.db'
try:
import markdown
except ImportError:
class markdown(object):
def markdown(self):
return ''
from docutils.core import publish_parts
def render_rest(markup):
parts = publish_parts(source=markup, writer_name="html4css1")
return parts["fragment"]
MARKUP_FIELD_TYPES = [
('markdown', markdown.markdown),
('ReST', render_rest),
]
INSTALLED_APPS = (
'markupfield.tests',
)

129
shared/markup/markupfield/tests/tests.py

@ -0,0 +1,129 @@
from django.test import TestCase
from django.core import serializers
from markupfield.fields import MarkupField
from markupfield.markup import Markup, RestructuredtextMarkup
from markupfield.widgets import MarkupTextarea, AdminMarkupTextareaWidget
from markupfield.tests.models import Post, Article, Concrete, CustomArticle
from django.forms.models import modelform_factory
ArticleForm = modelform_factory(Article)
class MarkupFieldTestCase(TestCase):
def setUp(self):
self.mp = Post(title='example markdown post', body='**markdown**',
body_markup_type='text/x-markdown')
self.mp.save()
self.rp = Post(title='example restructuredtext post', body='*ReST*', body_markup_type='ReST')
self.rp.save()
def test_verbose_name(self):
self.assertEquals(self.mp._meta.get_field('body').verbose_name, 'body of post')
def test_markup_body(self):
self.assertEquals(self.mp.body.raw, '**markdown**')
self.assertEquals(self.mp.body.rendered, '<p><strong>markdown</strong></p>')
self.assertEquals(self.mp.body.markup_type, 'text/x-markdown')
def test_markup_unicode(self):
u = unicode(self.rp.body.rendered)
self.assertEquals(u, u'<p><em>ReST</em></p>\n')
def test_from_database(self):
" Test that data loads back from the database correctly and 'post' has the right type."
p1 = Post.objects.get(pk=self.mp.pk)
self.assert_(isinstance(p1.body, Markup))
self.assertEquals(unicode(p1.body), u'<p><strong>markdown</strong></p>')
## Assignment ##
def test_body_assignment(self):
self.rp.body = '**ReST**'
self.rp.save()
self.assertEquals(unicode(self.rp.body), u'<p><strong>ReST</strong></p>\n')
def test_raw_assignment(self):
self.rp.body.raw = '*ReST*'
self.rp.save()
self.assertEquals(unicode(self.rp.body), u'<p><em>ReST</em></p>\n')
def test_rendered_assignment(self):
def f():
self.rp.body.rendered = 'this should fail'
self.assertRaises(AttributeError, f)
def test_body_type_assignment(self):
self.rp.body.markup_type = 'text/x-markdown'
self.rp.save()
self.assertEquals(self.rp.body.markup_type, 'text/x-markdown')
self.assertEquals(unicode(self.rp.body), u'<p><em>ReST</em></p>')
## Serialization ##
def test_serialize_to_json(self):
stream = serializers.serialize('json', Post.objects.all())
self.assertEquals(stream, '[{"pk": 1, "model": "tests.post", "fields": {"body": "**markdown**", "_body_rendered": "<p><strong>markdown</strong></p>", "body_markup_type": "text/x-markdown", "title": "example markdown post"}}, {"pk": 2, "model": "tests.post", "fields": {"body": "*ReST*", "_body_rendered": "<p><em>ReST</em></p>\\n", "body_markup_type": "ReST", "title": "example restructuredtext post"}}]')
def test_deserialize_json(self):
stream = serializers.serialize('json', Post.objects.all())
obj = list(serializers.deserialize('json', stream))[0]
self.assertEquals(obj.object, self.mp)
## Other ##
def test_inheritance(self):
# test that concrete correctly got the added fields
concrete_fields = [f.name for f in Concrete._meta.fields]
self.assertEquals(concrete_fields, ['id', 'content', 'content_markup_type', '_content_rendered'])
def test_markup_type_validation(self):
self.assertRaises(ValueError, MarkupField, 'verbose name', 'markup_field', 'bad_markup_type')
def test_custom_markup_class(self):
complex_rest = "Title of the article\n====================\n\nA paragraph with an *emphasized text*.\n\n"
a = CustomArticle(text=complex_rest)
a.save()
self.assertEquals(type(a.text), RestructuredtextMarkup)
self.assertEquals(a.text.rendered,
u'<div class="section" id="title-of-the-article">\n<h2>Title of the article</h2>\n<p>A paragraph with an <em>emphasized text</em>.</p>\n</div>\n')
self.assertEquals(a.text.plaintext(),
u'Title of the article\n\nA paragraph with an emphasized text.')
self.assertEquals(a.text.title(),
u'Title of the article')
class MarkupWidgetTests(TestCase):
def test_markuptextarea_used(self):
self.assert_(isinstance(MarkupField().formfield().widget, MarkupTextarea))
self.assert_(isinstance(ArticleForm()['normal_field'].field.widget, MarkupTextarea))
def test_markuptextarea_render(self):
a = Article(normal_field='**normal**', normal_field_markup_type='text/x-markdown',
default_field='**default**', markdown_field='**markdown**',
markup_choices_field_markup_type='nomarkup')
a.save()
af = ArticleForm(instance=a)
self.assertEquals(unicode(af['normal_field']), u'<textarea id="id_normal_field" rows="10" cols="40" name="normal_field">**normal**</textarea>')
def test_no_markup_type_field_if_set(self):
'ensure that a field with non-editable markup_type set does not have a _markup_type field'
self.assert_('markdown_field_markup_type' not in ArticleForm().fields.keys())
def test_markup_type_choices(self):
self.assertEquals(ArticleForm().fields['normal_field_markup_type'].choices,
[('text/x-markdown', 'text/x-markdown'), ('text/x-rst', 'text/x-rst')])
self.assertEquals(ArticleForm().fields['markup_choices_field_markup_type'].choices,
[('pandamarkup', 'pandamarkup'), ('nomarkup', 'nomarkup')])
def test_default_markup_type(self):
self.assert_(ArticleForm().fields['normal_field_markup_type'].initial is None)
self.assertEqual(ArticleForm().fields['default_field_markup_type'].initial, 'text/x-markdown')
def test_model_admin_field(self):
# borrows from regressiontests/admin_widgets/tests.py
from django.contrib import admin
ma = admin.ModelAdmin(Post, admin.site)
self.assert_(isinstance(ma.formfield_for_dbfield(Post._meta.get_field('body')).widget,
AdminMarkupTextareaWidget))

16
shared/markup/markupfield/widgets.py

@ -0,0 +1,16 @@
# -*- coding: UTF-8 -*-
# Erik Stein <code@classlibrary.net>, 10/2010
from django import forms
from django.contrib.admin.widgets import AdminTextareaWidget
class MarkupTextarea(forms.widgets.Textarea):
def render(self, name, value, attrs=None):
if value is not None and not isinstance(value, unicode):
value = value.raw
return super(MarkupTextarea, self).render(name, value, attrs)
class AdminMarkupTextareaWidget(MarkupTextarea, AdminTextareaWidget):
pass

0
shared/markup/templatetags/__init__.py

57
shared/markup/templatetags/markup_tags.py

@ -0,0 +1,57 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
# Erik Stein <code@classlibrary.net>, 2015
import re
from django import template
from django.template.defaultfilters import stringfilter
from django.utils.html import conditional_escape
from .. import markdown_utils
register = template.Library()
@register.filter(needs_autoescape=False)
@stringfilter
def inline_markdown(text, autoescape=None):
""" Doesn't wrap the markup in a HTML paragraph. """
if autoescape:
esc = conditional_escape
else:
esc = lambda x: x
return markdown_utils.markdown_to_inline_html(esc(text))
@register.filter(needs_autoescape=False)
@stringfilter
def markdown(text, autoescape=None):
if autoescape:
esc = conditional_escape
else:
esc = lambda x: x
return markdown_utils.markdown_to_html(esc(text))
@register.filter(needs_autoescape=True)
@stringfilter
def markdown_to_text(text, autoescape=None):
"""
Converts a string from markdown to HTML, then removes all
HTML markup (tags and entities).
"""
if autoescape:
esc = conditional_escape
else:
esc = lambda x: x
return markdown_utils.markdown_to_text(esc(text))
urlfinder = re.compile('^(http:\/\/\S+)')
urlfinder2 = re.compile('\s(http:\/\/\S+)')
@register.filter('urlify_markdown')
def urlify_markdown(value):
value = urlfinder.sub(r'<\1>', value)
return urlfinder2.sub(r' <\1>', value)
Loading…
Cancel
Save