Search code examples
djangodjango-modelsdjango-rest-frameworkdry

How do I combine my Django models so that I am not repeating myself?


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)


Solution

  • 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()