← All Articles

Using model callbacks in SQLAlchemy to generate slugs

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())

    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.

Made with JoyBird