简体   繁体   中英

Properly using modelformset_factory to use formset

Problem Description: I am building a formset to display all data in one of my models, and to edit it. My view is rendered properly, and the correct data as saved in the model is displayed. But on POST, I get an exception, stating that "ManagementForm data is missing or has been tampered with". I would like to know why this happens. Complete code and trace is given below.

My model:

class ProcedureTemplate(models.Model):
    templid = models.AutoField(primary_key=True, unique=True)
    title = models.CharField(max_length=200)
    description = models.CharField(max_length=5000, default='', blank=True)
    clinic = models.ForeignKey(Clinic, on_delete=models.CASCADE)

    def __str__(self):
        return f'{self.description}'

class SectionHeading(models.Model):
    procid = models.AutoField(primary_key=True, unique=True)
    name = models.CharField(max_length=200)
    default = models.CharField(max_length=1000)
    sortorder = models.IntegerField(default=1000)

    fieldtype_choice = (
        ('heading1', 'Heading1'),
        ('heading2', 'Heading2'),
        )
    fieldtype = models.CharField(
        choices=fieldtype_choice, max_length=100, default='heading1')

    template = models.ForeignKey(ProcedureTemplate, on_delete=models.CASCADE, null=False)

    def __str__(self):
        return f'{self.name} [{self.procid}]'

My Form:

class ProcedureCrMetaForm(ModelForm):
    class Meta:
        model = SectionHeading
        fields = [
            'name',
            'default',
            'sortorder',
            'fieldtype'
        ]
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.fields['name'].widget.attrs.update({'class': 'form-control'})
        self.fields['default'].widget.attrs.update({'class': 'form-control'})
        self.fields['sortorder'].widget.attrs.update({'class': 'form-control'})
        self.fields['fieldtype'].widget.attrs.update({'class': 'form-control'})

ProcedureCreationFormset = formset_factory(ProcedureCrMetaForm, extra=3)
ProcedureModificationFormset = modelformset_factory(SectionHeading, ProcedureCrMetaForm,
    fields=('name', 'default', 'sortorder','fieldtype'),
    # widgets={"name": Textarea()}
    )

My view:

def procedure_template_modification_alt(request, cliniclabel, template_id):
    msg = ''
    clinicobj = Clinic.objects.get(label=cliniclabel)
    template_id = int(template_id)
    template= ProcedureTemplate.objects.get(templid = template_id)

    formset = ProcedureModificationFormset(queryset=SectionHeading.objects.filter(template = template))


    if request.method == 'POST':
        print(request.POST.get)
        # Create a formset instance with POST data.
        formset = ProcedureModificationFormset(request.POST)
        if formset.is_valid():
            print("Form is valid")
        else:
            print("Form is invalid")
        # Assuming all is valid, save the data.
        instances = formset.save()
        print(instances)

    template= ProcedureTemplate.objects.get(templid = template_id)
    headings = SectionHeading.objects.filter(template = template)

    return render(request, 'procedures/create_procedure_formset_alt.html',
    {
        'template': template,
        'formset': formset,
        'headings': headings,
        'msg': msg,
        'rnd_num': randomnumber(),
    })

My template:

{% block content %}
{% load widget_tweaks %}
<div class="container">
    {% if user.is_authenticated %}
    <div class="row my-1">
            <div class="col-sm-2">Name</div>
            <div class="col-sm-22">
                <input type="text" name="procedurename" class="form-control" placeholder="Enter name of procedure (E.g. DNE)" value="{{ template.title }}" />
            </div>
    </div>
    <div class="row my-1">
        <div class="col-sm-2">Description</div>
        <div class="col-sm-22">
            <input type="text" name="proceduredesc" class="form-control" placeholder="Enter description of procedure (E.g. Diagnostic Nasal Endoscopy)" value="{{ template.description }}" />
        </div>
    </div>
    <form action="" method="post" enctype="multipart/form-data">
        {% csrf_token %}        
        <div class="row mt-3">
            <div class="col-sm-1">Select</div>
            <div class="col-sm-6">Heading</div>
            <div class="col-sm-8">Default (Normal description)</div>
            <div class="col-sm-2">Sort Order</div>
            <div class="col-sm-4">Type</div>
            <div class="col-sm-2">Action</div>
        </div>        
        {% for form in formset %}
            <div class="col-sm-6">
                {{ form.name }}
            </div>
            <div class="col-sm-8">
                <div class="input-group">
                    {{ form.default }}
                </div>
            </div>
            <div class="col-sm-2">
                <div class="input-group">
                    {{ form.sortorder }}
                </div>
            </div>
            <div class="col-sm-4">
                <div class="input-group">
                    {{ form.fieldtype }}
                </div>
            </div>
            <div class="col-sm-2">
                <div class="input-group">                    
                    <div class="input-group-append">
                        <button id="add{{ forloop.counter0 }}" class="btn btn-success add-row">+</button>
                    </div>
                    <div class="input-group-append">
                        <button id="del{{ forloop.counter0 }}" class="btn btn-danger del-row">-</button>
                    </div>
                </div>
            </div>
        </div>
        {% endfor %}        
    {% endif %}
    <div class="row my-3">
        <div class="col-sm-8"></div>
        <div class="col-sm-8">
            <div class="input-group">                    
                <div class="input-group-append mx-1">
                    <button id="save_report" type="submit" class="btn btn-success"><i class="fal fa-shield-check"></i> Save Report Format</button>
                </div>
                <div class="input-group-append mx-1">
                    <button id="save_report" type="button" class="btn btn-danger"><i class="fal fa-times-hexagon"></i> Cancel</button>
                </div>
            </div>    
        </div>
        <div class="col-sm-8"></div>
    </div>
    </form>
</div>
{% endblock %}

Complete trace:

[02/Feb/2019 22:59:16] "POST /clinic/joelent/procedures/template/modify/3 HTTP/1.1" 500 99735
[02/Feb/2019 22:59:17] "GET /favicon.ico/ HTTP/1.1" 200 14
<bound method MultiValueDict.get of <QueryDict: {'csrfmiddlewaretoken': ['7G5XVYRwt3LsrL9oXr9yOeaIU6HLYwu9cJtE8UpVB3R67Lb7oU8QXQ5kzfBJDJSP'], 'form-0-name': ['External ear canal'], 'form-0-default': ['Bilateral external ear canals appear normal. No discharge.'], 'form-0-sortorder': ['100'], 'form-0-fieldtype': ['heading1'], 'form-1-name': ['Tympanic membrane'], 'form-1-default': ['Tympanic membrane appears normal. Mobility not assessed.'], 'form-1-sortorder': ['500'], 'form-1-fieldtype': ['heading1'], 'form-2-name': [''], 'form-2-default': [''], 'form-2-sortorder': ['1000'], 'form-2-fieldtype': ['heading1']}>>
2019-02-02 23:04:03,415 django.request ERROR    Internal Server Error: /clinic/joelent/procedures/template/modify/3
Traceback (most recent call last):
File "/home/joel/.local/lib/python3.6/site-packages/django/core/handlers/exception.py", line 34, in inner
    response = get_response(request)
File "/home/joel/.local/lib/python3.6/site-packages/django/core/handlers/base.py", line 126, in _get_response
    response = self.process_exception_by_middleware(e, request)
File "/home/joel/.local/lib/python3.6/site-packages/django/core/handlers/base.py", line 124, in _get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
File "/home/joel/myappointments/clinic/views.py", line 5664, in procedure_template_modification_alt
    if formset.is_valid():
File "/home/joel/.local/lib/python3.6/site-packages/django/forms/formsets.py", line 301, in is_valid
    self.errors
File "/home/joel/.local/lib/python3.6/site-packages/django/forms/formsets.py", line 281, in errors
    self.full_clean()
File "/home/joel/.local/lib/python3.6/site-packages/django/forms/formsets.py", line 322, in full_clean
    for i in range(0, self.total_form_count()):
File "/home/joel/.local/lib/python3.6/site-packages/django/forms/formsets.py", line 110, in total_form_count
    return min(self.management_form.cleaned_data[TOTAL_FORM_COUNT], self.absolute_max)
File "/home/joel/.local/lib/python3.6/site-packages/django/utils/functional.py", line 37, in __get__
    res = instance.__dict__[self.name] = self.func(instance)
File "/home/joel/.local/lib/python3.6/site-packages/django/forms/formsets.py", line 92, in management_form
    code='missing_management_form',
django.core.exceptions.ValidationError: ['ManagementForm data is missing or has been tampered with']

The issue with formsets is that you don't know how many forms your formset will contain. Formsets are usually accompanied by some Javascript to allow adding new forms or deleting old ones. The management form is required to keep track of these changes.

If you fail to include the management form or it doesn't match with the POST data provided the Exception you encountered will be raised. To fix it, simply include the management form.


From the django documentation :

The management form is available as an attribute of the formset itself. When rendering a formset in a template, you can include all the management data by rendering {{ my_formset.management_form }} (substituting the name of your formset as appropriate).

So in your template just add the management form:

<form action="" method="post" enctype="multipart/form-data">
    {% csrf_token %}        
    {{ formset.management_form }}
    {# Your formset rendering... #}
</form>

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM