从继承自models.Model的类中创建Python子类

2 投票
4 回答
815 浏览
提问于 2025-04-17 15:07

我这里有一些疑问...

假设我有三个类:

class CarSpec(models.Model):
    x = models.IntegerField(default=20)
    y = models.CharField(max_length=100, blank=True)
    z = models.CharField(max_length=50, blank=True)
    chassis = models.ForeignKey(Chassis, unique=True, limit_choices_to={'type':'A'})
    car_brand = models.CharField(max_length=100, blank=True)
    car_model = models.CharField(max_length=50, blank=True)
    number_of_doors = models.IntegerField(default=2)

class MotoSpec(models.Model):
    x = models.IntegerField(default=20)
    y = models.CharField(max_length=100, blank=True)
    z = models.CharField(max_length=50, blank=True)
    chassis = models.ForeignKey(Chassis, unique=True, limit_choices_to={'type':'C'})
    motor_brand = models.CharField(max_length=100, blank=True)
    motor_model = models.CharField(max_length=50, blank=True)
    powered_weels = models.IntegerField(default=1)

class Chassis(models.Model):
    name = models.CharField(max_length=50, blank=False)
    type = models.CharField(max_length=2, choices = GAME_TYPES, default="A")

GAME_TYPES = (('A', 'Car'),('B', 'Truck'),('C', 'Motorcycle'))

我一直在使用这三个类,但在我的应用中,我需要不断检查底盘类型,以便根据不同情况应用一些业务规则……我觉得这样做不太对,所以我计划了这个:

class Spec(models.Model):
    x = models.IntegerField(default=20)
    y = models.CharField(max_length=100, blank=True)
    z = models.CharField(max_length=50, blank=True)

    class Meta:
        abstract = True

然后我有了两个子类:

class CarSpec(Spec):
    chassis = models.ForeignKey(Chassis, unique=True, limit_choices_to={'type':'A'})
    car_brand = models.CharField(max_length=100, blank=True)
    car_model = models.CharField(max_length=50, blank=True)
    number_of_doors = models.IntegerField(default=2)

class MotoSpec(Spec):
    chassis = models.ForeignKey(Chassis, unique=True, limit_choices_to={'type':'C'})
    motor_brand = models.CharField(max_length=100, blank=True)
    motor_model = models.CharField(max_length=50, blank=True)
    powered_weels = models.IntegerField(default=1)

class Chassis(models.Model):
    name = models.CharField(max_length=50, blank=False)
    type = models.CharField(max_length=2, choices = GAME_TYPES, default="A")

GAME_TYPES = (('A', 'Car'),('B', 'Truck'),('C', 'Motorcycle'))

到这里为止一切都还好,之前的应用没有变化,所有对象也都顺利地保存到数据库里,正如预期的那样……

但是,我的问题依然存在……因为我还是在实例化 CarSpec 和 MotoSpec,而不是 Spec……但是……我想一直使用 Spec,而不是那些扩展类……那么,我该怎么做才能实例化一个 Spec 对象,并在它的初始化方法中传入一个底盘,以便从这个(或其他)方法中得到 CarSpec 或 MotoSpec 呢……

编辑-重要:我为 MotoSpec 添加了 powered_weels 属性,为 CarSpec 添加了 number_of_doors 属性,因为这两个规格各自有一些特定的字段。

编辑-再次:在我的视图中,我想避免每次处理规格时都进行类型验证,而是把这个工作留给相关的类。总之,我希望能够添加一个新的 Spec 对象,而不必担心更改我的视图……只需要关注与规格相关的对象……

# CarSpec
if game_type == "A":
    stuff = CarSpec.restore_state(request, game_session)
# MotoSpec
elif game_type == "C":
    stuff = MotoSpec.restore_state(request, game_session)

编辑:我在我的 Spec 类中添加了一个 restore_state 方法,但我发现了一些与循环导入相关的问题……天哪,这让我很头疼。我有 .NET 的背景,而 Python 在这方面对我来说并不容易 :S

4 个回答

0

建议:

class Spec(models.Model):
    x = models.IntegerField(default=20)
    y = models.CharField(max_length=100, blank=True)
    z = models.CharField(max_length=50, blank=True)
    brand = models.CharField(max_length=100, blank=True)
    model = models.CharField(max_length=50, blank=True)


class CarSpec(Spec):
    chassis = models.ForeignKey(Chassis, unique=True, limit_choices_to={'type':'A'})
    number_of_doors = models.IntegerField(default=2)
CarSpec._meta.get_field('brand').verbose_name = 'Car Brand'
CarSpec._meta.get_field('model').verbose_name = 'Car Model'


class MotoSpec(Spec):
    chassis = models.ForeignKey(Chassis, unique=True, limit_choices_to={'type':'C'})
    powered_weels = models.IntegerField(default=1)
MotoSpec._meta.get_field('brand').verbose_name = 'Motor Brand'
MotoSpec._meta.get_field('model').verbose_name = 'Motor Model'


class Chassis(models.Model):
    GAME_TYPES = (
        ('A', 'Car'),
        ('B', 'Truck'),
        ('C', 'Motorcycle')
    )

    name = models.CharField(max_length=50, blank=False)
    type = models.CharField(max_length=2, choices = GAME_TYPES, default="A")

或者你可以在forms.py文件中设置详细名称。

class CarSpec(Spec):
    chassis = models.ForeignKey(Chassis, unique=True, limit_choices_to={'type':'A'})
    number_of_doors = models.IntegerField(default=2)


class MotoSpec(Spec):
    chassis = models.ForeignKey(Chassis, unique=True, limit_choices_to={'type':'C'})
    powered_weels = models.IntegerField(default=1)
0

虽然你可以去掉class Meta这个部分,这样就能得到Spec对象。但我觉得你不能在这个类里重写__init__方法来创建CarSpecMotoSpec对象。这样会导致循环依赖的问题;因为CarSpec对象的定义依赖于Spec对象的定义(因为CarSpecSpec的子类)。你不能让Spec对象的定义依赖于CarSpec对象的定义(如果CarSpec出现在Spec__init__定义里,就会出现这种情况)。

我个人认为你应该使用代理模型,正如lysergia25所建议的那样,并在代理中隐藏或要求额外的字段。

更新

假设你把所有字段都加到Spec里(对于只出现在一个子类中的字段,可以设置blank=True, null=True),那么你可以这样做 -

class MotoSpec(Spec):
    class Meta:
        proxy = True

    def __init__(self, *args, **kwargs):
        super(MotoSpec, self).__init__(*args, **kwargs)
        self.fields['number_of_doors'].editable = False
        self.feilds['powered_wheels'].blank = False

这样应该能在所有表单(包括管理员界面)中隐藏number_of_doors这个字段,同时要求powered_wheels字段。

1

给Spec类添加底盘、品牌和型号这几个属性,然后对CarSpec和MotoSpec类使用代理模型,也许还可以添加一些方法,比如car_brand()、motor_brand()等等...

撰写回答