Want more shares and clicks online? Open Graph tags determine how your link appears when anyone shares it, including on social media sites.
We'll make a Django app in 4 steps that will:
Our final product will look like:
Here's an optional video walkthrough (featuring me 🙂):
Let’s go 🚀
Install the following Python packages:
pip install django pillow requests beautifulsoup4 emoji
Create the Django project and a new app:
django-admin startproject core .
python manage.py startapp ogsig
Add your new app to INSTALLED_APPS
in core/settings.py
INSTALLED_APPS = [
...
'ogsig',
]
We want to create a link to our generated image that is permanent. So we neeUse whichever provider you'd like. I'm using imgBB: it's simple and free for our simple use case.
We will download the Roboto font to use in our generated image.
ogsig/media
and add the downloaded font to it (e.g. ogsig/media/Roboto-Regular.ttf
)services.py
in the ogsig
app.import io
from PIL import Image, ImageDraw, ImageFont
import requests
from bs4 import BeautifulSoup
from typing import Tuple
import emoji
def fetch_page_info(url: str) -> Tuple[str, str]:
"""
Fetch title and description from a URL.
"""
response = requests.get(url)
if response.status_code != 200:
return "", ""
soup = BeautifulSoup(response.content, "html.parser")
title = soup.title.string if soup.title else ""
description = soup.find("meta", {"name": "description"})["content"] if soup.find("meta", {"name": "description"}) else ""
return title, description
def calculate_text_height(draw, text, font, max_width, padding):
"""
Calculate the height needed for a block of text.
"""
y = 0
for word in text.split():
if draw.textlength(word, font=font) > max_width - 2 * padding:
y += font.size
return y + font.size
def draw_line(draw, x, y, line, font, color):
"""
Draw a line of text at a specific position.
"""
draw.text((x, y), line.strip(), font=font, fill=color)
return y + font.size
def get_words(text):
return text.split()
def is_line_too_long(line, word, draw, font, max_width, padding):
"""
Check if adding a word to a line makes it too long.
"""
return draw.textlength(line + word, font=font) > max_width - 2 * padding
def draw_text(draw, x, y, text, font, max_width, padding, color=(0, 0, 0)):
"""
Draw text onto an image, avoiding emojis.
"""
line = ""
for word in get_words(text):
if emoji.is_emoji(word):
continue
if is_line_too_long(line, word, draw, font, max_width, padding):
y = draw_line(draw, x + padding, y, line, font, color)
line = ""
line += word + " "
return draw_line(draw, x + padding, y, line, font, color)
def create_og_image(title: str, description: str, url: str) -> str:
"""
Create an OpenGraph image with a title, description, and URL.
"""
og_width, og_height, padding = 1200, 630, 20
img = Image.new("RGB", (og_width, og_height), (255, 255, 255))
draw = ImageDraw.Draw(img)
title_font = ImageFont.truetype("ogsig/media/Roboto-Regular.ttf", 60)
desc_font = ImageFont.truetype("ogsig/media/Roboto-Regular.ttf", 45)
url_font = ImageFont.truetype("ogsig/media/Roboto-Regular.ttf", 30)
title_height = calculate_text_height(draw, title, title_font, og_width, padding)
desc_height = calculate_text_height(draw, description, desc_font, og_width, padding)
total_height = title_height + desc_height + padding
start_y = (og_height - total_height - url_font.size) // 2
next_y = draw_text(draw, 0, start_y, title, title_font, og_width, padding)
draw_text(draw, 0, next_y, description, desc_font, og_width, padding)
draw.text((padding, og_height - url_font.size - padding), url, font=url_font, fill=(0, 0, 255))
img.show()
# Convert the image to bytes
img_bytes_io = io.BytesIO()
img.save(img_bytes_io, format='PNG')
img_bytes = img_bytes_io.getvalue()
uploaded_image_info = upload_image(img_bytes)
return uploaded_image_info["data"]["url"]
def upload_image(image_bytes: bytes) -> dict:
"""
Upload an image to get a permanent URL for our OpenGraph image.
"""
url = "https://api.imgbb.com/1/upload"
params = {
"key": "ADD_YOUR_API_KEY"
} # Move this key to an environment variable if uploading the code or sharing it with others.
files = {"image": image_bytes}
response = requests.post(url, params=params, files=files)
if response.status_code != 200:
raise ValueError(f"Failed to upload image: {response.content}")
return response.json()
def generate_og_image(url: str) -> dict:
"""
Generate OpenGraph image and return details.
"""
title, description = fetch_page_info(url)
img_path = create_og_image(title, description, url)
return {"title": title, "description": description, "image_path": img_path}
views.py
in your 'ogsig' app, and add:from django.shortcuts import render
from .services import generate_og_image
def index(request):
if request.method == 'POST':
url = request.POST.get('url')
if url:
og_image_data = generate_og_image(url)
print(f'{og_image_data = }')
return render(request, 'preview.html', {
'og_image': og_image_data, 'page_url': url
})
return render(request, 'index.html')
templates
inside your "ogsig" app if it doesn't already exist.ogsig/templates
, create a new file called index.html
:<!DOCTYPE html>
<html>
<head>
<title>Generate OG Image</title>
</head>
<body>
<h1>Generate Open Graph Social Image</h1>
<form method="post" action="">
{% csrf_token %}
<label for="url">Enter URL:</label>
<input type="text" id="url" name="url">
<button type="submit">Generate</button>
</form>
</body>
</html>
ogsig/templates
, create a new file called preview.html
:<!DOCTYPE html>
<html>
<head>
<meta property="og:title" content="{{ og_image.title }}" />
<meta property="og:description" content="{{ og_image.description }}" />
<meta property="og:image" content="{{ og_image.image_path }}" />
<meta property="og:url" content="{{ og_image.url }}" />
<meta name="twitter:card" content="summary_large_image">
<title>Generated preview</title>
<style>
/* Reset some default browser styles */
body, h1, p, a, code {
margin: 0;
padding: 0;
font-family: Arial, sans-serif;
}
/* Apply a background color */
body {
background: #f9f9f9;
color: #333;
line-height: 1.6;
}
/* Wrap all page content */
.container {
max-width: 900px;
margin: 0 auto;
padding: 20px;
}
/* Add some spacing and formatting to headers */
h1 {
padding: 10px;
background-color: #333;
color: #fff;
text-align: center;
}
/* Style the section */
section {
margin-bottom: 20px;
padding: 20px;
background-color: #fff;
border: 1px solid #ddd;
border-radius: 8px;
}
/* Style the image */
img {
max-width: 100%;
height: auto;
border: 1px solid #ddd;
margin: 10px 0 0 0;
}
/* Style the code block */
code {
display: block;
padding: 10px;
background-color: #eee;
border: 1px solid #ddd;
border-radius: 4px;
}
/* Style the link */
a {
color: #0077cc;
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
</style>
</head>
<body>
<div class="container">
<h1>Your OG social image</h1>
<section>
<p>
Your OG Image url is: <a href="{{ og_image.image_path }}">{{ og_image.image_path }}</a>
</p>
<p>
To add the image to your website, add the following HTML into the <code><head> </code> section of your HTML page:
</p>
<code>
<meta name="twitter:image" content="{{ og_image.image_path }}" /><br>
<meta property="og:image" content="{{ og_image.image_path }}" />
</code>
</section>
<section>
Your generated OG social image for: <a href="{{ page_url }}" target="_blank">{{ page_url }}</a>
<a href="{{ og_image.image_path }}" target="_blank">
<img src="{{ og_image.image_path }}" alt="OG Image">
</a>
</section>
</div>
</body>
</html>
urls.py
in your ogsig
app and add:from django.urls import path
from . import views
urlpatterns = [
path('', views.index, name='index'),
]
core/urls.py
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('', include('ogsig.urls')),
]
python manage.py runserver
Congrats - you’ve now created a Django app that visits a page and then creates an social sharing image.
Ideas to expand your app further:
I want to release high-quality products as soon as possible. Probably like you, I want to make my Django product ideas become reality as soon as possible.
That's why I built Photon Designer - an entirely visual editor for building Django frontend at the speed that light hits your eyes. Photon Designer outputs neat, clean Django templates.