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.
How to use Python’s abc module to implement interfaces.
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:
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)
<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.
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.)
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.
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.