Source code for django_webapps_fullstack.dashboard.forms

"""
This module provides the core business functionality forms for managing personal belongings through
the dingx logistics platform. It enables users to track, organize, and manage physical objects across
different locations (storage, home, disposal, transfer) with visual product management and comprehensive
user profile administration.

**Business Context:**

The dashboard app is the central hub of the dingx platform where users interact with their registered
objects (products). The system tracks physical belongings across four main locations:

- **Storage:** Items kept in long-term storage facilities
- **Home:** Items currently at the user's residence
- **Disposal:** Items marked for disposal or donation
- **Transfer:** Items currently in transit between locations

Users can visualize their inventory with product images from multiple perspectives (front, right, back, left),
organize items through drag-and-drop functionality, and manage order baskets for logistics operations.

**Key Business Processes:**

1. **Product Overview & Management:**

- :class:`OverviewForm`: Visual dashboard showing all user products with filtering by location
    - Product cards with images, descriptions, and location badges
    - Color-coded system for quick location identification
    - Drag-and-drop interface for organizing products into baskets
    - Real-time product count by location
    - Caching mechanism for performance optimization

2. **Product Editing:**

- :class:`Edit_ObjectForm`: Update product names and descriptions
    - View multi-perspective product images
    - Manage product attributes (volume, weight, location)
    - Cache invalidation after updates to ensure data consistency

3. **Basket Management:**

- :class:`BasketForm`: Create transfer orders by selecting products
    - Organize logistics operations through basket system
    - Support for bulk product selection and movement

4. **User Profile Management:**

- :class:`Account_ProfileForm`: Complete profile editing (name, contact, language preferences)
    - Multi-language support with dynamic title selection
    - Real-time validation against Odoo database
    - Phone number formatting with international support

5. **Address Management:**

- :class:`Delivery_AddressForm`: Manage delivery address details

- :class:`Invoice_AddressForm`: Manage billing address information
    - Country and state selection with localization
    - Support for Swiss cantons with multi-language names
    - Integration with Odoo partner address system

**Technical Features:**

- Dynamic form field generation based on user language
- Integration with Bootstrap design system for color coding
- Odoo XMLRPC backend synchronization
- Django caching framework for performance
- Multi-language support via Django i18n
- Image handling with base64 encoding

**Integration:**

All forms communicate with the Odoo ERP backend via Twisted XMLRPC server, ensuring
synchronized data across inventory management, order processing, and user administration systems.
"""

# 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 _
from django.utils.translation import activate, deactivate

# for the use of 'configparser' functionality
import configparser

# for the use of 'XMLRPC' functionality
import xmlrpc.client
import ssl
import platform

# provides a caching framework to store and retrieve data efficiently
from django.core.cache import cache

# for the conversion of a string to a list of integers
import ast

# ensure that static() works correctly in this form
from django.templatetags.static import static

# for the use of django-phonenumber-field
from phonenumber_field.formfields import PhoneNumberField


# ------- 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')

# Check if we're running under Sphinx documentation build
SPHINX_BUILD = os.environ.get('SPHINX_BUILD', False)

# Create an XMLRPC client for the calls of the Twisted Server
if not SPHINX_BUILD:
    # 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)
else:
    # Create a mock proxy client for Sphinx documentation builds
    proxy_client = None



# ------- get parameters from the file "parameter_design.ini"    
# ------- the section "bootstrap_colors" of "parameter_design.ini" will be used 

# 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 should be added
sub_dir = os.path.join(parent_dir, 'settings')
# file is in a subdirectory under the parent directory
pathname = sub_dir
# Name and full path of the "ini" file
filename = "parameter_design.ini"
fullpath = os.path.join(pathname,filename)
# read the settings file using configparser with UTF-8
config = configparser.ConfigParser()
config.read(fullpath, encoding='utf-8')

# get the backgroud colors of the different place
# used in the def "get_header_background_color"
custom_background_all       = config.get('bootstrap_colors', 'background_all')
custom_background_storage   = config.get('bootstrap_colors', 'background_storage')
custom_background_home      = config.get('bootstrap_colors', 'background_home')
custom_background_disposal  = config.get('bootstrap_colors', 'background_disposal')
custom_background_transfer  = config.get('bootstrap_colors', 'background_transfer')
custom_background_default   = config.get('bootstrap_colors', 'background_default')

# get the badge colors of objects of the customer
# used in the def "get_product_list" to add the colors classes ot the different products (= objects) 
badge_all       = config.get('bootstrap_colors', 'badge_all')
badge_storage   = config.get('bootstrap_colors', 'badge_storage')
badge_home      = config.get('bootstrap_colors', 'badge_home')
badge_disposal  = config.get('bootstrap_colors', 'badge_disposal')
badge_transfer  = config.get('bootstrap_colors', 'badge_transfer')
badge_default   = config.get('bootstrap_colors', 'badge_default')

# set the different perspectives of the images of a product 
ordered_perspectives = ["front", "right", "back", "left"]

# set the different places 
places = ["storage", "home", "disposal", "transfer"]


# ------- get for 'user profile' purposes the language and title values
# ------- and prepare them as tuples for 'Account_ProfileForm'

# get the installed languages from Odoo system in form of an array
# these languages are also the possible languages for the user
if not SPHINX_BUILD:
    languages = proxy_client.get_installed_languages()
else:
    # Provide default values for Sphinx documentation builds
    languages = [{'code': 'en_US', 'name': 'English'}, {'code': 'de_DE', 'name': 'German'}]
# Convert array of dictionaries to a tuple of tuples
# first element of each tuple is the code and the second element is the name
# this tuple is used in the 'ChoiceField' HTML templates field 'language'
language_tuple = tuple((item['code'], item['name']) for item in languages)
# Construct language tuple with descriptions
language_tuple_choices = [('', _("Select a language"))]  # Placeholder option
language_tuple_choices.extend(language_tuple)


# initialize the dictionary to store the available titles
available_titles = {}
# Loop through each language code and get the titles for that language
for item in languages:
    try:
        # set the language code
        lang_code = item['code']
        # Call the function to get the user titles from Odoo system
        # and add the result to the dictionary
        if not SPHINX_BUILD:
            available_titles[lang_code] = proxy_client.get_installed_user_titles(lang_code)
        else:
            # Provide default values for Sphinx documentation builds
            available_titles[lang_code] = [{'id': 1, 'name': 'Mr.'}, {'id': 2, 'name': 'Ms.'}]
    except Exception as e:
        # Handle any errors during the function call
        print(f"Error fetching titles for language '{lang_code}': {e}")    


# ------- get for 'user address' purposes the country values
# ------- and prepare them as tuples for 'Account_AddressForm'

# get the available countries from Odoo system in form of an array
# these countries are also the possible countries for the user
if not SPHINX_BUILD:
    countries = proxy_client.get_available_countries()
else:
    # Provide default values for Sphinx documentation builds
    countries = [{'id': 1, 'name': 'Switzerland'}, {'id': 2, 'name': 'Germany'}]
# Convert array of dictionaries to a tuple of tuples
# first element of each tuple is the id and the second element is the name
# this tuple is used in the 'ChoiceField' HTML templates field 'country'
country_tuple = tuple((item['id'], item['name']) for item in countries)
# Construct country tuple with descriptions
country_tuple_choices = [('', _("Select a country"))]  # Placeholder option
country_tuple_choices.extend(country_tuple)


# ------- get for 'user address' purposes the available states values    
# ------- and prepare them as a dictionary for 'Account_AddressForm' 
# ------- only states from Switzerland (= 43) will be selected

# set the default country_code = 43 (= Switzerland)
default_country_code = 43
# Initialize the dictionary to store the results
available_states = {}
# Loop through each language code and get the available states for the language
for item in languages:
    try:
        # set the language code
        lang_code = item['code']
        # Call the function and add the result to the dictionary
        if not SPHINX_BUILD:
            available_states[lang_code] = proxy_client.get_available_states(default_country_code, lang_code)
        else:
            # Provide default values for Sphinx documentation builds
            available_states[lang_code] = [{'id': 1, 'name': 'Zurich'}, {'id': 2, 'name': 'Bern'}]
    except Exception as e:
        # Handle any errors during the function call
        print(f"Error fetching states for language '{lang_code}': {e}")


# --------------------------------------------------------------------------------------------------


[docs] class BasketForm(forms.Form): """ Create the "dashboard_basket" form. The ordered objects of the customer will be shown. """ def __init__(self, *args, **kwargs): basketPlace = kwargs.pop('basketPlace', None) BasketElements = kwargs.pop('BasketElements', None) super().__init__(*args, **kwargs)
[docs] def get_ordered_products(self, basketPlace, BasketElements): """ Get the details from the ordered products including pictures and colors. """ products = [] # convert the single string to a list to use it in the loop if not all(isinstance(item, int) for item in BasketElements): BasketElements = ast.literal_eval(BasketElements) # Loop through each element of the list for product in BasketElements: product = proxy_client.get_product(product) # add the details of the product to "products" list products.append(product) # Update each product based on the value of 'x_place' # with the color of the badges (= 'badgecolor'), values come from the parameter file "parameter_design.ini" for product in products: if product['x_place'] == 'Storage': product['badgecolor'] = badge_storage elif product['x_place'] == 'Home': product['badgecolor'] = badge_home elif product['x_place'] == 'Disposal': product['badgecolor'] = badge_disposal elif product['x_place'] == 'Transfer': product['badgecolor'] = badge_transfer else: product['badgecolor'] = badge_default return (products)
[docs] class Edit_ObjectForm(forms.Form): """ Create the "edit_object" form. The selected object will be shown including the pictures. Some fields of the object could be edited. """ def __init__(self, *args, product=None, **kwargs): super().__init__(*args, **kwargs) self.fields['name'] = forms.CharField(required=True, widget=forms.TextInput(attrs={'id': 'id_name', 'class': 'form-control', 'placeholder': _("Enter name")}), max_length = 50, label=_("Name")) self.fields['description'] = forms.CharField(required=False, widget=forms.Textarea(attrs={'id': 'id_description', 'class': 'form-control', 'placeholder': _("Enter description")}), max_length = 500, label=_("Description")) self.fields['volume'] = forms.CharField(required=False, widget=forms.HiddenInput(), label=_("Volume (m3):")) self.fields['weight'] = forms.CharField(required=False, widget=forms.HiddenInput(), label=_("Weight (kg):")) self.fields['x_place'] = forms.CharField(required=False, widget=forms.HiddenInput(), label=_("Place:")) self.fields['badgecolor'] = forms.CharField(required=False, widget=forms.HiddenInput(),) self.fields['image_front'] = forms.CharField(required=False, widget=forms.HiddenInput(),) self.fields['image_right'] = forms.CharField(required=False, widget=forms.HiddenInput(),) self.fields['image_back'] = forms.CharField(required=False, widget=forms.HiddenInput(),) self.fields['image_left'] = forms.CharField(required=False, widget=forms.HiddenInput(),)
[docs] def clean(self): """ Validation of the entered values when changes are made. """ # Get the user submitted values from the cleaned_data dictionary cleaned_data = super().clean() # Track the changed fields changed_data = self.changed_data # get the values from the form name = cleaned_data.get("name") description = cleaned_data.get("description") return cleaned_data
[docs] def get_product(self, product_id): """ Get the details for the selected product and add badgecolor to the product. """ # Get the values for the product from the Odoo system product = proxy_client.get_product(product_id) if product: # Update the product based on the value of 'x_place' # with the color of the badges (= 'badgecolor'). # Values for 'badgecolor' come from the parameter file 'parameter_design.ini'. if product.get('x_place') == 'Storage': product['badgecolor'] = badge_storage elif product.get('x_place') == 'Home': product['badgecolor'] = badge_home elif product.get('x_place') == 'Disposal': product['badgecolor'] = badge_disposal elif product.get('x_place') == 'Transfer': product['badgecolor'] = badge_transfer else: product['badgecolor'] = badge_default return (product)
[docs] def get_product_pictures(self, product_id): """ Get the pictures from all perspectives for the selected product. """ # Define the dictionary with the pictures picture_string_dict = {} # Get the pictures from the different perspectives belonging to the product for perspective in ordered_perspectives: # Build the custom_res_field of the picture, someting like 'image_128' res_field = 'image_' + image_size # Build the name of the picture, someting like 'image_128_front' name = res_field + '_' + perspective # Build the picture_string_name of the picture picture_string_name = perspective # Get the picture of the specific perspective from the Odoo system picture_data = proxy_client.get_picture_string(product_id, res_field, name) # Set the picture string to be displayed if picture_data: picture_string = f"data:image/jpeg;base64,{picture_data}" else: # If no picture, return the URL of a dummy image picture_string = static("dashboard/assets/img/illustrations/dummy.svg") # Store that tuple in the picture dictionary picture_string_dict[picture_string_name] = picture_string return (picture_string_dict)
[docs] def update(self, partner_id, product_id, name, description): """ Update the product in the Odoo Database. """ partner_id = int(partner_id) product_id = int(product_id) # update the values in Odoo system result = proxy_client.update_product(product_id, name, description) if result == 'False': raise ValidationError(_("Product values could not be updated.")) # delete the cache of the overall 'product list' (= "all") as data has been changed place = "all" cache_key = cache_key = f"partner:{partner_id}:get_product_list:partner:{partner_id}:place:{place}" cache.delete(cache_key) # delete cache of the 'product place list' of the different places as data has been changed for place in places: cache_key = cache_key = f"partner:{partner_id}:get_product_list:partner:{partner_id}:place:{place}" cache.delete(cache_key) return (result)
[docs] class OverviewForm(forms.Form): """ Create the "overview" form. All products of the user will be shown. """ def __init__(self, *args, **kwargs): self.place = kwargs.pop('place', None) super().__init__(*args, **kwargs)
[docs] def get_product_list(self, partner_id, place): """ Get all products from the dedicated partner including pictures. In addition the individual CSS classes will be added to products based on the place of the product. """ try: # Unique cache key for storing Odoo data cache_key = f"partner:{partner_id}:get_product_list:partner:{partner_id}:place:{place}" products = cache.get(cache_key) # If not cached, fetch the data from Odoo system if products is None: # Fetch from Odoo system try: # Convert the place to capitalize first letter as the places are stored in uppercase in Odoo place = place.capitalize() products = proxy_client.get_product_list(partner_id, place) # Set the picture string of the product to be displayed for product in products: if product['image_128']: picture_string = f"data:image/jpeg;base64,{product['image_128']}" else: # If no picture, return the URL of a dummy image picture_string = static("dashboard/assets/img/illustrations/dummy.svg") # Store that tuple in the picture dictionary product['image'] = picture_string except Exception as e: print(f"Error occurred: {e}") return None # Store in cache cache.set(cache_key, products) except Exception as e: print('Error in "class OverviewForm - def get_product_list: proxy_client.get_product_list(partner_id)" in "Dashboard - forms.py":', e) # Update each product based on the value of 'x_place' # with the individual values for 'classcardportfolio', 'classcardbody', 'classcardtitle' and 'classcardbackground' for the 'CSS' classes # and for the color of the badges (= 'badgecolor'), these values come from the parameter file "parameter_design.ini". for product in products: if product['x_place'] == 'Storage': product['classcardportfolio'] = 'card-portfolio-storage' product['classcardbody'] = 'card-body-storage' product['classcardtitle'] = 'card-title-storage' product['classcardbackground'] = 'card-background-storage' product['badgecolor'] = badge_storage elif product['x_place'] == 'Home': product['classcardportfolio'] = 'card-portfolio-home' product['classcardbody'] = 'card-body-home' product['classcardtitle'] = 'card-title-home' product['classcardbackground'] = 'card-background-home' product['badgecolor'] = badge_home elif product['x_place'] == 'Disposal': product['classcardportfolio'] = 'card-portfolio-disposal' product['classcardbody'] = 'card-body-disposal' product['classcardtitle'] = 'card-title-disposal' product['classcardbackground'] = 'card-background-disposal' product['badgecolor'] = badge_disposal elif product['x_place'] == 'Transfer': product['classcardportfolio'] = 'card-portfolio-transfer' product['classcardbody'] = 'card-body-transfer' product['classcardtitle'] = 'card-title-transfer' product['classcardbackground'] = 'card-background-transfer' product['badgecolor'] = badge_transfer else: product['classcardportfolio'] = 'card-portfolio' product['classcardbody'] = 'card-body' product['classcardtitle'] = 'card-title' product['classcardbackground'] = 'card-background' product['badgecolor'] = badge_default return (products)
[docs] def get_card_classes(self, card_place, place): """ Set the 'CSS' classes of the card places (= card_place) for a dedicated place. This is done by building dictionaries for each card place. Explanation of the different classes: - "basket_enabled": enables the 'basket' function for that card place, objects can be dragged on that place - "select_enabled": enables the 'select' function for that card place, dashboard view with only objects of that the place can be selected - "lift": lift up a card place, place is enabled for dragging objects - "link_enabled": dashboard link of that place is enabled, dashboard view with only objects of that the place is enabled """ if card_place == "storage": card_classes_storage = {} if place == "all": card_classes_storage['class_basket'] = "basket_enabled" card_classes_storage['class_select'] = "select_enabled" card_classes_storage['class_lift'] = "lift" card_classes_storage['class_link'] = "link_enabled" if place == "storage": card_classes_storage['class_basket'] = "basket_disabled" card_classes_storage['class_select'] = "select_disabled" card_classes_storage['class_lift'] = "" card_classes_storage['class_link'] = "link_disabled" if place == "home": card_classes_storage['class_basket'] = "basket_disabled" card_classes_storage['class_select'] = "select_disabled" card_classes_storage['class_lift'] = "lift" card_classes_storage['class_link'] = "link_enabled" if place == "disposal": card_classes_storage['class_basket'] = "basket_disabled" card_classes_storage['class_select'] = "select_disabled" card_classes_storage['class_lift'] = "lift" card_classes_storage['class_link'] = "link_enabled" if place == "transfer": card_classes_storage['class_basket'] = "basket_disabled" card_classes_storage['class_select'] = "select_disabled" card_classes_storage['class_lift'] = "lift" card_classes_storage['class_link'] = "link_enabled" result = card_classes_storage elif card_place == "home": card_classes_home = {} if place == "all": card_classes_home['class_basket'] = "basket_enabled" card_classes_home['class_select'] = "select_enabled" card_classes_home['class_lift'] = "" card_classes_home['class_link'] = "link_disabled" if place == "storage": card_classes_home['class_basket'] = "basket_enabled" card_classes_home['class_select'] = "select_enabled" card_classes_home['class_lift'] = "" card_classes_home['class_link'] = "link_disabled" if place == "home": card_classes_home['class_basket'] = "basket_disabled" card_classes_home['class_select'] = "select_disabled" card_classes_home['class_lift'] = "" card_classes_home['class_link'] = "link_disabled" if place == "disposal": card_classes_home['class_basket'] = "basket_disabled" card_classes_home['class_select'] = "select_disabled" card_classes_home['class_lift'] = "" card_classes_home['class_link'] = "link_disabled" if place == "transfer": card_classes_home['class_basket'] = "basket_disabled" card_classes_home['class_select'] = "select_disabled" card_classes_home['class_lift'] = "" card_classes_home['class_link'] = "link_disabled" result = card_classes_home elif card_place == "disposal": card_classes_disposal = {} if place == "all": card_classes_disposal['class_basket'] = "basket_enabled" card_classes_disposal['class_select'] = "select_enabled" card_classes_disposal['class_lift'] = "lift" card_classes_disposal['class_link'] = "link_enabled" if place == "storage": # Add the class values to that dictionary card_classes_disposal['class_basket'] = "basket_enabled" card_classes_disposal['class_select'] = "select_enabled" card_classes_disposal['class_lift'] = "lift" card_classes_disposal['class_link'] = "link_enabled" if place == "home": # Add the class values to that dictionary card_classes_disposal['class_basket'] = "basket_disabled" card_classes_disposal['class_select'] = "select_disabled" card_classes_disposal['class_lift'] = "lift" card_classes_disposal['class_link'] = "link_enabled" if place == "disposal": # Add the class values to that dictionary card_classes_disposal['class_basket'] = "basket_disabled" card_classes_disposal['class_select'] = "select_disabled" card_classes_disposal['class_lift'] = "" card_classes_disposal['class_link'] = "link_disabled" if place == "transfer": # Add the class values to that dictionary card_classes_disposal['class_basket'] = "basket_disabled" card_classes_disposal['class_select'] = "select_disabled" card_classes_disposal['class_lift'] = "lift" card_classes_disposal['class_link'] = "link_enabled" result = card_classes_disposal elif card_place == "transfer": card_classes_transfer = {} if place == "all": card_classes_transfer['class_basket'] = "basket_disabled" card_classes_transfer['class_select'] = "select_disabled" card_classes_transfer['class_lift'] = "lift" card_classes_transfer['class_link'] = "link_enabled" if place == "storage": # Add the class values to that dictionary card_classes_transfer['class_basket'] = "basket_disabled" card_classes_transfer['class_select'] = "select_disabled" card_classes_transfer['class_lift'] = "lift" card_classes_transfer['class_link'] = "link_enabled" if place == "home": # Add the class values to that dictionary card_classes_transfer['class_basket'] = "basket_disabled" card_classes_transfer['class_select'] = "select_disabled" card_classes_transfer['class_lift'] = "lift" card_classes_transfer['class_link'] = "link_enabled" if place == "disposal": # Add the class values to that dictionary card_classes_transfer['class_basket'] = "basket_disabled" card_classes_transfer['class_select'] = "select_disabled" card_classes_transfer['class_lift'] = "lift" card_classes_transfer['class_link'] = "link_enabled" if place == "transfer": # Add the class values to that dictionary card_classes_transfer['class_basket'] = "basket_disabled" card_classes_transfer['class_select'] = "select_disabled" card_classes_transfer['class_lift'] = "" card_classes_transfer['class_link'] = "link_disabled" result = card_classes_transfer return(result)
[docs] def count_product_place(self, partner_id, card_place): """ Get the number of article belonging to the specific place. """ # Convert the place to capitalize first letter as the places are stored in uppercase in Odoo place = card_place.capitalize() result = proxy_client.count_product_place(partner_id, place) return(result)
[docs] def get_header_background_color(self, place): """ Get the background color of the header corresponding to the selected place. The colors come form the section "bootstrap_colors" of the parameter file "parameter_design.ini". """ if place == "all": result = custom_background_all elif place == "storage": result = custom_background_storage elif place == "home": result = custom_background_home elif place == "disposal": result = custom_background_disposal elif place == "transfer": result = custom_background_transfer else: # as default value result = custom_background_default return(result)
[docs] class Account_ProfileForm(forms.Form): """ Create a form for the "account_profile" template and create the corresponding functions of the form. """ def __init__(self, *args, **kwargs): # get the language code of the user from the view user_language_code = kwargs.pop('user_language_code', None) super().__init__(*args, **kwargs) # Initialize a list to store title tuple with value 'user_language_code' title_tuple = [] # Transforming the data from the available_titles dictionary for a selected language code into the desired tuple format title_tuple = tuple((title['id'], title['shortcut']) for title in available_titles[user_language_code]) # Construct state tuple with descriptions title_tuple_choices = [('', _("Select a title"))] # Placeholder option title_tuple_choices.extend(title_tuple) 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")) self.fields['email'] = forms.EmailField(required=True, widget=forms.EmailInput(attrs={'id': 'id_email', 'class': 'form-control', 'placeholder': _("Enter e-mail")}), max_length = 50, label=_("E-Mail")) self.fields['first_name'] = forms.CharField(required=False, widget=forms.TextInput(attrs={'id': 'id_first_name', 'class': 'form-control', 'placeholder': _("Enter first name")}), max_length = 50, label=_("First name")) self.fields['last_name'] = forms.CharField(required=True, widget=forms.TextInput(attrs={'id': 'id_last_name', 'class': 'form-control', 'placeholder': _("Enter last name")}), max_length = 50, label=_("Last name")) self.fields['title'] = forms.ChoiceField(required=False, choices=title_tuple_choices, widget=forms.Select(attrs={'id': 'id_title', 'class': 'form-select'}), label=_("Title")) self.fields['language'] = forms.ChoiceField(required=True, choices=language_tuple_choices, widget=forms.Select(attrs={'id': 'id_language', 'class': 'form-select'}), label=_("Language")) self.fields['phone_number'] = PhoneNumberField(required=False, widget=forms.TextInput(attrs={'id': 'id_phone_number', 'class': 'form-control', 'placeholder': _("Enter phone number")}), label=_("Phone number")) self.fields['mobile_number'] = PhoneNumberField(required=False, widget=forms.TextInput(attrs={'id': 'id_mobile_number', 'class': 'form-control', 'placeholder': _("Enter mobile number")}), label=_("Mobile number"))
[docs] def clean(self): """ Validation of the entered values during the update of the user values. """ # Get the user submitted values from the cleaned_data dictionary cleaned_data = super().clean() # Track the changed fields changed_data = self.changed_data # Get the values from the form login = cleaned_data.get("login") email = cleaned_data.get("email") first_name = cleaned_data.get("first_name") last_name = cleaned_data.get("last_name") title = cleaned_data.get("title") language = cleaned_data.get("language") phone_number = cleaned_data.get("phone_number") mobile_number = cleaned_data.get("mobile_number") # convert the phone numbers to string values, as it is the format of the Odoo database fields cleaned_data['phone_number'] = str(phone_number) cleaned_data['mobile_number'] = str(mobile_number) # Only perform validation on changed fields for field_name in changed_data: if field_name == 'login': result = proxy_client.login_exist(login) if result == 'True': raise ValidationError(_("Login is already used by someone else.")) return cleaned_data
[docs] def get_user_profile_values(self, user_id): """ get the profile values for a user from the Odoo system """ result = proxy_client.get_user_profile_values(user_id) if result == 'False': raise ValidationError(_("User values could not be read.")) return (result)
[docs] def update(self, user_id, login, email, name, title, language, phone_number, mobile_number): """ Update the user profile values in the Odoo Database. """ # if variable 'title' is not empty, convert to integer as to be accepted by Odoo system if title != '': title = int(title) # update the values in Odoo system result = proxy_client.update_user_profile_values(user_id, login, email, name, title, language, phone_number, mobile_number) if result == 'False': raise ValidationError(_("User profile values could not be updated.")) return (result)
[docs] class Delivery_AddressForm(forms.Form): """ Create the delivery form for the "account_address" template and create the corresponding functions of the form. """ def __init__(self, *args, **kwargs): # get the partner_id of the user from the view partner_id = kwargs.pop('partner_id', None) self.user_language_code = kwargs.pop('user_language_code', None) super().__init__(*args, **kwargs) # Initialize a list to store state tuple with value 'user_language_code' state_tuple = [] # Transforming the data from the available_states dictionary for a selected language code into the desired tuple format state_tuple = tuple((state['id'], state['name']) for state in available_states[self.user_language_code]) # Construct state tuple with descriptions state_tuple_choices = [('', _("Select a state (only states from Switzerland)"))] # Placeholder option state_tuple_choices.extend(state_tuple) # 'delivery address' fields self.fields['delivery_street'] = forms.CharField(required=True, widget=forms.TextInput(attrs={'id': 'id_delivery_street', 'class': 'form-control', 'placeholder': 'Enter Delivery Street'}), max_length = 100, label="Delivery Street") self.fields['delivery_street_2'] = forms.CharField(required=False, widget=forms.TextInput(attrs={'id': 'id_delivery_street_2','class': 'form-control', 'placeholder': 'Enter Delivery Street 2'}), max_length = 100, label="Delivery Street 2") self.fields['delivery_zip'] = forms.CharField(required=True, widget=forms.TextInput(attrs={'id': 'id_delivery_zip', 'class': 'form-control', 'placeholder': 'Enter Delivery ZIP'}), max_length = 10, label="Delivery ZIP") self.fields['delivery_city'] = forms.CharField(required=True, widget=forms.TextInput(attrs={'id': 'id_delivery_city', 'class': 'form-control', 'placeholder': 'Enter Delivery City'}), max_length = 100, label="Delivery City") self.fields['delivery_state'] = forms.ChoiceField(required=True, choices=state_tuple_choices, widget=forms.Select(attrs={'id': 'id_delivery_state', 'class': 'form-select'}), label="Delivery State") self.fields['delivery_country'] = forms.ChoiceField(required=True, choices=country_tuple_choices, widget=forms.Select(attrs={'id': 'id_delivery_country', 'class': 'form-select'}), label="Delivery Country")
[docs] def clean(self): """ Validation of the entered values during the update of the user values. """ # Get the user submitted values from the cleaned_data dictionary cleaned_data = super().clean() # Track the changed fields changed_data = self.changed_data # get the values from the form delivery_street = cleaned_data.get("delivery_street") delivery_street_2 = cleaned_data.get("delivery_street_2") delivery_zip = cleaned_data.get("delivery_zip") delivery_state = cleaned_data.get("delivery_state") delivery_country = cleaned_data.get("delivery_country") return cleaned_data
[docs] def get_user_address_values(self, partner_id): """ get the address values for a user from the Odoo system """ result = proxy_client.get_user_address_values(partner_id, default_country_code, self.user_language_code) if result == 'False': raise ValidationError(_("User address values could not be read.")) return (result)
[docs] def update(self, partner_id, type, delivery_street, delivery_street_2, delivery_zip, delivery_city, delivery_state, delivery_country): """ Update the user address values in the Odoo Database. """ # some update parameter must be 'integer' if delivery_state != '': delivery_state = int(delivery_state) if delivery_country != '': delivery_country = int(delivery_country) # update the values in Odoo system result = proxy_client.update_user_address_values(partner_id, type, delivery_street, delivery_street_2, delivery_zip, delivery_city, delivery_state, delivery_country) if result == 'False': raise ValidationError(_("User address values could not be updated.")) return (result)
[docs] class Invoice_AddressForm(forms.Form): """ Create the invoice form for the "account_address" template and create the corresponding functions of the form. """ def __init__(self, *args, **kwargs): # get the partner_id of the user from the view partner_id = kwargs.pop('partner_id', None) user_language_code = kwargs.pop('user_language_code', None) super().__init__(*args, **kwargs) # Initialize a list to store state tuple with value 'user_language_code' state_tuple = [] # Transforming the data from the available_states dictionary for a selected language code into the desired tuple format state_tuple = tuple((state['id'], state['name']) for state in available_states[user_language_code]) # Construct state tuple with descriptions state_tuple_choices = [('', _("Select a state (only states from Switzerland)"))] # Placeholder option state_tuple_choices.extend(state_tuple) # 'invoice address' fields self.fields['invoice_street'] = forms.CharField(required=False, widget=forms.TextInput(attrs={'id': 'id_invoice_street', 'class': 'form-control', 'placeholder': 'Enter Invoice Street'}), max_length = 100, label="Invoice Street") self.fields['invoice_street_2'] = forms.CharField(required=False, widget=forms.TextInput(attrs={'id': 'id_invoice_street_2', 'class': 'form-control', 'placeholder': 'Enter Invoice Street 2'}), max_length = 100, label="Invoice Street 2") self.fields['invoice_zip'] = forms.CharField(required=False, widget=forms.TextInput(attrs={'id': 'id_invoice_zip', 'class': 'form-control', 'placeholder': 'Enter Invoice ZIP'}), max_length = 10, label="Invoice ZIP") self.fields['invoice_city'] = forms.CharField(required=False, widget=forms.TextInput(attrs={'id': 'id_invoice_city', 'class': 'form-control', 'placeholder': 'Enter Invoice City'}), max_length = 100, label="Invoice City") self.fields['invoice_state'] = forms.ChoiceField(required=False, choices=state_tuple_choices, widget=forms.Select(attrs={'id': 'id_invoice_state', 'class': 'form-select'}), label="Invoice State") self.fields['invoice_country'] = forms.ChoiceField(required=False, choices=country_tuple_choices, widget=forms.Select(attrs={'id': 'id_invoice_country', 'class': 'form-select'}), label="Invoice Country")
[docs] def clean(self): """ Validation of the entered values during the update of the user values. """ # Get the user submitted values from the cleaned_data dictionary cleaned_data = super().clean() # Track the changed fields changed_data = self.changed_data # get the values from the form invoice_street = cleaned_data.get("invoice_street") invoice_street_2 = cleaned_data.get("invoice_street_2") invoice_zip = cleaned_data.get("invoice_zip") invoice_city = cleaned_data.get("invoice_city") invoice_state = cleaned_data.get("invoice_state",) invoice_country = cleaned_data.get("invoice_country") return cleaned_data
[docs] def get_user_address_values(self, partner_id): """ get the address values for a user from the Odoo system """ result = proxy_client.get_user_address_values(partner_id) if result == 'False': raise ValidationError(_("User address values could not be read.")) return (result)
[docs] def update(self, partner_id, type, invoice_street, invoice_street_2, invoice_zip, invoice_city, invoice_state, invoice_country): """ Update the user address values in the Odoo Database. """ # some update parameter must be 'integer' if invoice_state != '': invoice_state = int(invoice_state) if invoice_country != '': invoice_country = int(invoice_country) # update the values in Odoo system result = proxy_client.update_user_address_values(partner_id, type, invoice_street, invoice_street_2, invoice_zip, invoice_city, invoice_state, invoice_country) if result == 'False': raise ValidationError(_("User address values could not be updated.")) return (result)