Afdal Lismen
Afdal Lismen

Reputation: 299

django validation error message get displayed twice

I just want to have an error message when user failed to login due to invalid username or password. Is there any preferable why to have it than overriding clean method. I found that django have login_failed signals but i am unsure it best to use that.

Here is the print screen validation error message get displayed twice

Here is my updated code from github :

https://github.com/afdallismen/Django-error-message-displayed-twice

Here is my python version and output from pip list, and pip freeze

python -V

Python 3.5.2

pip list

Django (1.10.1)
pip (8.1.2)
setuptools (27.1.2)
wheel (0.29.0)

pip freeze

Django==1.10.1

forms.py

class AuthorLogin(forms.Form):
    username = forms.CharField(label='Your name', max_length=100)
    password = forms.CharField(
                           label='Your password',
                           max_length=100,
                           widget=forms.PasswordInput)

    def clean(self):
        username = self.cleaned_data.get('username')
        password = self.cleaned_data.get('password')
        user = authenticate(username=username, password=password)
        if not user or not user.is_active:
            raise forms.ValidationError('Invalid username or password', code='invalid')
        return self.cleaned_data

    def login(self, request):
        username = self.cleaned_data.get('username')
        password = self.cleaned_data.get('password')
        user = authenticate(username=username, password=password)
        return user

    def form_invalid(self, form):
        return self.render_to_response(self.get_context_data(form=form))

view.py

def author_login(request):
    form = AuthorLogin(request.POST or None)
    if request.POST and form.is_valid():
        user = form.login(request)
        if user is not None:
            login(request, user)
            return redirect('microblog:index')

    return render(request, 'microblog/author_login.html', {'form': form})

urls.py

app_name = 'microblog'
urlpatterns = [
    url(r'^$', views.index, name='index'),
    url(r'^login/', views.author_login, name='author_login'),
    url(r'^logout/', views.author_logout, name='author_logout'),
]

author_login.html

{% extends "base.html" %}

{% block content %}
{% if form.non_field_errors %}
  <ul>
    {% for error in form.non_field_errors %}
     <li>{{ error }}</li>
    {% endfor %}
  </ul>
{% endif %}
<form action="" method="post">
  {% csrf_token %}
  {{ form.as_p }}
  <input type="submit" value="Login" />
</form>
{% endblock %}

base.html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Microblog</title>
</head>
<body>
  {% if user.is_authenticated %}
    <p>Welcome, {{ request.user.username }}</p>
    <a href="{% url 'microblog:author_logout' %}">Logout</a>
  {% else %}
    <a href="{% url 'microblog:author_login' %}">Login</a>
  {% endif %}
  <nav>
    <ul>
      <li><a href="{% url 'microblog:index' %}">Microblog</a></li>
    </ul>
  </nav>
  {% block content %}{% endblock %}
</body>
</html>

As a workaround i tried to sent validation error through form.add_error method in views.py, and removing clean, login and form_invalid method on forms.py.

Here is my new views.py :

def author_login(request):
    if request.method == 'POST':
        form = AuthorLogin(request.POST)
        if form.is_valid():
            username = form.cleaned_data['username']
            password = form.cleaned_data['password']
            user = authenticate(username=username, password=password)
            if user is not None:
                login(request, user)
                return redirect('microblog:index')
            else:
                form.add_error(None, 'Invalid username or password.') # This line seems to get executed twice
    else:
        form = AuthorLogin()

    return render(request, 'microblog/author_login.html', {'form': form})

Turn out it still displayed error message twice. Now it seems form.add_error was called twice, was it ?.

Tried this :

def author_login(request):
    if request.method = 'POST':
        count = 0
        ...
        if form.is_valid()
            ....
            if user is not None:
                ....
            else:
                count = count + 1
                print(count)

It print 1 and only printed once, so author_login with POST method just executed once, same for else block that print count. But form.add_error get executed twice ?

Fix it

It seems form.non_field_errors was included in form.errors . Deleting call to forms.non_field_errors in templates show the error message only once.

Upvotes: 0

Views: 2319

Answers (3)

Alasdair
Alasdair

Reputation: 309049

The error is displayed once when you loop through form.non_field_errors

<ul>
  {% for error in form.non_field_errors %}
    <li>{{ error }}</li>
  {% endfor %}
</ul>

and once when you use form.as_p

{{ form.as_p }}

If you use form.as_p, then you don't have to render form.non_field_errors manually. If you do display form.non_field_errors manually, then you'll have to render the form fields individually instead of using form.as_p.

See the docs on working with forms in templates for more info.

Upvotes: 2

Helin Guo
Helin Guo

Reputation: 13

you can try this as is described in the documentation:

def clean(self):
    super(AuthorLogin, self).clean()
    ...
    # not return anything

Upvotes: 0

souldeux
souldeux

Reputation: 3755

The clean method needs to use super():

def clean(self):
    cleaned_data = super(AuthorLogin, self).clean() #insert this line
    username = cleaned_data.get('username')
    ...

Upvotes: 0

Related Questions