Search code examples
djangodjango-modelsmany-to-manymanytomanyfield

Need help with Django model design, ManyToManyField "through" an intermediate model and its implications for uniqueness


I have the following Django models: -

class Company(models.Model):
    name = models.CharField(max_length=50)
    is_active = models.BooleanField(db_index=True)

class Phase(models.Model):
    company = models.ForeignKey(Company)
    name = models.CharField(max_length=50)
    is_active = models.BooleanField(db_index=True)

class Process(models.Model):
    company = models.ForeignKey(Company)
    name = models.CharField(max_length=50)    
    phases = models.ManyToManyField(Phase, through='ProcessPhase')
    is_active = models.BooleanField(db_index=True)

class ProcessPhase(models.Model):
    process = models.ForeignKey(Process)
    phase = models.ForeignKey(Phase)
    order = models.PositiveIntegerField(help_text="At what step of your process will this phase occur?", unique=True)

A "company" has its "processes" and "phases". A process (of a company) is comprised of one or more phases (of the company). Each phase associated with a process has an "order". The requirement is that: -

  1. in a particular process of a company, a phase can appear only once;
  2. also "phase A" and "phase B" in a process cannot have the same order.

So I need to know: -

a) how to specify some "unique"s in the model definition to fulfill the above requirements;

b) what uniqueness, if any, is automatically implied by a ManyToManyField?


Solution

  • In your case, since you say "A process (of a company) is comprised of one or more phases (of the company)", it seems like you should have a structure like:

    Company <----* Process <----* Phase
    

    Company has its Processes, Process has its Phases. It's not really a ManyToMany relation, it's OneToMany (Process has many Phases, but each Phase is connected to one Process).

    If so, you should have

    class Phase(models.Model):
        process = models.ForeignKey(Process, null=True) # based on your comment, if a Phase does not belong to a Process, leave it null.
        phase = models.ForeignKey(Phase)
        order = models.PositiveIntegerField(help_text="At what step of your process will this phase occur?")
    
    
        class Meta:
            unique_togather = ("process", "order")
    

    The unique_together in Meta class is what you want, I think. It enforces both in admin and on database level the uniqueness of those 2 fields together.


    edit:
    (ForeignKey field can be null - see this)


    based on your comment:

    Don't use ManyToMany, as it auto-generates the "table-in-the-middle", while you need it specific for your needs. Instead, try defining the different model (together with your Company, Phase and Process):

    class PhaseOrder(models.Model):
      process = models.ForeignKey(Process)
      phase = models.ForeignKey(Phase)
      order = models.PositiveIntegerField(help_text="At what step of your process will this phase occur?")
      class Meta:
        unique_together = (("process", "order"), ("process", "phase"))