Skip to content

Instantly share code, notes, and snippets.

@gavinwahl
Created December 3, 2013 22:26
Show Gist options
  • Star 15 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save gavinwahl/7778717 to your computer and use it in GitHub Desktop.
Save gavinwahl/7778717 to your computer and use it in GitHub Desktop.
Abstract (PEP 3119) Django models.
from abc import ABCMeta, abstractmethod
class AbstractModelMeta(ABCMeta, type(models.Model)):
pass
class ABCModel(models.Model):
__metaclass__ = AbstractModelMeta
class Meta:
abstract = True
class Foo(ABCModel):
class Meta:
abstract = True
@abstractmethod
def foo(self):
return 1
class Bar(Foo):
def foo(self):
return 2
# >>> Foo()
# Traceback (most recent call last):
# File "<console>", line 1, in <module>
# TypeError: Can't instantiate abstract class Foo with abstract methods foo
# >>> Bar()
# <Bar: Bar object>
@sandeepbalagopal09
Copy link

sandeepbalagopal09 commented Jan 3, 2021

Great stuff. Just an addition. In python3 it should be like this

class ABCModel(models.Model, metaclass=AbstractModelMeta):
    class Meta:
        abstract = True

@steven2308
Copy link

Thanks for this workaround, I used the python3 version to answer a [related question on SO].(https://stackoverflow.com/questions/50085658/inheriting-from-both-abc-and-django-db-models-model-raises-metaclass-exception)

@FirstShanti
Copy link

Thanks! This solution passing the DRY principle, and I can refactor a lot of repeated code lines.

@piscvau
Copy link

piscvau commented Jul 18, 2023

Hello Thanks for your post.
I am not a specialist of metaclasses but I have to define abstract Django Models as AbstractModelMeta. And I also need to modifiy some fields for each model subclasses.
I tried to use init_subclass but there is a bug in django which make this solution unusable https://code.djangoproject.com/ticket/34555 .
SO I guess the solution is to redefine new but I have been turning into circles for several days and did not find any solution.
If I overwrite new in ABCModel then I get builtins.TypeError: object.new() takes exactly one argument (the type to instantiate).
When I check the mro of the ABCModel the base.Model is second in the list.

Any help would me most welcome!....
Here is a test

import unittest
import os
from unittest.mock import patch
from abc import abstractmethod, ABCMeta

from django.db.models.base import Model
import django.db.models as models
from django.apps.registry import Apps


class AbstractModelMeta(ABCMeta, type(models.Model)):
    """ a Django model and python abstract class"""
    pass

    #def __new__(cls, name, bases, attrs, **kwargs):

        #clas = super().__new__(cls, name, bases, attrs, **kwargs)
        #return clas


class Test_AbstractImplementation (unittest.TestCase):

    @classmethod
    def setUpClass(cls):
        os.environ['DJANGO_SETTINGS_MODULE'] = 'djangoLudd21.settings'
        cls.patcher = patch.object(Apps, 'check_apps_ready')
        cls.patcher.start()

        class AbsImplementation (models.Model,
                                 metaclass=AbstractModelMeta):

            class Meta:
                app_label = 'fakeapp'
                abstract = True

            field1 = models.CharField(
                max_length=20)
            field2 = models.IntegerField(
                default=5)

            @staticmethod
            def change_field():
                return 10

            @abstractmethod
            def absmethod(self):
                pass

            def __new__(cls, name, bases, attrs, **kwargs):
                field1 = models.CharField(
                    max_length=cls.change_field(),
                )
                attrs.update({'field1': field1})
                machine = kwargs.pop('machine', None)
                if not machine:
                    raise ValueError('implementation must have a machine attribute')
                clas = super().__new__(cls, name, bases, attrs, **kwargs)
                setattr(clas, 'machine', machine)
                return clas

        cls.absimp = AbsImplementation

    def test(self):
        class Imp (metaclass=self.absimp,
                   machine='machine'):

            class Meta:
                app_label = 'fakeapp'
                abstract = False

            def absmethod(self):
                    pass

        self.assertTrue(issubclass(Imp, models.Model))
        self.assertTrue(issubclass(Imp, self.absimp))

        self.assertEqual(Imp.machine, 'machine')
        field1 = Imp._meta.get_field('field1')
        field2 = Imp._meta.get_field('field2')
        self.assertEqual(field1.max_length, 10)
        self.assertEqual(field2.default, 5)

    @unittest.skip
    def test_missing_method(self):
        with self.assertRaises(TypeError) as e:
            class Imp1 (self.absimp,
                        machine='machine'):
                pass

                class Meta:
                    app_label = 'fakeapp'
                    abstract = False
        ex = e.exception
        self.assertTrue(isinstance(ex, TypeError))
        self.assertTrue('absmethod' in ex.args[0])

    @classmethod
    def tearDownClass(cls):
        cls.patcher.stop()
```
`

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment