noob87
noob87

Reputation: 41

How do I display error messages when I am not looping over form fields in Django templates?

My forms are not showing the error messages when I try to submit an empty form but I can see the errors while looping over the field errors in views.py. How do I overcome this problem?

the template (Updated):

    {% block formcontent %}
    {{form.non_field_errors}}
    <div class="row">
        <div class="col">
            {{form.username.label_tag}} {{form.username}} {{form.username.errors|striptags}}
        </div>
    </div><br>
    <div class="row">
        <div class="col">
            {{form.first_name.label_tag}} {{form.first_name}} {{form.first_name.errors|striptags}}
        </div>
        <div class="col">
            {{form.last_name.label_tag}} {{form.last_name}} {{form.last_name.errors|striptags}}
        </div>
    </div><br>
    <div class="row">
        <div class="col">
            {{form.email.label_tag}} {{form.email}} {{form.email.errors|striptags}}
        </div>
    </div><br>
    <div class="row">
        <div class="col">
            {{form.location.label_tag}} {{form.location}} {{form.location.errors|striptags}}
        </div>
        <div class="col">
            {{form.designation.label_tag}} {{form.designation}} {{form.designation.errors|striptags}}
        </div>
    </div><br>
    <div class="row">
        <div class="col">
            {{form.password1.label_tag}} {{form.password1}} {{form.password1.errors|striptags}}
        </div>
        <div class="col">
            {{form.password2.label_tag}} {{form.password2}} {{form.password2.errors|striptags}}
        </div>
    </div><br>
{% endblock formcontent %}

Edit 1: (Updated)

class MyRegistrationForm(UserCreationForm):
password1=forms.CharField(label='Password', widget=forms.PasswordInput(attrs={'class':'form-control'}))
password2=forms.CharField(label='Confirm Password', widget=forms.PasswordInput(attrs={'class':'form-control'}))
class Meta:
    model=MyRegistration
    fields=['username', 'first_name', 'last_name', 'email', 'location', 'designation']
    widgets={
        'username':forms.TextInput(attrs={'class':'form-control'}),
        'first_name':forms.TextInput(attrs={'class':'form-control'}),
        'last_name':forms.TextInput(attrs={'class':'form-control'}),
        'email':forms.EmailInput(attrs={'class':'form-control'}),
        'location':forms.Select(attrs={'class':'form-select'}),
        'designation':forms.TextInput(attrs={'class':'form-control'}),
    }

def clean_username(self):
    username = self.cleaned_data.get('username')
    if not username:
        raise ValidationError('Username is required!')
    else:
        try:
            MyRegistration.objects.get(username=username)
            raise ValidationError('This username already exists!', code='username_exists')
        except MyRegistration.DoesNotExist:
            pass
    return username

def clean_email(self):
    email=self.cleaned_data.get('email')
    if not email:
        raise ValidationError('Email is required!')
    else:
        try:
            MyRegistration.objects.get(email=email)
            raise ValidationError('This email already exists!', code='email_exists')
        except MyRegistration.DoesNotExist:
            pass
    return email

def clean_first_name(self):
    first_name=self.cleaned_data.get('first_name')
    if not first_name:
        raise ValidationError('First-name is required!')
    return first_name

def clean_last_name(self):
    last_name=self.cleaned_data.get('last_name')
    if not last_name:
        raise ValidationError('Last-name is required!')
    return last_name

def clean_location(self):
    location=self.cleaned_data.get('location')
    if not location:
        raise ValidationError('Location is required!')
    return location

def clean_designation(self):
    designation=self.cleaned_data.get('designation')
    if not designation:
        raise ValidationError('Designation is required!')
    return designation

I really have no idea what is wrong with my codes in template. I have checked, the Django documentation suggests the same way to approach such scenarios where the forms are not looped over.

Edit 2:

models.py:

class MyRegistration(AbstractBaseUser, PermissionsMixin):
    location_list=[
        ('Solapur', 'Solapur'),
        ('Dhule', 'Dhule'),
        ('Other', 'Other'),
        ]
    username=models.CharField(max_length=10, unique=True)
    email=models.EmailField(unique=True)
    first_name=models.CharField(max_length=150)
    last_name=models.CharField(max_length=150)
    location=models.CharField(max_length=10, choices=location_list, default=None)
    designation=models.CharField(max_length=70)
    is_active=models.BooleanField()
    is_staff=models.BooleanField(default=False)
    start_date=models.DateTimeField(default=timezone.now)
    last_login=models.DateTimeField(null=True)


    USERNAME_FIELD='username'
    REQUIRED_FIELDS=['email', 'first_name', 'last_name', 'location', 'designation']
    objects=FirstManager()
    def __str__(self):
        return self.first_name

views.py:(Updated)

def signup(request):
print('1')
if request.user.is_authenticated:
    print('2')
    if request.method=='POST':
        print('3')
        if request.POST.get('password1')==request.POST.get('password2'):
            print('4')
            fm=MyRegistrationForm(request.POST)
            for field in fm:
                print("Field Error:", field.name,  field.errors)
            if fm.is_valid():
                print('6')
                fm.save()
                messages.success(request, 'Registered successfully!!')
            fm=MyRegistrationForm()
            print('7')
            cur_user=request.user
            return render(request, 'account/signup.html', {'form':fm, 'cur_user':cur_user})
    else:
        fm=MyRegistrationForm()
        cur_user=request.user
        return render(request, 'account/signup.html', {'form':fm, 'cur_user':cur_user})
else:
    return HttpResponseRedirect('/')

Upvotes: 1

Views: 131

Answers (1)

vinkomlacic
vinkomlacic

Reputation: 1879

When you raise ValidationError in the clean method, these errors get added to the non_field_errors attribute on the form. This is why nothing gets rendered when using form.email.errors and other errors attributes on particular fields.

You should render the form.non_field_errors before you render your form, so you can see those errors, too.

However, to solve your issue, I would rather go with the option of splitting the validation of each field into particular methods clean_<field_name>. For example for username field:

def clean_username(self):
    username = self.cleaned_data.get('username')
    if not username:
        raise ValidationError('Username is required!')
    else:
        try:
            un=MyRegistration.objects.get(username=self.instance.username)
            raise ValidationError('This username already exists!')
        except MyRegistration.DoesNotExist:
            pass

    # Make sure you return the value of the data in 
    # the clean_<field_name> methods
    return username

And so on for other fields, too. Doing just this should fix your code, but here are some other recommendations:

  • Use codes when raising ValidationErrors. E.g.: raise ValidationError('This username already exists', code='username_exists')
  • Check out the django-crispy package, it can handle the HTML rendering of forms with minimal code
  • You can set constraints in the models for unique fields (e.g. username) and required fields. This further prevents users who are not adding data with your form (e.g. admin) to add duplicate usernames or null values. This would also mean that a lot of your custom validation code would be unnecessary.

EDIT #1: The use of instance to get the values from the submitted form is wrong. Since this form is used exclusively for create purposes as registration is creation of a new user, instance will always be empty. instance is only filled with data when you're updating a model instance.

You should replace the uses of instance with getting the form data from self.cleaned_data dict. For example:

# Instead of:
username = self.instance.username
# Use:
username = self.cleaned_data.get('username')

EDIT #2: After the author added the view code.

The issue might be in your view code. Also, there is no need to comparing password1 and password2 as the UserCreationForm already does that for your.

The core issue is that if your form is invalid, you need to re-render that same form, not create another instance. I propose the following update:

def signup(request):
    print('1')
    if request.user.is_authenticated:
        print('2')
        if request.method=='POST':
            print('3')
            form = MyRegistrationForm(request.POST)
            if form.is_valid():
                print('4')
                form.save()
                messages.success(request, 'Registered successfully!!')
            # If you do this, you always render the empty form
            # without the errors
            # fm=MyRegistrationForm()
            print('7')
            cur_user=request.user
            return render(
                request, 'account/signup.html', 
                {'form': form, 'cur_user':cur_user}
            )
        else:
            form = MyRegistrationForm()
            cur_user=request.user
            return render(
                request, 'account/signup.html', 
                {'form':form, 'cur_user':cur_user}
            )
    else:
        return HttpResponseRedirect('/')

Some other recommendations:

  • You probably don't need to check if the user is authenticated if this is registration view. How can the new users create an account? However, if this is needed probably the @login_required decorator works better for this.
  • On success, you need to redirect to the success URL. Don't use render for success scenarios, only when handling the GET method or when you need to re-render the form to display validation errors.

Upvotes: 1

Related Questions