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)