Source code for django_webapps_fullstack.account.forms
"""
This module provides comprehensive user authentication and account management forms for the dingx
web application. It handles the complete user lifecycle from registration to password recovery,
integrating with the Odoo ERP system via XMLRPC for centralized user data management.
**Business Context:**
The account app serves as the gateway for user interaction with the dingx platform, which provides
a logistics management system for tracking and managing physical objects across different locations
(storage, home, disposal, transfer). Users must authenticate through this system to access their
personal dashboard and manage their belongings.
**Key Business Processes:**
1. **Authentication:**
- :class:`LoginForm`: Secure login validation against Odoo user database
- Session management for authenticated users
- Credential retrieval for authorized access
2. **User Registration & Activation:**
- :class:`RegisterForm`: New users register with name, email, login, and password
- System validates uniqueness against Odoo database
- Activation token sent via email to verify user identity
- :class:`User_ActivateForm`: Users must activate account before first login via email token
3. **Password Management:**
- :class:`Password_RecoveryForm`: Password recovery via email-based reset tokens
- :class:`ResetForm`: Validate password reset token
- :class:`Password_RenewForm`: Set new password after reset
- :class:`Password_ChangeForm`: Change password functionality for logged-in users
- All password operations synchronized with Odoo
**Integration:**
All forms communicate with the Odoo ERP backend via Twisted XMLRPC server, ensuring
centralized user data management across the entire dingx ecosystem.
"""
# Initialize Django settings if running standalone (e.g., in Thonny IDE)
# This allows the module to be imported for inspection without running Django server
import sys
import os
if __name__ == "__main__" or 'django.conf.settings' not in sys.modules or not hasattr(sys.modules.get('django.conf.settings', None), 'configured'):
import django
from django.conf import settings
if not settings.configured:
# Add the project root to Python path
project_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
if project_root not in sys.path:
sys.path.insert(0, project_root)
# Set the Django settings module
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'settings.settings')
# Setup Django
django.setup()
# import the standard Django Forms from built-in library
from django import forms
from django.forms import ValidationError
# for the use of the gettext function to mark text for translation
from django.utils.translation import gettext as _
# for the use of 'configparser' functionality
import configparser
# for the use of 'XMLRPC' functionality
import xmlrpc.client
import ssl
import platform
# ------- get some parameter from the settings file "parameter_global.ini"
# Get the path to the parent directory
parent_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))
# Define the directory 'settings' under the parent directory that contains the settings file
sub_dir = os.path.join(parent_dir, 'settings')
# Append this directory to sys.path
sys.path.append(sub_dir)
# Name and full path of the "ini" file
filename = "parameter_global.ini"
path_name = sub_dir
fullpath = os.path.join(path_name,filename)
# read the settings file using configparser with UTF-8
config = configparser.ConfigParser()
config.read(fullpath, encoding='utf-8')
# Get the settings to log onto Twisted Server
server_url = config.get('settings_twisted_server', 'server_url')
# Get the image size of the picture stored in the Odoo system, for example '128'
image_size = config.get('settings_camera', 'image_size')
# Create an SSL context with proper certificate validation
# Uses system's trusted CA certificates (includes Let's Encrypt root CA)
context = ssl.create_default_context()
# On macOS development, use unverified context if certificate validation fails
# On Linux production, always use verified certificates
is_macos = platform.system() == 'Darwin'
if is_macos:
# Development mode: Use unverified context for macOS
context = ssl._create_unverified_context()
# Create a secure XMLRPC client with SSL/TLS encryption
proxy_client = xmlrpc.client.ServerProxy(server_url, context=context)
[docs]
class LoginForm(forms.Form):
"""
Create a form for the "login" template and
create the functions of the form.
"""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# define the input fields for the form
self.fields['login'] = forms.CharField(required=True, widget=forms.TextInput(attrs={'id': 'id_login', 'class': 'form-control', 'placeholder': _("Enter login name")}), max_length = 50, label=_("Login Name"))
self.fields['password'] = forms.CharField(required=True, widget=forms.PasswordInput(attrs={'id': 'id_password', 'class': 'form-control', 'placeholder': _("Enter password")}), initial='', max_length = 50, label=_("Password"))
[docs]
def clean(self):
"""
Check the credentials it gets and return a user object
that matches those credentials if the credentials are valid.
If they’re not valid, it return None.
"""
# Get the user submitted values from the cleaned_data dictionary
cleaned_data = super().clean()
# cleaned_data = super().clean()
login = cleaned_data.get('login')
password = cleaned_data.get('password')
# check the credentials against the users in Odoo
result = proxy_client.check_user_login(login, password)
if result == 'False':
raise ValidationError(_("Please enter a valid login name or password."))
return (cleaned_data)
[docs]
def get_credentials(self, login):
"""
get the credentials of the account
"""
result = proxy_client.get_credentials(login)
if result[0] != 'True':
raise ValidationError(_("The credentials of the user could not be loaded."))
return (result)
[docs]
class RegisterForm(forms.Form):
"""
Create a form for the "register" template and
create the corresponding functions of the form.
"""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# define the input fields for the form
self.fields['name'] = forms.CharField(required=True, widget=forms.TextInput(attrs={'id': 'id_name', 'class': 'form-control', 'placeholder': _("Enter name")}), max_length = 100, label=_("Name"))
self.fields['login'] = forms.CharField(required=True, widget=forms.TextInput(attrs={'id': 'id_login', 'class': 'form-control', 'placeholder': _("Enter login name")}), max_length = 100, label=_("Login Name"))
self.fields['email'] = forms.EmailField(required=True, widget=forms.EmailInput(attrs={'id': 'id_email', 'class': 'form-control', 'placeholder': _("Enter e-mail")}), max_length = 100, label=_("E-Mail"))
self.fields['password'] = forms.CharField(required=True, widget=forms.PasswordInput(attrs={'id': 'id_password', 'class': 'form-control', 'placeholder': _("Enter Password")}), initial='', max_length = 50, label=_("Password"))
self.fields['password_confirm'] = forms.CharField(required=True, widget=forms.PasswordInput(attrs={'id': 'id_password_confirm', 'class': 'form-control', 'placeholder': _("Enter Password Confirm")}), initial='', max_length = 50, label=_("Password Confirm"))
[docs]
def clean(self):
"""
Validation of the entered values
during the registration of a user.
"""
# Get the user submitted values from the cleaned_data dictionary
cleaned_data = super().clean()
name = cleaned_data.get("name", '').strip() # Ensure value is a string and not None
login = cleaned_data.get("login", '').strip() # Ensure value is a string and not None
email = cleaned_data.get("email", '').strip() # Ensure value is a string and not None
password = cleaned_data.get("password", '').strip() # Ensure value is a string and not None
password_confirm = cleaned_data.get("password_confirm", '').strip() # Ensure value is a string and not None
# Check the values against the different checks coming form Odoo system
result = proxy_client.name_exist(name)
if result == 'True':
raise ValidationError(_("Name is already used by someone else"))
result = proxy_client.login_exist(login)
if result == 'True':
raise ValidationError(_("Login name is already used by someone else"))
result = proxy_client.email_exist(email)
if result == 'True':
raise ValidationError(_("E-Mail is already used by someone else"))
if password != password_confirm:
raise ValidationError(_("Passwords do not match"))
return cleaned_data
[docs]
def save(self, name, login, email, password):
"""
Save the user in the Odoo Database.
Returns:
tuple: (result, user_id, partner_id) from create_user function
"""
result, user_id, partner_id = proxy_client.create_user(name, login, email, password)
if result == 'False':
raise ValidationError(_("User could not be created"))
return (result, user_id, partner_id)
[docs]
def create_activatetoken(self, login):
"""
Create the activate token for the user,
which is used for the activation of the user account.
"""
result = proxy_client.create_activatetoken(login)
if result == 'False':
raise ValidationError(_("Activatetoken could not be created"))
return (result)
[docs]
class User_ActivateForm(forms.Form):
"""
Create a form and
create the corresponding functions of the form
"""
def __init__(self, *args, **kwargs):
self.activate_token = kwargs.pop('activate_token', None)
super().__init__(*args, **kwargs)
[docs]
def clean(self):
"""
Test if the provided activate token is correct.
"""
cleaned_data = super().clean()
activate_token = self.activate_token
result = proxy_client.test_activatetoken(activate_token)
if result == 'False':
raise ValidationError(_("The provided activate token is not correct"))
return (cleaned_data)
[docs]
def save(self):
"""
Update of the provided activate token so that the user account could be used.
"""
activate_token = self.activate_token
result = proxy_client.update_activatetoken(activate_token)
if result[0] == 'False':
raise ValidationError(_("Update of the activate token could not be executed"))
return(result)
[docs]
class Password_RecoveryForm(forms.Form):
"""
Create a form for the "recovery" template and
create the corresponding functions of the form
"""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# define the input fields for the form
self.fields['email'] = forms.EmailField(required=True, widget=forms.EmailInput(attrs={'id': 'id_email', 'class': 'form-control', 'placeholder': _("Enter e-mail")}), max_length = 100, label=_("E-Mail"))
[docs]
def clean(self):
"""
Check if the email is valid.
"""
# Get the user submitted values from the cleaned_data dictionary
cleaned_data = super().clean()
email = cleaned_data.get("email", '').strip() # Ensure value is a string and not None
# Check if the e-mail exit
result = proxy_client.email_exist(email)
if result == 'False':
pass
return (cleaned_data)
[docs]
def create_resettoken(self):
"""
Create the password reset token und save it in the Odoo Database.
"""
# Get the email from the cleaned_data dictionary
cleaned_data = super().clean()
email = cleaned_data.get("email")
# Create the Resetttoken
result = proxy_client.create_resettoken(email)
return (result)
[docs]
class ResetForm(forms.Form):
"""
Create a form and
create the corresponding functions of the form
"""
def __init__(self, *args, **kwargs):
self._reset_token = kwargs.pop('reset_token', None)
super().__init__(*args, **kwargs)
[docs]
def clean(self):
"""
Test if the provided reset token is correct.
"""
cleaned_data = super().clean()
reset_token = self._reset_token
result = proxy_client.test_resettoken(reset_token)
if result == 'False':
raise ValidationError(_("The provided reset token is not correct"))
return (cleaned_data)
[docs]
def save(self):
"""
Update of the provided reset token so that the token could be used any longer.
"""
reset_token = self._reset_token
result = proxy_client.update_resettoken(reset_token)
if result[0] == 'False':
raise ValidationError(_("Update of the reset token could not be executed"))
return(result)
[docs]
class Password_RenewForm(forms.Form):
"""
Create a form for the "password_renew" template and
create the corresponding functions of the form
"""
def __init__(self, *args, **kwargs):
self._login = kwargs.pop('login', None)
super().__init__(*args, **kwargs)
# define the input fields for the form
self.fields['password'] = forms.CharField(required=True, widget=forms.PasswordInput(attrs={'id': 'id_password', 'class': 'form-control', 'placeholder': _("Enter new password")}), initial='', max_length = 50, label=_("New Password"))
self.fields['password_confirm'] = forms.CharField(required=True, widget=forms.PasswordInput(attrs={'id': 'id_password_confirm', 'class': 'form-control', 'placeholder': _("Enter confirm new password")}), initial='', max_length = 50, label=_("Confirm New Password"))
[docs]
def clean(self):
"""
Validation of the entered passwords.
"""
# Get the user submitted values from the cleaned_data dictionary
cleaned_data = super().clean()
password = cleaned_data.get("password", '').strip() # Ensure value is a string and not None
password_confirm = cleaned_data.get("password_confirm", '').strip() # Ensure value is a string and not None
# Check if the passwords are equal
if password != password_confirm:
raise ValidationError(_("Passwords do not match"))
return (cleaned_data)
[docs]
def save(self):
"""
Save the changed password in the Odoo Database.
"""
login = self._login
# Get the user submitted values from the cleaned_data dictionary
cleaned_data = super().clean()
password = cleaned_data.get("password")
result = proxy_client.change_password(login, password)
return result
[docs]
class Password_ChangeForm(forms.Form):
"""
Create a form for the "password_change" template and
create the corresponding functions of the form
"""
def __init__(self, *args, **kwargs):
self._login = kwargs.pop('login', None)
super().__init__(*args, **kwargs)
# define the input fields for the form
self.fields['password_current'] = forms.CharField(required=True, widget=forms.PasswordInput(attrs={'id': 'id_password_current', 'class': 'form-control', 'placeholder': _("Enter Current Password")}), initial='', max_length = 50, label=_("Current Password"))
self.fields['password_new'] = forms.CharField(required=True, widget=forms.PasswordInput(attrs={'id': 'id_password_new', 'class': 'form-control', 'placeholder': _("Enter New Password")}), initial='', max_length = 50, label=_("New Password"))
self.fields['password_confirm'] = forms.CharField(required=True, widget=forms.PasswordInput(attrs={'id': 'id_password_confirm', 'class': 'form-control', 'placeholder': _("Enter Confirm New Password")}), initial='', max_length = 50, label=_("Confirm New Password"))
[docs]
def clean(self):
"""
Validation of the entered passwords.
"""
login = self._login
# Get the user submitted values from the cleaned_data dictionary
cleaned_data = super().clean()
password_current = cleaned_data.get("password_current", '').strip() # Ensure value is a string and not None
password_new = cleaned_data.get("password_new", '').strip() # Ensure value is a string and not None
password_confirm = cleaned_data.get("password_confirm", '').strip() # Ensure value is a string and not None
# Check if name and passwort match against the users in Odoo
result = proxy_client.check_user_login(login, password_current)
if result == 'False':
raise ValidationError(_("Please enter a valid login or password"))
# Check if the passwords are equal
if password_new != password_confirm:
raise ValidationError(_("Passwords do not match"))
return (cleaned_data)
[docs]
def save(self):
"""
Save the changed password in the Odoo Database.
"""
login = self._login
# Get the user submitted values from the cleaned_data dictionary
cleaned_data = super().clean()
password = cleaned_data.get("password_new")
result = proxy_client.change_password(login, password)
return result