4

How do I make form fields in a model formset disabled when the forms already exist and just need a couple columns updated, but make those same fields editable when a new form is added to the formset by the user?

models.py:

from django.db import models
from django.utils import timezone

class MyModel(models.Model):
    idno = models.CharField(max_length=20)
    date = models.DateTimeField(default=timezone.now)
    entity = models.CharField(max_length=50)
    logic = models.CharField(max_length=100)
    choices = (
        ('1', 'Choice1'),
        ('2', 'Choice2'),
        ('3','Choice3'),
    )
    choices = models.CharField(
        max_length=20,
        choices=choices,
        null=True,
        )
    comment = models.CharField(max_length=500, null=True)
    def __str__(self):
        return self.idno

forms.py:

from .models import MyModel
from django.forms import modelformset_factory, ModelForm

class MyForm(ModelForm):

    class Meta:
        model = MyModel
        fields = '__all__'

    def __init__(self, *args, **kwargs):
        super(MyForm, self).__init__(*args, **kwargs)
        self.fields['idno'].disabled = True
        self.fields['date'].disabled = True
        self.fields['entity'].disabled = True
        self.fields['logic'].disabled = True

MyFormSet = modelformset_factory(MyModel, extra=1, exclude=(), form=MyForm)

views.py:

from django.shortcuts import render
from django.http import HttpResponseRedirect
from .models import Alert
from .forms import AlertFormSet
from django.contrib import messages

def index(request):
    newAlerts = MyModel.objects.filter(choices__isnull=True)
    modelformset = MyFormSet(request.POST or None, queryset=newAlerts)
    context = {'modelformset':modelformset}
    if request.method == 'POST':
        for form in modelformset:
            if form.is_valid():
                if form.has_changed():
                    form.save()
                    idno = form.cleaned_data['idno']
                    entity = form.cleaned_data['entity']
                    messages.success(request, 'idno %s for %s was saved' % (idno, entity))
        return HttpResponseRedirect('/')
    return render(request, 'selfserve/index.html', context)

index.html:

<form method="post" action="">
  {% csrf_token %}
  {{ modelformset.management_form }}
<div id="form_set">
    {% for form in modelformset %}
        <table class='no_error'>
            {{ form.as_table }}
        </table>
    {% endfor %}
</div>
<input type="button" value="Add More" id="add_more">
<input type="submit" value="Submit">
<div id="empty_form" style="display:none">
    <table class='no_error'>
        {{ modelformset.empty_form.as_table }}
    </table>
</div>
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<script>
    $('#add_more').click(function() {
        var form_idx = $('#id_form-TOTAL_FORMS').val();
        $('#form_set').append($('#empty_form').html().replace(/__prefix__/g, form_idx));
        $('#id_form-TOTAL_FORMS').val(parseInt(form_idx) + 1);
    });
</script>
{% if messages %}
<ul class="messages">
    {% for message in messages %}
    <li{% if message.tags %} class="{{ message.tags }}"{% endif %}>{{ message }}</li>
    {% endfor %}
</ul>
{% endif %}
</form>

Right now, the fields I want disabled for existing forms are also disabled when the user adds a new form with the "Add More" button. I've seen this question:In a Django form, how do I make a field readonly (or disabled) so that it cannot be edited?. However, I don't understand how to combine both elements of the solution: fields.disabled = True in django > 1.9, and an if statement to delineate between existing and new forms in the formset. I've also heard of the _construct_form method as a potential option, but I think an if statement in the Model Form creation would be cleaner and easier to understand.

Thank you for any and all insight you might have!

OverflowingTheGlass
  • 2,324
  • 1
  • 27
  • 75

1 Answers1

4

It seems to me that you can use the technique in the question you link to:

class MyForm(ModelForm):

    class Meta:
        model = MyModel
        fields = '__all__'

    def __init__(self, *args, **kwargs):
        super(MyForm, self).__init__(*args, **kwargs)
        instance = getattr(self, 'instance', None)
        if instance and instance.pk:
            self.fields['idno'].disabled = True
            self.fields['date'].disabled = True
            self.fields['entity'].disabled = True
            self.fields['logic'].disabled = True

Note that you can only do this by checking the instance exists because all fields are required. If some of them weren't you would need to check whether each of them had a value.

Paulo Almeida
  • 7,803
  • 28
  • 36