Well, at the vary least, you could make it easier for one to add their
own type of markup. Perhaps make the render-markup function such that
one could provide their own render function without needing to
subclass the field.
Personally, I would use this every time as I will never use the
default markdown. I will always be adding in some of the available
extensions[1]. Currently, the only way to do that is to write my own
render method. It would be nice to just pass that in on declaring the
field without creating my own subclass etc.
[1]: http://www.freewisdom.org/projects/python-markdown/Available_Extensions
--
----
\X/ /-\ `/ |_ /-\ |\|
Waylan Limberg
First, I don't think you actually addressed the question mentioned in
the ticket regarding the 3 fields. It seems the question was whether
there should be three attributes on the Python model instance,
regardless of how many columns are stored in the database. On this
note, though, I do have a thought: specify the markup type as an
argument to the MarkupField. You already do this with a
default_markup_type, but I don't see much use in having users specify
their markup type at the time they enter the text.
Essentially, it comes down to the developer choosing a markup type
during development, rather than a user choosing it in a form. Like it
or not, fewer choices generally makes for a better user experience,
especially since offering just one choice means you can supply some
help text alongside it. Trying to supply useful information for all
the various markup options, while also helping the user decide which
one is best for the (usually brief) text they want to enter ... well,
it just doesn't seem it would scale well.
Besides, specifying the markup type as an attribute of the model means
that you can do away with one of the three database fields. Plus, it
means you can specify any markup type, simply by supplying a callable
instead of a string. We make this same type of differentiation when
specifying the upload_to argument of FileFields. The implementation
becomes both simpler and more powerful, all in one swift stroke.
As for the issue Alex really did bring up in the ticket, I think
there's a question as to whether the different field types can be
contained by a single complex object, rather than individual
attributes on the model. Basically, if you had a model like this:
class InfoModel(models.Model):
title = models.CharField(max_length=255)
description = markup.MarkupField(formatter='markdown')
I would personally rather see something like this, when it comes time
to access that field's content:
>>> info = InfoModel.objects.get(id=4)
>>> info.title
u'Django'
>>> info.description
<Markup: *The* web framework for perfectioni...>
>>> info.description.raw
u'*The* web framework for perfectionists with deadlines'
>>>info.description.formatted
u'<p><em>The</em> web framework for perfectionists with deadlines</p>'
>>> unicode(info.description)
u'<p><em>The</em> web framework for perfectionists with deadlines</p>'
So essentially, one attribute contains both types of content, with the
default Unicode representation being the formatted output. Since
templates call __unicode__() by default, all you'd have to do is use
{{ info.description }} in a template to get it right. But you could
still use {{ info.description.raw }} to get the original content if
necessary. Or, optionally, you can pass that through a different
processor at display time if you wanted to, using {{
info.description.raw|textile }}
That's just one man's opinion, but hopefully that helps the discussion
a bit anyway.
-Gul
Actually, I think there's room for a few different behaviors. Not sure
that all of them should go in contrib.markup, but I see 4 possible
scenarios:
1. James current implementation where each instance has the formatter
set for that specific instance.
2. Marty's suggestion where the formatter is hard-coded into the model
definition.
3. And a ForeignKey dependent option. Imagine a User or Project
specific setting. Perhaps something like:
class Project(models.Model):
name = models.CharField(max_length=50)
formatter = models.IntegerField(choices=MARKUP_CHOICES)
class Page(models.Model):
project = models.ForeignKey(Project)
body = markup.MarkupField(formatter='project.formatter')
I would imagine the above would work like Option 2, in that whatever
formatter is set for the 'Project' is assumed for all 'Pages' in that
project. No need to store the formatter_type separately in the 'Page'
model.
4. However, in some situations, I could see Option 3 used in
conjunction with option 1. The User sets her default choice in her
User Profile. Then, whenever she creates a new instance, the formatter
defaults to her preferred formatter. However, this particular instance
may use a different type of formatter, so she selects a different
formatter on that instance - which needs to be saved specific to that
instance.
Hmm, guess I'm kind of proposing two different things here aren't I?
1. Per instance formatter or not.
I have a couple thoughts about how to differentiate this one. The
obvious way would to have two different Fields, one for each behavior.
However, what about this crazy idea: only offer one Field, but have to
keywords options: "default_formatter" & "formatter" (or whatever color
you choose). If "default_formatter" is set, then use that as the
default, but give the end user the option of selecting a different
formatter per instance. However, if "formatter" is set instead, then
set that formatter for all instances with no option for the user to
change it. Obviously, it would need to be worked out how to handle
having both set (ignore one, generate error, or something else), which
could get ugly, but I thought I'd throw it out there.
2. ForeignKey dependent default or not.
Again, the obvious way would be with different fields.
But what about checking to see if the string passed in matches an
existing foreignkey on the model, and using that if it does - falling
back to the current behavior if not. Again, this may be a bad idea.
Just throwing it out to generate some thinking on it.
> To me there are a few big problems with the markup_func proposal as
> I see it:
>
> * it removes the ability to provide multiple markup_types on a given
> field
> * what is unpythonic about taking in two extra parameters to
> customize the field?
Isn't it possible to define your choices+functions in settings? Like:
MARKUP_CHOICES = {
'markdown': markdown.markdown,
'textile': textile.textile,
and so on
}
This way you can generate your choice field iterating through keys and
render appropriate markup given values.
My 2c,
David
Or see the django-template-utils app [1]. It provides a nice wrapper
so that all the formatters use the same API. You probably don't need
to do exactly that, but something similar with a dict to map names to
functions. Then pass that dict in as an argument.
[1]: http://code.google.com/p/django-template-utils/source/browse/trunk/template_utils/markup.py
How to specify the markup type
==============================
Yes, you and your co-workers like having an internal app where
everybody chooses their own markup format every time they post
something. But if something's going to go into Django it's got to aim,
first and foremost, for the common case, for the 80% people, and
throwing choices in front of users when all they're trying to do is
post a comment on your blog just ain't gonna cut it.
So the first thing I'd recommend is that the per-instance choice of
format go out the window. If you'd like to maintain something which
does that in a third-party library, you should of course feel free,
but the overwhelmingly common use case is not going to need that (and,
in fact, will have its usability hurt by that -- don't make users
think).
The common case (and hence the one to optimize for) is going to be a
developer deciding "I like Textile, this site's going to use Textile"
(or similar, according to the developer's markup whims). This means
that the choice of markup format *also* shouldn't be an argument to
the field: if I hard-code ``format="textile"`` into a field in my app,
and you want to use my app but want Markdown instead, well, you're up
a creek.
Also, there's no real point to trying to store that information on the
model object: once you've rendered some input into HTML, you don't
need to know or care what formatter was used to do that, because
you've got the HTML.
Which brings us to... a setting, which is probably the best way to
handle the choice of markup formats. It's simple, it's easy, it gets
the behavior right for most people (most sites are going to use One
And Only One markup format throughout) right there out of the box.
When I wrote template_utils way back once upon a time, I basically
went through the above discussion in my head, and came to the same
conclusion: template_utils uses a setting called ``MARKUP_FILTER`` to
tell it what to do.
In template_utils I opted for the setting to be a 2-tuple: a (string)
name of a formatter, and a dictionary of keyword arguments to be
passed in (the formatter name was resolved at runtime by a class with
which one could register different formatting functions). That can
work here as well, though it might make more sense to:
1. Have the setting itself be a dict, with keys ``formatter`` and
``kwargs``. This is mostly because people seem to have trouble with
tuples, don't ask me why.
2. Have the formatter function actually be a dotted path of a
formatter function, which will be pulled in at runtime (this avoids
circular-import problems if you decide to use some bit of Django in
writing your formatter function, since that would need settings,
but settings need to import your function...).
So, say, something like this::
MARKUP_FILTER = { 'formatter': 'markdown.markdown',
'kwargs': { 'safe_mode': True }, }
Et voila: now any MarkupField for this install will, by default,
Markdown-ify its text in safe mode on save.
This is, incidentally, basically what I do with my own personal stuff,
although I just use the formatter object out of template_utils. It
works pretty well.
The only problem is that somewhere, eventually, somebody is going to
really need the ability to do multiple types of markup on the same
site. This is where the API needs to get creative: wasting a whole
extra DB column just to handle this is, well, wasteful. But I'll get
to a better solution in a moment.
What the field actually stores and returns
==========================================
Marty's suggestion above of having some sort of custom object returned
by the field, with both "raw" and processed text available (and a
sensible ``__unicode__()`` so that templates Just Work) pretty much
hits it on the nose, I think. This means you will need two columns in
the DB, but you were going to need that anyway: once I've saved my
blog entry written in Markdown, I'm gonna be pretty pissed if you
throw rendered HTML back at me on the edit screen.
So for this part, just do what Marty recommends.
How to handle markup override
=============================
That is, also, the solution to the problem of how to do a one-off (or
more-than-one-but-still-off) override of the default markup type: let
the ``Markup`` object (or whatever it ends up being called, though
that's a good name) expose methods for forcibly saving with a
formatting option of your choice, in much the same way file fields
already let you hand over some file contents and do manual saving
trickery.
So, suppose I write a blog entry::
>>> e = Entry.objects.create(title="Foo", body="Lorem ipsum dolor
sit *amet*")
where ``body`` is a ``MarkupField``. Now, let's say the default on
this site is Markdown, but I really want Textile instead for this
entry::
>>> e.body.save_markup(formatter='textile.textile')
Or some similar API (maybe let it just get the Python callable passed
in, since at that point the import problem doesn't exist).
Putting it all together
=======================
So, to recap, if I were designing this:
``django.contrib.markup.models.MarkupField`` would equate to two
columns in the DB: one for the raw text, one for the resulting
HTML.
It would be represented, at the Python level, by an object which can
spit back either the raw text or the HTML (and whose ``__unicode__()``
returns the HTML, marked safe for templates), and which offers a
method for custom-saving the markup using the formatter of your
choice.
It would be represented, at the form level, by a ``CharField`` +
``Textarea``, and for model forms would spit back the raw un-processed
text as the field's initial value on editing.
And... I'm spent. I think that covers everything, though it's
significantly more than two cents' worth of opinion :)
--
"Bureaucrat Conrad, you are technically correct -- the best kind of correct."
On Tue, Feb 24, 2009 at 2:33 AM, James Bennett <ubern...@gmail.com> wrote:
>
[snip]
> How to handle markup override
> =============================
>
> That is, also, the solution to the problem of how to do a one-off (or
> more-than-one-but-still-off) override of the default markup type: let
> the ``Markup`` object (or whatever it ends up being called, though
> that's a good name) expose methods for forcibly saving with a
> formatting option of your choice, in much the same way file fields
> already let you hand over some file contents and do manual saving
> trickery.
>
> So, suppose I write a blog entry::
>
> >>> e = Entry.objects.create(title="Foo", body="Lorem ipsum dolor
> sit *amet*")
>
> where ``body`` is a ``MarkupField``. Now, let's say the default on
> this site is Markdown, but I really want Textile instead for this
> entry::
>
> >>> e.body.save_markup(formatter='textile.textile')
>
[snip]
This needs to accept kwargs as well. Lets take the use case were
Markdown it the default. And most of the site is used by trusted users
so Markdown is not in safe_mode (we allow raw html). But now, we have
one field (perhaps comments) which is accessable to the general
untrusted public. In that one case, I still want to use Markdown, but
with ``safe_mode = True``. The only way that will work is to accept
kwargs. So, using the above example:
>>> e.body.save_markup(formatter='markdown.markdown',
kwargs={'safe_mode': True})
Actually I think I'd just write the method like this::
def save_markup(self, formatter, **kwargs):
markup = formatter(self.raw_text, **kwargs)
# ...etc...
The **kwargs syntax means we don't need to pass an actual dictionary there.
Also, a nice advantage of this is that you could mess around with
particular models, or in particular situations, just by writing a
function which calls the save_markup() method, and hooking it up to a
post_save signal or similar (assuming, of course, that save_markup()
does not itself trigger save()...).