views:

37

answers:

2

I'm implementing simple "grade book" application where the teacher would be able to update the grades w/o being allowed to change the students' names (at least not on the update grade page). To do this I'm using one of the read-only tricks, the simplest one. The problem is that after the SUBMIT the view is re-displayed with 'blank' values for the students. I'd like the students' names to re-appear.

Below is the simplest example that exhibits this problem. (This is poor DB design, I know, I've extracted just the relevant parts of the code to showcase the problem. In the real example, student is in its own table but the problem still exists there.)

models.py

class Grade1(models.Model):
    student = models.CharField(max_length=50, unique=True)
    finalGrade = models.CharField(max_length=3)

class Grade1OForm(ModelForm):
    student = forms.CharField(max_length=50, required=False)
    def __init__(self, *args, **kwargs):
        super(Grade1OForm,self).__init__(*args, **kwargs)
        instance = getattr(self, 'instance', None)
        if instance and instance.id:
            self.fields['student'].widget.attrs['readonly'] = True
            self.fields['student'].widget.attrs['disabled'] = 'disabled'
    def clean_student(self):
        instance = getattr(self,'instance',None)
        if instance:
            return instance.student
        else:
            return self.cleaned_data.get('student',None)
    class Meta:
        model=Grade1

views.py

from django.forms.models import modelformset_factory
def modifyAllGrades1(request):
    gradeFormSetFactory = modelformset_factory(Grade1, form=Grade1OForm, extra=0)
    studentQueryset = Grade1.objects.all()
    if request.method=='POST':
        myGradeFormSet = gradeFormSetFactory(request.POST, queryset=studentQueryset)
        if myGradeFormSet.is_valid():
            myGradeFormSet.save()
            info = "successfully modified"
    else:
        myGradeFormSet = gradeFormSetFactory(queryset=studentQueryset)
    return render_to_response('grades/modifyAllGrades.html',locals())

template

<p>{{ info }}</p>
<form method="POST" action="">
<table>
{{ myGradeFormSet.management_form }}
{% for myform in myGradeFormSet.forms %}
  {# myform.as_table #}
  <tr>
    {% for field in myform %}
    <td> {{ field }} {{ field.errors }} </td>
    {% endfor %}
  </tr>
{% endfor %}
</table>
<input type="submit" value="Submit">
</form>
A: 

I'm stretching myself a little here, so some thoughts:

% Have you sniffed the traffic to see exactly what's being sent between browser and server?

% Do you need to send the student name as a hidden field (your db update thing may assume you want student blank if you don't)?

% Have you looked at the source of your HTML after Python parses it?

barrycarter
Thanks for trying Barry... a few related comments. One is that another Readonly trick works just fine. However, that trick won't work if the Student field is a foreign key in another table (my full app). Another is, I've looked at the generated HTML and the POST variables. Django is not getting the student back, which is normal for a DISABLED and/or READONLY field. Not a problem since the CLEAN routine sets it to the correct value. Finally I put a print statement in the else clause of the clean routine and that's not being called. So the student is always available.
jamida
+1  A: 

Your way of displaying the readonly field is the problem.

Since the student field is disabled, the form submit will not have that as the input, so the error form that is displayed with validation error messages don't get the initial value.

That is why ReadOnly Widget has to be more complex than just being a html disabled field.

Try using a real ReadOnlyWidget, one that overrides _has_changed.

Following is what I use. For instantiation, it takes the original_value and optionally display_value, if it is different.

class ReadOnlyWidget(forms.Widget):

    def __init__(self, original_value, display_value=None):
        self.original_value = original_value
        if display_value:
            self.display_value = display_value
        super(ReadOnlyWidget, self).__init__()

    def _has_changed(self, initial, data):
        return False

    def render(self, name, value, attrs=None):
        if self.display_value is not None:
            return unicode(self.display_value)
        return unicode(self.original_value)

    def value_from_datadict(self, data, files, name):
        return self.original_value
Lakshman Prasad
Thanks Lakshman, but I'm still new to Django. How do I set the original value? I'm trying to call it like: student = forms.CharField(max_length=50, widget=ReadOnlyWidget()) but that's not right as I need to pass in an original value. Note... my eventual goal is to use this in a modelformset.
jamida
jamida: Pass to ReadOnlyWidget, a student instance, that you want to be displayed readonly on the form.
Lakshman Prasad
How do I do that in a modelformset? Do I do that in the code sample above or in the form's __init__ routine? (or either?) And if the init routine, how do I refer to the widget in that init routine?Note. I'm open to reading documentation, but this deep into django isn't well documented. If you have a link, that'd be great!
jamida
jamida: self.fields['student'].widget = ReadOnlyWidget(student)within the form init
Lakshman Prasad
Excellent! Thanks!!
jamida