Let's talk about this beautiful website
Published: Aug. 28, 2020, 1:35 p.m.
I have been programming a lot, and I was meaning to make a website to showcase some of my work, but I never got around to do so. But, due to how atoms in the Universe aligned, it, well, not Universe, our University tasked us with creating a blog site. So, here I am to walk you through its design.
We were tasked to use a few key technologies: Django, and Python. Also, we need to host it on pythonanywhere, which is free, but I will swiftly migrate to DigitalOcean (referral link with $100 in free credits) where I host several other projects.
So I have used Python quite a lot in the past, and I did make some flask based websites in the past and it was all cool. Flask shares some features with Django, so I will first talk about them.
Templates
It is a really nice feature, imagine doing OOP but on HTML. You define the bare bones website, like so:
{% load compress %}
{% load static %}
{% compress css %}
<link rel="stylesheet" type="text/x-scss" href="{% static 'scss/syntax.scss' %}">
<link rel="stylesheet" type="text/x-scss" href="{% static 'scss/style.scss' %}">
<link rel="stylesheet" type="text/x-scss" href="{% static 'scss/navbar.scss' %}">
<link rel="stylesheet" type="text/x-scss" href="{% static 'scss/logo.scss' %}">
{% endcompress %}
<div class="root">
<div class="logo">
<p class="logo1">
<a href="/">██████╗ ██╗ █████╗ ███████╗ ██╗███████╗</a><br/>
<a href="/">██╔══██╗██║ ██╔══██╗╚══███╔╝ ██║██╔════╝</a><br/>
<a href="/">██████╔╝██║ ███████║ ███╔╝ ██║███████╗</a><br/>
<a href="/">██╔══██╗██║ ██╔══██║ ███╔╝ ██║╚════██║</a><br/>
<a href="/">██████╔╝███████╗██║ ██║███████╗██╗██║███████║</a><br/>
<a href="/">╚═════╝ ╚══════╝╚═╝ ╚═╝╚══════╝╚═╝╚═╝╚══════╝</a>
</p>
</div>
<div class="content">
{% block content %}
{% endblock %}
</div>
</div>
</div>
And then just fill in the blanks (block content):
{% extends "base.html" %}
{% load markdown %}
{% block content %}
<h1>{{ post.title }}</h1>
<p>Published: {{ post.pub_date }}</p>
<p>{{ post.md_data | markdown | safe }}</p>
{% endblock %}
(probably not the finest indentation per file basis, but I wanted to make the final HTML look nicely indented)
So when a user goes to a post, the backend would render the template, which would then pull in the base template and then render it all into one nice page, and the best part - the base stays consistent across templates. Oh and yeah, you can access variables passed into the template to change the displayed contents. It is simply incredible! So that was something Flask had, but Django is something much, much bigger! Being a nerd, I will start from the smallest of things, and walk upwards until we reach the very big picture.
Everything is a Model
Or a view
Or something else
But Model is the core of it all
And I'm just showing off my markdownify feature here
Django aims to make database managment a kids game. With Flask I had to manually write mysql commands, and it was so error prone. Here, the idea is that you define your data as a model, and Django creates all the necessary database code for you. Here's an example of how a post category is defined in my site:
class Category(models.Model):
name = models.CharField(max_length=255)
slug = models.SlugField(max_length=255)
def __str__(self):
return self.name
All of the Categories then get some DB code generated, and put in a table of these models. All I need to care about, is knowing what fields this model has. A more complex example:
class BlogPost(models.Model):
title = models.CharField(max_length = 256)
slug = models.SlugField()
pub_date = models.DateTimeField('date published')
last_updated = models.DateTimeField('last updated')
md_data = models.TextField()
categories = models.ManyToManyField(Category, blank=True)
posted_by = models.ForeignKey(User, on_delete=models.CASCADE)
def __str__(self):
return self.title
I will walk through some of the important bits.
categories = models.ManyToManyField(Category, blank=True)
This allows to link a single post into a number of categories (you can see a category list in the main blog page, and I should probably list the categories in the post itself).
posted_by = models.ForeignKey(User, on_delete=models.CASCADE)
ForeignKey
allows only one author to be set. The on_delete=models.CASCADE
is really interesting. If a user gets removed, this causes all of their posts to also be deleted.
Enough about models, next step is a View
View - the thing that gets displayed
A view always gets called upon when a visitor comes across a page. And a view is supposed to render some HTML, and send a response back. Again, Django tries to make it super simple, if you think in terms of objects. The main thought is that a view is going to access some data. And Django provides several templates for different usecases.
Enter DetailView
Detail view is used when you want to render a single page, with a single model (think a blog post). All it takes is a model and a template, then does some magic and out comes a webpage! This is how my blog post view is defined:
class BlogPostView(generic.DetailView):
model = BlogPost
template_name = 'blog/post.html'
context_object_name = 'post'
context_object_name just names the variable that gets used in the template to display data.
TemplateView? 😲
I don't know if that emoji will render, I never tested that!
TemplateView, in terms of data, is even simpler! All it takes is a template_name, like so:
class IndexView(generic.TemplateView):
template_name = 'blog/index.html'
What's really cool about it, is that if you want to, you can fill in the context data yourself. I use it to access blog posts in a paginated fashion, but also provide a nice list of categories to choose from, like so:
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
posts = BlogPost.objects.order_by('-pub_date')
paginator = Paginator(posts, POSTS_PER_PAGE)
page_number = self.request.GET.get('page')
page_obj = paginator.get_page(page_number)
context['paginator'] = paginator
context['page_obj'] = page_obj
context['categories'] = Category.objects.order_by('name')
return context
ListView - the thing I was supposed to use above
So list view is for lists (lists of blog posts, for instance), and that's what I originally used, but then switched to DetailView for more flexibility and sharing of functionality between IndexView and CategoryView templates (the only difference is that the latter filters posts by category). Anyway, if I were to use it, it would have been this easy:
class ArticleListView(ListView):
model = Article
paginate_by = 100 # if pagination is desired
Those were all the views, let's check out some things about templates.
Templatetags, the thing that makes the whole site render properly
All my page content is basically markdown. That was a decision I made, since I used markdown quite a lot, and I like it. So, I wired it up to my templates. Remember this blog post template:
{% extends "base.html" %}
{% load markdown %}
{% block content %}
<h1>{{ post.title }}</h1>
<p>Published: {{ post.pub_date }}</p>
<p>{{ post.md_data | markdown | safe }}</p>
{% endblock %}
In particular, this line:
<p>{{ post.md_data | markdown | safe }}</p>
We take md_data, which is a peace of text, and run it through a markdown filter, and then safe filter, which will clean up some of the possible wrongdoings.
Markdown tag is defined like so:
@register.filter()
@stringfilter
def markdown(value):
renderer = CustomizedRenderer()
md_rend = md.Markdown(renderer=renderer)
return md_rend(value)
There is also a renderer which implements some special features like source code escaping, but feel free to check those out in the GitHub repo. I also have this shortdown filter which performs the basic markdown rendering, but only for a single paragraph, and removes any links. That's something I use in the blog post list. Anyways, let's go one layer up, to the App itself.
App, or application, well, it is not any specific class really
Think of app as a subset of your website, app has it's own models, it's own views, but it is not the entire website, no. We will get to that in a second. Important things that an app defines is URLs it acceps:
app_name = 'blog'
urlpatterns = [
path('', views.IndexView.as_view(), name='index'),
path('new_post', views.NewPostView.as_view(), name = 'new_post'),
path('post/id/<int:pk>/', views.BlogPostView.as_view(), name = 'blog_post_id'),
path('post/<str:slug>/', views.BlogPostView.as_view(), name = 'blog_post'),
path('cat/<str:slug>/', views.CategoryView.as_view(), name = 'category_name'),
]
Here you can see it routes differnt URLs through different views. Each path also has a name attached to them, which is useful in templates to figure out absolute URLs.
The big picture
The entire website also has a URL list used to route to different applications, which looks something like this:
urlpatterns = [
path('blog/', include('blog.urls')),
path('admin/', admin.site.urls),
path('hidden/', include('hidden.urls')),
path('', include('django.contrib.auth.urls')),
path('', include('index.urls')),
]
The blank can mess some things up if they are not the last in the list, so watch out for that!
Lastly, there is a settings.py file which configures the website. I won't show it, but there were basically some changes to make the website production ready.
Summary
So yeah, this is Django, and I really like it. It allows to focus on the website, instead of some database implementation details. That's what really matter in the end. I hope to implement some extra features like RSS feed and commenting, but that's for later :)
You can find the source code of this entire website on my GitHub. Feel free to dig into the fine details! Good luck!