The problem was to retrofit relationships for parts-explosion into an existing parts database.
(The database engine was sqlite3.)
A few rough tests of the "obvious" ways hadn't worked.
The minimal model for Parts Explosion was found in a Test-Driven way by changing model.py, guided by the error messages, until manage.py validate
stopped complaining:
from django.db import models
class Part (models.Model):
part_no = models.CharField (max_length=50, unique=True)
description = models.TextField (blank=True, default='')
on_hand = models.IntegerField (default=0)
partsof = models.ManyToManyField ('self', symmetrical=False, through='PartOf')
def __unicode__ (self):
return self.part_no
class PartOf (models.Model):
# this part (the assembly) needs the components in the component_set ..
assembly = models.ForeignKey (Part, related_name='component_set')
# this part is a component of assemblies in the assembly_set ..
component = models.ForeignKey (Part, related_name='assembly_set')
# how many of the component are required by the assembly ..
required = models.IntegerField (default=0)
def __unicode__ (self):
return '%s/%s' % (self.assembly, self.component)
(OK, not quite minimal. The "description", "on_hand", and "required" fields are the application data that provide the payload for the tables.)
The resulting database was exercised in manage.py shell
to straighten out the relations between assembly
and component_set
and between component
and assembly_set
, noted in the comments.
With that settled, the next problem was: fit that into the existing Part database without losing all the existing data.
It turned out to be surprisingly easy.
The schema from manage.py sqlall m2m
BEGIN;
CREATE TABLE "m2m_part" (
"id" integer NOT NULL PRIMARY KEY,
"part_no" varchar(50) NOT NULL UNIQUE,
"description" text NOT NULL,
"on_hand" integer NOT NULL
)
;
CREATE TABLE "m2m_partof" (
"id" integer NOT NULL PRIMARY KEY,
"assembly_id" integer NOT NULL REFERENCES "m2m_part" ("id"),
"component_id" integer NOT NULL REFERENCES "m2m_part" ("id"),
"required" integer NOT NULL
)
;
CREATE INDEX "m2m_partof_3d7199d8" ON "m2m_partof" ("assembly_id");
CREATE INDEX "m2m_partof_10fa268c" ON "m2m_partof" ("component_id");
COMMIT;
shows that none of the ManyToMany configuration makes any difference to the schema for m2m_part
.
As long as m2m_partof
has conforming foreign-key column names, all the other machinery is in the Object Reference Model that Django sets on top of the database.
The actual steps in the retrofit were:
models.py
by
partsof = models.ManyToManyField ('self', symmetrical=False, through='PartOf')
manage.py syncdb
.
admin.site.register (PartOf)
to the application's admin.py let me start populating PartOf objects and testing.
There are still some wrinkles. Attempting to include the PartOf relationships as detail lines on the admin site page for Part doesn't work. The admin site complains about multiple foreign keys for Part objects in the PartOf objects.
Django's conscientious way of separating the ORM from the schema makes other things easy, too. It's obvious that the model given above lacks a constraint that (assembly, component) pairs be unique in the database. There's no problem retorfitting this; add
class Meta:
unique_together = (('assembly', 'component'),)
to the PartOf class definition, then run manage.py syncdb
.
Django creates the necessary index with no interference to the rest of the schema.