- Tue 28 May 2024
- DataOps
- #serverless, #github, #django
TL;DR
I built a Django app to manage Kindle highlights and wanted to have the simplest task handler solution possible - mainly to send emails to users with some of their highlights and book recommendations.
- I used Django Management Commands for the logic of sending daily emails
- I used GitHub Actions for a simple (yet effective) task orchestration workflow
Django Management commands
First, I created a custom Command in highlights/management/commands/send_email.py:
from django.core.management.base import BaseCommand
from ...utils.mail_sender import MailSender
from ...models import Highlight
from django.contrib.auth.models import User
import logging
logger = logging.getLogger(__name__)
class Command(BaseCommand):
help = 'Sends a daily email to users with highlights'
def handle(self, *args, **kwargs):
users = User.objects.filter(emailconfig__send_emails=True)
for user in users:
number_of_highlights = user.emailconfig.number_of_highlights
highlights = Highlight.objects\
.filter(user_id=user.id)\
.order_by('?')\
.values('highlight', 'book__title', 'book__author')[:number_of_highlights]
if highlights:
logger.info(f'Sending email to {user.username} with {
number_of_highlights} highlights...')
MailSender(user.username, highlights).send_email()
else:
logger.warning(f'No highlights were found for {user.username}')
GitHub Actions
Then I added a GitHub Actions workflow in .github/workflows/send_email.yaml:
name: Send daily email with highlights
on:
schedule:
- cron: "0 14 * * *"
jobs:
send-emails:
runs-on: ubuntu-latest
env:
ENV1: ${{ secrets.ENV1 }}
ENV2: ${{ secrets.ENV2 }}
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: '3.x'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
- name: Send emails
run: python3 manage.py send_email
Why?
Whenever I needed a web app, I would always rely on Flask. Flask is fast and lightweight Python microframework, and usually all I needed for prototyping something from the ground up. However, I had always considered stepping up and using Django. After a few months, I can say I'm glad I took that step.
Django & manage.py
Django’s manage.py
is a utility script that acts as a command-line interface to various Django utilities and management commands. For my Kindle highlights management app, I used it to create a custom command to handle the logic of sending daily emails.
from django.core.management.base import BaseCommand
from ...utils.mail_sender import MailSender
from ...models import Highlight
from django.contrib.auth.models import User
import logging
logger = logging.getLogger(__name__)
class Command(BaseCommand):
help = 'Sends a daily email to users with highlights'
def handle(self, *args, **kwargs):
users = User.objects.filter(emailconfig__send_emails=True)
for user in users:
number_of_highlights = user.emailconfig.number_of_highlights
highlights = Highlight.objects\
.filter(user_id=user.id)\
.order_by('?')\
.values('highlight', 'book__title', 'book__author')[:number_of_highlights]
if highlights:
logger.info(f'Sending email to {user.username} with {
number_of_highlights} highlights...')
MailSender(user.username, highlights).send_email()
else:
logger.warning(f'No highlights were found for {user.username}')
The file name is
send_email.py
so, to call it, I only need to runpython3 manage.py send_email
.
I like custom commands because they allow for specific tasks to be separated from the main application logic, making the code cleaner and easier to maintain. These commands can be reused or changed without affecting other parts of the app, so if I want to adjust the email content or how highlights are selected, I can change it very easily. Also, Django management commands are easy to test and debug, which helps ensure that the email logic works correctly without needing to deploy the entire application.
GitHub Actions
GitHub Actions provides a flexible and really simple way to automate tasks. For this specific need, it was the perfect choice to handle the daily email-sending task for a few reasons:
- Setting up a workflow is straightforward due to the simple syntax and numerous pre-built actions and examples
- It runs inside a container ensuring a consistent and isolated environment that eliminates the "works on my machine" problem
- Since the code was already hosted on GitHub, using GitHub Actions for CI/CD and task scheduling meant I didn't need another tool or service, keeping everything managed in one place
- GitHub Actions supports various triggers and can run on different environments; I used a cron schedule to trigger the workflow daily at 14:00 UTC, ensuring users receive their emails at the same time each day
- Secrets and environment variables are securely managed by GitHub Actions, which was great for handling sensitive information like API keys and credentials to the database.
name: Send daily email with highlights
on:
schedule:
- cron: "0 14 * * *"
jobs:
send-emails:
runs-on: ubuntu-latest
env:
ENV1: ${{ secrets.ENV1 }}
ENV2: ${{ secrets.ENV2 }}
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: '3.x'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
- name: Send emails
run: python3 manage.py send_email