How to handle enums as foreign keys?

Hello,

I’m looking for a way to define a few enums with the models and associate them with other models. I’ve found a good way to handle the enums as sub-classes of models through Django’s docs. This keeps the implementation clean for the enums which are attributes of the model. However, I get an error when I try to refer the sub-class in another model. Here is the example of the models I created.

# Car Model:
from django.db import models
from django.utils.translation import gettext_lazy as _

class Car(models.Model):
        name = models.CharField(max_length=30)

	class FuelType(models.TextChoices):
		GASOLINE = "G", _("Gas")
		DISEL = "D", _("Disel")
		ELECTRICITY = "E", _("Electric")

	fuel_type = models.CharField(
	    max_length=1,
	    choices=FuelType.choices,
	    default=FuelType.GASOLINE
	)

# Refilling Station Model - Assumes refill stations may support multiple fuel types.

class RefillStation(models.Model):
        name = models.CharField(max_length=30)
        supported_fuel_types = models.ManyToManyField(
	    "Car.FuelType", related_name="supported_refill_stations", blank=True, null=True
	)

This gives me error:

TypeError: ManyToManyField(<enum 'FuelType'>) is invalid. First parameter to ManyToManyField must be either a model, a model name, or the string 'self'

One way to resolve this is by splitting FuelType as its own model instead of the sub-class and manually add the enum values of the FuelType in migration file. IMO, creating a whole model for something that has a handful of values which rarely change seems like an overkill to me.

Is there any other better way to handle these relationships?

Hello there!
Actually, you can’t use a Enum value as an ForeignKey entry.

So this is one way to do it, even though it’s “overkill”

Another way to do it is like this:

# Car Model:
from django.db import models
from django.utils.translation import gettext_lazy as _


# No inheritance from models.TextChoices here
class FuelType:
	GASOLINE = "G"
	DISEL = "D"
	ELECTRICITY = "E"

        # define choices manually
        choices = [
            (GASOLINE,  _("Gas")),
            (DISEL, _("Disel")),
            (ELECTRICITY, _("Electric"))
        ]


class Car(models.Model):
        name = models.CharField(max_length=30)
	fuel_type = models.CharField(
	    max_length=1,
	    choices=FuelType.choices,
	    default=FuelType.GASOLINE
	)

# Refilling Station Model - Assumes refill stations may support multiple fuel types.

# Assuming that you're using postgres, you can use the ArrayField
from django.contrib.postgres.fields.array import ArrayField


class RefillStation(models.Model):
        name = models.CharField(max_length=30)
        supported_fuel_types = ArrayField(
           base_field=models.CharField(choices=FuelType.choices, max_length=1), blank=True,
	)

You can get more information on the ArrayField here.

1 Like

To add to @leandrodesouzadev answer, this definitely is not “overkill”. These types of tables are sometimes referred to as “code tables”, and are a very commonly-used technique with relational databases for ensuring data integrity.

I’ve got a system that we support that has about 20 such tables in it. Just about every other system usually has at least 3 to 5. Creating them is a standard step in our data modeling process.

2 Likes

Thanks for suggesting the Arrayfield.