Saving class-based view formset items with a new “virtual” column

雨燕双飞 提交于 2019-12-05 02:38:48

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 1
print(item.box_two) # return for example 2
print(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.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!