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 Event
s 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
.