Django - Могу ли я изменить построение поля, определенного в абстрактной базовой модели для конкретной дочерней модели?
Я добавляю slug ко всем моим моделям для целей сериализации, поэтому я определил абстрактный базовый класс, который использует AutoSlugField из django_autoslug.
class SluggerModel(models.Model):
slug = AutoSlugField(unique=True, db_index=False)
class Meta:
abstract=True
У меня также есть настраиваемый менеджер и определенный метод natural_key, и на данный момент у меня есть около 20 дочерних классов, поэтому есть несколько вещей, которые делают использование абстрактной базовой модели стоящей, помимо единственной строки, которая определяет поле.
Однако я хочу иметь возможность переключать несколько аргументов по умолчанию для инициализации AutoSlugField
для некоторых дочерних моделей, при этом все еще имея возможность использовать абстрактный базовый класс. Например, я хотел бы, чтобы некоторые из них использовали параметр populate_from
, определяя поля из своей конкретной модели, а другие - db_index=True
вместо моего значения по умолчанию (False
).
Я начал делать это с помощью настраиваемого Metaclass, используя пользовательские параметры, определенные в каждом дочернем классе Meta класса, но это становится крысиным гнездом. Я открыт для руководства по этому подходу или любых других предложений.
Ответы
Ответ 1
Одним из решений было бы динамическое построение абстрактного базового класса. Например:
def get_slugger_model(**slug_kwargs):
defaults = {
'unique': True,
'db_index': False
}
defaults.update(slug_kwargs)
class MySluggerModel(models.Model):
slug = AutoSlugField(**defaults)
class Meta:
abstract = True
return MySluggerModel
class MyModel(get_slugger_model()):
pass
class MyModel2(get_slugger_model(populate_from='name')):
name = models.CharField(max_length=20)
Ответ 2
Обновление:. Я начал со следующего решения, которое было уродливым, и переключилось на решение Daniel, а это не так. Я оставляю здесь свое место для справки.
Вот моя ловушка для крысы Metaclass, которая, кажется, работает (без обширного тестирования еще).
class SluggerMetaclass(ModelBase):
"""
Metaclass hack that provides for being able to define 'slug_from' and
'slug_db_index' in the Meta inner class of children of SluggerModel in order to set
those properties on the AutoSlugField
"""
def __new__(cls, name, bases, attrs):
# We don't want to add this to the SluggerModel class itself, only its children
if name != 'SluggerModel' and SluggerModel in bases:
_Meta = attrs.get('Meta', None)
if _Meta and hasattr(_Meta, 'slug_from') or hasattr(_Meta, 'slug_db_index'):
attrs['slug'] = AutoSlugField(
populate_from=getattr(_Meta, 'slug_from', None),
db_index=getattr(_Meta, 'slug_db_index', False),
unique=True
)
try:
# ModelBase will reject unknown stuff in Meta, so clear it out before calling super
delattr(_Meta, 'slug_from')
except AttributeError:
pass
try:
delattr(_Meta, 'slug_db_index')
except AttributeError:
pass
else:
attrs['slug'] = AutoSlugField(unique=True, db_index = False) # default
return super(SlugSerializableMetaclass, cls).__new__(cls, name, bases, attrs)
Теперь SlugModel
выглядит следующим образом:
class SluggerModel(models.Model):
__metaclass__ = SluggerMetaclass
objects = SluggerManager()
# I don't define the AutoSlugField here because the metaclass will add it to the child class.
class Meta:
abstract = True
И я могу добиться желаемого эффекта с помощью:
class SomeModel(SluggerModel, BaseModel):
name = CharField(...)
class Meta:
slug_from = 'name'
slug_db_index = True
Мне нужно сначала поставить SluggerModel в список наследования для моделей, имеющих более одной абстрактной родительской модели, или же поля не будут отобраны другими родительскими моделями и проверка не удастся; однако я не мог понять, почему.
Я думаю, я мог бы ответить на этот вопрос, потому что он работает, но я надеюсь на лучший способ, так как его немного на уродливой стороне. Опять же, hax hax, так что вы можете сделать, так что, возможно, это ответ.