0

I am currently kidding around on how to model something using Django. The basic situation is that I have an object that should serve as a chassis and provide some sockets. Then there are loads of different modules that are placed on the sockets of a chassis. I would like to model these different modules as distinct classes in Django, but use a common class on the database layer, possibly involving some generic fields.

So the database model might look something like this:

class Module(models.model):
    name = models.CharField(max_length=128)
    # Or is there a better way to annotate a type?
    type = models.CharField(max_length=128) 
    string1 = models.CharField(max_length=128)
    string2 = models.CharField(max_length=128)
    ...
    int1 = models.IntegerField()

# Some kind of engine class that derives from Module
# but does nothing else then "mapping" the generic
# fields to something sensible

class Socket(models.Model):
    is_on = models.ForeignKey(Chassis)
    name = models.CharField(max_length=128)
    type = models.CharField(max_length=128)

class Connection(models.Model):
    chassis = models.ForeignKey(Chassis)
    module = models.ForeignKey(Module)
    via = models.ForeignKey(Socket)

class Chassis(models.Model):
    name = models.CharField(max_length=128)
    modules= models.ManyToManyField(Module, through='Connection')

class Group(models.Model):
    name = models.CharField(max_length=128)

Of course I wouldn't want to spoil my logic with this denormalization. Thats why I hinted for some kind of engine class that should use the Module table, but provide "logical" getters and setters, effectively mapping data like "Horsepower" to "int1".

So my questions are basicly:

  • Is what I am doing reasonable with Django? Or is there a better (possibly built in) way to deal with this?
  • Would it be possible to construct the correct type, the one providing the wrapper methods for the denormalized model, depending on the Module.type field automatically?
Marcus Riemer
  • 7,244
  • 8
  • 51
  • 76
  • Can you clarify your second question regarding Module.type a bit. I do not get it. – Thomas Kremmel Sep 19 '12 at 13:13
  • I would like to automatically constructed a class, possibly derived from the model class, that is stated in the `type` field. Or by any other means "simply" construct the "right" class, the one containing the wrapper methods. – Marcus Riemer Sep 19 '12 at 13:14

2 Answers2

1

Is what I am doing reasonable with Django?

The general idea is okay, but the denormalization may make your queries less than optimal. The standard solution would be to subclass Module for each type of module; this would create a module table plus a table per module type with the type-specific stuff. This, of course, assumes you won't be creating or deleting module types at runtime.

That said, there are some issues with your models:

class Module(models.model):
    name = models.CharField(max_length=128)
    # Or is there a better way to annotate a type?
    type = models.CharField(max_length=128) 
    string1 = models.CharField(max_length=128)
    string2 = models.CharField(max_length=128)
    ...
    int1 = models.IntegerField()

Normally, type would get normalized to save space:

class ModuleType(models.model):
    name = models.CharField(max_length=128)
    # Any other type-specific parameters.

class Module(models.model):
    name = models.CharField(max_length=128)
    type = models.ForeignKey(ModuleType, related_name="modules")
    string1 = models.CharField(max_length=128)
    string2 = models.CharField(max_length=128)
    ...
    int1 = models.IntegerField()

class Socket(models.Model):
    is_on = models.ForeignKey(Chassis)
    name = models.CharField(max_length=128)
    type = models.CharField(max_length=128)

class Connection(models.Model):
    chassis = models.ForeignKey(Chassis)
    module = models.ForeignKey(Module)
    via = models.ForeignKey(Socket)

class Chassis(models.Model):
    name = models.CharField(max_length=128)
    sockets = m
    modules= models.ManyToManyField(Model, through='Socket')

Chassis is a mess. You didn't define sockets, wrote Model where you probably want module, and through should probably refer to Connection (a through model has to have ForeignKeys to both ends of the link). But from your description, I get the far simpler:

class Socket(models.Model):
    chassis = models.ForeignKey(Chassis, related_name="sockets")
    name = models.CharField(max_length=128)
    # Is `type` a ModuleType? If so, use a ForeignKey.
    # If not, create a SocketType model.
    type = models.___
    module = models.ForeignKey(Module, related_name="sockets")

class Chassis(models.Model):
    name = models.CharField(max_length=128)
    sockets = models.IntegerField()
    modules = models.ManyToManyField(Socket)

With a better description of what you're modeling, this can be refined further. For example, I'm not sure a ManyToMany is what you want. You may need to split the design of a chassis (i.e. things common to all instances of a given kind of chassis, including its sockets) from the instances of that chassis (which would reference the design, and have another table mapping sockets to modules).


Would it be possible to construct the correct type depending on the Module.type field automatically?

That's the Factory design pattern. In Python, you'd implement it as a dictionary of constructors:

class Module(models.model):
    # ...
    CONSTRUCTORS = {}

    @staticmethod
    def register_constructor(type_name, constructor):
        Module.CONSTRUCTORS[type_name] = constructor

    def construct(self):
        return Module.CONSTRUCTORS[self.type.name](self)

I don't think you need a specific engine class; the various module classes will suffice.

Mike DeSimone
  • 41,631
  • 10
  • 72
  • 96
  • Ouch, sorry for all those wrong types flying around ... Doesn't seem to be a strong day of mine. – Marcus Riemer Sep 19 '12 at 13:16
  • Oh, and your are right regarding the further refinement I need to to regarding the actual instances. Thanks for your great answer, I have something to work with now :) Oh, and I updated the messy code, hope its ok now. – Marcus Riemer Sep 19 '12 at 13:23
  • Something else to note in your original schema: both `Socket` and `Connection` have `ForeignKey`s to `Chassis`, and `Connection` has a `ForeignKey` to `Socket`. This triangle-shaped link is a good indication that you have a redundant `ForeignKey`, in this case the one in `Connection`: `Connection.chassis` is really a duplicate of `Connection.via.is_on`. Eliminating `Connection.chassis` reduces `Connection` to just the two `ForeignKey`s needed for the `ManyToManyField`, which means you can get rid of `through=Connection` and thus `Connection`. – Mike DeSimone Sep 19 '12 at 14:44
1

In order to define an abstract base class you can do the following:

class Module(models.model):
    name = models.CharField(max_length=128)
    type = models.CharField(max_length=128) 
    string1 = models.CharField(max_length=128)
    string2 = models.CharField(max_length=128)
    ...
    int1 = models.IntegerField()

    class Meta:
        abstract = True

class SpecificModule(Module):
     subClassField = models.CharField(max_length=128)

I would recommend to read this part of the documentation as it is a perfectly good starting point for dealing with inheritance and abstract classes.

You could also define the parent Module class without class Meta: abstract = True . The only difference is that with abstract = True all fields of the class SpecificModule and all the fields of the abstract Module parent class will be created in the subclass table for the class SpecificModule, whereas with no abstract = True definition a table for the class Module will be created, having all "generic" fields available in the Module table, and all sub-class specific fields in the table for the class SpecificModule.

Edit: In reply to the question regarding relationship between parent and child.

As an implicit one-to-one field is created in the subclass table (docs), you can get the child object using this query.

#get the parent
parent = Module.objects.get(name="my first module")
#this is possible
print parent.name
>>> "my first module"
#this is not possible
print parent.subClassField
>>> Traceback (most recent call last):
    File "<console>", line 1, in <module>
    AttributeError: 'Module' object has no attribute 'subClassField'
#get the corresponding child
child = SpecificModule.objects.get(module_ptr_id=parent.pk)
#finally we can print the value
print child.subClassField

I think there should be also a default related_name be created by Django, but not sure which one it is.

Please note that an implicit one-to-one relationship from parent to child is only created in the abstract=False case. Nevertheless if abstract = True you will not have the abstract parent available as an object, since it is abstract..

Thomas Kremmel
  • 14,575
  • 26
  • 108
  • 177
  • Would I be able to have a foreign key to the abstract baseclass and "get" the correct instance when putting this in a model definition? – Marcus Riemer Sep 19 '12 at 13:27
  • Yes, in the abstract = False case. I updated my answer to show how to do it. – Thomas Kremmel Sep 19 '12 at 13:41
  • The nice thing about the non-abstract case is that, if you don't need any module-specific stuff, you can just work with the `Module` model. I'm not sure you can have `ForeignKey`s to `Module` in the abstract case (needed by `Socket`). This could also make it hard to run a query across multiple types of modules. – Mike DeSimone Sep 19 '12 at 14:38
  • It is not possible to set a ForeignKey to an abstract class (just checked it ;-). I anyhow would recommend the non-abstract approach, as there are a few advantages to the abstract approach (as you pointed one out). – Thomas Kremmel Sep 19 '12 at 14:52
  • Another advantage would be that you do not have to mess with generic relations (http://tiny.cc/7a1vkw), which are actually nice, if you know how to handle them, but makes things a bit more complicated. In the abstract case, a generic relation has to be set to Socket since the ForeignKey relation has to be able to deal with several childs of the Module parent class. – Thomas Kremmel Sep 19 '12 at 15:02