In this guide, I will go through the six steps I took to set up and configure my portfolio & blog website - buhlian.dk - with the use of Django. The steps are the following:
- Finding the right design template
- Setup a new Django project
- Integrating the design template into the Django project
- Defining the Django models
- Setting up the views and url routes
- Integrating blog style editor
You find the project files in my GitHub account (here).
Finding the right design template
To save time and in the realization that I am not a great web designer, I chose to use a design theme template for my portfolio website. This way, I could skip the process of coding the static files for the project (HTML, CSS, and Javascript). Thus, I started by searching different websites for relevant web designs. In the end, I stumbled upon a cool design theme from 3rdwavemedia called DevCard, which had the following benefits:
- Simple and easy to navigate and access
- Personal & professional in look and feel
- Both portfolio and blog features are covered
- Clean CTAs and a strong hierarchy
So, I purchased the theme and received a zip folder containing the static files for my portfolio website.
Setup a new Django project
For the development of the portfolio website, I use Django which is a high-level Python web framework that encourages rapid development and clean, pragmatic design. However, before installing Django, I first set up a new, local folder for my project. Then I open the terminal in Visual Code Studio and change the directory accordingly. Once this is done, I use virtualenv to set up a virtual environment called “venv”. To activate the virtual environment, I run this code in the terminal (you can deactivate it at any time by simply running “deactivate” in the terminal):
venv/Scripts/activate
You should now see a (venv) in the terminal line indicating that you are now in the virtual environment. Now, I install Django with the following terminal command:
pip install django
Once installed, I can then use Django to start a new project by running this terminal command, thus naming the project blog (a better name would probably have been "website" since it is not only a blog):
django-admin startproject blog
When the command has finished, you now see some new additions to your local project folder. You will see a new blog folder containing a settings.py, urls.py, etc. files. Furthermore, you will now have a manage.py at the root of the directory. These files are essential for running and configuring this new Django project. But before we proceed with these, I want to create a Django app (called “post” - again not the best naming convention used here) inside this Django project. I do this with the following terminal command using the new manage.py file.
python manage.py startapp post
Again, I see the local folder being enriched with a new “post”-folder containing several python files for app configuration. The last substep of setting up this new Django project is to add the new app “post” to the app overview in the settings.py file in the “blog”-folder. In addition, we will also set up an admin user for the website. This is done by running the following terminal command and filling out the username, email (optional), and password of the user:
django-admin createsuperuser
Once added, we can now run the following commands to apply the initial migrations and run the website locally.
python manage.py makemigrations
python manage.py migrate
python manage.py runserver
In the terminal, you should now see a link to the local port 127.0.0.1, where you will hopefully see this rocket screen (see screenshot below). If you add “/admin” to the url, you should now be able to login with your superuser credentials and then access Django’s excellent admin section default page. Now the Django project has been properly set up.
Integrating the design template into the Django project
Next step is to configure the static files for the website - this includes images, templates, styles, and JavaScripts files. First, we start with the HTML templates. In the root directory, we create a new folder called "templates". In addition, we adjust the "DIRS" file path of the templates dictionary in the settings.py file to:
'DIRS' : [os.path.join(BASE_DIR, 'templates')]
All HTML templates from the DevCard design theme are moved to the templates folder. Here, we can split each element of the site into different template files, in which we use jinja expressions to reference the different elements into a base.html file (see picture below).
The base.html represents the structure of all pages on the site. Everything here is based on the inclusion of every subelement on the site ranging from the head to the navigation (header) and footer section. All these elements are then specified in separate template files. The only, fully dynamic section here is the content section, which will be filled out with content based on the page, the user is requesting. See the index.html (the front page template) as an example here:
Here the entire content of the page is rendered within the jinja content block statements. You can see the configuration of all template files on my GitHub repo here.
To set up the static files, we create two folders (static, media) in the root directory and add the following code to the settings.py file (in the static files section):
STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles')
STATIC_URL = 'static/'
STATICFILES_DIRS = [
os.path.join(BASE_DIR, "static"),
]
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
MEDIA_URL = '/media/'
With this completed, we can now add CSS, fonts, images, JavaScripts, etc. from the DevCard design theme to the new static folder. With this configuration, we can now use the following jinja statement to load the static files for each of our template files: {% load static %}. Now, we can proceed to the next step.
Defining the models
To allow for user input, we need to set up models for the app. In this case, we have the following models, we need to define and configure:
- Signup: on the blog page, the user can signup to receive email notifications, when new blog posts are uploaded.
- Contact: on the contact page, the user can send add a question/comment in the contact form.
- Post: for each blog post, we need to complete several fields such as title, overview text, thumbnail image, and content.
- Category: for each blog post, we want to add one or more categories reflecting the theme of the post.
The model is instantiated with a class statement and then enriched with attributes and methods. As the Signup and Contact models are going to be used with onsite forms, we set up a forms.py file, in which we ascribe them to Django's built-in ModelForm. This way, we get access to some useful features in configuring the forms in the templates (see next step). Below you see the configuration of the Contact and Post models.
For each relevant data point, we define a model field. The get_readtime method for the Post model is used to get a measure of how long the post takes to read. The get_absolute_url method uses Django's reverse module to help create unique urls for each blog post. With the models set up, we can import and add them to the admin.py file:
from .models import *
admin.site.register(Signup)
admin.site.register(Contact)
admin.site.register(Post)
admin.site.register(Category)
Finally, we finish the migrations, migrate the changes, and run the local server again with the following commands:
python manage.py makemigrations
python manage.py migrate
python manage.py runserver
Now, you should be able to see the new models and add data to them in the admin section of the site (simply add "/admin" to the url). With this in place, we can proceed to the next step.
Setting up the views and url routes
Now, we are ready to define our url routings and what is going to happen, when one of these is requested by the user. We start with the configuration of the views.py file. Here, I use a function-based approach, but optimally the setup could be improved by applying a class-based approach. This will be the topic of a future blog post. In the screenshot below, you see the specifications of the bloghome, blogpost, and contact views.
For each of these views, we have an "if" code block, because each of these views relates to a page in which we have an HTML form. For bloghome and blogpost, we have a signup form, and for the contact view, we have a contact form. In the "if" block the form submit (the POST request) is handled. Thus, an instance of the form model is instantiated and then the form is checked for validity before it is saved. At last, we are also able to define a message for the successful completion of the form.
In addition to the "if" block, we also extract the relevant blog post(s) from the Post model in the bloghome and blogpost views. For the former, we extract all posts in descending, chronological order with the following line:
posts = Post.objects.order_by('-timestamp')
For the blogpost view, we only extract the relevant blog post. Thus, we include an id and a title argument to the blogpost function. The id is then used to filter out the relevant post (or return a 404 error) with the following command:
post = get_object_or_404(Post, id=id)
For all views, we end the function by defining the context. This is where we can set relevant data that can be tapped into through the templates (more on this shortly). The function is finished by a return statement in which we use Django's render module to link the request to the relevant template and enrich it with the defined context. Jumping to the blog-home template, we can now use the post data to enrich the content block of the site with the help of jinja for-loop statements like here.
Finally, we set up the urls in the post/urls.py file. First, we import all views from the post/views.py file with the following command at the top:
from .views import *
Then, we configure an url pattern path for each relevant page of the website. For each path, we reference the view function related to the url as well as a name string. For the post-specific pages, we include so-called slugs in the path (id, title), which is defined in the view. Thus, the url of each unique blog post will be "post/<id>/<title>" - i.e. my very first blog post will have the following url path: "post/2/prologue". With this in place, we need to include these urls in the overall project urls (i.e. the blog/urls.py file). This is done by adding the following lines of code:
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('', include('post.urls')),
]
With this done, we can now proceed to the process of adding a blog-style editor to the Post model.
Integrating blog-style editor
To add a blog-style editor to the blog post model, I have chosen TinyMCE. TinyMCE is a WYSIWYG (what you see is what you get) HTML editor, which provides many options for styling and adjusting new blog posts in a simple, easy way. Thus, we start by installing the django-tinymce. In the post/models.py file, we then import a special model type for the content field in the Post model:
from tinymce import models as tinymce_models
(...)
content = tinymce_models.HTMLField('Content')
Finally, we head to the settings.py file to add tinymce to the installed apps list. Furthermore, we include the following settings block at the end of the settings.py file:
#tinymce
TINYMCE_DEFAULT_CONFIG = {
"theme": "silver",
"height": 1000,
"menubar": True,
"plugins": "advlist,autolink,lists,link,image,charmap,print,preview,anchor,"
"searchreplace,visualblocks,code,fullscreen,insertdatetime,media,table,paste,"
"code,help,wordcount, codesample",
'toolbar1': '''
fullscreen preview bold italic underline | fontselect,
fontsizeselect | forecolor backcolor | alignleft alignright |
aligncenter alignjustify | indent outdent | bullist numlist table |
| link image media | codesample |
''',
'toolbar2': '''
visualblocks visualchars |
charmap hr pagebreak nonbreaking anchor | code |
''',
'contextmenu': 'formats | link image',
'menubar': True,
'statusbar': True,
}
This dictionary configures the editor and sets up a toolbar with different functionalities such as media link, code sample, and table. Thus, when we restart the local server again and go to the admin section to set up a new blog post in the post model, we see a blog-style editor with the defined functionalities above directly inside the admin configuration section.
With this done, we have now finalized the last step of setting up the website portfolio Django project. In the next blog post, I will outline how you push this project to GitHub and deploy it to Heroku.