How I Built a Resume Builder with Django and Fauna
Chukslord
Posted on April 2, 2021
Authored in connection with the Write With Fauna program.
For some time now, I have been learning about Fauna and exploring its serverless database management system by building various web applications with it. In this tutorial, I will walk you through a step-by-step guide that shows how I made a resume builder with Django and Fauna.
Prerequisites
To fully understand this tutorial, you are required to have the following in place:
- Python 3.7 or newer.
- Basic understanding of Fauna.
- Basic understanding of Django.
- A text editor.
Setting Up Fauna Database
To make use of Fauna, I had to set up required collections, indexes, and API keys. Below are the steps I took to achieve this:
Creating the Database
I created a database on Fauna’s dashboard for the project. To do this, login to your dashboard, click the New Database
button and provide the required information.
Creating the Collections
I needed to create collections for storing our data in Fauna. Collections are like tables in a standard database system. I created two collections for the resume builder project, which are Users
(storing registered users) and Resume_Info
collection (storing information used to generate the user’s resume).
To create collections in Fauna, navigate to the Collections
tab in Fauna’s dashboard side menu, click on the New Collection
button and provide the information required as seen in the images below:
The TTL
field is where you specify how long you want the data in the respective collections to last, while the History Days
stores historical records. For this project, I used the default value of 30 for History days
and left the TTL
blank.
Creating the Fauna Indexes
I needed to create indexes for browsing through data in the database collections. I created two indexes for this project, one for each collection named users_index
and resume_index
. I then set appropriate Terms
for them.
To create an index, navigate to the Indexes
tab in Fauna’s dashboard side menu, click on the New Index
button and provide the information required as seen in the images below:
For the users_index
, I set the connected collection to Users
, and the Terms
for browsing data with this index is the username
field. The Terms
are fields in the collection that Fauna will match when querying a collection with an index.
For the resume_index
, I set the connected collection to Resume_Info
and the Terms
to user
. Tick the unique checkbox for both indexes before saving to specify that all data queried is unique.
Creating the Database Security Key
To enable Django to interact with Fauna, a secret key is required by the Fauna Driver. To do this, navigate to the Security
tab in Fauna’s dashboard side menu, click on the New Key
button and provide the information required as seen in the images below:
After generating the secret key, you will be presented with a screen like the one above (hidden here as “SECRET_KEY” for privacy). Copy your secret key from the dashboard and save it somewhere you can easily retrieve it for later use.
Creating The Resume Builder Django Project
To install Django and Fauna, simply run the command below:
pip install django
pip install faunadb
At this point, I created the Django project using the commands below:
# create the django project
django-admin startproject RESUME_BUILDER
# change directory to the django project
cd RESUME_BUILDER
# create the django app
django-admin startapp App
In the command above, I first created a project called RESUME_BUILDER
, then changed the directory to the Django project. In the project directory, I created a new app called App
.
I then edited my settings.py
file in my project folder and added the following code. Navigate to the RESUME_BUILDER
directory and edit our settings.py
file by adding our app to the list of installed apps as seen below:
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'App'
]
I also defined the static and media paths at the end of the settings.py
file as seen in the code below:
STATIC_URL = '/static/'
STATIC_ROOT=os.path.join(BASE_DIR, 'static')
MEDIA_ROOT =os.path.join(BASE_DIR, 'media')
MEDIA_URL = '/media/'
Next, I edited the urls.py
file in the project folder with the code below, which links it to my app’s URLs.
from django.contrib import admin
from django.urls import path, include
from django.conf import settings
from django.contrib.staticfiles.urls import staticfiles_urlpatterns
from django.contrib.staticfiles.urls import static
urlpatterns = [
path('admin/', admin.site.urls),
path('', include("App.urls")),
]
urlpatterns += staticfiles_urlpatterns()
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
Finally, I made migrations to save and migrate all settings to the database. Run the code below in your command-line to make migrations.
python manage.py migrate
The Django Project Structure
I needed to structure the Django project in a particular way that follows Django’s rules and will make it easy to work with during this tutorial. To do this, navigate to the App
folder in your project, then create a new file, urls.py
.In the App
folder, create two new folders; templates
and static
. The template folder will house our HTML files, while the static folder will house our CSS, Javascript, and other static files. To do this, run the command below on your command-line.
#change directory to our App
cd App
mkdir templates
mkdir static
These commands will structure the App
folder as seen in the image below:
Defining the URL routes
A urls.py
in the App
folder will contain all defined URL routes for our app. To do this, create a urls.py
file and add the code as seen below:
from django.conf import settings
from django.conf.urls.static import static
from django.urls import path, include
from . import views
app_name = "App"
urlpatterns = [
path("", views.login, name="login"),
path("login", views.login, name="login"),
path("register", views.register, name="register"),
path("index", views.index, name="index"),
path("create-resume", views.create_resume, name="create-resume"),
path("resume", views.resume, name="resume"),
]
Building the User Interface
The user interface entails all the HTML, CSS, and Javascript files required to build a user-friendly interactive web application.
Creating the Static Files
I created two new files in the static
folder, styles.css
and script.js
. I then updated them with their respective CSS and Javascript codes as seen in the Github Gist here.
The Login Page
Users need to be authenticated to access the web application. In the templates
folder, I created a new file named login.html
and updated it with the code below:
{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<title>
Fauna Resume Builder
</title>
<link href="{% static './style.css' %}" rel="stylesheet"/>
</head>
<body>
<!-- partial:index.partial.html -->
<div id="javascript_header">
</div>
<form method="POST">
{% csrf_token %}
<div class="form__header">
<h1>
Login/Sign In
</h1>
<p>
Enter your login details below.
</p>
{% csrf_token %}
<ul class="messages">
{% for message in messages %}
<li{% if message.tags %} class="{{ message.tags }}"{% endif %} style="color:red;">{{ message }}</li>
{% endfor %}
</ul>
</div>
<div class="form-group">
<label for="name">
Username
<span>
*
</span>
</label>
<input id="username" name="username" type="text"/>
<div class="error" id="name__error">
</div>
</div>
<div class="form-group">
<label for="password">
Password
<span>
*
</span>
</label>
<input id="password" name="password" type="password"/>
</div>
<div class="line-break">
</div>
<input id="login" type="submit" value="Login"/>
Don't have an account? <a href="register">Sign Up</a>
</form>
<!-- partial -->
<script src="{% static './script.js' %}">
</script>
</body>
</html>
The Register Page
For users to successfully authenticate on the login page, they need to create login details on the register page. To achieve this, in the templates folder, I created a register.html
file and added the below HTML code:
{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<title>
Fauna Resume Builder
</title>
<link href="{% static './style.css' %}" rel="stylesheet"/>
</head>
<body>
<!-- partial:index.partial.html -->
<div id="javascript_header">
</div>
<form method="POST">
{% csrf_token %}
<div class="form__header">
<h1>
Register/Sign Up
</h1>
<p>
Enter your login details below.
</p>
</div>
<div class="form-group">
<label for="name">
Username
<span>
*
</span>
</label>
<input id="username" name="username" type="text"/>
<div class="error" id="name__error">
</div>
</div>
<div class="form-group">
<label for="email">
Email
<span>
*
</span>
</label>
<input id="email" name="email" type="email"/>
<div class="error" id="name__error">
</div>
</div>
<div class="form-group">
<label for="password">
Password
<span>
*
</span>
</label>
<input id="password" name="password" type="password"/>
</div>
<div class="line-break">
</div>
<input id="register" type="submit" value="Register"/>
Already have an account? <a href="login">Sign In</a>
</form>
<!-- partial -->
<script src="{% static './script.js' %}">
</script>
</body>
</html>
The Dashboard Page
This page is where users can access the functionalities of the resume builder easily. To achieve this, I created an index.html
file in the templates
folder and added the below HTML code:
{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<title>
Fauna Resume Builder
</title>
<link href="{% static './style.css' %}" rel="stylesheet"/>
</head>
<body>
<!-- partial:index.partial.html -->
<div id="javascript_header">
</div>
<form>
<div class="form__header">
<h1>
Build Your Resume
</h1>
<p>
Dashboard
</p>
</div>
<div class="line-break">
</div>
<a class="button" href="create-resume"> Create/Edit Resume</a>
<div class="line-break">
</div>
<a class="button" href="resume"> View Resume</a>
<div class="line-break">
</div>
<a class="button" style="background-color:red;" href="login"> Logout </a>
</form>
<!-- partial -->
<script src="{% static './script.js' %}">
</script>
</body>
</html>
The Create Resume Page
On this page, users submit their resume info for their resume to be generated. To achieve this page, I created a create-resume.html
page in the templates
folder and added the code below:
{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<title>
Fauna Resume Builder
</title>
<link href="{% static './style.css' %}" rel="stylesheet"/>
</head>
<body>
<!-- partial:index.partial.html -->
<div id="javascript_header">
</div>
<form method="POST">
{% csrf_token %}
<ul class="messages">
{% for message in messages %}
<li{% if message.tags %} class="{{ message.tags }}"{% endif %} style="color:red;">{{ message }}</li>
{% endfor %}
</ul>
<div class="form__header">
<h1>
Build Your Resume
</h1>
<p>
Enter the fields below to generate a resume file.
</p>
</div>
<h2>
Personal Details
</h2>
<div class="form-group">
<label for="name">
Full Name
<span>
*
</span>
</label>
<input id="name" name="name" value="{{resume_info.full_name}}" type="text"/>
<div class="error" id="name__error">
</div>
</div>
<div class="form-group">
<label for="address">
Address
</label>
<input id="address" name="address" value="{{resume_info.address}}" type="text"/>
</div>
<div class="form-group">
<label for="phone">
Phone
</label>
<input id="phone" name="phone" value="{{resume_info.phone}}" type="text"/>
</div>
<div class="form-group">
<label for="email">
Email
<span>
*
</span>
</label>
<input id="email" name="email" value="{{resume_info.email}}" type="email"/>
<div class="error" id="email__error">
</div>
</div>
<div class="form-group">
<label for="about">
About You
</label>
<textarea id="about" name="about" placeholder="{{resume_info.about_you}}"></textarea>
</div>
<div class="form-group">
<label for="career">
Career Objectives
</label>
<textarea id="career" name="career" placeholder="{{resume_info.career}}"></textarea>
</div>
<div class="form-group">
<label for="education">
Education
</label>
<textarea id="education" name="education" placeholder="{{resume_info.education}}"></textarea>
</div>
<div class="line-break">
</div>
<h2>
Work Experience
</h2>
<h3>
Most Recent Job
</h3>
<div class="form-date-group">
<div class="form-group">
<label for="job-1__start">
Start Date
</label>
<input id="job-1__start" name="job-1__start" value="{{resume_info.job_1__start}}" type="date"/>
</div>
<div class="form-group">
<label for="job-1__end">
End Date
</label>
<input id="job-1__end" name="job-1__end" value="{{resume_info.job_1__end}}" type="date"/>
</div>
</div>
<div class="form-group">
<label for="job-1__details">
Position Details
</label>
<textarea id="job-1__details" name="job-1__details" placeholder="{{resume_info.job_1__details}}"></textarea>
</div>
<div class="line-break">
</div>
<h3>
Past Job
</h3>
<div class="form-date-group">
<div class="form-group">
<label for="job-2__start">
Start Date
</label>
<input id="job-2__start" name="job-2__start" value="{{resume_info.job_2__start}}" type="date"/>
</div>
<div class="form-group">
<label for="job-2__end">
End Date
</label>
<input id="job-2__end" name="job-2__end" value="{{resume_info.job_2__end}}" type="date"/>
</div>
</div>
<div class="form-group">
<label for="job-2__details">
Position Details
</label>
<textarea id="job-2__details" name="job-2__details" placeholder="{{resume_info.job_2__details}}"></textarea>
</div>
<div class="line-break">
</div>
<h3>
Another Past Job
</h3>
<div class="form-date-group">
<div class="form-group">
<label for="job-3__start">
Start Date
</label>
<input id="job-3__start" name="job-3__start" value="{{resume_info.job_3__start}}" type="date"/>
</div>
<div class="form-group">
<label for="job-3__end">
End Date
</label>
<input id="job-3__end" name="job-3__end" value="{{resume_info.job_3__end}}" type="date"/>
</div>
</div>
<div class="form-group">
<label for="job-3__details">
Position Details
</label>
<textarea id="job-3__details" name="job-3__details" placeholder="{{resume_info.job_3__details}}"></textarea>
</div>
<div class="line-break">
</div>
<div class="form-group">
<label for="references">
References
</label>
<textarea id="references" name="references" placeholder="{{resume_info.references}}"></textarea>
</div>
<div class="line-break">
</div>
<input type="submit" value="Submit Info"/>
<br>
<a class="button"style="background-color:red;" href="index">Back to home</a>
</form>
<!-- partial -->
<script src="{% static './script.js' %}">
</script>
</body>
</html>
The Resume Page
The resume page is where the generated resume generated would be displayed for the user to view. To achieve this page, I created a new file resume.html
and added the code below:
<html>
<head>
<title>{% if resume_info.full_name %}{{resume_info.full_name}}'s Resume {% endif %}</title>
<style>
@import url('https://fonts.googleapis.com/css?family=Poppins:400,600&display=swap');
body {
font-family: 'Poppins', sans-serif;
background: #fafafa;
color: rgba(0,0,0,0.75);
}
h1 {
color: rgba(0,0,0,0.9);
}
h1, p {
box-sizing: border-box;
margin: 0px;
padding: 0px 24px;
}
.line-break {
width: 100%;
height: 1px;
margin: 16px auto;
border-bottom: 1px solid #eee;
}
.resume {
border-radius: 8px;
box-sizing: border-box;
display: flex;
flex-direction: column;
max-width: 800px;
margin: 48px auto;
padding: 16px 0px;
background: white;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.02), 0 1px 2px rgba(0, 0, 0, 0.14);
}
.resume-group {
box-sizing: border-box;
padding: 8px 0px;
width: 100%;
display: flex;
border-left: 3px solid transparent;
transition: 0.2s;
}
.resume-group:hover {
border-left: 3px solid #64FFDA;
}
.left-col {
width: 35%;
}
.right-col {
width: 65%;
}
.instructions {
opacity: 0.5;
text-align: center;
font-size: 0.8rem;
margin: 16px auto;
}
</style>
</head>
<body>
<div class="resume">
{% if resume_info.full_name %}
<h1>{{resume_info.full_name}}</h1>
{% endif %}
{% if resume_info.email %}
<p>{{resume_info.email}}</p>
{% endif %}
{% if resume_info.phone %}
<p>{{resume_info.phone}}</p>
{% endif %}
{% if resume_info.address %}
<p>{{resume_info.address}}</p>
{% endif %}
<div class="line-break"></div>
{% if resume_info.about_you%}
<div class="resume-group">
<div class="left-col"><p>ABOUT ME</p></div>
<div class="right-col">
<p>{{resume_info.about_you}}
</p>
</div>
</div>
{% endif %}
{% if resume_info.career %}
<div class="resume-group"><div class="left-col">
<p>CAREER OBJECTIVES</p>
</div>
<div class="right-col">
<p>{{resume_info.career}}</p>
</div>
</div>
{% endif %}
{% if resume_info.education %}
<div class="resume-group">
<div class="left-col">
<p>EDUCATION</p>
</div>
<div class="right-col">
<p>{{resume_info.education}}</p>
</div>
</div>
{% endif %}
<div class="resume-group">
<div class="left-col">
<p>EMPLOYMENT EXPERIENCE</p>
</div>
<div class="right-col">
<p></p>
</div>
</div>
{% if resume_info.job_1__start %}
<div class="resume-group">
<div class="left-col">
<p>{{resume_info.job_1__start}} to {{resume_info.job_1__end}}</p>
</div>
<div class="right-col">
<p>{{resume_info.job_1__details}}
</p>
</div>
</div>
{% endif %}
{% if resume_info.job_2__start %}
<div class="resume-group">
<div class="left-col">
<p>{{resume_info.job_2__start}} to {{resume_info.job_2__end}}</p>
</div>
<div class="right-col">
<p>{{resume_info.job_2__details}}
</p>
</div>
</div>
{% endif %}
{% if resume_info.job_3__start %}
<div class="resume-group">
<div class="left-col">
<p>{{resume_info.job_3__start}} to {{resume_info.job_3__end}}</p>
</div>
<div class="right-col">
<p>{{resume_info.job_3__details}}
</p>
</div>
</div>
{% endif %}
<div class="resume-group">
<div class="left-col">
<p>REFERENCES</p>
</div>
<div class="right-col">
<p>{% if resume_info.references %}{{resume_info.references}}{% else %}Available upon request{% endif %}</p>
</div>
</div>
</div>
<div class="instructions">Ctrl + P and "Save As PDF" to make a copy of this resume</div>
</body>
</html>
Building the Functionalities
We would now be focusing on how I created the app logic. In Django, the logic is built majorly in the views.py
file in the App
folder.
Importing the Required Modules
Firstly, I imported the modules required for this project to work in the views.py
file as seen in the code below:
from django.shortcuts import render,redirect
from django.contrib import messages
from django.http import HttpResponseNotFound
from faunadb import query as q
import pytz
from faunadb.objects import Ref
from faunadb.client import FaunaClient
import hashlib
import datetime
Initializing the FQL Client and Index
To allow the python app to interact with Fauna easily, I needed to initialize the client and indexes using the secret key I saved earlier. To do this, I added the code below to the views.py
file.
client = FaunaClient(secret="SECRET_KEY")
indexes = client.query(q.paginate(q.indexes()))
The Login Functionality
To create the logic for the login page, I added the code below to my views.py
as seen below:
def login(request):
if request.method == "POST":
username = request.POST.get("username").strip().lower()
password = request.POST.get("password")
try:
user = client.query(q.get(q.match(q.index("users_index"), username)))
if hashlib.sha512(password.encode()).hexdigest() == user["data"]["password"]:
request.session["user"] = {
"id": user["ref"].id(),
"username": user["data"]["username"]
}
return redirect("App:index")
else:
raise Exception()
except:
messages.add_message(request, messages.INFO,"You have supplied invalid login credentials, please try again!", "danger")
return redirect("App:login")
return render(request,"login.html")
In the code above, I checked if a POST request was received. If one was received, I collected the required data expected to be obtained from the POST request, which is the username and the password of the user. I then used the data stored in the username variable as a matching parameter for the FQL client to utilize in querying the Users
collection for a user matching the data in the username field using the users_index
index. If the user existed, I checked if the password for that users matched the one provided. If the passwords matched, the user is successfully authenticated, and the username is stored in the session. However, if the user does not exist or the passwords provided don’t match, then a message, “You have supplied invalid login credentials” is rendered.
The Register Functionality
To create the logic for the register page, I added the code below to my views.py
as seen below:
def register(request):
if request.method == "POST":
username = request.POST.get("username").strip().lower()
email = request.POST.get("email").strip().lower()
password = request.POST.get("password")
try:
user = client.query(q.get(q.match(q.index("users_index"), username)))
messages.add_message(request, messages.INFO, 'User already exists with that username.')
return redirect("App:register")
except:
user = client.query(q.create(q.collection("Users"), {
"data": {
"username": username,
"email": email,
"password": hashlib.sha512(password.encode()).hexdigest(),
"date": datetime.datetime.now(pytz.UTC)
}
}))
messages.add_message(request, messages.INFO, 'Registration successful.')
return redirect("App:login")
return render(request,"register.html")
In the code above, I checked if a POST request was received. If one was received, I then collected the required data expected to be obtained from the POST request, which is the user’s username, email, and password. I then used the data stored in the username as a matching parameter for the FQL client to utilize in querying the Users
collection for a user matching the data in the username field using the users_index
index. If the user existed, a message, “ User already exists with that username” is rendered. However, if there is no match in the database, an account with the required info is created in the database by making a query to the Users
collection using the FQL client.
The Dashboard
For the dashboard, no logic is required. I simply rendered the “index.html” file with the code below:
def index(request):
return render(request,"index.html")
The Create Resume Functionality
I added the code seen below to the views.py
file to implement the resume creation logic for the create resume functionality.
def create_resume(request):
if request.method=="POST":
username=request.session["user"]["username"]
full_name=request.POST.get("name")
address=request.POST.get("address")
phone=request.POST.get("phone")
email=request.POST.get("email")
about_you=request.POST.get("about")
education=request.POST.get("education")
career=request.POST.get("career")
job_1__start=request.POST.get("job-1__start")
job_1__end=request.POST.get("job-1__end")
job_1__details=request.POST.get("job-1__details")
job_2__start=request.POST.get("job-2__start")
job_2__end=request.POST.get("job-2__end")
job_2__details=request.POST.get("job-2__details")
job_3__start=request.POST.get("job-3__start")
job_3__end=request.POST.get("job-3__end")
job_3__details=request.POST.get("job-3__details")
references=request.POST.get("references")
try:
resume = client.query(q.get(q.match(q.index("resume_index"), username)))
quiz = client.query(q.update(q.ref(q.collection("Resume_Info"),resume["ref"].id()), {
"data": {
"user":username,
"full_name": full_name,
"address": address,
"phone": phone,
"email":email,
"about_you":about_you,
"education":education,
"career":career,
"job_1__start":job_1__start,
"job_1__end":job_1__end,
"job_1__details":job_1__details,
"job_2__start":job_2__start,
"job_2__end":job_2__end,
"job_2__details":job_2__details,
"job_3__start":job_3__start,
"job_3__end":job_3__end,
"job_3__details":job_3__details,
}
}))
messages.add_message(request, messages.INFO, 'Resume Info Edited Successfully. Download Resume Now')
return redirect("App:create-resume")
except:
quiz = client.query(q.create(q.collection("Resume_Info"), {
"data": {
"user":username,
"full_name": full_name,
"address": address,
"phone": phone,
"email":email,
"about_you":about_you,
"education":education,
"job_1__start":job_1__start,
"job_1__end":job_1__end,
"job_1__details":job_1__details,
"job_2__start":job_2__start,
"job_2__end":job_2__end,
"job_2__details":job_2__details,
"job_3__start":job_3__start,
"job_3__end":job_3__end,
"job_3__details":job_3__details,
}
}))
messages.add_message(request, messages.INFO, 'Resume Info Saved Successfully. Download Resume Now')
return redirect("App:resume")
else:
try:
resume_info = client.query(q.get(q.match(q.index("resume_index"), request.session["user"]["username"])))["data"]
context={"resume_info":resume_info}
return render(request,"create-resume.html",context)
except:
return render(request,"create-resume.html")
In the code above, I checked if a POST request was received. If one was received, I collected the required data from it and used it to generate the user’s resume. I then checked if the user has already submitted resume information earlier by using the username of the user stored in the session as a matching parameter for the FQL client to utilize in querying the database for data that match the user field of the Resume_Info
collection. If the user didn’t have any resume information saved before, then documents will be created using the resume information collected. However, if the user has saved his/her resume information earlier, then the resume info is updated using the new resume_info provided. I also rendered the resume info data for the user in the respective field to the create resume page using context.
The Resume Page Functionality
The only functionality this page has is to render already saved data on the resume page.
I achieved this by making a query using the FQL to get the resume info for the current user by using the username as a match for the user field in the Resume_Info
collection. I then passed the data retrieved to context which will be rendered on the resume page. To achieve this I added the code below and add to my views.py
file as seen below:
def resume(request):
try:
resume_info = client.query(q.get(q.match(q.index("resume_index"), request.session["user"]["username"])))["data"]
context={"resume_info":resume_info}
return render(request,"resume.html",context)
except:
return render(request,"resume.html")
Finally, I succeeded in building my resume builder web application. If you followed through with this tutorial, run your article by executing the below command.
python manage.py runserver
You can now view your application on your localhost
Conclusion
In this article, I walked you through a step-by-step guide showing how I built a resume builder using Fauna and Django. We saw how easy it is to integrate Fauna into a Python/Django application and got our hands dirty by exploring some of its core features and functionalities.
The source code of the resume builder application is available on Github, and you can access a live demo here. If you have any questions, don't hesitate to contact me on Twitter: @LordChuks3.
Posted on April 2, 2021
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.