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:
(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
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)
manage.py shellto straighten out the relations between
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
shows that none of the ManyToMany configuration makes any difference to the schema for
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;
m2m_part. As long as
m2m_partofhas 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:
partsof = models.ManyToManyField ('self', symmetrical=False, through='PartOf')
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
to the PartOf class definition, then run
class Meta: unique_together = (('assembly', 'component'),)
manage.py syncdb. Django creates the necessary index with no interference to the rest of the schema.