views:

75

answers:

1

I was merrily developing a web application using the Django development version. After some time had passed, I updated to version 1.2 alpha 1 SVN-12271. Now, I am getting form field default cleaning error messages like "Value u'A325' is not a valid choice." This is on a choice field with no possibility of an invalid choice. Also, I'm seeing "This field cannot be blank" when it can be blank, in fact. I don't think this is some kind of form field to model attribute indexing error. The application continues to work properly under Django 1.1.1. So, what major change has occurred in Django that would account for this? I've looked for backwards-incompatible changes in http://docs.djangoproject.com/en/dev/releases/1.2-alpha-1/ but don't really see anything.

Here are some big clues: 1) My field-by-field custom validation continues to work perfectly; only default validation is screwing up. 2) Only choice fields are affected, but some choice fields are not affected. 3) Of the affected choice fields, some are standard and some use the undocumented optgroup feature, so that doesn't seem to be a factor. Any ideas?

I really want to get back on the development version because I need the new object-level (row-level) permissions.

ADDED CODE

My pleasure, Zalew. This is my base model:

class BXEEP_L_Dataset(models.Model):
    date_time         = models.DateTimeField(editable=False, auto_now=True, auto_now_add=True,
                        help_text="Date and time created or last saved.")
    unique_ID         = models.CharField(max_length=20, blank=True, default="",
                        help_text="Unique identifier for problem")
    project_name      = models.CharField(max_length=55, blank=True, default="", help_text="Project name")
    project_number    = models.CharField(max_length=20, blank=True, default="", help_text="Project number")
    description       = models.CharField(max_length=55, blank=True, default="",
                        help_text="Problem description")
    n_bolts           = models.IntegerField(choices=BXEEP_BOLTS, blank=False, default=4,
                        help_text="4 or 8 bolts?")
    stiffeners        = models.BooleanField(blank=True, help_text="Stiffeners?")
    cont_plates       = models.BooleanField(blank=True, help_text="Continuity plates?")
    partial_width     = models.BooleanField(blank=True, help_text="Partial width plates?")
    doubler_plate     = models.BooleanField(blank=True, help_text="Doubler plate?")
    column_top        = models.BooleanField(blank=True, help_text="Top of column?")
    shims             = models.BooleanField(blank=True, help_text="Finger shims?")
    flange_weld_type  = models.CharField(max_length=11, choices=WELD_TYPE, blank=False, default="fillet",
                        help_text="Flange weld type")
    flange_weld_size  = models.IntegerField(blank=True, null=True, default=0, help_text="Flange weld size")
    web_weld_type     = models.CharField(max_length=11, choices=WELD_TYPE, blank=False, default="fillet")
    web_weld_size     = models.IntegerField(blank=True, null=True, default=0, help_text="Web weld size")
    bolt_grade        = models.CharField(max_length=8, choices=Bolt_grades, blank=False, default="A325",
                        help_text="Bolt grade")
    db                = models.FloatField(choices=Bolt_diameters, blank=False, default=0.750,
                        help_text="d<sub>b</sub>")
    liw               = models.BooleanField(blank=True, help_text="Load-indicating washers?")
    end_pl_grade      = models.CharField(max_length=8, choices=PL_grades_50, blank=False, default="A36",
                        help_text="End PL grade")
    stiff_pl_grade    = models.CharField(max_length=8, choices=PL_grades, blank=False, default="A36",
                        help_text="Stiffener PL grade")
    cont_pl_grade     = models.CharField(max_length=8, choices=PL_grades, blank=False, default="A36",
                        help_text="Continuity PL grade")
    doubler_pl_grade  = models.CharField(max_length=8, choices=PL_grades, blank=False, default="A36",
                        help_text="Doubler PL grade")
    bm_profile        = models.CharField(max_length=8, choices=W_profiles, help_text="Beam profile")
    col_profile       = models.CharField(max_length=8, choices=W_profiles, help_text="Column profile")
    beam_grade        = models.CharField(max_length=11, choices=W_grades, blank=False, default="A992",
                        help_text="Beam grade")
    col_grade         = models.CharField(max_length=11, choices=W_grades, blank=False, default="A992",
                        help_text="Column grade")
    Muc               = models.FloatField(help_text="M<sub>uc</sub>")
    Vu                = models.FloatField(help_text="V<sub>u</sub>")
    Nu                = models.FloatField(help_text="N<sub>u</sub>", blank=True, null=True, default=0.0)
    Pcu               = models.FloatField(help_text="P<sub>cu</sub>", default=0.0)
    Vcu               = models.FloatField(help_text="V<sub>cu</sub>", blank=True, null=True, default=0.0)
    tp                = models.FloatField(help_text="t<sub>p</sub>")
    bp                = models.FloatField(help_text="b<sub>p</sub>")
    a                 = models.FloatField(help_text="a", default=0.0)
    g                 = models.FloatField(help_text="g")
    pfo               = models.FloatField(help_text="p<sub>fo</sub>")
    pfi               = models.FloatField(help_text="p<sub>fi</sub>")
    de                = models.FloatField(help_text="d<sub>e</sub>")
    pb                = models.FloatField(blank=True, null=True, default=0.0, help_text="p<sub>b</sub>")
    ts                = models.FloatField(blank=True, null=True, default=0.0, help_text="t<sub>s</sub>")
    tcp               = models.FloatField(blank=True, null=True, default=0.0, help_text="t<sub>cp</sub>")
    bcp               = models.FloatField(blank=True, null=True, default=0.0, help_text="b<sub>cp</sub>")
    td                = models.FloatField(blank=True, null=True, default=0.0, help_text="t<sub>d</sub>")
    hcol              = models.FloatField(blank=True, null=True, default=0.0, help_text="h<sub>col</sub>")
    tshim             = models.FloatField(blank=True, null=True, default=0.0, help_text="t<sub>shim</sub>")

    def __unicode__(self):
    #    return self.date_time
        return u"%s" % (self.date_time)

The "not a valid choice" errors occurs on bolt_grade, even though it should be impossible to make an invalid selection. The "this field is required" errors occur on beam_profile, column_profile, even when a selection has been made, and on other fields (e.g. stiffener_grade, doubler_grade), even though they are not required. Here is the ModelForm:

class BXEEP_L_Form(ModelForm):
    unique_ID         = forms.CharField(max_length=20, required=False,
                        widget=forms.TextInput(attrs={"class": "short-char"}))
    project_name      = forms.CharField(max_length=55, required=False,
                        widget=forms.TextInput(attrs={"class": "long-char"}))
    project_number    = forms.CharField(max_length=20, required=False,
                        widget=forms.TextInput(attrs={"class": "short-char"}))
    description       = forms.CharField(max_length=55, required=False,
                        widget=forms.TextInput(attrs={"class": "long-char"}))
    flange_weld_size  = forms.IntegerField(min_value=2, required=False,
                        widget=forms.TextInput(attrs={"class": "short-int"}))
    web_weld_size     = forms.IntegerField(min_value=2, required=False,
                        widget=forms.TextInput(attrs={"class": "short-int"}))
    stiff_pl_grade    = forms.ChoiceField(choices=PL_grades, required=False)
    cont_pl_grade     = forms.ChoiceField(choices=PL_grades, required=False)
    doubler_pl_grade  = forms.ChoiceField(choices=PL_grades, required=False)
    Muc               = forms.FloatField(widget=forms.TextInput(attrs={"class": "std-float"}))
    Vu                = forms.FloatField(widget=forms.TextInput(attrs={"class": "std-float"}))
    Nu                = forms.FloatField(widget=forms.TextInput(attrs={"class": "std-float"}), required=False)
    Pcu               = forms.FloatField(widget=forms.TextInput(attrs={"class": "std-float"}))
    Vcu               = forms.FloatField(widget=forms.TextInput(attrs={"class": "std-float"}), required=False)
    tp                = forms.FloatField(widget=forms.TextInput(attrs={"class": "std-float"}),
                        min_value=0.125)
    bp                = forms.FloatField(widget=forms.TextInput(attrs={"class": "std-float"}))
    a                 = forms.FloatField(widget=forms.TextInput(attrs={"class": "std-float"}), min_value=0.0)
    g                 = forms.FloatField(widget=forms.TextInput(attrs={"class": "std-float"}))
    pfo               = forms.FloatField(widget=forms.TextInput(attrs={"class": "std-float"}))
    pfi               = forms.FloatField(widget=forms.TextInput(attrs={"class": "std-float"}))
    de                = forms.FloatField(widget=forms.TextInput(attrs={"class": "std-float"}))
    pb                = forms.FloatField(required=False, widget=forms.TextInput(attrs={"class": "std-float"}))
    ts                = forms.FloatField(required=False, widget=forms.TextInput(attrs={"class": "std-float"}),
                        min_value=0.125)
    tcp               = forms.FloatField(required=False, widget=forms.TextInput(attrs={"class": "std-float"}),
                        min_value=0.125)
    bcp               = forms.FloatField(required=False, widget=forms.TextInput(attrs={"class": "std-float"}),
                        min_value=0.125)
    td                = forms.FloatField(required=False, widget=forms.TextInput(attrs={"class": "std-float"}),
                        min_value=0.125)
    hcol              = forms.FloatField(required=False, widget=forms.TextInput(attrs={"class": "std-float"}),
                        min_value=0.0)
    tshim             = forms.FloatField(required=False, widget=forms.TextInput(attrs={"class": "std-float"}),
                        min_value=0.0625, max_value=0.250)

    def clean(self):

    ...

    <extensive custom validation omitted>
    ...

    return cleaned_data

    def __unicode__(self):
        return self.unique_ID

    class Meta:
        model = BXEEP_L_Dataset

Finally, a snippet from the view:

if form.is_valid():
# Station K
# The form is saved into a new record only if it is valid.
    new_dataset = form.save()
    # Station L
    # We need the new_dataset object so we can get the primary key from it, so we can't just say
    # "form.save()."

    dataset_id = new_dataset.pk
    # Station M
    # We reset the dataset_id from False to the new primary key.
    #
    # Now, with valid form data stored in the correct database record, the normalized version of
    # the dataset, form.cleaned_data, may be processed, yielding preliminary analysis results        
    # in the form of unity checks (ratios) and status lines.

    results = analyze_bxeep_l(form.cleaned_data)
+1  A: 

Sorry to be answering my own question, but I've figured this out to my satisfaction and you might as well have the benefit.

The problem is either two different bugs, or one bug and one misunderstanding. The genuine bug is that selects with optgroups is pretty much broken in the Django development version right now, at least for use with a model form. In Django, when you want optgroups you use nested tuples to define them. For the history, see http://code.djangoproject.com/ticket/4412, and in the documentation, see the MEDIA_CHOICES example at http://docs.djangoproject.com/en/dev/ref/models/fields/. My theory is that currently, the validation code doesn't initially find the values in the inner tuples, so, when the check is performed a valid choice may not be on the list that is checked against, and definitely won't be on the list if you have no "outer" options. Here's the ticket: http://code.djangoproject.com/ticket/12722.

The second bug (or misunderstanding) is that with ordinary (not optgroup) selects, blank=False, default="A36" doesn't work if the field is disabled (as by JavaScript) and no selection is made. It worked under 1.1.1, but not now. Maybe this should be a second ticket, but maybe it's correct and I don't understand the philosophy. I've changed to blank=True and all is well enough with me.

For completeness, let me point out that 1) I haven't checked the optgroup problem in the Admin yet, 2) all of this relates to a model form, and I don't know if matters would be different if there were a model with no model form, and 3) my problems (in both categories) all relate to CharFields.

Thomas B. Higgins
A previous ticket, #12667 dealt with the same bug. It was corrected in the Django development trunk on February 1, 20120. On the second bug (or misunderstanding) there is no clarity yet.
Thomas B. Higgins