Suppose a desk coordinator needs to change available quantities for several equipment records. “Recorder” and “Headphones” may appear next to each other, but their screen position is not their database identity. The server must update the correct records even when the page displays a different order.
Start with a deliberately small editable field set
The following integration fragments target Django 5.2 documentation. They assume an existing Equipment model with owner, name, and quantity fields, authenticated users, an existing template route, and normal CSRF middleware. They do not create a complete application.
from django import forms
from django.forms import modelformset_factory
from .models import Equipment
class EquipmentForm(forms.ModelForm):
quantity = forms.IntegerField(min_value=0, max_value=500)
class Meta:
model = Equipment
fields = ["quantity"]
EquipmentFormSet = modelformset_factory(
Equipment,
form=EquipmentForm,
extra=0,
edit_only=True,
)
The upper limit of 500 is an illustrative desk rule. Replace it with the actual requirement. extra=0 removes blank creation rows; edit_only=True states that this screen updates existing objects. Restricting the editable fields also prevents an innocent layout change from exposing unrelated model fields.
Bind the same authorized rows on GET and POST
Scope the queryset to rows the current user may edit. Keep that restriction when processing the submission. This example also restricts the hidden primary-key field's choices to the same queryset:
from django.contrib.auth.decorators import login_required
from django.db import transaction
from django.shortcuts import redirect, render
@login_required
def equipment_table(request):
rows = Equipment.objects.filter(owner=request.user).order_by("pk")
formset = EquipmentFormSet(
request.POST if request.method == "POST" else None,
queryset=rows,
)
for form in formset:
form.fields[Equipment._meta.pk.name].queryset = rows
if request.method == "POST" and formset.is_valid():
with transaction.atomic():
formset.save()
return redirect(request.path)
return render(request, "equipment/table.html", {"formset": formset})
Use your actual authorization rules; ownership is only this example's rule. A hidden input is submitted by the browser and is not an authorization boundary. This view does not implement concurrent-edit conflict detection. If several coordinators edit the same record, decide how the application will detect an outdated submission.
The transaction groups the save operations. It does not make invalid data valid, and it does not provide a user-facing conflict policy. Django describes those boundaries in its transaction documentation.
Render the fields that connect the rows
In equipment/table.html, keep the management form outside the table and hidden row fields inside a cell:
<form method="post">
{% csrf_token %}
{{ formset.management_form }}
{{ formset.non_form_errors }}
<table>
<thead>
<tr><th scope="col">Equipment</th><th scope="col">Available</th></tr>
</thead>
<tbody>
{% for form in formset %}
<tr>
<th scope="row">
{{ form.instance.name }}
{% for hidden in form.hidden_fields %}
{{ hidden }}{{ hidden.errors }}
{% endfor %}
{{ form.non_field_errors }}
</th>
<td>
{{ form.quantity.label_tag }}
{{ form.quantity }}{{ form.quantity.errors }}
</td>
</tr>
{% endfor %}
</tbody>
</table>
<button type="submit">Save quantities</button>
</form>
The management form supplies bookkeeping such as the submitted form count. The hidden primary key associates each form with its existing record. A manually rendered table needs both; the names printed in the first column cannot replace them. Django formset management data
Check an invalid row before trusting a successful save
Use two fictional records, Recorder with quantity 6 and Headphones with quantity 12. Change Recorder to 7 and submit Headphones as -1. If browser validation prevents that submission, use your development test client to exercise server validation. The server should return a bound formset with the invalid field's error and retain the attempted values; it should not save Recorder's 7 while rejecting Headphones.
Then correct Headphones to 10 and submit again. Reopen the page and inspect the actual records: Recorder should be 7 and Headphones 10. Also test an identifier outside the user's authorized rows. The submission must not change another user's equipment.
Run those development checks before relying on the table for actual stock changes. The standalone editable HTML table solves a different problem with browser editing and local persistence. For Django model rows, finish by checking the saved record identities, visible errors, and the table at the width coordinators use.