0

I have a lesson model:

class Lesson(models.Model):
    list_name = models.CharField(max_length=50, primary_key=True)

    def __str__(self):
        return self.list_name

A sound model:

class Sound(models.Model):
    sound_hash = models.CharField(max_length=40, primary_key=True, editable=False)
    lessons = models.ManyToManyField(Lesson, verbose_name="associated lessons", related_name="words")

And a test_pair model:

class TestPair(models.Model):
    master_sound = models.ForeignKey(Sound, related_name="used_as_master")
    user_sound = models.ForeignKey(Sound, related_name="used_as_user")

My form looks something like this:

class SoundTestPairForm(ModelForm):
    user_sounds = forms.ModelMultipleChoiceField(Sound.objects.all())

    class Meta:
        model = TestPair
        fields = ['master_sound']

    def __init__(self, *args, **kwargs):
        super(SoundTestPairForm, self).__init__(*args, **kwargs)
        self.fields['master_sound'] = forms.CharField()
        self.fields['master_sound'].widget.attrs['readonly'] = 'readonly'

    def clean(self):
        cleaned_data = super(SoundTestPairForm, self).clean()
        if 'user_sounds' not in cleaned_data.keys():
            raise forms.ValidationError('You must select at least one sound to be compared')
        cleaned_data['master_sound'] = Sound.objects.get(pk=self.fields['master_sound'])
        return cleaned_data

This is throwing a DoesNotExist error. The traceback points to this line: cleaned_data['master_sound'] = Sound.objects.get(pk=self.fields['master_sound'])

The local vars are as follows:

self    

<SoundTestPairForm bound=True, valid=True, fields=(master_sound;associated_test;associated_lesson;user_sounds)>

cleaned_data    

{'associated_lesson': u'pooooooooooooooooooooooop',
 'associated_test': u'cats a',
 'master_sound': u'ad27ec5e0d048ddbb17d0cef0c7b9d4406a2c33',
 'user_sounds': [<Sound: Pants>]}

But when I go to python manage.py shell, and import my model: Sound.objects.get(pk=u'ad27ec5e0d048ddbb17d0cef0c7b9d4406a2c33') <Sound: oombah>

It hits the appropriate object.

This is something I've been dealing with for a long time, and it's super frustrating. I try to switch my logic around and ultimately it ends up throwing a DoesNotExist error, regardless of what I try.

Does anyone have any ideas?

3
  • If you don't want master_sound to be changed, why not just remove it from the form? Then you won't have to make the field readonly or set it in cleaned_data. Commented May 1, 2018 at 14:42
  • @Alasdair I want it to be displayed (so the user knows which master sound they're associating to a user sound). Commented May 1, 2018 at 14:49
  • You don't need a field to display the value. For example you could do {{ form.instance.master_sound }} in the template. Commented May 1, 2018 at 14:51

1 Answer 1

3

I think you should replace:

Sound.objects.get(pk=self.fields['master_sound'])

with:

Sound.objects.get(pk=cleaned_data['master_sound'])

When the Form is valid, cleaned_data will include a key and value for all its fields and you can see that in your question as well (The local vars paragraph).

In general speaking you should do validation in the individual clean_<fieldname> methods. So:

def clean_master_sound(self):
    master_sound_key = self.cleaned_data.get('master_sound')
    try:
        master_sound=Sound.objects.get(pk=master_sound_key)
    except Sound.DoesNotExist:
        raise forms.ValidationError("Sound with id: %s does not exist", master_sound_key)
    return master_sound
Sign up to request clarification or add additional context in comments.

6 Comments

Dude. I hate you so much. Thank you.
You can't necessarily trust cleaned_data. The user can remove the readonly attribute using their browser tools and submit a different value. However, if you disable the field with self.fields['master_sound'].disabled = True in the __init__ method, then Django will ignore the value from the user and it is safe to use the value from cleaned_data for that field.
@Alasdair This doesn't work in Django 1.8, I believe
The disabled argument was added in Django 1.9. Django 1.8 is now end of life and doesn't receive security updates, so I wouldn't write about it unless the question specifies it.
@Alasdair Ah, yes. My mistake—I was using 1.8 because that's what Xubuntu installed from apt, but I've upgraded to 1.11. That being said, fields when disabled don't send through their information in a POST. In my case, I think readonly is still the easiest (I'm not too concerned about security; this is all in-house).
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.