Can I use something like Hyde from within Django?

619 views Asked by At

I have a site with a few hundred pages where maybe 75% of pages are static content and the rest fit the typical "web application" model. My preference is Django, so I've mostly been looking at solutions based on that.

The content is very bespoke -- most pages share little beyond the basic site chrome, and are complex enough that it's simpler to write them in HTML rather than try to wrangle a rich text editor into giving the correct output. So the direction I'm currently going is to just define the content in templates -- I have a single view and use the incoming path as the template path. This keeps each page on the site as a page in the file system (easy to browse, easy to track in revision control), but lets each page share any number of common elements (headers, footers, navigation) and inject its own data into them as needed.

This gets bogged down in a lot of the details, though. For instance:

  • Sharing of page data with other pages. For example, the title defined by a page should show up in navigation menus on other pages, etc. I found this question about getting a block value from a template, but this seems really convoluted (and not scalable).
  • Related issue: if I define something as a block I can only use it once. I've seen the example of {% block title %} -- which typically goes in multiple places in a page -- several times in SO without a great solution.
  • Multiple/flexible inheritance. For a breadcrumb I might want to inherit from a page's ancestor, but for layout I'd probably want to inherit from something else (eg. a one-column vs two-column base template).

I think these specific issues are solvable on their own, mostly by using includes and custom template tags, but looking down that road I see hacks piled on top of hacks, which I'd like to avoid -- this needs to be a fairly simple and easily grokked system.

In the course of looking into these, I came across Hyde, which seems to address a lot of these issues. In particular, I really like that it has a sense of the site structure, and it gives pages some nice tools to navigate.

But I still have all the dynamic pieces, which really need to fit seamlessly. So anything I do for the content pages should really be available for any template that's part of a dynamic application. Also, one thing I really like about the "each page a template" approach is that I can alter the treatment of any particular page just by adding its path to urls.py and specifying a custom view.

Is there a good solution for this type of use case? More generally, is this just something that Django shouldn't be asked to do? It occurs to me that I'm sort of trying to use the file system as a CMS database here, which seems likely to lead to scaling problems, but Django seems to process and cache template content pretty well, and after looking at some existing CMS solutions (django-cms, feincms, fiber) I really don't like the idea of having one solution for static content and a totally different one for interactive content.

Edit

Here's what I got using custom tags to handle page metadata/configuration:

  1. A dictionary for page data is passed in at the top level (so that a tag can write into it and then code higher in the stack can read it back)
  2. A custom data tag allows pages to write data into this page data
  3. Other custom tags read and render structures (navigation, breadcrumbs, etc) from the data

The main piece is a tag that will read data (written as JSON) into the global dict:

class PageInfoNode(Node):

    def __init__(self, page_info):
        self.title = page_info['title']
        self.breadcrumb_title = page_info.get('breadcrumb_title', self.title)
        self.show_breadcrumb = page_info.get('show_breadcrumb', False)
        self.nav_title = page_info.get('nav_title', self.breadcrumb_title)
        self.side_nav = page_info.get('side_nav', None)

    def render(self, context):
        # 'page_info' must be set someplace higher in the context stack
        page_info = context['page_info']
        page_info['title'] = self.title
        page_info['nav_title'] = self.nav_title
        if self.show_breadcrumb:
            if 'breadcrumb' in page_info:
                page_info['breadcrumb'] = [self.breadcrumb_title] + page_info['breadcrumb']
            else:
                page_info['breadcrumb'] = [self.breadcrumb_title]
        if self.side_nav != None:
            page_info['side_nav'] = self.side_nav
        return ''

@register.tag
def pageinfo(parser, token):
    nodelist = parser.parse(('endpageinfo',))
    parser.delete_first_token()
    return PageInfoNode(json.loads(nodelist.render(Context())))

Each page sets its data like:

{% block data %}
{{ block.super }}
{% load my_page_tags %}
{% pageinfo %}
{
  "title": "My Page Title",
  "show_breadcrumb": true,
  "side_nav": ["/section1/page.html", "/section2/page.html"]
}
{% endpageinfo %}
{% endblock data %}

This works, but it seems really opaque and fragile:

  • The global dict needs to be added somehow -- right now I do it in the view, but I guess a custom context processor would be better
  • This needs to be in an inherited block so that it will actually render
  • Because we sometimes need the super's data (eg. for breadcrumbs) it needs to call {{ block.super }} but it needs to be in the right order to keep the super's data from overwriting the target page's data.

I just feel like I'm working against the way Django wants to operate, and I was hoping that there was some better way of handling this sort of thing that I was missing.

2

There are 2 answers

1
lakshmivyas On

One solution is to go with a Static site + services model. You use hyde to generate the static site but have your dynamic content dealt with using javascript on the client site and nice REST API on your server.

0
Marcin On

Stop creating data in your templates. Create it in your views, pass it to your templates. For example, with breadcrumbs, there is no reason whatsoever that the code to add to breadcrumb trails has to live in the template. It could live in a view, or even better, be a context processor.