views:

338

answers:

2

I'm having a problem with a test app I'm writing to verify some Django functionality. The test app is a small "grade book" application that is currently using Alex Gaynor's readonly field functionality http://lazypython.blogspot.com/2008/12/building-read-only-field-in-django.html

There are 2 problems which may be related. First, when I flop the comment on these 2 lines below:

#        myform = GradeForm(data=request.POST, instance=mygrade)
        myform = GradeROForm(data=request.POST, instance=mygrade)

it works like I expect, except of course that the student field is changeable.

When the comments are the shown way, the "studentId" field is displayed as a number (not the name, problem 1) and when I hit submit I get an error saying that studentId needs to be a Student instance.

I'm at a loss as to how to fix this. I'm not wedded to Alex Gaynor's code. ANY code will work. I'm relatively new to both Python and Django, so the hints I've seen on websites that say "making a read-only field is easy" are still beyond me.

// models.py

class Student(models.Model):
    name = models.CharField(max_length=50)
    parent = models.CharField(max_length=50)
    def __unicode__(self):
        return self.name

class Grade(models.Model):
    studentId = models.ForeignKey(Student)
    finalGrade = models.CharField(max_length=3)

# testbed.grades.readonly is alex gaynor's code
from testbed.grades.readonly import ReadOnlyField
class GradeROForm(ModelForm):
    studentId = ReadOnlyField()
    class Meta:
        model=Grade

class GradeForm(ModelForm):
    class Meta:
        model=Grade

// views.py

def modifyGrade(request,student):
    student = Student.objects.get(name=student)
    mygrade = Grade.objects.get(studentId=student)
    if request.method == "POST":
#        myform = GradeForm(data=request.POST, instance=mygrade)
        myform = GradeROForm(data=request.POST, instance=mygrade)
        if myform.is_valid():
            grade = myform.save()
            info = "successfully updated %s" % grade.studentId
    else:
#        myform=GradeForm(instance=mygrade)
        myform=GradeROForm(instance=mygrade)
    return render_to_response('grades/modifyGrade.html',locals())

// template

<p>{{ info }}</p>
<form method="POST" action="">
<table>
{{ myform.as_table }}
</table>
<input type="submit" value="Submit">
</form>

// Alex Gaynor's code

from django import forms
from django.utils.html import escape
from django.utils.safestring import mark_safe

from django.forms.util import flatatt
class ReadOnlyWidget(forms.Widget):
    def render(self, name, value, attrs):
        final_attrs = self.build_attrs(attrs, name=name)
        if hasattr(self, 'initial'):
            value = self.initial
        return mark_safe("<span %s>%s</span>" % (flatatt(final_attrs), escape(value) or ''))

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

class ReadOnlyField(forms.FileField):
    widget = ReadOnlyWidget
    def __init__(self, widget=None, label=None, initial=None, help_text=None):
        forms.Field.__init__(self, label=label, initial=initial, 
            help_text=help_text, widget=widget)

    def clean(self, value, initial):
        self.widget.initial = initial
        return initial
+1  A: 

Django 1.2 (released about a week and a half ago) supports read-only fields for the admin out of the box:

http://docs.djangoproject.com/en/dev/ref/contrib/admin/#django.contrib.admin.ModelAdmin.readonly_fields

I'm not sure how hard it is to extend that new functionality into something like a ModelForm to be displayed on your site, but it would serve as a more up-to-date starting point than Alex's (excellent but dated) code.

Gabriel Hurley
While my code will eventually be behind a userlogin page, I'm not (yet?) interested in using the full power of the Admin system. Is ModelAdmin available outside of the general Admin framework? At any rate, I'll at least look at django's code there and see if I can "update" Alex's code for my purposes.
jamida
+2  A: 

Don't bother with the ReadOnlyField. Use the Widget instead.

Here is the one I use regularly.

class ReadOnlyWidget(forms.Widget):

    def __init__(self, original_value, display_value):
        self.original_value = original_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

Use it with CharField.

Lakshman Prasad
I'm not having much luck with this. I don't think I'm using it correctly. I keep getting "TypeError: __init__() takes exactly 3 arguments (1 given)". I'm changing studentId in the ROForm to be a "forms.CharField" with "widget=ReadOnlyWidget"
jamida
Should be a `ModelChoiceField`, not a CharField. But use Alex's widget rather than this one, which seems to have some odd requirements in its `__init__` method.
Daniel Roseman
One problem I'm having is I'm trying to do this with a "mandatory" field. It appears that many "read-only" work arounds assume the read-only value is optional, possibly because the system does not store/report-back the read-only value. Since mine is required (it's the student ID) I'm getting an error when validating the form. "This field is required". This appears to be happening BEFORE the custom clean_field() routine is called. I may post this as a different question.
jamida