views:

392

answers:

3

I'm writing a simple CMS based on Django. Most content management systems rely on having a fixed page, on a fixed URL, using a template that has one or many editable regions. To have an editable region, you require a Page. For the system to work out which page, you require the URL.

The problem comes when you're no longer dealing with "pages" (be those FlatPages pages, or something else), but rather instances from another Model. For example if I have a Model of products, I may wish to create a detail page that has multiple editable regions within.

I could build those regions into the Model but in my case, there are several Models and is a lot of variance in how much data I want to show.

Therefore, I want to build the CMS at template level and specify what a block (an editable region) is based on the instance of "page" or the model it uses.

I've had the idea that perhaps I could dump custom template tags on the page like this:

{% block unique_object "unique placeholder name" %}

And that would find a "block" based on the two arguments passed in. An example:

<h1>{{ product_instance.name }}</h1>
{% block product_instance "detail: product short description" %}
{% block product_instance "detail: product video" %}
{% block product_instance "detail: product long description" %}

Sounds spiffy, right? Well the problem I'm running into is how do I create a "key" for a zone so I can pull the correct block out? I'll be dealing with a completely unknown object (it could be a "page" object, a URL, a model instance, anything - it could even be a boat</fg>).

Other Django micro-applications must do this. You can tag anything with django-tagging, right? I've tried to understand how that works but I'm drawing blanks.

So, firstly, am I mad? And assuming I not, and this looks like a relatively sane idea to persue, how should I go about linking an object+string to a block/editable-region?

Note: Editing will be done on-the-page so there's no real issue in letting the users edit the zones. I won't have to do any reverse-mumbo-jumbo in the admin. My eventual dream is to allow a third argument to specify what sort of content area this is (text, image, video, etc). If you have any comments on any of this, I'm happy to read them!

+6  A: 

django-tagging uses Django's contenttypes framework. The docs do a much better job of explaining it than I can, but the simplest description of it would be "generic foreign key that can point to any other model."

This may be what you are looking for, but from your description it also sounds like you want to do something very similar to some other existing projects:

  • django-flatblocks ("... acts like django.contrib.flatpages but for parts of a page; like an editable help box you want show alongside the main content.")

  • django-better-chunks ("Think of it as flatpages for small bits of reusable content you might want to insert into your templates and manage from the admin interface.")

and so on. If these are similar then they'll make a good starting point for you.

Van Gale
that's exactly the app I was going to bring us as an example ;)
Jiaaro
Those examples are close but they're missing the most important part of my requirements: being bound to an object. I've opened a bounty on the question. If you can elaborate enough, the points are yours.
Oli
+2  A: 

You want a way to display some object-specific content on a generic template, given a specific object, correct?

In order to support both models and other objects, we need two intermediate models; one to handle strings, and one to handle models. We could do it with one model, but this is less performant. These models will provide the link between content and string/model.

from django.db import models
from django.contrib.contenttypes.models import ContentType
from django.contrib.contenttypes import generic

CONTENT_TYPE_CHOICES = (
    ("video", "Video"),
    ("text", "Text"),
    ("image", "Image"),
)

def _get_template(name, type):
    "Returns a list of templates to load given a name and a type"
    return ["%s_%s.html" % (type, name), "%s.html" % name, "%s.html" % type]

class ModelContentLink(models.Model):
    key = models.CharField(max_length=255) # Or whatever you find appropriate
    type = models.CharField(max_length=31, choices= CONTENT_TYPE_CHOICES)
    content_type = models.ForeignKey(ContentType)
    object_id = models.PositiveIntegerField()
    object = generic.GenericForeignKey('content_type', 'object_id')

    def get_template(self):
        model_name = self.object.__class__.__name__.lower()
        return _get_template(model_name, self.type)

class StringContentLink(models.Model):
    key = models.CharField(max_length=255) # Or whatever length you find appropriate
    type = models.CharField(max_length=31, choices= CONTENT_TYPE_CHOICES)
    content = models.TextField()

    def get_template(self):
        return _get_template(self.content, self.type)

Now, all we need is a template tag to grab these, and then try to load the templates given by the models' get_template() method. I'm a bit pressed on time so I'll leave it at this and update it in ~1 hour. Let me know if you think this approach seems fine.

Cide
It's funny you mention CONTENT_TYPE_CHOICES. I had left that out both briefs because I thought it overcomplicated things but it's good to see you're on the same page. But... cont...
Oli
The major issue here is (and please correct me if I'm wrong) this just links to content *either* from a generic model ***or*** from a string. I need the keys for the content blocks to be a variable combination of both. IE the system knows to load a block because you specify something specific to that page (eg an object instance of a product) and then you specify something that is specific to that style of page (eg a string saying "title"). The combination gives you a block of content unique to a situation where both that product and "title" are asked for.
Oli
Or am I being short-sighted? I think I am slightly because I see the need for these classes but how would it work on a page with a fictional templatetag on? Assuming it uses one string and one object, it would need to cross-compare the results of two lookups (one on ModelContentLink and the other on StringContentLink). There may be a fast (read: efficient) way of doing that but I don't know it...
Oli
Well, I figured you'd have a unified way of describing a specific element on a page; a combination of an `object` (which is either a model or a string) and a `key`, which describes what part of the object to display. These two fields are represented by `key` in both models, and `object` versus `content` in the Model and String classes, respectively. That way, you can do things like "display the short description of <my class>", or "take this url and display it as an embedded youtube video". However, I realize now that the String class is missing some kind of unique identifer to realize this..
Cide
+2  A: 

It's pretty straightforward to use the contenttypes framework to implement the lookup strategy you are describing:

class Block(models.Model):
    content_type = models.ForeignKey(ContentType)
    object_id = models.PositiveIntegerField()
    object = generic.GenericForeignKey() # not actually used here, but may be handy
    key = models.CharField(max_length=255)
    ... other fields ...

    class Meta:
       unique_together = ('content_type', 'object_id', 'key')

def lookup_block(object, key):
    return Block.objects.get(content_type=ContentType.objects.get_for_model(object),
                             object_id=object.pk,
                             key=key)

@register.simple_tag
def block(object, key)
   block = lookup_block(object, key)
   ... generate template content using 'block' ...

One gotcha to be aware of is that you can't use the object field in the Block.objects.get call, because it's not a real database field. You must use content_type and object_id.

I called the model Block, but if you have some cases where more than one unique (object, key) tuple maps to the same block, it may in fact be an intermediate model that itself has a ForeignKey to your actual Block model or to the appropriate model in a helper app like the ones Van Gale has mentioned.

John