Problem: I need a bit of data available to all templates
I decided to add a sidebar that has a list of posts - a standard "Archive" bit with links. So, naturally, I set about modifying all my views to include a helper function that returns data. I went through each of them and had them return the output of a function into the context. And hey, it worked!
But, there's an issue with how this was written. Django encourages a DRY philosophy - Don't Repeat Yourself. And I was repeating myself quite a bit here. Going through each view and doing the same thing is inefficient and can get to be hard to maintain in large codebases. This blog won't ever be all that big, but, I figured I'd try to find a way to make things better. You know, do it right. So I hit Google and found a bunch of possible answers. One, though, seems like the "right" way to do it.
Solution: Context Processors
Context processors in Django work to gather and process data at runtime. From there, you can have them return data and make it available to templates. There appears to be additional functionality to context processors, and I'll have to look into that in the future. But for now, this seems to do exactly what I want.
First, I created a file in my blog module (since this is a blog-related function) called 'context_processors.py'. I created my function, which had to return an ordered dict of years containing a list of post titles and url slugs.
posts = Posts.objects.filter().values('title','posttime','slug').order_by('-posttime')
archive = {}
for post in posts:
try:
year = archive.get(str(post['posttime'].year), [])
year.append(post)
archive[str(post['posttime'].year)]=year
except:
pass
archive = sorted(archive.items())
return {'sidebar': archive}
Quick and easy. It looks like Django's context processing loads this dict into a high level of the templating system, making the variable "sidebar" available to templates.
*(Todo: Add try-catch for getting data, I haven't experimented with what happens when a context processor has an exception yet, and if there are no posts, this won't return anything.)
After that, I had to add it to settings.py:
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [ os.path.join(BASE_DIR, 'templates'), ],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
'blog.context_processors.sidebar_archive_processor',
],
},
},
]
So that loads sidebar_archive_processor from blog.context_processors. Very straightforward. From here, it's just a simple matter of accessing that data in the template.
Here's the loop I used:
{% for year,posts in sidebar reversed %}
<div class="sidebar-box">
{{ year }}
<div id="sidebar-{{ year }}">
{% for post in posts %}
<a href="{% url 'blog_slug' post.slug %}"><div class="sidebar-link sidebar-button">{{ post.title }}</div></a>
{% endfor %}
</div>
</div>
{% endfor %}
I wanted to have it so that the most recent posts appeared first, so I loop through the sidebar backwards. I've given each sidebar div a unique id so we can eventually hook javascript into it for an accordion-style collapse. Oh, and I could have done a regroup here, but I like to keep code out of the template as much as possible, so I figured it would be best to put that logic in the context processor itself.
And there you have it. Works as intended, feature addition complete. I'll have to look into the other things that context processors can do next. Or, maybe start adding other modules to the site. Right now the Games, Code, and About links just go link to blog_home, so that should probably be updated. Those placeholders should probably have someplace more apt to link to.