markupmirror.fields: 100 total statements, 51.7% covered

Generated: Wed 2012-04-11 05:19 CDT

Source file: /home/buechler/Sites/django-markupmirror/markupmirror/fields.py

Stats: 45 executed, 42 missed, 13 excluded, 132 ignored

  1. from django.core.exceptions import ImproperlyConfigured
  2. from django.db import models
  3. from django.utils.html import escape
  4. from django.utils.safestring import mark_safe
  5. from markupmirror import widgets
  6. from markupmirror.markup.base import markup_pool
  7. # suffixes for rendered and markup_type fields
  8. _rendered_field_name = lambda name: '%s_rendered' % name
  9. _markup_type_field_name = lambda name: '%s_markup_type' % name
  10. class Markup(object):
  11. """Wrapper class for markup content output.
  12. Stores the names of the associated field, the rendered field and the
  13. markup_type field to make assignment possible.
  14. """
  15. def __init__(self, instance, field_name,
  16. rendered_field_name, markup_type_field_name):
  17. self.instance = instance
  18. self.field_name = field_name
  19. self.rendered_field_name = rendered_field_name
  20. self.markup_type_field_name = markup_type_field_name
  21. @property
  22. def raw(self):
  23. return self.instance.__dict__[self.field_name]
  24. @raw.setter
  25. def raw(self, value):
  26. return setattr(self.instance, self.field_name, value)
  27. @property
  28. def markup_type(self):
  29. return self.instance.__dict__[self.markup_type_field_name]
  30. @markup_type.setter
  31. def markup_type(self, value):
  32. return setattr(self.instance, self.markup_type_field_name, value)
  33. @property
  34. def rendered(self):
  35. return getattr(self.instance, self.rendered_field_name)
  36. def __unicode__(self):
  37. """Allows display via templates to work without safe filter."""
  38. return mark_safe(self.rendered)
  39. class MarkupMirrorFieldDescriptor(object):
  40. """Descriptor class for field functionality."""
  41. def __init__(self, field):
  42. self.field = field
  43. self.rendered_field_name = _rendered_field_name(self.field.name)
  44. self.markup_type_field_name = _markup_type_field_name(self.field.name)
  45. def __get__(self, instance, owner):
  46. if instance is None:
  47. raise AttributeError("Can only be accessed via an instance.")
  48. markup = instance.__dict__[self.field.name]
  49. if markup is None:
  50. return None
  51. return Markup(instance, self.field.name,
  52. self.rendered_field_name, self.markup_type_field_name)
  53. def __set__(self, obj, value):
  54. if isinstance(value, Markup):
  55. obj.__dict__[self.field.name] = value.raw
  56. setattr(obj, self.rendered_field_name, value.rendered)
  57. setattr(obj, self.markup_type_field_name, value.markup_type)
  58. else:
  59. obj.__dict__[self.field.name] = value
  60. class MarkupMirrorField(models.TextField):
  61. """Field to store markup content.
  62. MarkupMirrorField adds three fields to the model it is used in.
  63. * One field for the raw markup content.
  64. * One field for the rendered HTML content.
  65. * One field that specifies the markup type.
  66. """
  67. def __init__(self, verbose_name=None, name=None,
  68. markup_type=None, default_markup_type=None,
  69. escape_html=False, **kwargs):
  70. if markup_type and default_markup_type:
  71. raise ImproperlyConfigured(
  72. "Cannot specify both markup_type and default_markup_type")
  73. self.default_markup_type = markup_type or default_markup_type
  74. self.markup_type_editable = markup_type is None
  75. self.escape_html = escape_html
  76. if (self.default_markup_type and
  77. self.default_markup_type not in markup_pool):
  78. raise ImproperlyConfigured(
  79. "Invalid default_markup_type for field '%r', "
  80. "available types: %s" % (
  81. name or verbose_name,
  82. ', '.join(sorted(markup_pool.markups.keys()))))
  83. # for South FakeORM compatibility: the frozen version of a
  84. # MarkupMirrorField can't try to add a _rendered field, because the
  85. # _rendered field itself is frozen as well. See introspection
  86. # rules below.
  87. self.rendered_field = not kwargs.pop('rendered_field', False)
  88. super(MarkupMirrorField, self).__init__(verbose_name, name, **kwargs)
  89. def contribute_to_class(self, cls, name):
  90. """Adds two additional fields for rendered HTML content and markup type
  91. to the model.
  92. """
  93. if not cls._meta.abstract:
  94. # markup_type
  95. choices = [(markup_type, markup.title)
  96. for markup_type, markup in markup_pool.markups.items()]
  97. markup_type_field = models.CharField(
  98. choices=choices, max_length=30,
  99. default=self.default_markup_type, blank=self.blank,
  100. editable=self.markup_type_editable)
  101. markup_type_field.creation_counter = self.creation_counter + 1
  102. # rendered
  103. rendered_field = models.TextField(
  104. editable=False, blank=True, null=True)
  105. rendered_field.creation_counter = self.creation_counter + 2
  106. # add fields to class
  107. cls.add_to_class(_markup_type_field_name(name), markup_type_field)
  108. cls.add_to_class(_rendered_field_name(name), rendered_field)
  109. super(MarkupMirrorField, self).contribute_to_class(cls, name)
  110. # use MarkupMirrorFieldDescriptor to access this field
  111. setattr(cls, self.name, MarkupMirrorFieldDescriptor(self))
  112. def pre_save(self, model_instance, add):
  113. value = super(MarkupMirrorField, self).pre_save(model_instance, add)
  114. # check for valid markup type
  115. if value.markup_type not in markup_pool:
  116. raise ValueError(
  117. 'Invalid markup type (%s), available types: %s' % (
  118. value.markup_type,
  119. ', '.join(sorted(markup_pool.markups.keys()))))
  120. # escape HTML
  121. if self.escape_html:
  122. raw = escape(value.raw)
  123. else:
  124. raw = value.raw
  125. rendered = markup_pool[value.markup_type](raw)
  126. setattr(model_instance, _rendered_field_name(self.attname), rendered)
  127. return value.raw
  128. def get_prep_value(self, value):
  129. if isinstance(value, Markup):
  130. return value.raw
  131. else:
  132. return value
  133. def value_to_string(self, obj):
  134. value = self._get_val_from_obj(obj)
  135. return value.raw
  136. def formfield(self, **kwargs):
  137. """Adds attributes necessary for CodeMirror initialization to the
  138. field's widget.
  139. The class "item-markupmirror" is used to identify textareas that should
  140. be enhanced with the editor.
  141. The ``data-mode`` and ``data-markuptype`` attributes depend on a
  142. selected ``default_markup_type``. If a field does not have a default
  143. markup type selected, the attributes will be added in the widgets'
  144. ``render`` method by accessing the ``markup_type`` property of the
  145. markup content wrapper ``markupmirror.fields.Markup``.
  146. """
  147. widget_attrs = {
  148. 'class': 'item-markupmirror',
  149. }
  150. if (self.default_markup_type and
  151. self.default_markup_type in markup_pool):
  152. widget_attrs['data-mode'] = markup_pool[
  153. self.default_markup_type].codemirror_mode
  154. widget_attrs['data-markuptype'] = self.default_markup_type
  155. defaults = {
  156. 'widget': widgets.MarkupMirrorTextarea(attrs=widget_attrs),
  157. }
  158. defaults.update(kwargs)
  159. return super(MarkupMirrorField, self).formfield(**defaults)
  160. __all__ = ('Markup', 'MarkupMirrorFieldDescriptor', 'MarkupMirrorField')
  161. # register MarkupMirrorField to use the custom widget in the Admin
  162. from django.contrib.admin.options import FORMFIELD_FOR_DBFIELD_DEFAULTS
  163. FORMFIELD_FOR_DBFIELD_DEFAULTS[MarkupMirrorField] = {
  164. 'widget': widgets.AdminMarkupMirrorTextareaWidget,
  165. }
  166. # allow South to handle MarkupMirrorField smoothly
  167. try:
  168. from south.modelsinspector import add_introspection_rules
  169. # For a normal MarkupMirrorField, the add_rendered_field attribute is
  170. # always True, which means no_rendered_field arg will always be
  171. # True in a frozen MarkupMirrorField, which is what we want.
  172. add_introspection_rules(
  173. rules=[
  174. ((MarkupMirrorField,), [], {
  175. 'rendered_field': ['rendered_field', {}],
  176. })
  177. ],
  178. patterns=['markupmirror\.fields\.MarkupMirrorField'])
  179. except ImportError:
  180. pass