Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

158

159

160

161

162

163

164

165

166

167

168

169

170

171

172

173

174

175

176

177

178

179

180

181

182

183

184

185

186

187

188

189

190

191

192

193

194

195

196

197

198

199

200

201

202

203

204

205

206

207

208

209

210

211

212

213

214

215

216

217

218

219

220

221

222

223

224

225

226

227

228

229

230

231

232

233

234

235

236

237

238

239

240

241

242

243

244

245

246

247

248

249

250

251

252

253

254

255

256

257

258

259

260

from django.core.exceptions import ImproperlyConfigured 

from django.db import models 

from django.utils import simplejson as json 

from django.utils.html import escape 

from django.utils.safestring import mark_safe 

 

from markupmirror import widgets 

from markupmirror.markup.base import markup_pool 

 

 

# suffixes for rendered and markup_type fields 

_rendered_field_name = lambda name: '%s_rendered' % name 

_markup_type_field_name = lambda name: '%s_markup_type' % name 

 

 

class Markup(object): 

    """Wrapper class for markup content output. 

 

    Stores the names of the associated field, the rendered field and the 

    markup_type field to make assignment possible. 

 

    When accessing the value of a ``MarkupField``, a ``Markup`` instance will 

    be returned. This provides a few methods to access the raw and rendered 

    markup, and the markup type of the saved content. 

 

    """ 

    def __init__(self, instance, field_name, 

                 rendered_field_name, markup_type_field_name): 

        self.instance = instance 

        self.field_name = field_name 

        self.rendered_field_name = rendered_field_name 

        self.markup_type_field_name = markup_type_field_name 

 

    @property 

    def raw(self): 

        """The raw markup content.""" 

        return self.instance.__dict__[self.field_name] 

 

    @raw.setter 

    def raw(self, value): 

        return setattr(self.instance, self.field_name, value) 

 

    @property 

    def markup_type(self): 

        """Markup type of the current markup content.""" 

        return self.instance.__dict__[self.markup_type_field_name] 

 

    @markup_type.setter 

    def markup_type(self, value): 

        return setattr(self.instance, self.markup_type_field_name, value) 

 

    @property 

    def rendered(self): 

        """Returns the rendered markup content (read only). This is only 

        available after ``Model.save`` has been called. 

 

        """ 

        return getattr(self.instance, self.rendered_field_name) 

 

    def __unicode__(self): 

        """Allows display via templates to work without safe filter. Same as 

        ``rendered``. 

 

        """ 

        return mark_safe(self.rendered) 

 

 

class MarkupMirrorFieldDescriptor(object): 

    """Descriptor class for field functionality.""" 

 

    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) 

 

    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) 

 

    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 MarkupMirrorField(models.TextField): 

    """Field to store markup content. 

 

    The ``MarkupMirrorField`` adds three fields to the model it is used in. 

 

    * One field for the raw markup content (``<field_name>``). 

    * One field for the rendered HTML content (``<field_name>_rendered``). 

    * One field that specifies the markup type (``<field_name>_markup_type``). 

 

    The field can be used with a fixed ``markup_type`` or a 

    ``default_markup_type``, which displays an additional selection widget 

    for the available markup types. However, only one of ``markup_type`` and 

    ``default_markup_type`` can be provided. 

 

    If neither is provided, the setting ``MARKUPMIRROR_DEFAULT_MARKUP_TYPE`` 

    is used as ``default_markup_type`` instead. 

 

    """ 

    def __init__(self, verbose_name=None, name=None, 

                 markup_type=None, default_markup_type=None, 

                 escape_html=False, **kwargs): 

        if markup_type and default_markup_type: 

            raise ImproperlyConfigured( 

                "Cannot specify both markup_type and default_markup_type") 

 

        self.default_markup_type = markup_type or default_markup_type 

        self.markup_type_editable = markup_type is None 

        self.escape_html = escape_html 

 

        if (self.default_markup_type and 

            self.default_markup_type not in markup_pool): 

            raise ImproperlyConfigured( 

                "Invalid default_markup_type for field '%r', " 

                "available types: %s" % ( 

                    name or verbose_name, 

                    ', '.join(sorted(markup_pool.markups.keys())))) 

 

        # for South FakeORM compatibility: the frozen version of a 

        # MarkupMirrorField can't try to add a _rendered field, because the 

        # _rendered field itself is frozen as well. See introspection 

        # rules below. 

        self.rendered_field = not kwargs.pop('rendered_field', False) 

 

        super(MarkupMirrorField, self).__init__(verbose_name, name, **kwargs) 

 

    def contribute_to_class(self, cls, name): 

        """Adds two additional fields for rendered HTML content and markup type 

        to the model. 

 

        """ 

        if not cls._meta.abstract: 

            # markup_type 

            choices = [(markup_type, markup.title) for markup_type, markup in 

                sorted(markup_pool.markups.iteritems(), 

                    key=lambda markup: markup[1].title.lower())] 

            markup_type_field = models.CharField( 

                choices=choices, max_length=30, 

                default=self.default_markup_type, blank=self.blank, 

                editable=self.markup_type_editable) 

            markup_type_field.creation_counter = self.creation_counter + 1 

 

            # rendered 

            rendered_field = models.TextField( 

                editable=False, blank=True, null=True) 

            rendered_field.creation_counter = self.creation_counter + 2 

 

            # add fields to class 

            cls.add_to_class(_markup_type_field_name(name), markup_type_field) 

            cls.add_to_class(_rendered_field_name(name), rendered_field) 

 

        super(MarkupMirrorField, self).contribute_to_class(cls, name) 

 

        # use MarkupMirrorFieldDescriptor to access this field 

        setattr(cls, self.name, MarkupMirrorFieldDescriptor(self)) 

 

    def pre_save(self, model_instance, add): 

        value = super(MarkupMirrorField, self).pre_save(model_instance, add) 

 

        # check for valid markup type 

        if value.markup_type not in markup_pool: 

            raise ValueError( 

                'Invalid markup type (%s), available types: %s' % ( 

                    value.markup_type, 

                    ', '.join(sorted(markup_pool.markups.keys())))) 

 

        # escape HTML 

        if self.escape_html: 

            raw = escape(value.raw) 

        else: 

            raw = value.raw 

 

        rendered = markup_pool[value.markup_type](raw) 

        setattr(model_instance, _rendered_field_name(self.attname), rendered) 

        return value.raw 

 

    def get_prep_value(self, 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): 

        """Adds attributes necessary for CodeMirror initialization to the 

        field's widget. 

 

        The class "markupmirror-editor" is used to identify textareas that 

        should be enhanced with the editor. 

 

        The ``data-mode`` and ``data-markuptype`` attributes depend on a 

        selected ``default_markup_type``. If a field does not have a default 

        markup type selected, the attributes will be added in the widgets' 

        ``render`` method by accessing the ``markup_type`` property of the 

        markup content wrapper ``markupmirror.fields.Markup``. 

 

        """ 

        widget_attrs = { 

            'class': 'markupmirror-editor', 

        } 

        if (self.default_markup_type and 

            self.default_markup_type in markup_pool): 

            # prepare default settings for CodeMirror and preview in case 

            # the widget has no value yet. 

            mm_settings = { 

                'mode': markup_pool[self.default_markup_type].codemirror_mode, 

                'markup_type': self.default_markup_type, 

            } 

            widget_attrs['data-mm-settings'] = json.dumps(mm_settings, 

                                                          sort_keys=True) 

 

        defaults = { 

            'widget': widgets.MarkupMirrorTextarea(attrs=widget_attrs), 

        } 

 

        defaults.update(kwargs) 

        return super(MarkupMirrorField, self).formfield(**defaults) 

 

 

__all__ = ('Markup', 'MarkupMirrorFieldDescriptor', 'MarkupMirrorField') 

 

 

# register MarkupMirrorField to use the custom widget in the Admin 

from django.contrib.admin.options import FORMFIELD_FOR_DBFIELD_DEFAULTS 

 

FORMFIELD_FOR_DBFIELD_DEFAULTS[MarkupMirrorField] = { 

    'widget': widgets.AdminMarkupMirrorTextareaWidget, 

} 

 

 

# allow South to handle MarkupMirrorField smoothly 

try: 

    from south.modelsinspector import add_introspection_rules 

    # For a normal MarkupMirrorField, the add_rendered_field attribute is 

    # always True, which means no_rendered_field arg will always be 

    # True in a frozen MarkupMirrorField, which is what we want. 

    add_introspection_rules( 

        rules=[ 

            ((MarkupMirrorField,), [], { 

                'rendered_field': ['rendered_field', {}], 

            }) 

        ], 

        patterns=['markupmirror\.fields\.MarkupMirrorField']) 

except ImportError: 

    pass