diff --git a/django/contrib/markup/fields.py b/django/contrib/markup/fields.py
new file mode 100644
index 0000000..230e388
--- /dev/null
+++ b/django/contrib/markup/fields.py
@@ -0,0 +1,91 @@
+from django.db import models
+from django.utils.html import linebreaks, urlize
+from django.utils.safestring import mark_safe
+
+PLAIN_TEXT, HTML, MARKDOWN, REST, TEXTILE = range(5)
+
+# determine available choices dynamically upon first import
+MARKUP_TYPES = (
+    (PLAIN_TEXT, 'plain'),
+    (HTML, 'html'),
+)
+
+try:
+    import markdown
+    MARKUP_TYPES += ( (MARKDOWN, 'markdown'), )
+except ImportError:
+    pass
+
+try:
+    from docutils.core import publish_parts
+    MARKUP_TYPES += ( (REST, 'restructuredtext'), )
+except ImportError:
+    pass
+
+try:
+    import textile
+    MARKUP_TYPES += ( (TEXTILE, 'textile'), )
+except ImportError:
+    pass
+
+_rendered_field_name = lambda name: '%s_rendered' % name
+_markup_type_field_name = lambda name: '%s_markup_type' % name
+
+class MarkupField(models.TextField):
+    def __init__(self, verbose_name=None, name=None,
+                 default_markup_type=PLAIN_TEXT, sanitize=False,
+                 extra_options=None, **kwargs):
+        self.default_markup_type = default_markup_type
+        self.extra_options = extra_options
+        self.sanitize = sanitize
+        super(MarkupField, self).__init__(verbose_name, name, **kwargs)
+
+    def contribute_to_class(self, cls, name):
+        # add markup_type field with creation_counter 1 greater than self
+        # so that admin by default shows these fields together
+        markup_type_field = models.PositiveIntegerField(choices=MARKUP_TYPES,
+                                                        default=self.default_markup_type,
+                                                        blank=self.blank)
+        markup_type_field.creation_counter = self.creation_counter+1
+        cls.add_to_class(_markup_type_field_name(name), markup_type_field)
+        rendered_field = models.TextField(editable=False)
+        cls.add_to_class(_rendered_field_name(name), rendered_field)
+        super(MarkupField, self).contribute_to_class(cls, name)
+
+        # define read-only property to easily access rendered string
+        def as_html(self):
+            return mark_safe(getattr(self, _rendered_field_name(name)))
+        cls.add_to_class('%s_as_html' % name, property(as_html))
+
+    def pre_save(self, instance, add):
+        # update rendered value on save
+        markup = getattr(instance, self.attname)
+        markup_type = getattr(instance, _markup_type_field_name(self.attname))
+        rendered = self.render_markup(markup, markup_type)
+        setattr(instance, _rendered_field_name(self.attname), rendered)
+
+        return markup
+
+    def render_markup(self, markup, markup_type):
+        """
+        Renders ``markup`` string to HTML calling the renderer specified by
+        ``markup_type``.
+        """
+        if markup_type == MARKDOWN:
+            # markdown.version implies support for extensions
+            if hasattr(markdown, 'version'):
+                return markdown.markdown(markup, self.extra_options,
+                                         safe_mode=self.sanitize)
+            return markdown.markdown(markup)
+        elif markup_type == REST:
+            # use extra_options as settings_overrides
+            parts = publish_parts(source=markup, writer_name="html4css1",
+                                  settings_overrides=self.extra_options)
+            return parts["fragment"]
+        elif markup_type == TEXTILE:
+            return textile.textile(markup, encoding='utf-8', output='utf-8',
+                                   sanitize=self.sanitize)
+        elif markup_type == PLAIN_TEXT:
+            return urlize(linebreaks(markup))
+        else:
+            return markup
diff --git a/docs/ref/contrib/index.txt b/docs/ref/contrib/index.txt
index 82a8955..16d0c32 100644
--- a/docs/ref/contrib/index.txt
+++ b/docs/ref/contrib/index.txt
@@ -34,6 +34,7 @@ those packages have.
    formtools/index
    humanize
    localflavor
+   markup
    redirects
    sitemaps
    sites
@@ -126,29 +127,12 @@ contains a ``USZipCodeField`` that you can use to validate U.S. zip codes.
 
 See the :ref:`localflavor documentation <ref-contrib-localflavor>`.
 
-.. _ref-contrib-markup:
-
 markup
 ======
 
-A collection of template filters that implement common markup languages:
-
-    * ``textile`` -- implements `Textile`_
-    * ``markdown`` -- implements `Markdown`_
-    * ``restructuredtext`` -- implements `ReST (ReStructured Text)`_
-
-In each case, the filter expects formatted markup as a string and returns a
-string representing the marked-up text. For example, the ``textile`` filter
-converts text that is marked-up in Textile format to HTML.
-
-To activate these filters, add ``'django.contrib.markup'`` to your
-:setting:`INSTALLED_APPS` setting. Once you've done that, use ``{% load markup %}`` in
-a template, and you'll have access to these filters. For more documentation,
-read the source code in django/contrib/markup/templatetags/markup.py.
+Utilities for using common markup languages.
 
-.. _Textile: http://en.wikipedia.org/wiki/Textile_%28markup_language%29
-.. _Markdown: http://en.wikipedia.org/wiki/Markdown
-.. _ReST (ReStructured Text): http://en.wikipedia.org/wiki/ReStructuredText
+See the :ref:`markup documentation <ref-contrib-markup>`.
 
 redirects
 =========
diff --git a/docs/ref/contrib/markup.txt b/docs/ref/contrib/markup.txt
new file mode 100644
index 0000000..c9d6119
--- /dev/null
+++ b/docs/ref/contrib/markup.txt
@@ -0,0 +1,102 @@
+.. _ref-contrib-markup:
+
+======================
+django.contrib.markup
+======================
+
+.. module:: django.contrib.markup
+   :synopsis: Utilities for using common markup languages.
+
+Two different methods of dealing with markup are offered: `Template Filters`_
+for rendering any block of text and a special :class:`MarkupField` that
+provides a convenient way to store markup alongside it's type and
+pre-rendered HTML in the database.
+
+Template Filters
+----------------
+
+A collection of template filters that implement common markup languages:
+
+    * ``textile`` -- implements `Textile`_
+    * ``markdown`` -- implements `Markdown`_
+    * ``restructuredtext`` -- implements `ReST (ReStructured Text)`_
+
+In each case, the filter expects formatted markup as a string and returns a
+string representing the marked-up text. For example, the ``textile`` filter
+converts text that is marked-up in Textile format to HTML.
+
+To activate these filters, add ``'django.contrib.markup'`` to your
+:setting:`INSTALLED_APPS` setting. Once you've done that, use
+``{% load markup %}`` in a template, and you'll have access to these filters.
+For more documentation, read the source code in
+django/contrib/markup/templatetags/markup.py.
+
+.. _Textile: http://en.wikipedia.org/wiki/Textile_%28markup_language%29
+.. _Markdown: http://en.wikipedia.org/wiki/Markdown
+.. _ReST (ReStructured Text): http://en.wikipedia.org/wiki/ReStructuredText
+
+
+``MarkupField``
+---------------
+
+.. class:: MarkupField([default_markup_type=PLAIN_TEXT, **options])
+
+A :class:`TextField` that also adds an associated field for storing a markup
+type and a read only accessor for getting the rendered HTML. Has a few optional
+arguments:
+
+.. attribute:: MarkupField.default_markup_type
+
+    Optional. Choice from ``MARKUP_TYPES`` to be used as the default value
+    for the associated markup_type field.
+
+.. attribute: MarkupField.sanitize
+
+    Optional. Tell underlying renderer to strip HTML. (supported for
+    Markdown and Textile)
+
+.. attribute: MarkupField.extra_options
+
+    Optional. Set of extra options to pass to renderer.  This parameter is
+    passed to ``markdown.markdown`` as ``extensions`` and to
+    ``docutils.core.publish_parts`` as ``settings_overrides``.
+
+This field has slightly more complicated internals than the typical field.
+Behind the scenes it actually creates additional fields, for example::
+
+    from django.contrib.markup.fields import MarkupField
+    
+    class Post(models.Model):
+        body = MarkupField()
+
+...Instances of Post would have the additional attributes ``body_markup_type``
+and ``body_as_html``. ``body_markup_type`` is a :class:`PositiveIntegerField`
+with choices defined in ``MARKUP_TYPES``. ``body_as_html`` is a read-only
+property containing a pre-rendered HTML version of the content assigned to
+``body``.
+
+.. versionadded:: 1.2
+   ``MarkupField`` was added in this version.
+
+MARKUP_TYPES
+------------
+
+Choices for markup_type field:
+
+======================  ======================================================
+Type                    Description
+======================  ======================================================
+``PLAIN_TEXT``          Applies urlize and linebreak filters.
+
+``HTML``                Applies no filters, ``field_as_html`` returns the body
+                        verbatim.
+
+``MARKDOWN``            `Markdown`_ (only available if markdown library is
+                        available)
+
+``REST``                `ReST (ReStructured Text)`_ (only available if
+                        docutils is installed)
+
+``TEXTILE``             `Textile`_ (only available if textile library is
+                        installed)
+======================  ======================================================
