sheepeatingtaz
sheepeatingtaz

Reputation: 143

Django Model Design - Many-To-Many Fields

Coming from a php background, I know how I would do this 'by hand' but I can't seem to get my head round how to structure my database with Django ORM.

The app centres around a checklist. A Checklist has some identifying features, lets say Name, DateOfBirth and ChecklistType

There are several ChecklistTypes, each of which has many ChecklistOptions. A ChecklistOption may or may not be in several ChecklistTypes

My initial thought was to create a model for ChecklistType and ChecklistTypeOptions to store the 'configuration' of the Checklists, and a seperate model for Checklist and Requirements (respectively) to link to the Customer model. I thought I would manually look up the configuration and create the latter model entries in the view that creates the Customer. I then realised this defeats to point of the ORM!

I'm sure that this question could be solved by a proper understanding of the way to use Many-to-many fields in the models, but the Django Documentation seems to only deal with simple applications of this. If I create a ManyTomany field in the Checklist to ChecklistOption (with ChecklistType as a ChoiceField), I lose the ability to specify which options are applicable to that type.

I'm sure this type of thing must be done regularly, but I can't get my head around it.

Thanks in advance,

Clarification - Example Data

ChecklistOptions

Cheese {boolean}
Tomato {boolean}
Bacon {boolean}
Chicken {boolean}
Coffee {boolean}
Pepperoni {boolean}
Salami {boolean}
Mushrooms {boolean}
Eggs {boolean}

ChecklistTypes

ThingsToPutOnPizza: {options: Cheese, Tomato, Bacon, Chicken, Salami, Mushrooms}
ThingsToEatAtBreakfast: {options: Cheese, Bacon, Coffee, Eggs}

Customer

John Smith: {Name: "John Smith", DOB: 1984-12-17, Checklist: ThingsToEatAtBreakfast}
Jane Jones: {Name: "Jane Jones", DOB: 1987-07-22, Checklist: ThingsToPutOnPizza}
Frank Allen {Name: "Frank Allen", DOB: 1990-04-01, Checklist: ThingsToPutOnPizza}

Workflow

A user creates a customer, and selects the checklist type. Another user has to 'complete' the checklist, so selects the customer from list and is presented with a list of checkboxes for the options in that checklist type, and of those options they may tick some and not others. On saving the form/model their user and the time is logged against the checklist to mark it as complete.

Upvotes: 1

Views: 787

Answers (2)

Todor
Todor

Reputation: 16010

Тhis is probably the simplest solution:

models.py

from django.db import models

class CheckList(models.Model):
    name = models.CharField(max_length=255)
    checklist_type = models.ForeignKey('CheckListType')
    options = models.ManyToManyField('CheckListOption', blank=True)

    def __unicode__(self):
        return self.name

class CheckListType(models.Model):
    name = models.CharField(max_length=255)
    options = models.ManyToManyField('CheckListOption')

    def __unicode__(self):
        return self.name

class CheckListOption(models.Model):
    name = models.CharField(max_length=255)

    def __unicode__(self):
        return self.name

forms.py

from django import forms

from .models import CheckList, CheckListOption

class CheckListForm(forms.ModelForm):
    class Meta:
        model = CheckList
        fields = '__all__'

    def __init__(self, *args, **kwargs):
        super(CheckListForm, self).__init__(*args, **kwargs)
        if self.instance.pk:
            self.fields['options'].queryset = CheckListOption.objects.filter(
                checklisttype=self.instance.checklist_type_id
            )
        else:
            self.fields['options'].queryset = CheckListOption.objects.none()

admin.py

from django.contrib import admin

from .forms import CheckListForm
from .models import CheckList, CheckListType, CheckListOption

class CheckListAdmin(admin.ModelAdmin):
    form = CheckListForm

admin.site.register(CheckList, CheckListAdmin)
admin.site.register(CheckListType)
admin.site.register(CheckListOption)

There is only one drawback - when you already have a saved CheckList instance and you want to change the checklist_type, you wont get the new options on the moment. The user doing the change should unselect the selected options (this is kind of an optional, but if not done, the selected options will remain until the next save), save the model and edit it again to chose the new options.

Upvotes: 1

Burhan Khalid
Burhan Khalid

Reputation: 174614

Lets start with the basic requirements:

The app centres around a checklist. A Checklisthas some identifying features, lets say Name, DateOfBirth and ChecklistType

There are several ChecklistTypes, each of which has many ChecklistOptions. A ChecklistOption may or may not be in several ChecklistTypes

The below models take care of your initial requirements:

class CheckListOption(models.Model):
    name = models.CharField(max_length=200)

class CheckListType(models.Model):
    name = models.CharField(max_length=200)
    options = models.ManyToManyField(CheckListOption)

class Checklist(models.Model):
    name = models.CharField(max_length=200)
    dob = models.DateField()
    type = models.ForeignKey(CheckListType)

Now, with those in mind; lets see how you plan to use these models:

A user creates a customer, and selects the checklist type. Another user has to 'complete' the checklist, so selects the customer from list and is presented with a list of checkboxes for the options in that checklist type, and of those options they may tick some and not others. On saving the form/model their user and the time is logged against the checklist to mark it as complete.

To take care of your tracking, you need to add a few more fields to the Checklist model:

class Checklist(models.Model):
    name = models.CharField(max_length=200)
    dob = models.DateField()
    type = models.ForeignKey(CheckListType)
    created_by = models.ForeignKey(User, name='creator')
    created_on = models.DateTimeField(auto_now_add=True)
    completed_by = models.ForeignKey(User,
                                     name='maker', blank=True, null=True)
    completed_on = models.DateTimeField(blank=True, null=True)

    def is_complete(self):
        return self.completed_on

To take care of your requirement that "and of those options they may tick some and not others", you'll need to modify the CheckListType model, by creating a custom through model; which will allow you specify options on a relationship:

class CheckListOptionConfig(models.Model):
    checklisttype = models.ForeignKey('CheckListType')
    optiontype = models.ForeignKey('CheckListOption')
    is_required = models.BooleanField(default=False)

class CheckListType(models.Model):
    name = models.CharField(max_length=200)
    options = models.ManyToManyField(CheckListOption, through='CheckListOptionConfig')

The rest of your workflow describes the user interface; which can easily be configured to show all manners of alerts/warnings if the right options are not ticked.

Upvotes: 0

Related Questions