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:
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)
A custom data tag allows pages to write data into this page data
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.
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.
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.
Related
Ive changed certain parts of my admin page and played around extending the templates (so I have the file structure set up and working). I now want to go a bit further.
I want to add a column next to the 'recent activity' column on the admin page which would list all the most recent django-notifications the admin has received.
I am guessing I'd extend the base.html and add my notifications there. My first question is is this the best place to do this?
The next, bigger thing is that I would need to modify the original views.py file to do this. Surely this isnt a good idea? Poking around in the Django Admin views.py sounds like a terrible idea and makes me think that this kind of thing shouldnt be done, however id still like to know for sure before I give up on it.
I can find information on adding views to Django Admin, but nothing on adding content like this to the admin front page.
Thank you.
This won't be overly difficult to do.
A rough outline, you'll need to use your own AdminSite, and overwrite the index method (this is the method which renders the main view). Something like this:
from django.contrib import admin
class MyAdminSite(admin.AdminSite):
index_template = "path/to/my_template.html"
def index(self, request, extra_context=None):
# do whatever extra logic you need to do here
extra_context = ... # this will be added to the template context
return super().index(request, extra_context=extra_context)
Notice I've also added in a new index_template. This is what the index method will use to render it's response. So we had better set that up. We'll extend the old index.html and just add in our extra code in the bit that we want.
{% extends "admin/index.html" %}
{% block sidebar %}
{{ block.super }} // this just includes everything that was there before
// add your extra html here
{% endblock %}
And this should do the trick :) So, no. You don't really have to go messing around with anything you shouldn't be.
You'll want to register the MyAdminSite class as your new admin. There are clear instructions to do that in the django docs.
However... It is always worth thinking about the first few lines from the django-admin docs:
The admin’s recommended use is limited to an organization’s internal management tool. It’s not intended for building your entire front end around.
The admin has many hooks for customization, but beware of trying to use those hooks exclusively. If you need to provide a more process-centric interface that abstracts away the implementation details of database tables and fields, then it’s probably time to write your own views.
I would go a step further and say it should probably only be used by fellow developers. It's far to easy for others to cause major problems. So depending upon the reason why you want to add in these notifications here, it may or may not be a good thing to do.
I hope this helps :)
I need to replace same plugin template on different subpages - e.g. on frontpage I need specific slider template for latest articles, in detail article I need small list, on search result without images etc.
[note: everything about aldryn newsblog app - I don't mean my own plugin!]
*(something like custom template per plugin instance)
How to replace it ? Extending template is not quite what I need - inheritance is from bottom - from lower subtemplate to base.html - but that plugin have hardcoded lower template.
Tons of IF block in template is irrational then we think in MVC.
*( like here Render Django-CMS plugins differently on different splaceholders/templates )
Or maybe just write custom template with using hardcoded including plugins ? But using django cms placeholder editor is very useful and it'll be better to keep working in that way :///
So, I create front.html base template for frontpage,
put some plugins to placeholders - and need to replace subtemplates for this plugins only in this front.html and keep subtemplates for that plugin in other places - this is main goal.
It will be the best, when django cms / aldryn newsblog provide option "custom template" per plugin instance :|
( like this one http://www.ilian.io/django-cms-plugins-with-selectable-template/ )
If I understand your question correctly (it's late here), you need a way to override plugin templates on a plugin instance basis because hacking django templates is not the way to go (I agree).
Django CMS does allow you to override plugin templates on an instance basis.
As shown in http://docs.django-cms.org/en/develop/how_to/custom_plugins.html?highlight=get_render_template#the-simplest-plugin
In your CMSPluginBase subclass add the following:
def get_render_template(self, context, instance, placeholder):
# criteria goes here
return 'sometemplate.html'
As far as how to know which template to render when (criteria), you can do a few things.
Use the page's reverse id:
page = instance.page
templates = {
'homepage': 'plugin_home.html',
'about': 'plugin_about.html',
'contact': 'plugin_contact.html',
}
return templates[plugin.page.reverse_id]
This approach has a few drawbacks:
Relies on plugin being bound to a page. (plugins can live outside of pages)
Can only work with pages that have reverse id set and reverse ids
are unique per page which means you would have to list reverse id
for every page you want to change template for.
Use a page extension to set a category per page:
Checkout http://docs.django-cms.org/en/develop/how_to/extending_page_title.html
With this approach you can then set some sort of category to multiple pages and so you can target multiple pages in one shot like so:
page = instance.page
extension = get_page_extension(page) # Check out docs for this
templates = {
'category_1': 'plugin_category_1.html',
'category_2': 'plugin_category_2.html',
'category_3': 'plugin_category_3.html',
}
return templates[extension.category.name]
Pros:
Can target multiple pages in one shot
Cons:
Relies on plugin being bound to a page.
A bit more complex
Use a template context variable:
In your templates, depending on how you're rendering your plugins, you can
provide a context variable like so:
{% with category='category_1' %}
{% placeholder 'content' %}
{% endwith %}
or
{% with category='category_1' %}
{% render_plugin yourplugin %}
{% endwith %}
Then in your get_render_template method you can access this context variable and do the following:
# Use .get() to provide fallback
category = context['category']
templates = {
'category_1': 'plugin_category_1.html',
'category_2': 'plugin_category_2.html',
'category_3': 'plugin_category_3.html',
}
return templates[category]
Pros:
No extra models.
Can target multiple pages in one shot.
Cons:
The only one I can think of is these random {% with %} in
templates.
I completely missed the newblog part, so in order to override the newsblog plugins or any plugin, just subclass the plugin class you want to override and unregister the original and then register yours, make sure yours has the same class name.
Maybe you can make the whole template logic above into a mixin to use throughout your project.
I've always wanted to give Zinnia a look, but I'm too far into working with NewsBlog on a site right now to do it (and blogs have already been posted and whatnot). You can always just add a few extra placeholders in the template (it's not the most efficient looking thing ever, but it's no load on the framework if you add a placeholder and leave it blank), that way they aren't static, and then you can put whatever plugins you want to inside of them. You can customize each component in NewsBlog pretty easily by just adding whatever you want in the structure mode. Things get trickier when it comes to having multiple blogs that act differently, but even then, as long as you're not adding components into the static placeholders provided by NewsBlog (or as I so elegantly learned it, "don't put the stuff in the blocky-things with the pins next to them), you can create different namespace for the different blogs (either in the admin, under "Configs" in the NewsBlog section, or when creating a new page and hooking it to the NewsBlog app), and you can have different templates on different blogs.
EDIT: This is a really excellent resource for touching up NewBlog without throwing the baby out with the bathwater (after three months of learning DjangoCMS, I'm still finding myself referencing it for fine-tuning pieces of NewsBlog, and to refresh my grasp on templatetags and other things that are overwhelming and have quickly left my brain along the way): https://www.django-cms.org/en/blog/2016/02/16/build-a-website-without-knowing-python-django-part-two/
*I linked to part two, as the first part deals with how to initially setup a project, and I assumed it probably wasn't relevant. Then again, if you're using Aldryn, there are some useful bits in there that can extrapolate if you're having trouble with customizing the boilerplate (or other things you'd like to configure that an Aldryn setup handles for you -- which is super awesome most of the times, but when it's not super awesome, it's usually super frustrating :)
I’m planning to use the Django templating system in a slightly uncommon way. I need an app that will let me easily create simple “fill in the gap”-style forms and I decided to use Django templates with custom tags to design the forms.
Here is a sketch of what I mean:
<p>
This is the <i>form</i>. Two plus two is {% gap four 4 vier %}.<br>
The best programming language is {% case_gap Python Haskell %}.
</p>
{% if all_correct %}
You are smart.
{% else %}
<input type="submit">
{% endif %}
The idea is that the *gap tags render a text input control, look into the context to see if the answer for them was sent, and if the answer is correct, mark the text inputs green.
The question is how to implement the all_correct logic. The simple solution is to add this variable to the context and then make each *gap tag update it based on its correct answer, and, I believe, this should work with my example. But what if I want to move the {% if all_correct %} code to the top of the template, where none of the gaps were rendered, and thus none of them have validated their answers yet?
Looks like I need some way to iterate over all the *gap tags before starting to render the template and [ask them to] validate the answers, but I don’t know the internals of the templating system well enough to implement this. Or there might be a completely different and better way.
I believe I figured how to do this after reading Chapter 9 of The Django Book.
Creating of a custom template tag involves defining of two things: how to compile the tag and how to render it. The compile-function should parse the tag data and return a Node—basically a thing that has a .render(self, context) method and some other data derived from the tag text.
The solution is to create FormNode—a subclass of Node that will also have a .validate(self, context) method. Then our custom tag will be compiled to a subclass of FormNode that implements validation logic.
The next thing to do is to create FormTemplate—a subclass of Template with a super-power: before starting to render individual nodes it will validate all the nodes by iterating over them and calling validate(context) on all the subclasses of FormNode and it will set the all_valid variable in the context.
Here is my proof-of-concept implementation: https://github.com/kirelagin/django-template-forms. I believe it works pretty well.
I used the ability to instantiate a custom template engine, which was added in Django 1.8. Overall, I had to dig a little deeper than one might expect and I always had this feeling that splitting of the templating API and the django engine (which is now just one of the available backends) wasn’t complete yet. I hope that Django 2.0 will bring us new cool stuff and all the hacks I had to add will vanish.
I wonder if Django native support changes in DOM. I don't know if it is correct name for it now so I guess I explain it instead. For example if I make an e-shop site with django. I want when I click on a product it should add to the basket, which is in html maybe looks something like this. So for each product I add a new <li></li> is added dynamically. Can I do that with django. Or do I have to use Javascript for it?
<div id="basket">
<ul>
<li>
// some product
</li>
</ul>
</div>
It depends.
1) You may want to make you app very dynamic, so another element apears in your basket without page reload. This will be done by combining ajax requests (your server needs to know what you have in basket) with DOM manipulation (purely JavaScript);
2) You can use more classical approach. Adding element to basket is just a POST request. Django handles the request (stores in session or somewhere else current basket) and generates new HTML for you.
Imho, the first approach is a way faster and it looks better for the end-user. The downside is that you may loose track of some valuable information which automatically updates when user reloads entire page (like for example the price of an item). But this should not be a problem if we're talking about store. After all how often product data changes?
Django does not natively generate javascript for you. The usual way is to import your javascript into your pages in your templates.
I've got an existing Django site, with a largish variety of templates in use. We've agreed to offer a custom-skinned version of our site for use by one of our partners, who want the visual design to harmonize with their own website. This will be on a separate URL (which we can determine), using a subset of the functionality and data from our main site.
So my question is: what's the best way to add reskin functionality to my site, without duplicating a lot of code or templates?
As I see it, there are several components which need to work together:
URL: need to have a different set of URLs which points to the partner-branded version of the site, but which can contain all the standard path info the site needs to build pages.
Template 'extends': need to have the templates extend a different base, like {% extends 'partner.html' %} instead of {% extends 'base.html' %}
View logic: need to let the views know when this is the partner-branded version, so they can change the business logic appropriately
My idea so far is to put the partner site on a subdomain, then use a middleware to parse the domain name and add 'partner' and 'partner_template' variables to the request object. Thus, I can access request.partner inside my views, to handle business logic. Then, I have to edit all my templates to look like this:
{% extends request.partner_template|default:'base.html' %}
(According to this answer, 'extends' takes a variable just like any other template tag.)
Will this work properly? Is there a better way?
If you are using different settings.py for the different sites you can
specifiy different template loading directories. (Which may default to your unskinned pages.)
Personally I'm not convinced by having different business logic in the same view code, that smells like a hack to me - the same as extensive conditional compilation does in C.
So to sum up.
Use django.contrib.sites and different settings.py
Get a clear idea, how much this is a new app/website using the same data, or just different css/templates.