-
-
Save gavinwahl/7778717 to your computer and use it in GitHub Desktop.
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> |
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)
Thanks! This solution passing the DRY principle, and I can refactor a lot of repeated code lines.
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()
```
`
Great stuff. Just an addition. In python3 it should be like this