Reputation: 678
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
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
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