Jake Mulhern
Jake Mulhern

Reputation: 772

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)

Upvotes: 0

Views: 119

Answers (3)

Samir Hinojosa
Samir Hinojosa

Reputation: 825

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

Upvotes: 2

Prakash
Prakash

Reputation: 411

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)

Upvotes: 2

SMoenig
SMoenig

Reputation: 514

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.

Upvotes: 2

Related Questions