Search code examples
pythondjangoforms

display form errors. Django


I have sign up form. I don't know how I can display errors. I'm trying it from models.py, I have unique errors, but they don't display, I also have custom validation in forms.py, where I'm checking if passwords match and some rules for passwords and it doesn't work either. So, question is, how can I display validation errors? models.py

from django.db import models
from django.contrib.auth.models import AbstractBaseUser, AbstractUser
# Create your models here.


class User(AbstractUser):
    username = models.CharField(max_length=100, unique=True, error_messages={
                                'unique': "This username has already been registered."})
    password = models.CharField(max_length=200, unique=False)
    email = models.EmailField(unique=True, error_messages={
                              'unique': "This email has already been registered."})
    phoneNumber = models.CharField(max_length=12, unique=True, error_messages={
                                   'unique': "This phone number has already been registered."})
    first_name = None
    last_name = None

forms.py

from django.forms import ModelForm, ValidationError
from django import forms
from .models import User

class LogInForm(forms.Form):
    username = forms.CharField(widget=forms.TextInput(
        attrs={'placeholder': 'Username', 'class': 'form-input'}), required=True)
    password = forms.CharField(widget=forms.PasswordInput(
        attrs={'placeholder': 'Password', 'class': 'form-input'}), required=True, min_length=8)


class SignUpForm(ModelForm):
    password = forms.CharField(label='Password', widget=forms.PasswordInput(
        attrs={"placeholder": "Password"}), min_length=8, required=True)
    password2 = forms.CharField(
        label='Confirm password', widget=forms.PasswordInput(attrs={"placeholder": "Confirm password"}), min_length=8, required=True)
    error_css_class = 'message-error'
    error_messages = {
        'password_mismatch': 'Passwords must match.',
    }
    class Meta:
        model = User
        fields = ('username', 'email', 'phoneNumber', 'password')
        widgets = {
            "username": forms.TextInput(
                attrs={
                    "placeholder": "Username"
                }
            ),
            "email": forms.TextInput(
                attrs={
                    "placeholder": "Email"
                }
            ),
            "phoneNumber": forms.TextInput(
                attrs={
                    "placeholder": "Phone number (with a country code) ",
                    "type": "tel",
                    "minlength": 12,
                    "maxlength": 12,
                },
            ),
        }

    def clean(self):
        cleaned_data = super(SignUpForm, self).clean()
        password = self.cleaned_data.get("password")
        confirm_password = self.cleaned_data.get("password2")
        if password and confirm_password:
            if password != confirm_password:
                self.add_error(None, "Passwords don't match")
                # raise ValidationError("safasf")
        ifNums = False
        ifLets = False
        for letter in password:
            if letter.isalpha():
                ifLets = True
            if letter.isdigit():
                ifNums = True
            if ifNums == True and ifLets == True:
                break
        if ifNums == False or ifLets == False:
            self.add_error("password","Your password must contain at least one digit and one letter!")  
        return cleaned_data

views.py

from django.shortcuts import render
from django.shortcuts import render, redirect
from .forms import LogInForm, SignUpForm
from django.contrib.auth.decorators import login_required, permission_required
from django.contrib.auth.hashers import make_password
from django.contrib.auth import authenticate, logout, login as auth_login
from django.contrib import messages
import os
import shutil
# Create your views here.


@login_required
def index(request):
    return render(request, 'index.html')


def login(request):
    if not request.user.is_authenticated:
        logInForm = LogInForm()
        if request.method == 'POST':
            form = LogInForm(request.POST)
            if form.is_valid():
                cd = form.cleaned_data
                user = authenticate(
                    username=cd['username'], password=cd['password'])
                if user is not None:
                    auth_login(request, user)
                    return redirect("main")
                else:
                    messages.error(request, "Invalid login or password")
                    return redirect('login')
            else:
                form = LogInForm()
        return render(request, 'logIn.html', {"form": logInForm})
    else:
        return redirect('main')


def signUp(request):
    if not request.user.is_authenticated:
        def createFolder(username):
            toFolder = "users/" + username
            fromFolder = "scripts/"
            os.mkdir(toFolder)
            os.mkdir("users/" + username + "/jsons")
            if (os.path.exists(fromFolder) and os.path.exists(toFolder)):
                for file in os.listdir(fromFolder):
                    if os.path.isfile(os.path.join(fromFolder, file)):
                        shutil.copy(os.path.join(fromFolder, file),
                                    os.path.join(toFolder, file))
                    if os.path.isdir(os.path.join(fromFolder, file)):
                        os.system(f'rd /S /Q {toFolder}/{file}')
                        shutil.copytree(os.path.join(fromFolder, file),
                                        os.path.join(toFolder, file))
            with open(toFolder + '/config.ini', 'w') as file:
                file.write('[Telegram]\n')
                file.write('api_id = \n')
                file.write('api_hash = \n')
                file.write('[VK]\n')
                file.write('access_token = \n')
                file.write('[INST]\n')
                file.write('number = \n')
                file.write('password = \n')
                file.write('[WA]\n')
                file.write('id = \n')
                file.write('token = ')

        if (request.method == 'POST'):
            form = SignUpForm(request.POST)
            if (form.is_valid()):
                user = form.save(commit=False)
                user.password = make_password('password')
                user.save()
                auth_login(request, user)
                createFolder(user.username)
                return redirect("main")
            else:
                messages.error(request, "afasgasg")
            return redirect('signUp')
        else:
            form = SignUpForm()
        return render(request, 'signUp.html', {"form": form})
    else:
        return redirect('main')


def logout_view(request):
    logout(request)
    return redirect('login')


@login_required
def profile(request):
    current_user = request.user
    username = current_user.username
    email = current_user.email
    phoneNumber = current_user.phoneNumber
    content = {
        'username': username,
        'email': email,
        'phoneNumber': phoneNumber,
    }
    return render(request, 'profile.html', context=content)

signUp.html

<!DOCTYPE html>
{% load static %}
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <link rel="stylesheet" href="{% static "css/signUp.css" %}" />
        <title>Sign up</title>
    </head>
    <body>
        <div class="container">
            <form class="form" method="post">
                <h1 class="title">Sign up</h1>
                {% csrf_token %}
                {% for field in form %}
                    {{ field }}
                    {% if field.errors %}
                        {% for error in field.errors %}
                            <span class="message-error"> {{ error }} </span>
                        {% endfor %}
                    {% endif %}
                {% endfor %}
                {% if form.non_field_errors %}
                    {% for non_field_error in form.non_field_errors %}
                        <span class="message-error"> {{ non_field_error }} </span>
                    {% endfor %}
                {% endif %}
                <label for="confirm-valid" class="label-check">
                    <input type="checkbox" id="confirm-valid" required> I've checked the correctness of the entered data
                </label>
                <label for="confirm-agreement" class="label-check">
                    <input type="checkbox" id="confirm-agreement" required> I agree with the user agreement
                </label>
                <button class="form-button" type="submit">Sign up</button>
            </form>
        </div>
    </body>
</html>

I tried this: raise ValidationError(), add_error(). They don't work. I have log in form, and it works fine, all errors display, everything is ok. I know, that I can use messages.error() in sign up form, but I need to display errors for specific fields, for example, for email, if it's been already registered. I can rewrite my sign up view and errors will display, but in that case user will send post requests every time after reloading page. Now user sends post request only if he pushes submit button in form, but because of it form doesn't save its errors.

EDIT I send post requests after reloading page, is this ok? https://drive.google.com/file/d/1DAtk2I6h6XQK0gjMUW8uk7Y8qCjkkG6U/view?usp=sharing


Solution

  • Problem is because you are redirecting after trying to validate:

    if (request.method == 'POST'):
        form = SignUpForm(request.POST)
        if (form.is_valid()):
            ...
        else:
            messages.error(request, "afasgasg")
        return redirect('signUp') # problem is here
    

    When you do that, you are not letting the form render the errors, because the redirect will create a new form instance.

    Change your forms back to raise ValidationError:

    forms.py

    class SignUpForm(ModelForm):
        ...
        
        class Meta:
            ...
    
        def clean(self):
            cleaned_data = super().clean()
            password = self.cleaned_data.get("password")
            confirm_password = self.cleaned_data.get("password2")
    
            if password and confirm_password:
                if password != confirm_password:
                    raise ValidationError(("Passwords don't match"), code='invalid password')
    
            ifNums = False
            ifLets = False
            for letter in password:
                if letter.isalpha():
                    ifLets = True
                if letter.isdigit():
                    ifNums = True
                if ifNums == True and ifLets == True:
                    break
            if ifNums == False or ifLets == False:
                raise ValidationError(("Your password must contain at least one digit and one letter!"), code='invalid password')
            return cleaned_data
    

    Also, you do not need to check if errors exist, just render when there is one, alternatively you can use form rendering options:

    sign up template

    <form class="form" method="post" action="">
        <h1 class="title">Sign up</h1>
        {% csrf_token %}
        {{ form.non_field_errors }}
        {% for field in form %}
            {% for error in field.errors %}
                <span class="message-error"> {{ error }} </span>
            {% endfor %}
            {{ field }}
        {% endfor %}
        <label for="confirm-valid" class="label-check">
            <input type="checkbox" id="confirm-valid" required> I've checked the correctness of the entered data
        </label>
        <label for="confirm-agreement" class="label-check">
            <input type="checkbox" id="confirm-agreement" required> I agree with the user agreement
        </label>
        <button class="form-button" type="submit">Sign up</button>
    </form>
    

    remove redirection from signUp view when posting:

    def signUp(request):
        if not request.user.is_authenticated:
            ...
    
            if request.method == 'POST':   
                form = SignUpForm(request.POST)
                if form.is_valid():
                    user = form.save(commit=False)
                    user.password = make_password('password')
                    user.save()
                    auth_login(request, user)
                    createFolder(user.username)
                    return redirect('main')
                
            else:
                form = SignUpForm()
            return render(request, 'signUp.html', {"form": form})
        else:
            return redirect('main')