简体   繁体   中英

python SubClasses from a Class that inherits from models.Model

I have some doubts here...

Imagine that I have 3 classes:

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'))

I was working with this 3 classes, but in my apps I would have to check the chassis type all the time in order to apply some business rules to each situation... I thought this would not be the correct approach.. so I planned this:

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

and have two Subclasses:

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'))

Ok, until here all ok, changed nothing in my apps that worked with previous classes and all objects were being persisted in the database quite nicely as expected..

But, my problem remains.. Because I continue to instantiate CarSpec and MotoSpec and not Spec... but... I want to use Spec all the time instead of the extending classes... being so, what can I do to be able to instantiate a Spec object passing a Chassis to his init method in order to get a CarSpec or a MotoSpec from that (or other) method..

EDITED-IMPORTANT : I've added powered_weels attribute for MotoSpec and number_of_doors for CarSpec because I have some specific fields for each of the two Specs

EDITED-YET AGAIN : In my views I wanted to avoid doing a type verification every time I mess around with Specs and leave that to one of the classes involved. Resuming, I want to be able to add a new Spec Object and not have to worry about changing my views.. only the objects related to Specs..

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

EDITED : I've added a restore_state on my Spec class, but then I think I find some problems related to circular imports.. OMG.. this is killing me. I have a .NET background, and python is not getting easy on me in this kinds of stuff :S

将底架,品牌和模型属性添加到Spec类,然后对CarSpec和MotoSpec类使用代理模型 ,也许添加诸如car_brand(),motor_brand()等方法。

Although you could remove the class Meta on the Spec class (so that you have Spec objects). I don't think you can override the __init__ method on the class to instantiate CarSpec or MotoSpec objects. That would give you a circular dependancy; The definition of CarSpec objects relies on the definition of Spec objects (because CarSpec is a descendant of Spec ). You can't make the definition of Spec objects dependant on CarSpec objects (which it would be if CarSpec appeared in Spec 's __init__ definition).

Personally I think you should use proxy models as suggested by lysergia25, and hide/require the additional fields in the proxy.

UPDATE

Assuming you add all fields to Spec (with blank=True, null=True on the fields that only appear in one subclass), then you can do something like -

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

This should hide the field number_of_doors from all forms (inc admin), whilst requiring powered_wheels .

Propose:

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")

OR you can put verbose name in 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)

您应该使用多表继承,如Django文档中所述

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM