Benjamin Riggs
Benjamin Riggs

Reputation: 678

How best to architect django models to fit a scenario?

I'm modeling a system involving a process using a small number of inputs in a recipe to create outputs. The inputs have a subset of the attributes of outputs, but the complication is that outputs can also be used as inputs. The recipes need different quantities of different inputs. Ideally, I'd like to do this:

class CommonAttrs(models.Model):
    name = model.CharField()
    value = model.FloatField()
    cost = model.FloatField()

    class Meta:
        abstract = True

class Input(CommonAttrs):
    pass

class Output(CommonAttrs):
    price = model.FloatField()
    demand = model.FloatField()
    ...

class Recipe(models.Model):
    output = model.ForeignKey(Output)
    input_1 = model.OneToMany([Input, Output])  # I can dream
    quantity_1 = model.FloatField()
    input_2 = model.OneToMany([Input, Output])
    quantity_2 = model.FloatField()
    input_3 = model.OneToMany([Input, Output])
    quantity_3 = model.FloatField()
    input_4 = model.OneToMany([Input, Output])
    quantity_4 = model.FloatField()

However, in lieu of a OneToMany relationship in django, I've done:

class Recipe(models.Model):
    output = model.ForeignKey(Output)
    input = model.ManyToManyField(Input, through='Measures')

class Measures(models.Model):
    recipe = model.ForeignKey(Recipe)
    input = model.ForeignKey(Input)
    quantity = model.FloatField()

That's all fine, but misses the crucial detail of outputs also being inputs of the recipe. I created a view in my database and attempted to create a new unmanaged model to in place of Input:

class InputOrOutput(CommonAttrs):

    class Meta:
        managed = False
        db_table = 'input_union_output_view'  # Does what is says on the tin

However this lead to a quagmire of problems with initialization and migrations.

My last resort would be creating a full-fledged table instead of a view, but that feels like asking for a headache from inconsistent data.

What are better options?

Upvotes: 0

Views: 90

Answers (2)

dylrei
dylrei

Reputation: 1738

Assuming, per the comments that Inputs and Outputs can't go in the same table, then a Generic Foreign Key is probably your best option to have a relationship that binds to either model. To start with your Recipe code:

from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType

class Recipe(models.Model):
    output = model.ForeignKey(Output)
    input_1_content_type = models.ForeignKey(ContentType)
    input_1_object_id = models.PositiveIntegerField()
    input_1_content_object = GenericForeignKey('input_1_content_type', 'input_1_object_id')
    quantity_1 = model.FloatField()

This would get you a generic FK for your input_1. That's a lot of code if you have to do each input manually, but maybe that's OK.

It would be nice if there were a simple way to do generic M2M out of the box, but I don't believe there is. Instead, perhaps an intermediate class would get you most of the way there:

class Recipe(models.Model):
    output = model.ForeignKey(Output)
    @property
    def inputs(self):
        return self.recipeinput_set


class RecipeInput(models.Model):
    recipe = models.ForeignKey(Recipe)
    content_type = models.ForeignKey(ContentType)
    object_id = models.PositiveIntegerField()
    content_object = GenericForeignKey('content_type', 'object_id')
    quantity = model.FloatField()

Upvotes: 3

user764357
user764357

Reputation:

Without knowing what an Input or an Output is, I'll assume its food. Which is handy.

A recipe uses food to produce a different type of food.

class Recipe(models.Model):
    output = model.ForeignKey(Food, related_name="produced_by")
    input = model.ManyToManyField(Food, through='Measures')

For example (this code may or may not work, but illustrates the point):

cheese = Food("Cheese")
bread  = Food("Bread")
toasty = Food("Toasted Cheese Sandwich")

toasty.produced_by = Recipe(input=[cheese,bread])  

A input measure then just references a Food item.

class Measures(models.Model):
    recipe = model.ForeignKey(Recipe)
    input = model.ForeignKey(Food)
    quantity = model.FloatField()

Each Food then has a purchase and sale price - assuming there is nothing stopping me from buying cheese and then selling it.

class Food(models.Model):
    name = model.CharField()
    value = model.FloatField()
    purchase_price = model.FloatField()
    sale_price = model.FloatField()
    demand = model.FloatField()

Upvotes: 1

Related Questions