As mentioned in the Projects section, this website it’s a Flask (Python framework) web application and, initially, the blog section was a bunch of rendered .md (Markdown) files. This solution worked well, however, since the .md files were being stored in the application’s folder, every time I’ve created a new blog post, I’d have to rebuild the entire application. This was not a very practical setup and so I’ve started looking for alternatives.
As an aspiring web developer, I often listen to the Syntax podcast and Sanity is a regular sponsor of their shows. One day, they had the guys from Sanity in one of their Supper Club episodes and I though “hmm, could be a nice alternative for the setup that I currently have”. And so, I’ve decided to give it a try…
Ok but… What’s Sanity.io?
Sanity, as defined on Jamstack’s website, is “the platform for structured content”. It’s a powerful CMS built in JavaScript, highly customizable and with a real-time hosted data store.
Here’s the documentation on how to get started with Sanity.
Flask and Sanity integration
In this article, I’ll show you a similar setup to the one that I currently have on my personal website.
First, this is a the file structure that I’ve used:
rubenoliveira-tech/
app/
func_posts.py
routes.py
configs/
configs.ini
app.py
.env
01. Installing requirements
# Library to load the env vars
pip install python-dotenv
# Library to render text to HTML
pip install portabletext-html
02. Configs and environment variables
configs.ini
In this file, I’ve stored the Sanity API and CDN URL.
[sanity]
URL = <https://<ID>.api.sanity.io/>
CDN = <https://cdn.sanity.io/images/<ID>/production/>
.env
For the Sanity API token, I’ve set it as a env var.
CMS_TOKEN=<API_TOKEN>
03. Loading the environment variables
Inside the app.py file, we need to import the dotenv library and than load the variables.
from dotenv
import load_dotenv load_dotenv()
04. Working with Sanity API
In order to interact with the Sanity API, I’ve created a specific file called func_posts.py. Here, I’ve created 2 functions: get_all_posts() and get_post().
But first, we need to load the configuration settings and setup the authentication headers:
# Import modules
import requests
import configparser
from portabletext_html import PortableTextRenderer
# Set auth headers
CMS_TOKEN = "Bearer " + str(os.getenv("CMS_TOKEN"))
headers = {"Authorization": CMS_TOKEN}
# Setup configuration file
config = configparser.ConfigParser()
config.read('configs/configs.ini')
SANITY_URL = config['sanity']['URL']
get_all_posts()
This function gets all posts and compiles a list with: post id, post title and post creation date. This list is used to populate the POSTS page.
# Get all posts
def get_all_posts():
endpoint = "v2021-10-21/data/query/production?query="
# GROQ query to get all posts
query = "*[_type == 'post'] | {_id, title, _createdAt}"
data = requests.get(f"{SANITY_URL}{endpoint}{query}", headers = headers)
all_posts = data.json()
all_posts_list = [] # List to be sent to FE
# Loop through the fetched data and populate the posts list
for post in range(len(all_posts['result'])):
post_id = all_posts['result'][post]['_id']
post_title = all_posts['result'][post]['title']
post_created_at = time_formatter(all_posts['result'][post]['_createdAt'])
all_posts_list += [{'id': post_id, 'title': post_title, 'createdAt': post_created_at}]
return all_posts_list

get_post()
This function gets the post content by ID. This is used to populate the POST page.
# Get post by ID
def get_post(post_id):
endpoint = "v2021-10-21/data/query/production?query="
query = f"*[_id == '{post_id}']" # GROQ query to get post by ID
data = requests.get(f"{SANITY_URL}{endpoint}{query}", headers = headers)
post = data.json() block = 0 # Aux var for the 'for loop'
post_content = "" # Post content to be sent to FE
# Loop through the post and renders the content to be sent to the FE
for block in range(len(post['result'][0]['body'])):
if 'children' in post['result'][0]['body'][block]: # Text
renderer = PortableTextRenderer({
'_key': post['result'][0]['body'][block]['_key'],
'_type': post['result'][0]['body'][block]['_type'],
'children': post['result'][0]['body'][block]['children'],
'markDefs': post['result'][0]['body'][block]['markDefs'],
'style': post['result'][0]['body'][block]['style']
})
post_content += renderer.render()
if 'asset' in post['result'][0]['body'][block]: # Images
image_ref = post['result'][0]['body'][block]['asset']['_ref']
image_url = create_img_url(image_ref)
post_content += f'<img src = "{image_url}">'
block += 1
# Update pre to keep code format
post_content_fmt = post_content.replace('pre', 'pre')
return post_content_fmt
Additionally, I’ve created this extra function to build the URL for the images added to the posts.
# Create image URL
def create_img_url(ref):
cdn_url = config['sanity']['CDN']
image_ref_temp = ref.replace("image-", "") # Remove the 'image-'
# Replace the '-ext' by '.ext'
image_ref = image_ref_temp[:48] + image_ref_temp.replace(image_ref_temp[:49], ".")
image_url = cdn_url + image_ref
return image_url
And that’s it! I hope you find this post useful and feel free to reach me out through my social platforms if you’ve any doubt 🙂
See you on the next one!