Tidbits | Aug. 20, 2018

Pro-Tip – Slugs as primary keys

by Flavio Curella |   More posts by Flavio

Slugs as primary keys

A very common pattern I've seen in Django Project is to have some kind of 'model-type' relationship, where you have some kind of object that can only belong to one of the types defined in the database.

A typical implementation would look something like this:

class EventType(models.Model):
    slug = models.SlugField(unique=True)
    name = models.CharField(max_length=100)


class Event(models.Model):
    type = models.ForeignKey(EventType, on_delete=models.CASCADE)
    title = models.CharField(max_length=100)
    day = models.DateField()

A subtle issue with this implementation is that you may have to query the DB for an EventType when creating an Event:

work_type = EventType.objects.get(slug='work')
Event.objects.create(
    type=work_type,
    title='release',
    day=datetime(2018, 12, 31),
)

In other words, the minimum amount of queries to create an Event is 2: a SELECT for fetching the type, and the INSERT that saves the instance.

If somehow you already have the pk of the EventType (for example, it might come from an API payload or from the URL), then you can easily avoid the lookup by setting the primary key directly into the type_id column:

def post(self, request, *args, **kwargs):
    type_id = self.kwargs['pk']
    Event.objects.create(
        type_id=type_id,
        title='release',
        day=datetime(2018, 12, 31),
    )

But dealing directly with column names is discouraged by the docs

However, your code should never have to deal with the database column name

We can get around this by instantiating an EventType instance with just the primary key:

def post(self, request, *args, **kwargs):
    type_id = self.kwargs['pk']
    Event.objects.create(
       type=EventType(pk=type_id),
       title='release',
       day=datetime(2018, 12, 31),
    )

But this requires us to know the numerical id beforehand.

We already have slug as source of uniqueness for the event_eventtypes table, and it's URL-friendly. We could just use that as the EventType primary key.

class EventType(models.Model):
    slug = models.SlugField(primary_key=True)
    name = models.CharField(max_length=100)


class Event(models.Model):
    type = models.ForeignKey(
        EventType, 
        on_delete=models.CASCADE,
    )
    title = models.CharField(max_length=100)
    day = models.DateField()
def post(self, request, *args, **kwargs):
    type_slug = self.kwargs['slug']
    Event.objects.create(
       type=EventType(pk=type_slug),
       title='release',
       day=datetime(2018, 12, 31),
    )

This also allows to easily create Events of a specific type without having to fetching any EventType. This is especially useful in data migrations, or tests:

types =  ['home', 'work', 'community']
for type_slug in types:
    Event.objects.create(
       type=EventType(pk=type_slug),
       title='release',
       day=datetime(2018, 12, 31),
    )

In cases like this, you may want to consider using a slug as primary key, rather than the default integer-based default. It's more performant and just as straight forward.

There are some caveat to consider, though: You won't be able to modify the slug from the Django admin.

Another thing to consider is that changing the type of your primary key is a one-way road: once you made the slug your pk, you won't be able to convert it back to a regular AutoField.


orm   slugs   django  

A very common pattern in a Django Project is to have some kind of 'model-type' relationship, where you have some kind of object that can only belong to one of the types defined in the database.{% else %}

2018-08-20T11:39:00 2019-08-30T14:31:01.258754 2018 orm,slugs,django