Formsets. Initial data cannot be displayed in ModelChoiceField

56 views Asked by At

In my application, the user can create and edit reports. When rendering the report edit page, the fields are filled with initial data.
I ran into the following problem - one of the fields (operator) does not display the value that I pass to it in the view through the initial argument. Instead, the field either remains empty or takes on a default value, depending on the settings. All other fields are filled with initial data without errors.
I'm using Formsets, the problem field is ModelChoiceField. The initial data is taken from the json array.
As far as I could understand the HTML code - in the problem field "operator", in the "option" tag (it seems to be generated by the Select widget) the "selected" attribute is missing. But how to put this attribute there is not clear. Adding to the confusion is that the "eq_type" field, which is also a select field, is displayed without errors. "selected" appears next to the desired option.
Maybe someone came across a similar one? Please, tell me what could be the problem.

fragment of forms.py:

class AMSEquipmentForm(forms.Form):
    EQ_TYPES = (
        ('panel_antenna', 'панельная антенна'),
        ('RRL_antenna', 'РРЛ антенна'),
        ('radio_module', 'радиомодуль')
    )
    eq_type = forms.ChoiceField(choices=EQ_TYPES, label='Тип')
    height = forms.IntegerField(label='Высота')
    proportions = forms.IntegerField(label='Размеры')
    amount = forms.IntegerField(label='Количество')
    manufacturer = forms.CharField(max_length=50, label='Производитель')
    model = forms.CharField(max_length=50, label='Модель')
    operator = forms.ModelChoiceField(queryset=Operator.objects.all(), label='Оператор')  # Проблемное поле
    note = forms.CharField(max_length=100, label='Примечание')

    eq_type.widget.attrs.update({'class': 'formset-field'})
    height.widget.attrs.update({'class': 'formset-field'})
    proportions.widget.attrs.update({'class': 'formset-field'})
    amount.widget.attrs.update({'class': 'formset-field'})
    manufacturer.widget.attrs.update({'class': 'formset-field'})
    model.widget.attrs.update({'class': 'formset-field'})
    operator.widget.attrs.update({'class': 'formset-field'}) # Проблемное поле
    note.widget.attrs.update({'class': 'formset-field'})

fragment of views.py:

def report_update(request, pk):
    EquipmentFormset = formset_factory(AMSEquipmentForm, max_num=1)
    report = Report.objects.get(idReport=pk)
    form = ReportModelForm(instance=report)
    formset = EquipmentFormset(initial=json.loads(report.reportEquipAms), prefix='reports_report') # Здесь передаю начальные данные
    if request.method == 'POST':
        form = ReportModelForm(request.POST or None)
        formset = EquipmentFormset(request.POST or None, prefix='reports_report')
        if form.is_valid() and formset.is_valid():
            report.reportYear = form.cleaned_data['reportYear']
            report.reportObject_id = form.cleaned_data['reportObject']
            report.reportTemplate_id = form.cleaned_data['reportTemplate']
            report.reportTeam_id = form.cleaned_data['reportTeam']
            report.reportEquipment_id = form.cleaned_data['reportEquipment']
            report.reportWind = form.cleaned_data['reportWind']
            report.reportWeather = form.cleaned_data['reportWeather']
            report.reportSoil = form.cleaned_data['reportSoil']
            report.reportTemp = form.cleaned_data['reportTemp']
            report.reportWeather3 = form.cleaned_data['reportWeather3']
            report.reportElVoltage = form.cleaned_data['reportElVoltage']
            report.reportElCableL = form.cleaned_data['reportElCableL']
            report.reportElCableR = form.cleaned_data['reportElCableR']
            report.reportElRope = form.cleaned_data['reportElRope']
            report.reportElBus = form.cleaned_data['reportElBus']
            report.reportMeasuresDate = form.cleaned_data['reportMeasuresDate']
            report.reportData = form.cleaned_data['reportData']
            report.reportEquipAms = json.dumps(formset.cleaned_data, cls=OperatorEncoder, ensure_ascii=False)
            report.save()
            return redirect('reports:report-list')
    context = {
        'report': report,
        'form': form,
        'formset': formset
    }
    return render(request, "reports/report_update.html", context)

fragment of models.py:

class Operator(models.Model):
    idOperator = models.AutoField(primary_key=True)
    operatorName = models.CharField(max_length=45)
    operatorReport = models.ForeignKey("Report", null=True, on_delete=models.SET_NULL)
    operatorContract = models.ForeignKey("Contract",null=True, on_delete=models.SET_NULL)

    def __str__(self):
        return self.operatorName

    class Meta:
        verbose_name = "Оператор"
        verbose_name_plural = "Операторы"

Displaying fields in the browser as viewed through the code inspector:

field eq_type:

<select name="reports_report-1-eq_type" class="formset-field" id="id_reports_report-1-eq_type">
    <option value="panel_antenna">панельная антенна</option>
    <option value="RRL_antenna" selected>РРЛ антенна</option>
    <option value="radio_module">радиомодуль</option>
</select>

field operator:

<select name="reports_report-1-operator" class="formset-field" id="id_reports_report-1-operator">
    <option value="8">Оператор3</option>
    <option value="9">Оператор2</option>
    <option value="10">Оператор1</option>
</select>

A fragment of the html template that is responsible for the form:

            {% for equipment in formset %}
                <tr class="item">
                    <td>
                        {{ equipment.eq_type }}
                    </td>
                    <td>
                        {{ equipment.height }}
                    </td>
                    <td>
                        {{ equipment.proportions }}
                    </td>
                    <td>
                        {{ equipment.amount }}
                    </td>
                    <td>
                        {{ equipment.manufacturer }}
                    </td>
                    <td>
                        {{ equipment.model }}
                    </td>
                    <td>
                        {{ equipment.operator }}
                    </td>
                    <td>
                        {{ equipment.note }}
                    </td>
                    <td>
                        <button type="button" class="btn btn-danger btn-sm remove-form-row" id="{{ formset.prefix }}">Delete</button>
                    </td>
                </tr>
            {% endfor %}

update

The initial data that I transfer in reportEquipAms looks like this:

[  
  {  
    'eq_type': 'panel_antenna',  
    'height': 18,  
    'proportions': 10,  
    'amount': 1,  
    'manufacturer': 'manufacturer_name',  
    'model': 'some model',  
    'operator': 'Оператор2',  # Operator class object
    'note': 'some note'  
   },  
]

The list can contain one or more dictionaries. Based on the number of fields in the formset.

1

There are 1 answers

0
Павел Москвин On

@NKSM told me in comment that I should pass the primary key of the Operator object in the "operator" field. I passed the name of the object there.
I rewrote OperatorEncoder:

class OperatorEncoder(json.JSONEncoder):
    def default(self, obj):
        if isinstance(obj, Operator):
            return obj.idOperator
        return json.JSONEncoder.default(self, obj)

Now everything works as it should.