Search code examples
python-3.xdjango-modelsabc

Inheriting from both ABC and django.db.models.Model raises metaclass exception


I am trying to implement a Django data model class, which is also an interface class, using Python 3. My reason for doing so is, I'm writing a base class for my colleague, and need him to implement three methods in all of the classes he derives from mine. I am trying to give him a simplified way to use the functionality of a system I've designed. But, he must override a few methods to supply the system with enough information to execute the code in his inherited classes.

I know this is wrong, because it's throwing exceptions, but I'd like to have a class like the following example:

from django.db import models
from abc import ABC, abstractmethod

class AlgorithmTemplate(ABC, models.Model):
    name = models.CharField(max_length=32)

    @abstractmethod
    def data_subscriptions(self):
        """
        This method returns a list of topics this class will subscribe to using websockets

        NOTE: This method MUST be overriden!
        
        :rtype: list
        """

I understand I could avoid inheriting from the ABC class, but I'd like to use it for reasons I won't bore you with here.

The Problem


After including a class, like the one above, into my project and running python manage.py makemigrations I get the error: TypeError: metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases. I have searched Stack Overflow, but have only find solutions like the following one:

class M_A(type): pass
class M_B(type): pass
class A(metaclass=M_A): pass
class B(metaclass=M_B): pass

class M_C(M_A, M_B): pass
class C:(A, B, metaclass=M_C): pass

I've read the following posts:

Using ABC, PolymorphicModel, django-models gives metaclass conflict

Resolving metaclass conflicts

And I've tried many variations of those solutions, but I still get the dreaded metaclass exception. Help me Obi-Wan Kenobi, you're my only hope. :-)


Solution

  • I had the same need and found this. I've altered the code for clarity and completeness. Basically you need an extra class which you can use for all your model interfaces.

    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'
    
    
    test = MyModel(code='test')
    > TypeError: Can't instantiate abstract class MyModel with abstract methods must_implement
    

    Now you have the best of both worlds.