问题 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类中定义的自定义选项,但这成为了老鼠的巢。我对这种方法或任何其他建议的指导持开放态度。


5074
2018-02-26 20:34


起源

非常好的问题。 - Brandon


答案:


一种解决方案是动态构建抽象基类。例如:

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)

13
2018-03-02 20:42



啊。基础模型工厂!我喜欢。我没有真正考虑过这种模式。比我的元类thingee更清洁和可读 - Ben Roberts
乐意效劳。伟大的创业理念,BTW! - Daniel Naab


更新: 我开始使用以下解决方案,这很难看,并切换到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所以你能做什么,所以也许这就是答案。


0
2018-03-12 04:58