在Django中 - 模型继承 - 它是否允许您覆盖父模型的属性?

Joh*_*y 5 87 python django django-inheritance

我希望这样做:

class Place(models.Model):
   name = models.CharField(max_length=20)
   rating = models.DecimalField()

class LongNamedRestaurant(Place):  # Subclassing `Place`.
   name = models.CharField(max_length=255)  # Notice, I'm overriding `Place.name` to give it a longer length.
   food_type = models.CharField(max_length=25)
Run Code Online (Sandbox Code Playgroud)

这是我想要使用的版本(虽然我对任何建议持开放态度):http: //docs.djangoproject.com/en/dev/topics/db/models/#id7

这是Django支持吗?如果没有,有没有办法取得类似的结果?

小智 58

不,它不是:

字段名称"隐藏"是不允许的

在普通的Python类继承中,子类允许覆盖父类的任何属性.在Django中,对于作为Field实例的属性(至少目前不是),不允许这样做.如果基类具有调用的字段author,则无法创建author在从该基类继承的任何类中调用的另一个模型字段.

  • 看看我的回答为什么不可能.人们喜欢这样,因为它确实有意义,它并不是立即显而易见的. (11认同)
  • `MyUser._meta.get_field('email').blank = False` (10认同)
  • @ leo-the-manic我认为`User._meta.get_field('email').required = True`可以工作,不确定. (4认同)

qma*_*ats 56

更新的答案:正如人们在评论中指出的那样,原始答案没有正确回答问题.实际上,只有LongNamedRestaurant模型是在数据库中创建的,Place而不是.

解决方案是创建表示"地点"的抽象模型,例如.AbstractPlace,并从中继承:

class AbstractPlace(models.Model):
    name = models.CharField(max_length=20)
    rating = models.DecimalField()

    class Meta:
        abstract = True

class Place(AbstractPlace):
    pass

class LongNamedRestaurant(AbstractPlace):
    name = models.CharField(max_length=255)
    food_type = models.CharField(max_length=25)
Run Code Online (Sandbox Code Playgroud)

还请阅读@Mark 答案,他给出了一个很好的解释,为什么你不能改变从非抽象类继承的属性.

(注意,这只能在Django 1.10之后:在Django 1.10之前,修改从抽象类继承的属性是不可能的.)

原始答案

从Django 1.10开始,它是可能的!你只需要做你要求的:

class Place(models.Model):
    name = models.CharField(max_length=20)
    rating = models.DecimalField()

    class Meta:
        abstract = True

class LongNamedRestaurant(Place):  # Subclassing `Place`.
    name = models.CharField(max_length=255)  # Notice, I'm overriding `Place.name` to give it a longer length.
    food_type = models.CharField(max_length=25)
Run Code Online (Sandbox Code Playgroud)

  • 地方需要抽象,不是吗? (8认同)
  • 我不认为我回答了另一个问题,因为我只是说问题中发布的代码现在正在运行,因为Django 1.10.请注意,根据他发布的关于他想要使用的内容的链接,他忘记了将Place类抽象化. (4认同)
  • @NoamG 在我原来的答案中,“Place”是抽象的,因此它**不是**在数据库中创建的。但 OP 希望在数据库中创建“Place”和“LongNamedRestaurant”。因此,我更新了我的答案以添加“AbstractPlace”模型,这是“Place”和“LongNamedRestaurant”继承的“基础”(即抽象)模型。现在,按照OP的要求,“Place”和“LongNamedRestaurant”都已在数据库中创建。 (3认同)
  • 不知道为什么这是公认的答案...... OP正在使用多表继承.此答案仅对抽象基类有效. (2认同)

Mar*_*ark 27

除非抽象,否则这是不可能的,这就是为什么:LongNamedRestaurant也是一个Place,不仅是一个类,而且还在数据库中.地方表包含每个纯净Place和每个的条目LongNamedRestaurant.LongNamedRestaurant只需创建一个额外的表,其中food_type包含对place表的引用.

如果你这样做Place.objects.all(),你也会获得每个地方LongNamedRestaurant,而且它将是Place(没有food_type)的实例.因此Place.name,LongNamedRestaurant.name共享相同的数据库列,因此必须属于同一类型.

我认为这对于普通模型来说是有意义的:每个餐馆都是一个地方,至少应该拥有所有的地方.也许这种一致性也是为什么1.10之前的抽象模型不可能,尽管它不会给那里的数据库问题.正如@lampslave所言,它在1.10中成为可能.我个人建议小心:如果Sub.x覆盖了Super.x,请确保Sub.x是Super.x的子类,否则Sub不能代替Super使用.

解决方法:AUTH_USER_MODEL如果您只需要更改电子邮件字段,则可以创建自定义用户模型(),其中涉及相当多的代码重复.或者,您可以保留电子邮件,并确保所有表格都需要.如果其他应用程序使用它,则不保证数据库完整性,并且不能以相反的方式工作(如果您不想要用户名).


blu*_*yed 19

请参阅/sf/answers/446568951/:

class BaseMessage(models.Model):
    is_public = models.BooleanField(default=False)
    # some more fields...

    class Meta:
        abstract = True

class Message(BaseMessage):
    # some fields...
Message._meta.get_field('is_public').default = True
Run Code Online (Sandbox Code Playgroud)

  • AttributeError:无法设置属性(((但我正在尝试设置选项 (2认同)

Bri*_*uft 9

将您的代码粘贴到一个全新的应用程序中,将应用程序添加到INSTALLED_APPS并运行syncdb:

django.core.exceptions.FieldError: Local field 'name' in class 'LongNamedRestaurant' clashes with field of similar name from base class 'Place'
Run Code Online (Sandbox Code Playgroud)

看起来像Django不支持.


JF *_*mon 7

也许你可以处理contrib_to_class:

class LongNamedRestaurant(Place):

    food_type = models.CharField(max_length=25)

    def __init__(self, *args, **kwargs):
        super(LongNamedRestaurant, self).__init__(*args, **kwargs)
        name = models.CharField(max_length=255)
        name.contribute_to_class(self, 'name')
Run Code Online (Sandbox Code Playgroud)

Syncdb工作正常.我没试过这个例子,在我的情况下,我只是覆盖一个约束参数,所以......等等看!

  • 类中的`Place._meta.get_field('name').max_length = 255`应该可以解决问题,而不会覆盖`__init __()`.也会更简洁. (3认同)

Dev*_*vin 7

这个超酷的代码片段允许您"覆盖"抽象父类中的字段.

def AbstractClassWithoutFieldsNamed(cls, *excl):
    """
    Removes unwanted fields from abstract base classes.

    Usage::
    >>> from oscar.apps.address.abstract_models import AbstractBillingAddress

    >>> from koe.meta import AbstractClassWithoutFieldsNamed as without
    >>> class BillingAddress(without(AbstractBillingAddress, 'phone_number')):
    ...     pass
    """
    if cls._meta.abstract:
        remove_fields = [f for f in cls._meta.local_fields if f.name in excl]
        for f in remove_fields:
            cls._meta.local_fields.remove(f)
        return cls
    else:
        raise Exception("Not an abstract model")
Run Code Online (Sandbox Code Playgroud)

从抽象父类中删除字段后,您可以根据需要自由重新定义它们.

这不是我自己的工作.来自此处的原始代码:https://gist.github.com/specialunderwear/9d917ddacf3547b646ba


Noa*_*amG 7

我的解决方案和 next 一样简单monkey patching,请注意我如何更改模型中max_lengthname字段属性LongNamedRestaurant

class Place(models.Model):
   name = models.CharField(max_length=20)

class LongNamedRestaurant(Place):
    food_type = models.CharField(max_length=25)
    Place._meta.get_field('name').max_length = 255
Run Code Online (Sandbox Code Playgroud)

  • 国际海事组织的最佳解决方案 (2认同)