6

I'm trying to build a system for managing the various types of content in a page. For example, a page may have text content, hyperlink content, video content, ect.

In my modeling code, I have a base class:

class ContentItem(models.Model):
    title = models.CharField(max_length=1000)
    page_order = models.IntegerField()
    last_update_date = models.DateTimeField(default=datetime.now())

    class Meta:
      abstract = True
      ordering = ['page_order', 'last_update_date', 'title']

This is the base class for all content items. The page order controls what position it is on the page, for example the item with page_order = 0 should be at the top of the page. Next I define a few specific content models that inherit from this one.

class LinkContent(ContentItem):
  url = models.URLField()
  link_text = models.CharField(max_lenth=1000)

class TextContent(ContentItem):
 text = models.CharField()


class VideoContent(ContentItem):
      title = models.CharField()
      video_file = models.FieldField(upload_to = 'videos')

There could be many more such content types. Then I would define a Page model that is composed of all the various content types. Ideally, I could put all the types in on relation based on the base type. So in this one relation you would have a mixture of LinkContents, TextContents, and VideoContents. They would be sorted by page_order to determine their order on the page when rendering the template.

class Page(models.Model):
  contents = models.ManyToManyField(ContentItem)
  title = models.CharField()

Is there any way to make such a scheme work? Or is it problematic to have one relation with different types of models in it? I know this is a good solution from and object oriented programming standpoint, basically using polymorphism to my advantage, but I am not sure it makes sense at the database level.

Do I instead need something more like this:

class Page(models.Model):
  video_contents = models.ManyToManyField(VideoContent)
  link_contents = models.ManyToManyField(LinkContent)
  text_contents = models.ManyToManyField(TextContent)
  title = models.CharField()

I know this would work, but my scheme of determining the placement of the objects on the page becomes more difficult. I would need to traverse all the content relations, sort them by page_order and then render them.

I think in both cases, I want to declare a render() method on the base class that each specific content type can inherit. This way if I have a list of ContentItems I can use duck typing to render them without worrying about their specific type.

My final question is how do I make admin place nice with this? How would I make an easy way to see all of the ContentItems that make up a page in one view, so they can easily be moved around by changing page_order?

Thanks for reading of this, let me know if you need more information.

1 Answer 1

3

This is a fine way to do it. Unfortunately, Django's ORM doesn't handle model inheritance as smoothly as you might want. page.contents will contain a QuerySet of Content objects. If you want to access the subtypes, you need to create some way of downcasting a content object. The problem is that this requires an query per object, which can rapidly get out of hand. This blog post describes a technique for getting the mixed subtypes in one queryset, using select_related() behind the scenes.

1
  • 1
    Thanks @jcdyer. That was very helpful information. One thing I am still unclear on though is how I would make this work in the Admin interface. Ideally, I would like a way to list the Page's contents and have the appropriate admin form be shown for the item based on the specific class. For example, If a Page has 2 LinkContents and 1 TextContent in it's contents attribute, I want the first two to be editable with the Link form and the other with the text form. Is there a simple way to do something like this? I've done a lot of admin customization, but never seen anything like that.
    – Jon
    Commented Mar 21, 2011 at 20:09

Not the answer you're looking for? Browse other questions tagged or ask your own question.