0

I realize that the 3 models that I use right now have a ton of shared fields. I was wondering what the best way to condense these models would be. I've read some articles on metaclasses and model inheritance but wanted to see what the "best" way to do this would be.

models.py

class Car(models.Model):
    created = models.DateTimeField(auto_now_add=True)
    make = models.CharField(max_length=100)
    model = models.CharField(max_length=100)
    year = models.IntegerField(default=2021, validators=[MinValueValidator(1886), MaxValueValidator(datetime.now().year)])
    seats = models.PositiveSmallIntegerField()
    color = models.CharField(max_length=100)
    VIN = models.CharField(max_length=17, validators=[MinLengthValidator(11)])
    current_mileage = models.PositiveSmallIntegerField()
    service_interval = models.CharField(max_length=50)
    next_service = models.CharField(max_length=50)


class Truck(models.Model):
    created = models.DateTimeField(auto_now_add=True)
    make = models.CharField(max_length=100)
    model = models.CharField(max_length=100)
    year = models.IntegerField(default=datetime.now().year, validators=[MinValueValidator(1886), MaxValueValidator(datetime.now().year)])
    seats = models.PositiveSmallIntegerField()
    bed_length = models.CharField(max_length=100)
    color = models.CharField(max_length=100)
    VIN = models.CharField(max_length=17, validators=[MinLengthValidator(11)])
    current_mileage = models.PositiveSmallIntegerField()
    service_interval = models.CharField(max_length=50)
    next_service = models.CharField(max_length=50)


class Boat(models.Model):
    created = models.DateTimeField(auto_now_add=True)
    make = models.CharField(max_length=100)
    model = models.CharField(max_length=100)
    year = models.PositiveSmallIntegerField(default=datetime.now().year, validators=[MaxValueValidator(datetime.now().year)])
    length = models.CharField(max_length=100)
    width = models.CharField(max_length=100)
    HIN = models.CharField(max_length=14, validators=[MinLengthValidator(12)], blank=True)
    current_hours = models.PositiveSmallIntegerField()
    service_interval = models.CharField(max_length=50)
    next_service = models.CharField(max_length=50)

Jake Mulhern
  • 660
  • 5
  • 13

3 Answers3

2

You can use model inheritance or model Mixins

model inheritance with abstract base model:

class AbstractVehicle(models.Model):
    created = models.DateTimeField(auto_now_add=True)
    make = models.CharField(max_length=100)
    model = models.CharField(max_length=100)
    year = models.IntegerField(default=2021, validators=[MinValueValidator(1886), MaxValueValidator(datetime.now().year)])
    
    class Meta:
        abstract = True

class Car(AbstractVehicle):
    seats = models.PositiveSmallIntegerField()    
    color = models.CharField(max_length=100)
    VIN = models.CharField(max_length=17, validators=[MinLengthValidator(11)])
    current_mileage = models.PositiveSmallIntegerField()
    service_interval = models.CharField(max_length=50)
    next_service = models.CharField(max_length=50)


class Truck(AbstractVehicle):
    seats = models.PositiveSmallIntegerField()
    bed_length = models.CharField(max_length=100)
    color = models.CharField(max_length=100)
    VIN = models.CharField(max_length=17, validators=[MinLengthValidator(11)])
    current_mileage = models.PositiveSmallIntegerField()
    service_interval = models.CharField(max_length=50)
    next_service = models.CharField(max_length=50)


class Boat(AbstractVehicle):
    length = models.CharField(max_length=100)
    width = models.CharField(max_length=100)
    HIN = models.CharField(max_length=14, validators=[MinLengthValidator(12)], blank=True)
    current_hours = models.PositiveSmallIntegerField()
    service_interval = models.CharField(max_length=50)
    next_service = models.CharField(max_length=50)

model mixins:

class VehicleMixin(object):
    created = models.DateTimeField(auto_now_add=True)
    make = models.CharField(max_length=100)
    model = models.CharField(max_length=100)
    year = models.IntegerField(default=2021, validators=[MinValueValidator(1886), MaxValueValidator(datetime.now().year)])

class Car(VehicleMixin, models.Model):
    seats = models.PositiveSmallIntegerField()    
    color = models.CharField(max_length=100)
    VIN = models.CharField(max_length=17, validators=[MinLengthValidator(11)])
    current_mileage = models.PositiveSmallIntegerField()
    service_interval = models.CharField(max_length=50)
    next_service = models.CharField(max_length=50)


class Truck(VehicleMixin, models.Model):
    seats = models.PositiveSmallIntegerField()
    bed_length = models.CharField(max_length=100)
    color = models.CharField(max_length=100)
    VIN = models.CharField(max_length=17, validators=[MinLengthValidator(11)])
    current_mileage = models.PositiveSmallIntegerField()
    service_interval = models.CharField(max_length=50)
    next_service = models.CharField(max_length=50)


class Boat(VehicleMixin, models.Model):
    length = models.CharField(max_length=100)
    width = models.CharField(max_length=100)
    HIN = models.CharField(max_length=14, validators=[MinLengthValidator(12)], blank=True)
    current_hours = models.PositiveSmallIntegerField()
    service_interval = models.CharField(max_length=50)
    next_service = models.CharField(max_length=50)

Both of these solutions will leave you with the same tables as your code. Also these are just simple examples. You may be able to improve them further. For Example you could add even more mixins or abstract base classes that contain the other fields.

SMoenig
  • 486
  • 3
  • 11
  • 1
    Do you know if there are any pros/cons between the two approaches? I was planning on using the model inheritance approach but would like to be able to discuss the benefits of that over using Mixins. – Jake Mulhern Mar 16 '21 at 16:22
  • 1
    I believe this might help you: https://stackoverflow.com/questions/3263417/django-abstract-models-vs-simple-python-mixins-vs-python-abcs – SMoenig Mar 16 '21 at 16:25
2

You can combine the model by creating Vehicle and VehicleType in vehicle model you keep all common vehicle fields, in VehicleType keep the car, truck,boat etc with foreign-key reference to Vehicle model.

class VehicleType(models.Model):
    name = models.CharField(max_length=100, unique=True)
    created = models.DateTimeField(auto_now_add=True)

class Vehicle(models.Model):
    vehicle_type = models.Foreinkey(VehicleType, on_delete=model.CASCADE)
    created = models.DateTimeField(auto_now_add=True)
    make = models.CharField(max_length=100)
    model = models.CharField(max_length=100)
    year = models.IntegerField(default=datetime.now().year, validators=[MinValueValidator(1886), MaxValueValidator(datetime.now().year)])
    seats = models.PositiveSmallIntegerField()
    bed_length = models.CharField(max_length=100)
    color = models.CharField(max_length=100)
    VIN = models.CharField(max_length=17, validators=[MinLengthValidator(11)])
    current_mileage = models.PositiveSmallIntegerField()
    service_interval = models.CharField(max_length=50)
    next_service = models.CharField(max_length=50)
Prakash
  • 391
  • 3
  • 4
  • So the Car, Truck, and Boat models only have a foreignKey reference to Vehicle? – Jake Mulhern Mar 16 '21 at 16:43
  • No, this would make it so that you would only have one table Vehicle where each entry can be a Car, Truck or Boat. The type for each object would be given via the vehicle_type ForeignKey. – SMoenig Mar 17 '21 at 01:23
2

I can notice that some fields are similar but aren't the same, for example year are not the same for all. Due to that I propose to you the following.

class TimeStampedModel(models.Model):
    created = models.DateTimeField(auto_now_add=True)
    modified = models.DateTimeField(auto_now=True)

    class Meta:
        abstract = True


class Vehicule(TimeStampedModel):
    make = models.CharField(max_length=100)
    model = models.CharField(max_length=100)
    service_interval = models.CharField(max_length=50)
    next_service = models.CharField(max_length=50)

    class Meta:
        abstract = True


class WheeledVehicle(Vehicule):
    seats = models.PositiveSmallIntegerField()
    color = models.CharField(max_length=100)
    VIN = models.CharField(max_length=17, validators=[MinLengthValidator(11)])
    current_mileage = models.PositiveSmallIntegerField()

    class Meta:
        abstract = True


class Car(WheeledVehicle):
    year = models.IntegerField(default=2021, validators=[MinValueValidator(1886), MaxValueValidator(datetime.now().year)])
    
    
class Truck(WheeledVehicle):
    year = models.IntegerField(default=datetime.now().year, validators=[MinValueValidator(1886), MaxValueValidator(datetime.now().year)])
    bed_length = models.CharField(max_length=100)
    

class Boat(Vehicule):
    year = models.PositiveSmallIntegerField(default=datetime.now().year, validators=[MaxValueValidator(datetime.now().year)])
    length = models.CharField(max_length=100)
    width = models.CharField(max_length=100)
    HIN = models.CharField(max_length=14, validators=[MinLengthValidator(12)], blank=True)
    current_hours = models.PositiveSmallIntegerField()

And if you want to go further, I suggest you to add created_by and modified_by fields to audit.

class TimeStampedModel(models.Model):
    created = models.DateTimeField(auto_now_add=True)
    modified = models.DateTimeField(auto_now=True)

    class Meta:
        abstract = True

class TimeStampedAuthModel(TimeStampedModel):
    created_by = models.ForeignKey(settings.AUTH_USER_MODEL, null=True, on_delete=models.SET_NULL,
                                   related_name="%(app_label)s_%(class)s_created_by")
    modified_by = models.ForeignKey(settings.AUTH_USER_MODEL, null=True, on_delete=models.SET_NULL,
                                    related_name="%(app_label)s_%(class)s_modified_by")

    class Meta:
        abstract = True


class Vehicule(TimeStampedAuthModel):
    make = models.CharField(max_length=100)
    model = models.CharField(max_length=100)
    service_interval = models.CharField(max_length=50)
    next_service = models.CharField(max_length=50)

    class Meta:
        abstract = True


class WheeledVehicle(Vehicule):
    seats = models.PositiveSmallIntegerField()
    color = models.CharField(max_length=100)
    VIN = models.CharField(max_length=17, validators=[MinLengthValidator(11)])
    current_mileage = models.PositiveSmallIntegerField()

    class Meta:
        abstract = True


class Car(WheeledVehicle):
    year = models.IntegerField(default=2021, validators=[MinValueValidator(1886), MaxValueValidator(datetime.now().year)])
    
    
class Truck(WheeledVehicle):
    year = models.IntegerField(default=datetime.now().year, validators=[MinValueValidator(1886), MaxValueValidator(datetime.now().year)])
    bed_length = models.CharField(max_length=100)
    

class Boat(Vehicule):
    year = models.PositiveSmallIntegerField(default=datetime.now().year, validators=[MaxValueValidator(datetime.now().year)])
    length = models.CharField(max_length=100)
    width = models.CharField(max_length=100)
    HIN = models.CharField(max_length=14, validators=[MinLengthValidator(12)], blank=True)
    current_hours = models.PositiveSmallIntegerField()
Samir Hinojosa
  • 825
  • 7
  • 24