Formsets do not recognize Model property decorators?

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')>
>>> 

Keep in mind that a “property” is not a Field, it is an attribute of a class. It is not an entity that would be handled by any ModelForm.

Your fields are _amount_weight and _amount_volume - that’s what’s available to you in the form.

Now, if it’s the case that you want one or the other in a particular instance of the form, then you could create a custom form that initializes itself based upon the values in the instance. (You’ll have to decide what to do when using that form to create a new instance.)