We are implementing a webservice, that displays data from my database with flask and sqlalchemy. The database schema is done, but now we struggle with the implementation of the slugs to display all the elements from the database.
Example of the database relation:
class Parent(Base, db.Model):
__tablename__ = "parent"
id = Column(Integer, primary_key=True)
name = Column(String(80), unique=True, nullable=False)
slug = Column(String, nullable=False, default=set_slug())
class Child(Base, db.Model):
__tablename__ = "child"
id = Column(Integer, primary_key=True)
parent_id = Column(Integer, ForeignKey("parent.id"), nullable=False)
name = Column(String(80), unique=True, nullable=False)
slug = Column(String, nullable=False, default=set_slug())
The default value for the slug is just the name, that get's slugified with the slugify method from the slugify package. In the database also exist elements with the same name, but they are different because of their relationship to the parent element. So the slug should be the same, if there are two elements with the same name but a different parent. Also if two elements have the same name and parent, the slug should be unique for both elements.
My first idea was to implement an attribute event for child.name and parent.name whenever a set event is triggered.
from functools import partial
from slugify import slugify
def set_slug(target: Base, value: str, oldvalue: str, initiator: Event, global_unique=False) -> None:
slug_value = slugify(value)
# build the database query
db_query = target.__class__.query
if not global_unique:
# case, the slug is local unique
db_query = db_query.filter(target.__class__.parent_id == target.parent_id )
all_slugs = [entry.slug for entry in db_query.all()]
if value and (not target.slug or value != oldvalue):
if slug_value not in all_slugs:
# case, the standard slug (name = slug) is available
target.slug = slug_value
else:
# case, the standard slug is already in use
counter = 1
while True:
# iterate as long as no unique slug was found
new_slug = f"{slug_value}-{counter}"
if new_slug not in all_slugs:
target.slug = new_slug
return
counter += 1
event.listen(Child.name, "set", set_slug, retval=False)
event.listen(Parent.name, "set", partial(set_slug, global_unique=True), retval=False)
The method works overall, if the target object already exists in the database. The problem is, that this doesn't work if the object was created, because every attribute is just None. Is there a way to get the parent_id in that state of the event or do you have any other ideas, how to implement that uniquify?