31

I've been looking for a way to create a read-only form field and every article I've found on the subject comes with a statement that "this is a bad idea". Now for an individual form, I can understand that there are other ways to solve the problem, but using a read only form field in a modelformset seems like a completely natural idea.

Consider a teacher grade book application where the teacher would like to be able to enter all the students' (note the plural students) grades with a single SUBMIT. A modelformset could iterate over all the student-grades in such a way that the student name is read-only and the grade is the editable field. I like the power and convenience of the error checking and error reporting you get with a modelformset but leaving the student name editable in such a formset is crazy.

Since the expert django consensus is that read-only form fields are a bad idea, I was wondering what the standard django best practice is for the example student-grade example above?

a paid nerd
  • 30,702
  • 30
  • 134
  • 179
jamida
  • 2,869
  • 5
  • 25
  • 22

4 Answers4

26

The reason you don't want to do this is because someone can change your disabled field to enabled and then submit the form. You would have to change the save function as to not insert the "disabled" data.

The standard way to do this is to not put the name in an input, but to display it as text

<form>
    <div>
        <label>Name</label>
        <p>Johnny Five</p>
    </div>
    <div>
        ....

This is not possible in django.

I say if you really trust your userbase to not "mess" with things then go for it, but if its a public facing website with possible sensitive data then stay away.

Galen
  • 29,976
  • 9
  • 71
  • 89
  • 1
    Thanks Galen that explains most of the concern, but I'm still curious how (or if) folks would use Django to implement the grade book page I suggested above. Abandon the built-in tools? – jamida May 25 '10 at 15:02
  • 3
    check out this answer http://stackoverflow.com/questions/324477/in-a-django-form-how-to-make-a-field-readonly-or-disabled-so-that-it-cannot-be/325038#325038 – Galen May 25 '10 at 16:20
  • A fair number of people reading the request for "read-only" interpret it literally as in adding a "readonly" or "disabled" attribute to the widget. That has ALL the problems you spoke of above (and will work in a pinch) but ideally there'd be other solutions like your

    (or ) tag above. I'm going to try this next: http://lazypython.blogspot.com/2008/12/building-read-only-field-in-django.html

    – jamida May 25 '10 at 19:34
  • right the disabled input is dangerous like i said, i thought you were asking for the solution anyway. – Galen May 25 '10 at 21:32
  • @Galen what about `csrftoken`. the `csrftoken` will prevent the malicious POST. am i right?? – suhailvs Sep 02 '13 at 10:46
  • 1
    @suhail: No, CSRF will not prevent users from using the DOM inspector to remove the readonly/disabled attributes and submitting the form. – Marius Gedminas Sep 10 '13 at 12:21
  • This really shouldn't have been marked as an accepted answer, take a look at http://stackoverflow.com/questions/324477/in-a-django-form-how-to-make-a-field-readonly-or-disabled-so-that-it-cannot-be/325038#325038 – SleepyCal Apr 18 '14 at 19:57
  • In django 1.9, there is a 'disabled' field argument which looks like it covers this use-case: see this answer, http://stackoverflow.com/a/34538169/37481 – Symmetric Feb 08 '16 at 22:27
12

As far as I can see for your situation, this is the ideal answer:

https://stackoverflow.com/a/2242468/1004781

Ie, simply print the model variables in the template:

{{ form.instance.LastName }}
Community
  • 1
  • 1
rix
  • 10,104
  • 14
  • 65
  • 92
2

When using a disabled field, you also need to make sure it remains populated correctly if the form fails validation. Here's my method, which also takes care of malicious attempts to change the data submitted:

class MyForm(forms.Form):

    MY_VALUE = 'SOMETHING'
    myfield = forms.CharField(
        initial=MY_VALUE,
        widget=forms.TextInput(attrs={'disabled': 'disabled'})

    def __init__(self, *args, **kwargs):

        # If the form has been submitted, populate the disabled field
        if 'data' in kwargs:
            data = kwargs['data'].copy()
            self.prefix = kwargs.get('prefix')
            data[self.add_prefix('myfield')] = MY_VALUE
            kwargs['data'] = data

        super(MyForm, self).__init__(*args, **kwargs) 
seddonym
  • 16,304
  • 6
  • 66
  • 71
0

for student/grading example, I have come up with a solution, where students are non editable fields and grades can be edited and updated as required. something like this

I am combining students objects and formset for grades in grade_edit class in view.py using zip function.

def grade_edit(request, id):
    student = student.objects.get(id=id)
    grades = grades.objects.filter(studentId=id)
    gradeformset = GradeFormSet(request.POST or None)
    if request.POST:
        gradeformset = GradeFormSet(request.POST, request.FILES, instance=student)
        if gradeformset.is_valid():
            gradeformset.save()
            grades = grades.objects.filter(studentId=id)
            return render(request, 'grade_details.html', {'student': student, 'grades': grades})
    else:
        gradeformset = GradeFormSet(instance=student)
        grades = grades.objects.filter(studentId=id)
        zips = zip(grades, gradeformset)
    return render(request, 'grade_edit.html', {'zips': zips, 'student': student, 'gradeformset': gradeformset })

My template looks something like this

<table>
         <tr>
     {% for field in gradeformset.forms.0 %}
          {% if not field.is_hidden %}
               <th>{{ field.label }}</th>
          {% endif %}
     {% endfor %}
     </tr>
     {% for f in gradeformset.management_form %}
          {{ f }}
     {% endfor %}
     {% for student, gradeform in zips %}
          <tr>
             {% for hidden in form.hidden_fields %}
                 {{ hidden }}
             {% endfor %}
             <td> {{ student.name }} </td>
             <td> {{ gradeform.gradeA }} </td>
             <td> {{ gradeform.gradeB }} </td>
          </tr>
     {% endfor %}
</table>

You can read more about Django formset here http://whoisnicoleharris.com/2015/01/06/implementing-django-formsets.html

Jaimin Patel
  • 371
  • 2
  • 8