Using model callbacks in SQLAlchemy to generate slugs

Feb 7th, 2017 in  by Michael Cho

Dive into SQLAlchemy's little known `event.listen` method to automatically generate slugs when saving a model instance.

When working with Python's SQLAlchemy I find myself missing Rails' active record callbacks, which were an easy way to insert hooks before and after saving any database records. 

One scenario where this is especially useful is for generating slugs, a URL-valid and SEO friendly version of a blog post title for example. If I have a BlogPost model with a title field, I want a BlogPost instance with a title "Dive into Python" to be automatically saved with a slug "dive-into-python". 

This can be accomplished using the event.listen method from SQLAlchemy. Here is an example:


from sqlalchemy import event
from slugify import slugify

from models import db, BaseModel


class BlogPost(BaseModel):
    __tablename__ = 'blog_posts'
    post_id = db.Column(db.Integer, primary_key=True)
    post_title = db.Column(db.String(140))
    slug = db.Column(db.String(140))
    post_content = db.Column(db.Text)
    create_time = db.Column(db.DateTime, server_default=db.func.current_timestamp())

    @staticmethod
    def generate_slug(target, value, oldvalue, initiator):
        if value and (not target.slug or value != oldvalue):
            target.slug = slugify(value)

event.listen(BlogPost.post_title, 'set', BlogPost.generate_slug, retval=False)

This creates a listener so that everytime `post_title` is set, the `BlogPost.generate_slug` method will be called. The `target` in this method is the instance object, and the (new) `value` and `oldvalue` are also available.

I have set the `generate_slug` method to use python-slugify to generate the slugs.

Of course, if you have many models which need slugs you could also move this method into the BaseModel class which BlogPost is inheriting from instead.


Other articles you may like

Method delegation in Python
Jul 11th, 2018
Using Python enums in SQLAlchemy models
May 16th, 2018
Python command-line scripts with argparse
Feb 15th, 2018
SQLAlchemy commit(), flush(), expire(), refresh(), merge() - what's the difference?
Nov 2nd, 2017
Prioritized Code Review Checklist - what to look for first, second, and last
Sep 21st, 2017