Adding nested models to existing Django project?

My app contains the following two models:

class Counter(models.Model):
	external_id = models.CharField(max_length=255)
	service = models.ForeignKey(Service, on_delete=models.CASCADE)
	unit = models.ForeignKey(Unit, on_delete=models.CASCADE)

class CounterValue(models.Model):
	counter = models.ForeignKey(Counter, on_delete=models.CASCADE)
	date = models.DateField()
	value = models.DecimalField(max_digits=10, decimal_places=3)
	current_baseline = models.BooleanField(default=False)

I need to add another Counter model, and wonder whether I should use an abstract base class –

# new base model
class Counter(models.Model):
	external_id = models.CharField(max_length=255)
	service = models.ForeignKey(Service, on_delete=models.CASCADE)

	class Meta:
		abstract=True

class ServiceCounter(Counter):
	pass

# former Counter() model
class UnitCounter(Counter):
	unit = models.ForeignKey(Unit, on_delete=models.CASCADE)

class CounterValue(models.Model):
	counter = models.ForeignKey(Counter, on_delete=models.CASCADE)
	date = models.DateField()
	value = models.DecimalField(max_digits=10, decimal_places=3)
	current_baseline = models.BooleanField(default=False)

– or multi-table inheritance –

# new model
class ServiceCounter(models.Model):
	external_id = models.CharField(max_length=255)
	service = models.ForeignKey(Service, on_delete=models.CASCADE)

# former Counter() model
class UnitCounter(ServiceCounter):
	unit = models.ForeignKey(Unit, on_delete=models.CASCADE)

	class Meta:
		db_table = 'services_counter'

class CounterValue(models.Model):
	counter = models.ForeignKey(ServiceCounter, on_delete=models.CASCADE)
	date = models.DateField()
	value = models.DecimalField(max_digits=10, decimal_places=3)
	current_baseline = models.BooleanField(default=False)

– to be able to filter CounterValue() objects with both ServiceCounter() and UnitCounter() objects.

If the abstract base class approach is recommended: How would I migrate the existing table ‘services_counter’ (which is currently used for the old Counter() model)?

An abstract model is purely a Python construct - it does not affect the database at all. It creates a set of definitions that can be used by the child classes.

In other words, if you have:

class MyModel(models.Model):
   ...

and you convert this to:

class MyBaseModel(models.Model):
    class Meta:
        abstract = True

class MyModel(MyBaseModel):
    ....

There is no migration created and the database does not change.

And, given how you have defined CounterValue with:

I’m not sure I understand what you mean by:

when there’s no apparent relationship between CounterValue and UnitCounter.

Thank you! The relation between CounterValue and UnitCounter does work in my second example, because UnitCounter is derived from ServiceCounter. I have tested this solution, and there remains only a single problem. Although I defined a custom manager to exclude UnitCounter instances when querying the ServiceCounter model like this –

class ServiceCounterManager(models.Manager):
    def get_queryset(self):
        return super(ServiceCounterManager, self).get_queryset().filter(counter_type='S')

class ServiceCounter(models.Model):
	objects = ServiceCounterManager()
	external_id = models.CharField(max_length=255)
	service = models.ForeignKey(Service, on_delete=models.CASCADE)
	counter_type = models.CharField(max_length=1, default='S')

class UnitCounter(ServiceCounter):
	objects = models.Manager()
	unit = models.ForeignKey(Unit, on_delete=models.CASCADE)

– added a pre-save signal for UnitCounter instances –

@receiver(pre_save, sender=UnitCounter)
def switch_counter_type(sender, **kwargs):
    kwargs['instance'].counter_type = 'U'

– but ServiceCounter.objects.all() still includes UnitCounter instances (counter_type == "U"). When I filter manually (ServiceCounter.filter(counter_type='S')), the correct objects are returned. What am I missing here?

Sorry, I copied a typo when defining the custom Manager (get_query_set instead of get_queryset). The typo is corrected in my previous post, and all is well now. Thanks again, Ken!