Saving class-based view formset items with a new "virtual" column
Annotating query with virtual column
Sum
is an aggregate expression and is not how you want to be annotating this query in this case. Instead, you should use an F exrepssion to add the value of two numeric fields
qs.annotate(virtual_col=F('field_one') + F('field_two'))
So your corrected queryset would be
Item.objects.order_by('code__name').annotate(amount=F('box_one') + F('box_two'))
The answer provided by cezar works great if intend to use the property only for 'row-level' operations. However, if you intend to make a query based on amount
, you need to annotate the query.
Saving the formset
You have not provided a post
method in your view class. You'll need to provide one yourself since you're not inheriting from a generic view that provides one for you. See the docs on Handling forms with class-based views. You should also consider inheriting from a generic view that handles forms. For example ListView
does not implement a post
method, but FormView
does.
Note that your template is also not rendering form errors. Since you're rendering the formset manually, you should consider adding the field errors (e.g. {{ form.field.errors}}
) so problems with validation will be presented in the HTML. See the docs on rendering fields manually.
Additionally, you can log/print the errors in your post
method. For example:
def post(self, request, *args, **kwargs): formset = MyFormSet(request.POST) if formset.is_valid(): formset.save() return SomeResponse else: print(formset.errors) return super().post(request, *args, **kwargs)
Then if the form does not validate you should see the errors in your console/logs.
You're already on the right path. So you say you need a virtual column. You could define a virtual property in your model class, which won't be stored in the database table, nevertheless it will be accessible as any other property of the model class.
This is the code you should add to your model class Item
:
class Item(models.Model): # existing code @property def amount(self): return self.box_one + self.box_one
Now you could do something like:
item = Item.objects.get(pk=1)print(item.box_one) # return for example 1print(item.box_two) # return for example 2print(item.amount) # it will return 3 (1 + 2 = 3)
EDIT:
Through the ModelForm
we have access to the model instance and thus to all of its properties. When rendering a model form in a template we can access the properties like this:
{{ form.instance.amount }}
The idea behind the virtual property amount
is to place the business logic in the model and follow the approach fat models - thin controllers. The amount
as sum of box_one
and box_two
can be thus reused in different places without code duplication.