Django with Abstract Base Classes & Composition

I’m following this Python tutorial from ArjanCodes that talks about composition over inheritance and I wonder how to implement it in Django.

I would like to implement the example used (see code below) and I wonder how to implement it in Django. I’m thinking in applying the SOLID principles using interfaces.

The questions are related to Django Models.

  1. How to use Python’s abc module to implement interfaces.
  2. How to use composition instead of inhertinace.

1. How to use Python’s abc module to implement interfaces.

I would like to design the model as an interface, having the implementation in the sublcasses, like this:

class Contract(ABC):
    """Represents a contract and a payment process for a particular employeee."""

    @abstractmethod
    def get_payment(self) -> float:
        """Compute how much to pay an employee under this contract."""


@dataclass
class HourlyContract(Contract):
    """Contract type for an employee being paid on an hourly basis."""

    pay_rate: float
    hours_worked: int = 0
    employer_cost: float = 1000

    def get_payment(self) -> float:
        return self.pay_rate * self.hours_worked + self.employer_cost

I don’t know if the abstract model class ( abstract = True) can do this.
I’ve read this answers:

They propose a solution:

import abc

from django.db import models


class AbstractModelMeta(abc.ABCMeta, type(models.Model)):
    pass


class AbstractModel(models.Model, metaclass=AbstractModelMeta):    
    # You may have common fields here.

    class Meta:
        abstract = True

    @abc.abstractmethod
    def must_implement(self):
        pass


class MyModel(AbstractModel):
    code = models.CharField("code", max_length=10, unique=True)

    class Meta:
        app_label = 'my_app'

But I don’t know if this is a good solution.

2. How to implement composition instead of using inhertinace.

It seems that you can apply composition using relationships within your models (like OneToOne).

Is there a different way? Like using Dependency Injection in the traditional sense?

Like this:

@dataclass
class Employee:
    """Basic representation of an employee at the company."""

    name: str
    id: int
    contract: Contract #  ***Dependency Injection here***
    commission: Optional[Commission] = None

    def compute_pay(self) -> float:
        """Compute how much the employee should be paid."""
        payout = self.contract.get_payment()
        if self.commission is not None:
            payout += self.commission.get_payment()
        return payout

henry_contract = HourlyContract(pay_rate=50, hours_worked=100)
henry = Employee(name="Henry", id=12346, contract=henry_contract)

The full example code is here:

Sorry if this is too long.
Thanks.

<opinion>
There are a number of reasons why I think you should ignore all this non-Django oriented architecture advice when working with Django.

Django has a well-established and stable architecture that has been established over the past 20 years, and trying to force additional patterns and ideas into it doesn’t tend to work well.

Keep in mind that you’re working within a framework, not developing a system that happens to use Django as a library.

For example, for what you’re describing with the models, Django Model classes are not your typical Python class. They’re constructed using a great degree of metaprogramming within the models. I wouldn’t think about altering what those classes are doing without ensuring a very deep undertanding of how those models work internally.

Yes, I understand the ideas and concepts behind SOLID, and if I were considering the construction of a web framework from scratch, I might even consider implementing them.

But that’s not the case here.

I have an even more fundamental principle that I follow, and that’s “Don’t fight the framework”. When I’m working in a new-to-me framework, I look for the architecture, patterns, and techniques used by that framework, and try to adjust my approach accordingly.

So at the end of it all, unless you can identify a specific and quantifiable improvement to be gained by worrying about issues like this, I would say that I believe you’re wasting time trying to solve non-existent problems.

</opinion>

2 Likes

Hi Ken, thanks for the insight.

I wonder as the solid principles are quiet interesting for developing robust applications: isn’t SOLID used with django apps?
Is it very uncommon to use interfaces?

SOLID is a set of principles. Like any other principles, it only has meaning within a context. In this case the context would include the language being used and the environment in which your code is going to be run.

<opinion>
Few of the “ivory tower” principles being espoused as “good design” for object-oriented languages are built around the features and capabilities of Python, or the idea that code being written is part of a larger, pre-existing framework.
<opinion>

No, I’ve never worked in or with any environment that focused on the SOLID principles within any Python system.

No, I’ve never seen any usage of interfaces as a design principle within a Python system. (My general impression has been that they’re a work-around for the limitations imposed by an object-oriented environment that doesn’t support multiple inheritance, or is more rigid in their data typing. Python’s “duck-typing” resolves a lot of those limitations intrinsically.)

1 Like

I’m not a fan of multiple inheritance :stuck_out_tongue_winking_eye:

Thanks for the answer Ken.

Then you must really not be a fan of Python / Django because it’s such a fundamental part of the framework. (:stuck_out_tongue_winking_eye: back at ya)

2 Likes

Part of the framework of not, it’s not taboo to have the opinion that Django View classes over-rely on multiple inheritance making it very hard to understand if you don’t have something like https://ccbv.co.uk open

The model classes, however, it’s a different story. They are fairly robust and with several ways to appropriately handle polymorphism without overlying on multiple inheritance.

Composition in model classes usually ends up with classes highly coupled. If you just want to reuse the same behavior for multiple models, just use functions.

1 Like

I absolutely agree with this. It’s one of the reasons why Django Vanilla Views exists.

However, while I think we can both agree that neither of us would have designed the generic CBVs in the way they currently exist, they are what they are and understanding how they are constructed is part of using them effectively.

1 Like