Content
Catchup
Today we will spend a little bit of time at the beginning addressing ongoing issues with installs and projects. Answering questions and playing catch-up.
Design Goals
Why am I making a new django-driven blog?
- I really like the idea of writing in markdown
-
I want to store my markdown files in a directory structure separate from the moving parts of the website.
Often the files are located in separate directories in a structure like hugo, but up to now they were in the same big git repository. This helps me accomplish my next aim:
-
I want to manage changes to my website content asynchronously from the changes to the website's look, feel, and functionality.
- I need more functionality that a static site generator can accommodate
How do I have to hack Django to make it work?
URL handling. My current site structure is more semantic than a typical django website. How do I mean? Even though most pages under the hood of my current website are driven by markdown files, the file structure output by Hugo is derived by each file's location in a directory / file tree on my computer. This makes organizing your site trivial, because you just have to rearrange the files in their folders to generate a new website structure.
However, in a Django site, it seems that a lot of query logic is often built into the django url logic associated with a specific app, rather than by a nested set of directory paths.
Thus a post that might be found in my old site might be
https://danaukes.com/notebook/ubuntu/installation-instructions/, it seems django would be more likely to support
https://danaukes.com/blog?/tag=notebook&slug=installation-instructions. I know this is not a perfect comparison between path handlign in the two platforms, but it suffices to illustrate that they are, by default, different. Django is quite flexible in its url handling, so I would like to, for the time being, keep my current urls the way they are.
Another small thing about hugo is that, because it assumes a file structure-driven organization strategy, it permits mixing of lists and posts. A list would typically be a webpage that lists all the posts beneath a given directory in the underlying file structure, while a post is associated with a file in that directory structure.
Django makes no such assumptions, and it's url mapping, while powerful, can get complicated rather quickly if you're trying to logic-out which kind of page to serve simply by the URL. In my case, I need to know if a page exists before I choose to serve something in a page format. If a path exists, and has pages under it, I need to show a formatted list of those posts. And if neither a path nor a file exists, I should return an error. I discuss some of the logic I implemented in the views and urls page later, but you get the drift -- it's not as easy as using Django's out of the box functionality.
Building a markdown-based blog app in Django
What is Markdown?
The orignal post by daringfireball outlined a way to write simply, in a way that is legibile, but with some context clues for how it could be rendered into html. Some have argued that that original specification was far from complete, and its ambiguity, especially with corner cases, has led to confusion and a disparate way of handling common situations.
31 Flavors
Today, perhaps because of that early ambiguity, and because the original outline was quite limited, there are a number of different "flavors" of markdown, which complicates things. Some flavors are defined as a standard, and other standards are a de-facto standard derived from how a specific rendering engine works. Take pandoc, for example. The pandoc standard is defined by the pandoc manual -- they are one and the same. Some common markdown flavors include
- the original markdown spec
- github-flavored-markdown
- pandoc markdown
- Python-markdown
- commonmark
There are also implementations, including python-markdown, a library in python that forms the basis for a number of markdown-themed projects, as well as implementations in Go, Ruby, etc, that form the basis for other static site generators such as Hugo and Jekyll, respectively. Each implementation and its own idiosyncracies are layered on top of their chosen specification or loosely defined standard.
For me, the original markdown encompasses only the most basic use-case for markdown. When writing content oriented towards presentations, I tend to use pandoc's presentation formatting, such as the div mark :::, its support of styling with braces ({style="background-color:000000;"}), and its support of a number of niche functions only available in pandoc.
When I'm writing for websites or documents, I want finer-grained control of styles, to be able to specify the size and placement of images, and to integrate with the pdf or html template it will merge with more tightly. Hugo, MkDocs, and Pandoc each support extra attributes, either natively or with the help of plugins, though the extent of their support is not 1:1.
And let's not forget, code, math, footnotes, and potentially even inline citations and bibliographies. Coming from an academic background I spent many long hours in the past getting all these features working in one markdown renderer or another. For example, there are many ways to specify code blocks, but for me, I have already switched over to "fenced" code, such as:
```python
import os
def my_function(input):
pass
```
which renders as
import os
def my_function(input):
pass
This is enabled by including ``` before and after each code block, with the optional language specified right after the first fence (such as ```python). I like this method over the four-space indent because it enables code-specific highlighting to be integrated.
So What?
So... this means I need to reimplement markdown parsing in a way that matches my existing documents, so that I don't need to reformat my documents to suit a new markdown flavor. For this, I followed how MkDocs does it: using the python-markdown library, with specific extensions for all my extra use-cases. Much of my markdown is written for an MkDocs endpoint, so this makes a lot of sense for me.
Add Markdown Rendering
At the bottom of your imports in homepage/blog/models.py, add
import markdown
at the end of your Post class, add:
class Post(models.Model):
# ...
@staticmethod
def _render_markdown(input):
md = markdown.Markdown(extensions=settings.MARKDOWN_EXTENSIONS)
html_pass_1=md.convert(input)
toc_tokens=md.toc_tokens
my_dict = dict(html=html_pass_1,toc_tokens=toc_tokens)
return my_dict
def render_full_markdown(self):
return self._render_markdown(self.content)
Settings
in homepage/homepage/settings.py, add the following code to the bottom:
# Custom addition. This is the list of enabled markdown extensions
MARKDOWN_EXTENSIONS = [
'markdown.extensions.tables',
'markdown.extensions.codehilite',
'markdown.extensions.toc',
'markdown.extensions.attr_list',
'markdown.extensions.footnotes',
'markdown.extensions.wikilinks',
'pymdownx.superfences',
]
post_detail.html
Change homepage/blog/templates/blog/post_detail.html. Remove:
{{ post.content | safe}}
and replace it with:
{% with rendered_markdown=post.render_full_markdown %}
{{ rendered_markdown.html | safe}}
{%endwith%}
Update Your Posts
In a second tab, navigate to your admin site at: http://localhost:8000/admin/. Log in with the user name and password you just set with the startup script you just ran.
You should see:
- Now, next to "Posts, select the post you created last lecture
- In the "Content" box, replace the HTML with markdown. We will talk more about what markdown is and why it is so convenient later, but for now, you can copy and paste in the following text:
## Heading 1: An interesting Thing to Say
> Here is a comment in a box
Here is my text
| Name | Quantity | Unit Cost |
| ----: | :------: | --------: |
| Nuts | 20 | $0.20 |
| Bolts | 5 | $1.10 |
Note that all the functionality of the admin interface is provided automatically by the way you define your models, through Django's admin app.
Now go back to your first tab at http://localhost:8000/posts/. You should see a single post in the list. Clicking on it should take you to a page that looks like this:

