Search code examples
mysqldjangoinspectdb

Django's inspectdb doesn't do ManyToManyField


So I just tested one thing, making the following tables.

# Dump of table driverclass
# ------------------------------------------------------------

CREATE TABLE `driverclass` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(20) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

# Dump of table event
# ------------------------------------------------------------

CREATE TABLE `event` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(20) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

# Dump of table driver
# ------------------------------------------------------------

CREATE TABLE `driver` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(20) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

# Dump of table driver_driverclass_event
# ------------------------------------------------------------

CREATE TABLE `driver_driverclass_event` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `driver_id` int(11) unsigned DEFAULT NULL,
  `event_class_id` int(11) unsigned DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `driver_id` (`driver_id`),
  KEY `event_class_id` (`event_class_id`),
  CONSTRAINT `driver_driverclass_event_ibfk_2` FOREIGN KEY (`event_class_id`) REFERENCES `driverclass_event` (`id`),
  CONSTRAINT `driver_driverclass_event_ibfk_1` FOREIGN KEY (`driver_id`) REFERENCES `driver` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

# Dump of table driverclass_event
# ------------------------------------------------------------

CREATE TABLE `driverclass_event` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `event_id` int(11) unsigned DEFAULT NULL,
  `driverclass_id` int(11) unsigned DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `event_id` (`event_id`),
  KEY `driverclass_id` (`driverclass_id`),
  CONSTRAINT `driverclass_event_ibfk_2` FOREIGN KEY (`driverclass_id`) REFERENCES `driverclass` (`id`),
  CONSTRAINT `driverclass_event_ibfk_1` FOREIGN KEY (`event_id`) REFERENCES `event` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

Which should be relations in terms of ManyToManyField. However, Djangos inspectdb looked at it as 5 models with tons of ForeignKeys. Doesn't Djangos inspectdb take ManyToManyFields into account, or is my databasemodel wrong?


Solution

  • Your database schema appears to be alright. However Django doesn't automatically create Many-To-Many fields as you have found out. The models for driverclass_event and ** driver_driverclass_event** are a so-called 'through' models. Normally you would also define ManyToManyField on both ends of the relationship and specify the correct 'through' model:

    class Event(db.Model):
        pass
    
    class DriverClass(db.Model):
        events = db.ManyToManyField(Event, through='DriverClassEvent')
    
    class DriverClassEvent(db.Model):
        driver_class = db.ForeignKeyField(DriverClass)
        event = db.ForeignKeyField(Event)
    

    See also: https://docs.djangoproject.com/en/dev/topics/db/models/#extra-fields-on-many-to-many-relationships

    Normally through models are used when you want to store information about the relationship, like the start date of the relationship or to refer to this relationship using a foreign key. In your example, you store which drivers have a relationship to a DriverClassEvent. So DriverClassEvent should have a model to be able to refer to it.

    class DriverDriverClassEvent(db.Model):
        driver = db.ForeignKeyField(Driver)
        driver_class_event = db.ForeignKeyField(DriverClassEvent)
    
    class Driver(db.Model):
        driver_class_events = db.ManyToManyField(DriverClassEvent, through='DriverDriverClassEvent')
    

    Now you could drop the DriverDriverClassEvent as you don't store any data about that relationship, or refer to the relationship in other models. So the last example becomes*:

    class Driver(db.Model):
        driver_class_events = db.ManyToManyField(DriverClassEvent, db_table='driver_driverclass_event')
    

    * Note that you don't have control over the field names, so those would have to match to Django's auto-generated field names.