Hey Everyone! I have a Model where I store an amount of something. Inside my model, I have two fields: amount_weight and amount_volume. Either one or the other is used.
In order to not force the user to fill in two fields, I use a property decorator to create “amount” and a setter decorator to assign which field to use. See my model below:
models.py
class RecipeItem(models.Model):
class Meta:
abstract = True # We don't want our own table. Inherit these fiels
intended_use = models.ForeignKey(AdjunctUsage, on_delete=models.CASCADE)
_amount_weight = DescriptiveQuantityField('kilograms', null=True, blank=True, unit_choices=['lb', 'gram', 'oz', 'milligram', 'kilogram'])
_amount_volume = DescriptiveQuantityField('liters', null=True, blank=True, unit_choices=['floz', 'ml', 'gallon', 'liter'])
recipe_notes = models.CharField(max_length=200, null=True, blank=True)
@property
def amount(self):
if self._amount_weight is not None:
return self._amount_weight
return self._amount_volume
@amount.setter
def amount(self, value):
"""
Setter function. Identifies whether the amount is a volume or a weight.
Needed since volume has specific base units which are different than weights.
Single value to manage both types.
:param value:
:return:
"""
logger.debug(f"Setting amount: {value}" )
dimension_amount = Quantity(value)
if dimension_amount.check('[volume]'):
self._amount_volume = value
logger.debug(f"Found volume metric for RecipeItem. Storing: {value}")
elif dimension_amount.check('[mass]'):
self._amount_weight = value
logger.debug(f"Found weight metric for RecipeItem. Storing: {value}")
else:
logger.error(f"Cannot save Recipe Amount. Dimension does not match weight or volume: {value}")
class RecipeAdjunct(RecipeItem):
adjunct = models.ForeignKey(Adjunct, on_delete=models.CASCADE)
# Minutes since start of Batch on when to add
time_to_add = DescriptiveQuantityField(base_units='min', unit_choices='min')
recipe = models.ManyToManyField(Recipe, related_name='adjuncts')
def __str__(self):
return self.adjunct.display_name
This works really well. However, where it fails is in my formsets. I can’t load the ‘amount’ field in a formset. You can see my RecipeAdjunct is a many-to-many to a “Recipe” class. So, I use formsets to add more “RecipeAdjuncts” to a “Recipe”.
Once I added the ‘@property’ decorator, I can no longer pull in ‘amount’. But it does save and display fine. Here is a formset that I manually made:
views.py
def editAdjuncts(request, pk=None):
if request.method == "GET":
if pk:
recipe = Recipe.objects.get(pk=pk)
if len(recipe.adjuncts.all()) > 0:
#adjunct_set_form = modelformset_factory(RecipeAdjunct, fields=('adjunct', 'amount', 'time_to_add', 'intended_use','recipe_notes'))
adjunct_set_form = formset_factory(AdjunctForm, extra=0, can_delete=True, can_delete_extra=True)
adjunct_set = adjunct_set_form(initial=recipe.adjuncts.all().values())
#adjunct_set = adjunct_set_form(queryset=recipe.adjuncts.all())
else:
adjunct_set = formset_factory(AdjunctForm, extra=1)
context = {'recipe': recipe, 'adjunct_set': adjunct_set}
return render(request, 'batchthis/editAdjuncts.html', context)
So, above, I can’t use a manual formset, since it expects a dict of values(), which doesn’t bring over the ‘amount’ field, only the defined fields in the model. So, I tried to make a ModelFormset to use a queryset (note the commented fields), but it doesn’t read the decorator:
Error: Unknown field(s) (amount) specified for RecipeAdjunct
Otherwise, the property decorator and setter work beautifully. Should I be looking at doing this differently so I can use a formset?
See the shell for the success:
>>> adjunct = RecipeAdjunct.objects.get(pk=1)
>>> adjunct.amount
<Quantity(1.0, 'ounce')>
>>>