How I Designed a Personal Portfolio Website with Django (Part 2)

3.7/5 - (3 votes)

We are designing a personal portfolio website using the Django web framework.

In the first part of this series, we learned how to create Django models for our database. We created an admin panel, and added ourselves as a superuser. I expect that by now you have used the admin panel to add your sample projects.

You are required to go through the first part of this series for you to follow along with us if you haven’t already done so. In this series, we will create a view function using the sample projects. By the end of this series, we will have created a fully functioning personal portfolio website.

The View Function

We can choose to use class-based views or function-based views, or both to create our portfolio website.

If you use class-based views, you must subclass it with Django’s ListView class to list all your sample projects. For a full sample project description, you must create another class and subclass it with Django’s DetailView class.

For our portfolio website, we will use function-based views so that you can learn how to query our database. Open the file and write the following code:

from django.shortcuts import render
from .models import Project

# Create your views here.

def project_list(request):
    projects = Project.objects.all()
    context = {
        'projects': projects
    return render(request, 'index.html', context)

We import the Projects class and perform a query to retrieve all the objects in the table. This is a simple example of querying a database.

The context dictionary that contains the object is sent to the template file using the render() function. The function also renders the template, index.html. This tells us that all information available in the context dictionary will be displayed in the given template file.

This view function will only list all our sample projects. For a full description of the projects, we will have to create another view function.

def full_view(request, pk):
    project = Project.objects.get(pk=pk)
    context = {
        'project': project
    return render(request, 'full_view.html', context)

This view function looks similar to the previous one only that it comes with another parameter, pk. We perform another query to get an object based on its primary key. I will soon explain what is meant by primary key.

Template Files

We have to create two template files for our view functions. Then, a base.html file with Bootstrap added to make it look nice. The template files will inherit everything in the base.html file. Copy the following code and save it inside the templates folder as base.html.

<link rel="stylesheet" href="" integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous">

<nav class="navbar navbar-expand-lg navbar-light bg-light">
    <div class="container">
        <a class="navbar-brand" href="{% url 'index' %}">Personal Portfolio</a>
        <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
          <span class="navbar-toggler-icon"></span>

        <div class="collapse navbar-collapse" id="navbarSupportedContent">
          <ul class="navbar-nav mr-auto">
            <li class="nav-item active">
              <a class="nav-link" href="{% url 'index' %}">Home</a>


<div class="container">
    {% block content %}{% endblock %}

<script src="" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
<script src="" integrity="sha384-ZMP7rVo3mIykV+2+9J3UJ46jBk0WLaUAdn689aCwoqbBJiSnjAK/l8WvCWPIPm49" crossorigin="anonymous"></script>
<script src="" integrity="sha384-ChfqqxuZUCnJSK3+MXmPNIyE6ZbWh2IMqE241rYiqJxyMiZ6OW/JmZQ5stwEULTy" crossorigin="anonymous"></script>

The a tag has a {% ... %} tag in its href attribute. This is Django’s way of linking to the index.html file. The block content and endblock tags inside the div tag is reserved for any template file inheriting from the base.html file.

As we will see, any template file inheriting from the base.html file must include such tags.

The Bootstrap files are beyond the scope of this article. Check the documentation to learn more.

Create the index.html file inside the template folder as indicated in the view function. Then, write the following script:

{% extends "base.html" %}
{% load static %}
{% block content %}
<h1>Projects Completed</h1>
<div class="row">
{% for project in projects %}
    <div class="col-md-4">
        <div class="card mb-2">
            <img class="card-img-top" src="{{ project.image.url }}">
            <div class="card-body">
                <h5 class="card-title">{{ project.title }}</h5>
                <p class="card-text">{{ project.description }}</p>
                <a href="{% url 'full_view' %}"
                   class="btn btn-primary">
                    Read More
    {% endfor %}
{% endblock %}

The index.html file inherits the base.html as shown in the first line of the code. Imagine all the scripts we have to write to render hundreds or even thousands of sample projects! Once again, Django comes to the rescue with its template engine, for loops.

Using the for loop, we loop through all the projects (no matter how many they are) passed by the context dictionary. Each iteration renders the image (using the img tag), the title, the description and a link to get the full description of the project.

Notice that the a tag is pointing to a given project represented as This is the primary key passed as a parameter in the second view function. More on that soon.

Again, notice the block content and the endblock tags. Since the index.html extends the base.html file, it will only display all that is found in the file. Any addition must be written inside the block content and endblock tags. Otherwise, it won’t be displayed.

Finally, the full_view.html file:

{% extends "base.html" %}
{% load static %}
{% block content %}
<h1>{{ project.title }}</h1>
<div class="row">
    <div class="col-md-8">
        <img src="{% project.image.url %}" alt="" width="100%">
    <div class="col-md-4">
        <h5>About the project:</h5>
        <p>{{ project.description }}</p>
        <h5>Technology used:</h5>
        <p>{{ }}</p>
{% endblock %}

That’s all about our template files. Notice how everything is linked to our database model as we learned in the first part of this series. We retrieve data from the database, pass them to the view function, and render them in the template files.

We haven’t let Django know of an existing templates folder. Go to the file, under the TEMPLATES section. Register the templates folder there.

        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [os.path.join(BASE_DIR, 'templates')], #add these
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [

Registering the URLs

We need to hook up our view functions to URLs. First is the project-level URL. Open the in the project folder, and add these:

from django.contrib import admin
from django.urls import path, include
from django.conf import settings
from django.conf.urls.static import static

urlpatterns = [
    path('', include('portfolio.urls')),

if settings.DEBUG:
    urlpatterns += static(settings.MEDIA_URL,

We register the file using the include() method. So, once we start the local server, and go to, we will see our home page which is the index.html file. Then, in the if statement, we tell Django where to find the user-uploaded images.

Next are the app-level URLs. Create a file in the portfolio folder.

from django.urls import path
from .views import project_list, full_view

urlpatterns = [
    path('', project_list, name='index'),
    path('<int:pk>/', full_view, name='full_view'),

The full_view URL is hooked up with a primary key. It is the same primary key in the templates file and in the second view function. This is an integer representing the number of each project. For the first project you added in the previous series, the URL will be

Hoping that everything is set, start the local server, and you will see everything displayed.


We have successfully come to the end of the second series of this project. The first series is here:

πŸ’‘ First Part: How I Designed a Personal Portfolio Website with Django

If you encounter errors, be sure to check the full code on my GitHub page. Our app is now running in the local server. In the final series of this project, we will see how we can deploy this to a production server.