Well, that is quite a confusing post title, isn't it? What the heck does all of that mean?
I have been working on a multi-location inventory management system that requires widgets to move back and forth between multiple locations, receives items into inventory, ship items to customers, return items to multiple vendors and keep track of all of the inventory totals across all locations, including what is currently on order and what items are in transit.
I ended up creating 3 models that are important to this post. There are a couple of other models you'll see in the Gist, ItemCategory, ItemGroup, Vendor, etc.. however, those are not relevant to this topic.
I'll use these 3 models to build out the initial Django admin interface.
Now that the models look good, I start by creating the ItemLocationInLine in admin.py
class ItemLocationInline(admin.TabularInline): model = ItemLocation formset = ItemLocationInlineFormset verbose_name_plural = 'Items By Location' extra = 1 can_delete = True show_change_link = True
Next, I set up the BaseInlineFormSet in admin.py...
class ItemLocationInlineFormset(BaseInlineFormSet): def save_new(self, form, commit=True): return super(ItemLocationInlineFormset, self).save_new(form, commit=commit) def save_existing(self, form, instance, commit=True): return form.save(commit=commit)
This is looking pretty good so far.
Next, I define the properties for the ItemAdmin by inheriting from admin.ModelAdmin.
OK, now for the special sauce...
When I save or update my Item, I want to calculate the totals by location for each of these fields:
* total_qty_on_hand * total_qty_on_order * total_qty_in_transit
Since my location inventory totals are part of the ItemLocation model, I have created an inline form that will display a very nicely formatted inline tabular form for all of my active Locations within my Item instance.
Items By Location
When I click Save, I need to SUM the totals and save it back to the parent model.
Example. I have an Item - Widget 1, with inventory in 3 locations. If I have 5 widgets in 3 locations, my total inventory on hand is 15. That is the total quantity that should be saved to the Item instance.
This is achieved in two different ways.
The first is a model override in which I create a function to update the Item model's field total inventory.
def update_totals(self): total_qty_on_hand = 0 total_qty_on_order = 0 total_qty_in_transit = 0 for item in self.itemlocation_set.all(): total_qty_on_hand += item.qty_on_hand total_qty_on_order += item.qty_on_order total_qty_in_transit += item.qty_in_transit self.total_qty_on_hand = total_qty_on_hand self.total_qty_on_order = total_qty_on_order self.total_qty_in_transit = total_qty_in_transit self.save()
The important part of this function is:
for item in self.itemlocation_set.all():
This query passes in the Item instance's PK and returns a queryset in which I have access to all of the ItemLocation's quantities in order to sum the totals.
The other important part of this workflow is the save__formset override:
# override the save_formset method and update the total qty's def save_formset(self, request, form, formset, change): instances = formset.save(commit=False) for instance in instances: instance.save() instance.item.update_totals() formset.save_m2m()
Here, for each form instance, I am calling the Item models's
update_totals() function in the
save_formset override before saving the formset.
This works perfectly and each subsequent save on an Item instance updates the Item's fields for total quantities.
Everything you want to do in Django that is outside the scope of Django's default behavior is done by overriding a model or formset. This is part of the standard workflow for Django and makes extending Django much easier. It may be difficult to visualize the complexities of the underlying functions, but once you understand this, the concepts become much clearer.