2

I have two models:

class Studio(models.Model):
    name = models.CharField("Studio", max_length=30, unique=True)

class Film(models.Model):
    studio = models.ForeignKey(Studio, verbose_name="Studio")
    name = models.CharField("Film Name", max_length=30, unique=True)

I have a Film form that allows the user to either select a preexisting Studio, or type in a new one (with help from an earlier question:

class FilmForm(forms.ModelForm):
    required_css_class = 'required'
    studio = forms.ModelChoiceField(Studio.objects, required=False, widget = SelectWithPlus)
    new_studio = forms.CharField(max_length=30, required=False, label = "New Studio Name", widget = DeSelectWithX(attrs={'class' : 'hidden_studio_field'}))

    def __init__(self, *args, **kwargs):
        super(FilmForm, self).__init__(*args,**kwargs)
        self.fields['studio'].required = False

    def clean(self):
        cleaned_data = self.cleaned_data
        studio = cleaned_data.get('studio')
        new_studio = cleaned_data.get('new_studio')

        if not studio and not new_studio:
            raise forms.ValidationError("Must specify either Studio or New Studio!")
        elif not studio:
            studio, created = Studio.objects.get_or_create(name = new_studio)
            self.cleaned_data['studio'] = studio

        return super(FilmForm,self).clean()

    class Meta:
        model = Film

Now, my first issue is that when both studio and new_studio are missing I get a django ValueError: Cannot assign None: "Film.studio" does not allow null values error. I thought I was capturing all the errors, thus django should never get so far as to realize Film.studio is empty.

A second issue is an order of operations. What if I want to only save the new_studio after I'm sure the rest of the FilmForm is valid (thus preventing a bunch of studio names getting saved before full Film entries go through as well)? Am I in the clear or do I risk premature saving because new_studio's are saved in the form's cleaning?

Edit: Added Traceback and edited validation if-statements

Community
  • 1
  • 1
Ed.
  • 4,439
  • 10
  • 60
  • 78
  • Check that this line does what you mean: `if studio is None and new_studio == '':`. Could studio be `''` or new_studio be `None`? Does it correctly show the form error if you change that to `if not (studio or new_studio):`? – AdamKG Jan 25 '12 at 22:08
  • I put a print "here" right before the ValidationError. It came out in the terminal, so I can confirm that the code is reaching that portion. I tried changing the criteria as you suggested and got the same error – Ed. Jan 25 '12 at 22:21
  • Can you pastebin the traceback? – AdamKG Jan 25 '12 at 22:25
  • Why did you change the `if` and `elif` checks? The one's your using now open up holes in the logic that allows certain scenarios to fall through unchecked. Should've stuck to what I gave you. Additionally, the `clean` method is only ran after the individual `clean_FIELD` methods are run, so effectively, you only reach this point if everything else is valid. You don't have to worry about Studios getting created when there's other problems in the form still. – Chris Pratt Jan 25 '12 at 22:41
  • -ChrisPratt: Fair point. The first few times I ran it, I couldn't get the instances to work correctly. Was probably user error. Edited above, but I still have the same issue – Ed. Jan 25 '12 at 23:16
  • -AdamKG: Traceback linked above – Ed. Jan 25 '12 at 23:17
  • I commented out the line: self.fields['studio'].required = False. On one hand, I no longer get the error. On the other hand, I now can't submit a new_studio without the studio field throwing the error "This field is required" – Ed. Jan 26 '12 at 00:49
  • -ChrisPratt: Ran a test. Actually, if I populate new_studio but omit name, I get a "field is required" error on name, but my new_studio enters the database – Ed. Jan 26 '12 at 00:58

2 Answers2

2

Delete studio and new_studio from cleaned_data.

    if not studio and not new_studio:
        del cleaned_data['studio'], cleaned_data['new_studio']
        raise forms.ValidationError("Must specify either Studio or New Studio!")
rockingskier
  • 9,066
  • 3
  • 40
  • 49
  • Excellent. This works. Any input on how to prevent new_studio being created before the rest of the form is fully validated? If I enter a new_studio, but omit film name, the form doesn't go through because name is required, but the new_studio gets created anyway. – Ed. Jan 26 '12 at 01:00
  • You could change the `elif` to be more specific. Check that **studio** and **film** are in `cleaned_data` and then only create the new Studio if everything is valid. – rockingskier Jan 26 '12 at 14:49
1

FYI on preventing the presaving of new_studio:

New def clean in form: def clean(self): cleaned_data = self.cleaned_data studio = cleaned_data.get('studio') new_studio = cleaned_data.get('new_studio')

    if not studio and not new_studio:
        del cleaned_data['studio'], cleaned_data['new_studio']
        raise forms.ValidationError("Must specify either Studio or New Studio!")
    elif not studio:
        del cleaned_data['studio']

    return super(FilmForm,self).clean()

And in the view:

def testone(request):
    if request.method == 'POST': # If the form has been submitted...
        form = FilmForm(request.POST) # A form bound to the POST data

        if form.is_valid(): # All validation rules pass
            if form.cleaned_data['new_studio']:
                studio, created = Studio.objects.get_or_create(name = form.cleaned_data['new_studio'])
                new_film = form.save(commit=False)
                new_film.studio = studio
            else:
                new_film = form

            new_film.save()
            return HttpResponseRedirect('/') # Redirect after POST
        else:
            form = FilmForm() # An unbound form

        return render_to_response('testone.html', {
            'form': form
        }, context_instance=RequestContext(request))
Ed.
  • 4,439
  • 10
  • 60
  • 78