2

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

4 Answers4

1

Add chasis, brand and model attributes to the Spec class, then use proxy models for the CarSpec and MotoSpec classes, maybe add methods like car_brand(), motor_brand(), etc...

Lysergia25
  • 134
  • 2
  • 6
0

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.

Aidan Ewen
  • 13,049
  • 8
  • 63
  • 88
  • Isn't there any way this could a good way to go: I pass all Spec attributes back to the subclasses, use Spec as abstract without any fields and start from there... Because I've already CarSpec and MotoSpec tables with thousands of rows.. – samthgreat_pt Feb 08 '13 at 10:35
  • And what do you mean with: "and hide/require the additional fields in the proxy". – samthgreat_pt Feb 08 '13 at 11:49
  • Thanks. But... and.. what about the other way round, I mean, what if I implement two separate tables (like I have at the moment CarSpec and MotoSpec) and I have a Spec proxy class in order to manipulate and call several methods from CarSpec and MotoSpec. To say the truth that was what really interested me in the first place: to have a way that in my views I don't have to check what Spec type it is, and do that internally in one of the used classes. That way I could had an independent spec at any time, and would avoid changing my views.. I just don't seem to be getting it from my head.. – samthgreat_pt Feb 13 '13 at 11:09
  • ok, not possible:"Base class restrictions¶ A proxy model must inherit from exactly one non-abstract model class. You can’t inherit from multiple non-abstract models as the proxy model doesn’t provide any connection between the rows in the different database tables. A proxy model can inherit from any number of abstract model classes, providing they do not define any model fields." – samthgreat_pt Feb 13 '13 at 14:59
  • Go with the base class and 2 proxy models. Add a field on the base class which tells you which child class your db tuple represents. Then populate the field automatically by overriding save() in the proxy classes. – Aidan Ewen Feb 13 '13 at 15:14
0

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)
catherine
  • 22,492
  • 12
  • 61
  • 85
  • But I do not want to have two tables (One for Spec and other for Car/Moto). By the other side, that would let me define and implement some methods on the Spec superclass... Anyway I can have the best of the two worlds? – samthgreat_pt Feb 13 '13 at 10:43
  • Yeah and you can modify my answer by converting Spec into Abstract – catherine Feb 13 '13 at 10:47
  • Did that, still have a problem and that was from the start my main concern about all this – samthgreat_pt Feb 13 '13 at 11:11
  • Did that (was my first approach to the problem), still have a problem and that was from the start my main concern about all this: Imagine that CarSpec and MotoSpec have a (static) method called doStuff(x,y) and in one of my views, I have like: if game_type == "A": stuff = CarSpec.restore_state(request, game_session) elif game_type == "C": stuff = MotoPlay.restore_state(request, game_session) and I want to take that type verification out of my views... The same would apply if instead of a static method I would have an object method – samthgreat_pt Feb 13 '13 at 11:18
-1

You should use multitable inheritance, as explained in django documentation

Emanuele Paolini
  • 9,912
  • 3
  • 38
  • 64