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, так что вы можете сделать, так что, возможно, это ответ.