1. Simon Meers
  2. django-markitup


django-markitup / markitup / fields.py

from django.db import models
from django.utils.safestring import mark_safe, SafeData
from django.core.exceptions import ImproperlyConfigured
from . import settings
from . import widgets

_rendered_field_name = lambda name: '_%s_rendered' % name

class Markup(SafeData):
    def __init__(self, instance, field_name, rendered_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

    # raw is read/write
    def _get_raw(self):
        return self.instance.__dict__[self.field_name]
    def _set_raw(self, val):
        setattr(self.instance, self.field_name, val)
    raw = property(_get_raw, _set_raw)

    # 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 mark_safe(self.rendered)

    # Return length of rendered string so that bool tests work as expected
    def __len__(self):
        return len(self.rendered)

class MarkupDescriptor(object):
    def __init__(self, field):
        self.field = field
        self.rendered_field_name = _rendered_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]
        if markup is None:
            return None
        return Markup(instance, self.field.name, self.rendered_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)
            obj.__dict__[self.field.name] = value

class MarkupField(models.TextField):
    def __init__(self, *args, **kwargs):
        # for South FakeORM compatibility: the frozen version of a
        # MarkupField can't try to add a _rendered field, because the
        # _rendered field itself is frozen as well. See introspection
        # rules below.
        self.add_rendered_field = not kwargs.pop('no_rendered_field', False)
        self.config_key = kwargs.pop('config_key', settings.DEFAULT_CONFIG_KEY)
        if not self.config_key in settings.MARKITUP_CONFIG:
            raise RuntimeError(
                u'Invalid MarkupField.config_key (%s); options are: %s' % (
                    self.config_key, u', '.join(
        super(MarkupField, self).__init__(*args, **kwargs)

    def contribute_to_class(self, cls, name):
        if self.add_rendered_field and not cls._meta.abstract:
            rendered_field = models.TextField(editable=False, blank=True)
            cls.add_to_class(_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)
        rendered = settings.get_filter_function(self.config_key)(value.raw)
        setattr(model_instance, _rendered_field_name(self.attname), rendered)
        return value.raw

    def value_to_string(self, obj):
        value = self._get_val_from_obj(obj)
        return value.raw

    # this method should be renamed to get_prep_value but
    # for django 1.1 compatibility only signature is updated
    def get_db_prep_value(self, value, connection=None, prepared=False):
            return value.raw
        except AttributeError:
            return value

    def formfield(self, **kwargs):
        defaults = {'widget': widgets.MarkupTextarea}
        field = super(MarkupField, self).formfield(**defaults)
        field.hidden_widget = widgets.MarkupHiddenWidget
        if hasattr(field.widget, 'set_config'):
        return field

# register MarkupField to use the custom widget in the Admin
from django.contrib.admin.options import FORMFIELD_FOR_DBFIELD_DEFAULTS
    'widget': widgets.AdminMarkItUpWidget}
# TODO: set config in admin widget automatically also

# allow South to handle MarkupField smoothly
    from south.modelsinspector import add_introspection_rules
    # For a normal MarkupField, the add_rendered_field attribute is
    # always True, which means no_rendered_field arg will always be
    # True in a frozen MarkupField, which is what we want.
                                    {'no_rendered_field': ('add_rendered_field',
except ImportError: