Question

I have a Django ModelForm that isn't displaying field errors properly in the template. I have several fields that are required, and what I believe to be the correct logic to capture and display the errors to the user.

When submitting the form, I get the following error:

Traceback (most recent call last):

 File "/home/thevariable/.virtualenvs/va_jobs/lib/python2.7/site-packages/django/core/handlers/base.py", line 114, in get_response
   response = wrapped_callback(request, *callback_args, **callback_kwargs)

 File "/home/thevariable/.virtualenvs/va_jobs/lib/python2.7/site-packages/django/views/generic/base.py", line 69, in view
   return self.dispatch(request, *args, **kwargs)

 File "/home/thevariable/webapps/va_jobs/va_jobs/jobs/views.py", line 29, in dispatch
   return super(ApplicationCreateView, self).dispatch(*args, **kwargs)

 File "/home/thevariable/.virtualenvs/va_jobs/lib/python2.7/site-packages/django/views/generic/base.py", line 87, in dispatch
   return handler(request, *args, **kwargs)

 File "/home/thevariable/.virtualenvs/va_jobs/lib/python2.7/site-packages/django/views/generic/edit.py", line 205, in post
   return super(BaseCreateView, self).post(request, *args, **kwargs)

 File "/home/thevariable/.virtualenvs/va_jobs/lib/python2.7/site-packages/django/views/generic/edit.py", line 170, in post
   if form.is_valid():

 File "/home/thevariable/.virtualenvs/va_jobs/lib/python2.7/site-packages/django/forms/forms.py", line 129, in is_valid
   return self.is_bound and not bool(self.errors)

 File "/home/thevariable/.virtualenvs/va_jobs/lib/python2.7/site-packages/django/forms/forms.py", line 121, in errors
   self.full_clean()

 File "/home/thevariable/.virtualenvs/va_jobs/lib/python2.7/site-packages/django/forms/forms.py", line 274, in full_clean
   self._clean_form()

 File "/home/thevariable/.virtualenvs/va_jobs/lib/python2.7/site-packages/django/forms/forms.py", line 300, in _clean_form
   self.cleaned_data = self.clean()

 File "/home/thevariable/webapps/va_jobs/va_jobs/jobs/forms.py", line 23, in clean
   resume_ext = resume.name.lower().split('.')[1]

AttributeError: 'NoneType' object has no attribute 'name'

Here is my forms.py:

from django.forms import ModelForm

from  .models import Application

class ApplicationForm(ModelForm):
    class Meta:
        model = Application
        fields = [
            'first_name',
            'last_name',
            'email_address',
            'phone_number',
            'salary_requirement',
            'resume',
            'portfolio_url',
            'description',
            'can_relocate',
            'start_date',
        ]

    def __init__(self, *args, **kwargs):
        super(ApplicationForm, self).__init__(*args, **kwargs)
        self.fields['first_name'].required = True
        self.fields['last_name'].required = True
        self.fields['email_address'].required = True
        self.fields['phone_number'].required = True
        self.fields['salary_requirement'].required = False
        self.fields['resume'].required = True
        self.fields['portfolio_url'].required = False
        self.fields['description'].required = False
        self.fields['can_relocate'].required = True
        self.fields['start_date'].required = False

    def clean(self):
        cleaned_data = super(ApplicationForm, self).clean()
        resume = cleaned_data.get('resume')
        resume_ext = resume.name.lower().split('.')[1]
        if not resume_ext in ('pdf', 'doc', 'docx'):
            del cleaned_data["resume"]
            msg = u"Your file must be a PDF or DOC file type."
            raise forms.ValidationError(msg)
            #self._errors["resume"] = self.error_class([msg])       
        return cleaned_data

Here is my view:

class ApplicationCreateView(CreateView):
    model = Application
    form_class = ApplicationForm
    success_url = 'submitted/'

    def dispatch(self, *args, **kwargs):
        self.job = get_object_or_404(Job, slug=kwargs['slug'])
        return super(ApplicationCreateView, self).dispatch(*args, **kwargs)

    def form_valid(self, form):
        #Get associated job and save
        self.object = form.save(commit=False)
        self.object.job = self.job
        self.object.save()

        # Gather cleaned data for email send
        first_name = form.cleaned_data.get('first_name')
        last_name = form.cleaned_data.get('last_name')
        email_address = form.cleaned_data.get('email_address')
        phone_number = form.cleaned_data.get('phone_number')
        salary_requirement = form.cleaned_data.get('salary_requirement')
        description = form.cleaned_data.get('description')
        portfolio_url = form.cleaned_data.get('portfolio_url')
        can_relocate = form.cleaned_data.get('can_relocate')
        start_date = form.cleaned_data.get('start_date')
        resume = self.object.resume
        job = self.object.job

        #Compose message
        email = EmailMessage()
        email.body = 'Name: ' + first_name + last_name + '\n' + 'Email: '  + email_address + '\n' + 'Phone number: ' + str(phone_number) + '\n' + 'Salary requirement: ' + str(salary_requirement) + '\n' + 'Description: ' + description + '\n' + 'Portfolio URL: ' + portfolio_url + '\n' + 'Can relocate: ' + str(can_relocate) + '\n' + 'Start date: ' + str(start_date)
        email.subject = 'A new application has been submitted for %s' % (job)
        email.from_email = 'noreply@abc.com'
        email.to = ['jobs@abc.com',]
        email.bcc = ['abc@abc.com',]
        email.attach(resume.name, resume.read())
        email.send()
        return HttpResponseRedirect(self.get_success_url())

    def get_context_data(self, *args, **kwargs):
        context_data = super(ApplicationCreateView, self).get_context_data(*args, **kwargs)
        context_data.update({'job': self.job})
        return context_data

And here is my template (using Django widget tweaks https://pypi.python.org/pypi/django-widget-tweaks):

<p><em>Note: Fields with an asterisk are required.</em></p>
<form role="form" action="" enctype="multipart/form-data" method="post">
{% csrf_token %}
{% for field in form.visible_fields %}
    <div class="form-group">
        {% if field.errors %}
        <ul class="list-unstyled list-inline">
            {% for error in field.errors %}
            <li class="text-warning"><span class="glyphicon glyphicon-warning-sign"></span> {{ error|escape }}</li>
            {% endfor %}
        </ul>
        {% endif %}
        {{ field.label_tag }}
        {% if field.name == "portfolio_url" %}
            {% if job.portfolio_required %} <small><span class="glyphicon glyphicon-asterisk"></span></small>
            {% endif %}
        {% endif %}
        {% if field.field.required %} <small><span class="glyphicon glyphicon-asterisk"></span></small>{% endif %}
        {% if field.name == "resume" or field.name == "can_relocate" %}
            {{ field|add_class:"form-control short" }}
        {% elif field.name == "start_date" %}
            <input id="id_start_date" class="form-control short" name="start_date" type="date" /><input id="initial-id_start_date" name="initial-start_date" type="hidden" />
        {% elif field.name == "portfolio_url" %}
            {% if job.portfolio_required %}
                <input class="form-control" id="id_portfolio_url" maxlength="200" name="portfolio_url" type="url" required />
            {% endif %}
        {% else %}
        {{ field|add_class:"form-control" }}
        {% endif %}
        {% if field.help_text %}<p class="help-block">{{ field.help_text }}</p>{% endif %}
    </div>
{% endfor %}
    <input type="submit" class="btn btn-default" value="Apply">
</form>
Was it helpful?

Solution

For the record, my solution was as follows (in forms.py):

def clean(self):
    cleaned_data = super(ApplicationForm, self).clean()
    if cleaned_data.get('resume'):
        resume = cleaned_data.get('resume')
        resume_ext = resume.name.lower().split('.')[1]
        if not resume_ext in ('pdf', 'doc', 'docx'):
            del cleaned_data["resume"]
            msg = u"Your file must be a PDF or DOC file type."
            self._errors["resume"] = self.error_class([msg])        
        return cleaned_data
    else:
        return cleaned_data

This was inspired by a comment on the original question (Django ModelForm not showing field errors).

OTHER TIPS

All simple, in stack-trace says: "Object resume have no attribute name"

resume = cleaned_data.get('resume')

Maybe resume not in cleaned data ? try to print it, or put pudb there.

Here is Exception, cuz he is None:

resume_ext = resume.name.lower().split('.')[1]
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top