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 views.py
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="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" 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> </button> <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> </li> </ul> </div> </div> </nav> <div class="container"> {% block content %}{% endblock %} </div> <script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.3/umd/popper.min.js" integrity="sha384-ZMP7rVo3mIykV+2+9J3UJ46jBk0WLaUAdn689aCwoqbBJiSnjAK/l8WvCWPIPm49" crossorigin="anonymous"></script> <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/js/bootstrap.min.js" 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' project.pk %}" class="btn btn-primary"> Read More </a> </div> </div> </div> {% endfor %} </div> {% 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 project.pk
. 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> <div class="col-md-4"> <h5>About the project:</h5> <p>{{ project.description }}</p> <br> <h5>Technology used:</h5> <p>{{ project.technology }}</p> </div> </div> {% 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 settings.py
file, under the TEMPLATES
section. Register the templates
folder there.
TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': [os.path.join(BASE_DIR, 'templates')], #add these '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', ], }, }, ]
Registering the URLs
We need to hook up our view functions to URLs. First is the project-level URL. Open the urls.py
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('admin/', admin.site.urls), path('', include('portfolio.urls')), ] if settings.DEBUG: urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
We register the file using the include()
method. So, once we start the local server, and go to http://127.0.0.1:8000
, 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 urls.py
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 http://127.0.0.1:8000/1
Hoping that everything is set, start the local server, and you will see everything displayed.
Conclusion
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.