Adjusted to new class defs and config format
This commit is contained in:
parent
0b58ac5d27
commit
8c4ae8dd88
@ -1,137 +1,205 @@
|
||||
from datetime import datetime
|
||||
from typing import Optional, List, Tuple
|
||||
import re
|
||||
from typing import List, Optional, Tuple
|
||||
|
||||
# Base Attribute class
|
||||
class Attribute:
|
||||
"""Base class for all attribute types."""
|
||||
def __init__(
|
||||
self,
|
||||
attrib_name: str, # Attribute name and id.
|
||||
display_name: str, # Input label or table column header.
|
||||
html_input_type: str = "text", # HTML form input type. Determines MySQL data type.
|
||||
placeholder: str = "", # HTML form input placeholder.
|
||||
required: bool = False, # HTML form input "required" attribute and MySQL "Not Null" constraint
|
||||
unique: bool = False, # MySQL "unique" constraint
|
||||
primary: bool = False, # MySQL "primary key" constraint
|
||||
index: bool = False, # bool: MySQL index
|
||||
compareto: Optional[List[Tuple[str, str]]] = None, # Compare to another attribute of the item for validation
|
||||
title: str = None, # Description text, HTML "title" attribute
|
||||
attrib_name: str,
|
||||
display_name: str,
|
||||
html_input_type: str,
|
||||
required: bool = False,
|
||||
unique: bool = False,
|
||||
primary: bool = False,
|
||||
default_val: Optional[str] = None,
|
||||
compareto: Optional[List[Tuple[str, str]]] = None,
|
||||
):
|
||||
if not attrib_name:
|
||||
raise ValueError("Attribute name cannot be empty.")
|
||||
if not display_name:
|
||||
raise ValueError(f"Display name cannot be empty for attribute '{attrib_name}'.")
|
||||
self.attrib_name = attrib_name
|
||||
self.display_name = display_name
|
||||
self.html_input_type = html_input_type
|
||||
self.placeholder = placeholder
|
||||
self.required = required
|
||||
self.unique = unique
|
||||
self.primary = primary
|
||||
self.index = index
|
||||
self.default_val = default_val
|
||||
self.compareto = compareto
|
||||
self.title = title
|
||||
|
||||
# Validate compareto
|
||||
if self.compareto is not None:
|
||||
validate_comparison(self)
|
||||
def validate(self) -> Optional[str]:
|
||||
"""Validate common attributes. Returns an error message if invalid, otherwise None."""
|
||||
if not self.attrib_name:
|
||||
return "Missing attribute name."
|
||||
if not self.display_name:
|
||||
return f"Missing display name for attribute '{self.attrib_name}'."
|
||||
if not self.html_input_type:
|
||||
return f"Missing input type for attribute '{self.attrib_name}'."
|
||||
if self.html_input_type not in ["text", "number", "date", "select"]:
|
||||
return f"Invalid input type for attribute '{self.attrib_name}'."
|
||||
return None
|
||||
|
||||
|
||||
# Text Attribute
|
||||
class textAttribute(Attribute):
|
||||
"""Class for text attributes."""
|
||||
def __init__(
|
||||
self,
|
||||
regex: Optional[str] = None, # Regex for value validation
|
||||
default_val: str = None, # Default value
|
||||
compareto: Optional[List[Tuple[str, str]]] = None,
|
||||
**kwargs # Additional arguments for the base class
|
||||
attrib_name: str,
|
||||
display_name: str,
|
||||
regex: Optional[str] = None,
|
||||
**kwargs
|
||||
):
|
||||
super().__init__(html_input_type="text", **kwargs)
|
||||
super().__init__(attrib_name, display_name, html_input_type="text", **kwargs)
|
||||
self.regex = regex
|
||||
self.default_val = default_val
|
||||
self.compareto = compareto
|
||||
|
||||
# Integer Attribute
|
||||
def validate(self) -> Optional[str]:
|
||||
"""Validate text-specific attributes."""
|
||||
error = super().validate()
|
||||
if error:
|
||||
return error
|
||||
if self.default_val is not None and self.regex is not None:
|
||||
if not re.match(self.regex, str(self.default_val)):
|
||||
return f"default_val does not match the regex pattern for attribute '{self.attrib_name}'."
|
||||
return None
|
||||
|
||||
|
||||
class intAttribute(Attribute):
|
||||
"""Class for integer attributes."""
|
||||
def __init__(
|
||||
self,
|
||||
min_val: Optional[int] = None, # Min value
|
||||
max_val: Optional[int] = None, # Max value
|
||||
step_val: Optional[int] = None, # Increment step
|
||||
default_val: Optional[int] = None, # Default value
|
||||
auto_increment: bool = False, # bool: MySQL autoincrement
|
||||
compareto: Optional[List[Tuple[str, str]]] = None,
|
||||
**kwargs # Additional arguments for the base class
|
||||
attrib_name: str,
|
||||
display_name: str,
|
||||
min_val: Optional[int] = None,
|
||||
max_val: Optional[int] = None,
|
||||
**kwargs
|
||||
):
|
||||
super().__init__(html_input_type="number", **kwargs)
|
||||
super().__init__(attrib_name, display_name, html_input_type="number", **kwargs)
|
||||
self.min_val = min_val
|
||||
self.max_val = max_val
|
||||
self.step_val = step_val
|
||||
self.default_val = default_val
|
||||
self.auto_increment = auto_increment
|
||||
self.compareto = compareto
|
||||
|
||||
# Validate min_val and max_val
|
||||
def validate(self) -> Optional[str]:
|
||||
"""Validate integer-specific attributes."""
|
||||
error = super().validate()
|
||||
if error:
|
||||
return error
|
||||
if self.min_val is not None and not isinstance(self.min_val, int):
|
||||
return f"min_val must be an integer for attribute '{self.attrib_name}'."
|
||||
if self.max_val is not None and not isinstance(self.max_val, int):
|
||||
return f"max_val must be an integer for attribute '{self.attrib_name}'."
|
||||
if self.min_val is not None and self.max_val is not None and self.min_val > self.max_val:
|
||||
raise ValueError(f"min_val ({self.min_val}) cannot be greater than max_val ({self.max_val}).")
|
||||
return f"min_val cannot be greater than max_val for attribute '{self.attrib_name}'."
|
||||
if self.default_val is not None and not isinstance(self.default_val, int):
|
||||
return f"default_val must be an integer for attribute '{self.attrib_name}'."
|
||||
if self.default_val is not None:
|
||||
if self.min_val is not None and self.default_val < self.min_val:
|
||||
return f"default_val cannot be less than min_val for attribute '{self.attrib_name}'."
|
||||
if self.max_val is not None and self.default_val > self.max_val:
|
||||
return f"default_val cannot be greater than max_val for attribute '{self.attrib_name}'."
|
||||
return None
|
||||
|
||||
|
||||
# Float Attribute
|
||||
class floatAttribute(Attribute):
|
||||
"""Class for float attributes."""
|
||||
def __init__(
|
||||
self,
|
||||
min_val: Optional[float] = None, # Min value
|
||||
max_val: Optional[float] = None, # Max value
|
||||
step_val: Optional[float] = None, # Increment step
|
||||
default_val: Optional[float] = None, # Default value
|
||||
auto_increment: bool = False, # bool: MySQL autoincrement
|
||||
compareto: Optional[List[Tuple[str, str]]] = None,
|
||||
**kwargs # Additional arguments for the base class
|
||||
attrib_name: str,
|
||||
display_name: str,
|
||||
min_val: Optional[float] = None,
|
||||
max_val: Optional[float] = None,
|
||||
**kwargs
|
||||
):
|
||||
super().__init__(html_input_type="number", **kwargs)
|
||||
super().__init__(attrib_name, display_name, html_input_type="number", **kwargs)
|
||||
self.min_val = min_val
|
||||
self.max_val = max_val
|
||||
self.step_val = step_val
|
||||
self.default_val = default_val
|
||||
self.auto_increment = auto_increment
|
||||
self.compareto = compareto
|
||||
|
||||
# Validate min_val and max_val
|
||||
def validate(self) -> Optional[str]:
|
||||
"""Validate float-specific attributes."""
|
||||
error = super().validate()
|
||||
if error:
|
||||
return error
|
||||
if self.min_val is not None and not isinstance(self.min_val, (int, float)):
|
||||
return f"min_val must be a number for attribute '{self.attrib_name}'."
|
||||
if self.max_val is not None and not isinstance(self.max_val, (int, float)):
|
||||
return f"max_val must be a number for attribute '{self.attrib_name}'."
|
||||
if self.min_val is not None and self.max_val is not None and self.min_val > self.max_val:
|
||||
raise ValueError(f"min_val ({self.min_val}) cannot be greater than max_val ({self.max_val}).")
|
||||
return f"min_val cannot be greater than max_val for attribute '{self.attrib_name}'."
|
||||
if self.default_val is not None and not isinstance(self.default_val, (int, float)):
|
||||
return f"default_val must be a number for attribute '{self.attrib_name}'."
|
||||
if self.default_val is not None:
|
||||
if self.min_val is not None and self.default_val < self.min_val:
|
||||
return f"default_val cannot be less than min_val for attribute '{self.attrib_name}'."
|
||||
if self.max_val is not None and self.default_val > self.max_val:
|
||||
return f"default_val cannot be greater than max_val for attribute '{self.attrib_name}'."
|
||||
return None
|
||||
|
||||
|
||||
# Date Attribute
|
||||
class dateAttribute(Attribute):
|
||||
"""Class for date attributes."""
|
||||
def __init__(
|
||||
self,
|
||||
min_val: Optional[str] = None, # Min value (string in "YYYY-MM-DD" format)
|
||||
max_val: Optional[str] = None, # Max value (string in "YYYY-MM-DD" format)
|
||||
default_val: Optional[str] = None, # Default value (string in "YYYY-MM-DD" format)
|
||||
compareto: Optional[List[Tuple[str, str]]] = None,
|
||||
**kwargs # Additional arguments for the base class
|
||||
attrib_name: str,
|
||||
display_name: str,
|
||||
min_val: Optional[str] = None,
|
||||
max_val: Optional[str] = None,
|
||||
**kwargs
|
||||
):
|
||||
super().__init__(html_input_type="date", **kwargs)
|
||||
self.min_val = self._parse_date(min_val) if min_val else None
|
||||
self.max_val = self._parse_date(max_val) if max_val else None
|
||||
self.default_val = self._parse_date(default_val) if default_val else None
|
||||
self.compareto = compareto
|
||||
super().__init__(attrib_name, display_name, html_input_type="date", **kwargs)
|
||||
self.min_val = min_val
|
||||
self.max_val = max_val
|
||||
|
||||
def _parse_date(self, date_str: str) -> datetime:
|
||||
"""Parse a date string into a datetime object."""
|
||||
def _is_date(self, value: str) -> bool:
|
||||
"""Check if a value is a valid date in YYYY-MM-DD format."""
|
||||
try:
|
||||
return datetime.strptime(date_str, "%Y-%m-%d")
|
||||
except ValueError as e:
|
||||
raise ValueError(f"Invalid date format for '{date_str}'. Expected 'YYYY-MM-DD'.") from e
|
||||
datetime.strptime(value, "%Y-%m-%d")
|
||||
return True
|
||||
except ValueError:
|
||||
return False
|
||||
|
||||
def validate(self) -> Optional[str]:
|
||||
"""Validate date-specific attributes."""
|
||||
error = super().validate()
|
||||
if error:
|
||||
return error
|
||||
if self.min_val is not None and not self._is_date(self.min_val):
|
||||
return f"min_val must be a valid date (YYYY-MM-DD) for attribute '{self.attrib_name}'."
|
||||
if self.max_val is not None and not self._is_date(self.max_val):
|
||||
return f"max_val must be a valid date (YYYY-MM-DD) for attribute '{self.attrib_name}'."
|
||||
if self.min_val is not None and self.max_val is not None:
|
||||
min_date = datetime.strptime(self.min_val, "%Y-%m-%d")
|
||||
max_date = datetime.strptime(self.max_val, "%Y-%m-%d")
|
||||
if max_date < min_date:
|
||||
return f"max_val cannot be earlier than min_val for attribute '{self.attrib_name}'."
|
||||
if self.default_val is not None and not self._is_date(self.default_val):
|
||||
return f"default_val must be a valid date (YYYY-MM-DD) for attribute '{self.attrib_name}'."
|
||||
if self.default_val is not None:
|
||||
default_date = datetime.strptime(self.default_val, "%Y-%m-%d")
|
||||
if self.min_val is not None:
|
||||
min_date = datetime.strptime(self.min_val, "%Y-%m-%d")
|
||||
if default_date < min_date:
|
||||
return f"default_val cannot be earlier than min_val for attribute '{self.attrib_name}'."
|
||||
if self.max_val is not None:
|
||||
max_date = datetime.strptime(self.max_val, "%Y-%m-%d")
|
||||
if default_date > max_date:
|
||||
return f"default_val cannot be later than max_val for attribute '{self.attrib_name}'."
|
||||
return None
|
||||
|
||||
|
||||
# Select Attribute
|
||||
class selectAttribute(Attribute):
|
||||
"""Class for select attributes."""
|
||||
def __init__(
|
||||
self,
|
||||
options: Optional[List[str]] = None, # List of options
|
||||
default_val: Optional[str] = None, # Default value
|
||||
**kwargs # Additional arguments for the base class
|
||||
attrib_name: str,
|
||||
display_name: str,
|
||||
options: List[str],
|
||||
**kwargs
|
||||
):
|
||||
super().__init__(html_input_type="select", **kwargs)
|
||||
self.options = options if options else []
|
||||
self.default_val = default_val if default_val else (self.options[0] if self.options else None)
|
||||
super().__init__(attrib_name, display_name, html_input_type="select", **kwargs)
|
||||
self.options = options
|
||||
|
||||
# Validate default_val
|
||||
if self.default_val and self.default_val not in self.options:
|
||||
raise ValueError(f"Default value '{self.default_val}' is not in the options list.")
|
||||
def validate(self) -> Optional[str]:
|
||||
"""Validate select-specific attributes."""
|
||||
error = super().validate()
|
||||
if error:
|
||||
return error
|
||||
if not self.options:
|
||||
return f"options cannot be empty for attribute '{self.attrib_name}'."
|
||||
if self.default_val is not None and self.default_val not in self.options:
|
||||
return f"default_val must be one of the options for attribute '{self.attrib_name}'."
|
||||
return None
|
@ -1,31 +1,35 @@
|
||||
from flask_sqlalchemy import SQLAlchemy
|
||||
from config import item_attributes # Import the configuration
|
||||
from sqlalchemy import Enum, Integer, String, Date, Column
|
||||
from config import sql_conf
|
||||
from config import item_attributes, sql_conf
|
||||
from definitions.attribute import textAttribute, intAttribute, floatAttribute, dateAttribute, selectAttribute
|
||||
|
||||
# Initialize SQLAlchemy
|
||||
db = SQLAlchemy()
|
||||
|
||||
# Dynamically create the Asset model
|
||||
def create_asset_model():
|
||||
"""
|
||||
Dynamically creates the Asset model based on the configuration in item_attributes.
|
||||
"""
|
||||
# Define the table name and basic methods
|
||||
attrs = {
|
||||
'__tablename__': sql_conf.SQL_TABLE, # Table name
|
||||
'__tablename__': sql_conf.SQL_TABLE, # Table name from config
|
||||
'__init__': lambda self, **kwargs: self.__dict__.update(kwargs), # Constructor
|
||||
'__repr__': lambda self: f"<Asset {getattr(self, next(attrib for attrib in item_attributes if attrib.primary))}>" # Representation
|
||||
'__repr__': lambda self: f"<Asset {getattr(self, next(attrib.attrib_name for attrib in item_attributes if attrib.primary))}>" # Representation
|
||||
}
|
||||
|
||||
# Define columns based on item_attributes
|
||||
for attrib in item_attributes:
|
||||
# Determine the column type based on the configuration
|
||||
if attrib.html_input_type == "text":
|
||||
# Determine the column type
|
||||
if isinstance(attrib, textAttribute):
|
||||
column_type = String(50)
|
||||
elif attrib.html_input_type == "date":
|
||||
elif isinstance(attrib, dateAttribute):
|
||||
column_type = Date
|
||||
elif attrib.html_input_type == "select":
|
||||
elif isinstance(attrib, selectAttribute):
|
||||
column_type = Enum(*attrib.options)
|
||||
elif attrib.html_input_type == "number":
|
||||
column_type = Integer
|
||||
elif isinstance(attrib, (intAttribute, floatAttribute)):
|
||||
column_type = Integer if isinstance(attrib, intAttribute) else Float
|
||||
else:
|
||||
raise ValueError(f"Unsupported attribute type: {type(attrib)}")
|
||||
|
||||
# Define the column with additional properties
|
||||
attrs[attrib.attrib_name] = Column(
|
||||
@ -33,8 +37,7 @@ def create_asset_model():
|
||||
primary_key=attrib.primary,
|
||||
unique=attrib.unique,
|
||||
nullable=not attrib.required,
|
||||
default=attrib.default_val,
|
||||
#title=attrib.title
|
||||
default=attrib.default_val
|
||||
)
|
||||
|
||||
# Create the Asset class dynamically
|
||||
|
@ -7,16 +7,22 @@ def get_csv_data(file):
|
||||
reader = csv.DictReader(csv_file, delimiter='|')
|
||||
|
||||
# Validate CSV headers based on config
|
||||
required_headers = set(item_attributes.keys()) # Get required headers as per configuration
|
||||
csv_headers = set(reader.fieldnames) # Get headers present in the csv file
|
||||
required_headers = {attrib.display_name for attrib in item_attributes} # Use display names
|
||||
csv_headers = set(reader.fieldnames) # Get headers present in the CSV file
|
||||
|
||||
# Check if the required headers exist
|
||||
if not required_headers.issubset(reader.fieldnames):
|
||||
if not required_headers.issubset(csv_headers):
|
||||
raise ValueError(f"CSV file must include headers: {', '.join(required_headers)}.")
|
||||
|
||||
# Check for invalid headers
|
||||
if extra_headers := csv_headers - required_headers:
|
||||
raise ValueError(f"Unexpected headers found: {', '.join(extra_headers)}")
|
||||
|
||||
# Map display names to attribute names
|
||||
display_to_attrib = {attrib.display_name: attrib.attrib_name for attrib in item_attributes}
|
||||
|
||||
# Extract data dynamically based on config
|
||||
return [{attrib: row[attrib] for attrib in item_attributes} for row in reader]
|
||||
return [
|
||||
{display_to_attrib[header]: row[header] for header in required_headers}
|
||||
for row in reader
|
||||
]
|
@ -1,121 +1,32 @@
|
||||
from datetime import datetime
|
||||
import re
|
||||
from typing import List, Optional
|
||||
from definitions.attribute import textAttribute, intAttribute, dateAttribute, selectAttribute
|
||||
from typing import List
|
||||
from definitions.attribute import Attribute
|
||||
|
||||
def _is_int(value) -> bool:
|
||||
"""Check if a value is a valid integer."""
|
||||
return isinstance(value, int)
|
||||
|
||||
def _is_date(value) -> bool:
|
||||
"""Check if a value is a valid date in YYYY-MM-DD format."""
|
||||
if not isinstance(value, str):
|
||||
return False
|
||||
try:
|
||||
datetime.strptime(value, "%Y-%m-%d")
|
||||
return True
|
||||
except ValueError:
|
||||
return False
|
||||
|
||||
def _validate_number(attrib: intAttribute) -> Optional[str]:
|
||||
"""Validate number-specific attributes. Returns an error message if invalid, otherwise None."""
|
||||
if attrib.min_val is not None and not _is_int(attrib.min_val):
|
||||
return f"min_val must be an integer for attribute '{attrib.attrib_name}'."
|
||||
if attrib.max_val is not None and not _is_int(attrib.max_val):
|
||||
return f"max_val must be an integer for attribute '{attrib.attrib_name}'."
|
||||
if attrib.min_val is not None and attrib.max_val is not None and attrib.min_val > attrib.max_val:
|
||||
return f"min_val cannot be greater than max_val for attribute '{attrib.attrib_name}'."
|
||||
if attrib.default_val is not None and not _is_int(attrib.default_val):
|
||||
return f"default_val must be an integer for attribute '{attrib.attrib_name}'."
|
||||
if attrib.default_val is not None:
|
||||
if attrib.min_val is not None and attrib.default_val < attrib.min_val:
|
||||
return f"default_val cannot be less than min_val for attribute '{attrib.attrib_name}'."
|
||||
if attrib.max_val is not None and attrib.default_val > attrib.max_val:
|
||||
return f"default_val cannot be greater than max_val for attribute '{attrib.attrib_name}'."
|
||||
return None
|
||||
|
||||
def _validate_date(attrib: dateAttribute) -> Optional[str]:
|
||||
"""Validate date-specific attributes. Returns an error message if invalid, otherwise None."""
|
||||
if attrib.min_val is not None and not _is_date(attrib.min_val):
|
||||
return f"min_val must be a valid date (YYYY-MM-DD) for attribute '{attrib.attrib_name}'."
|
||||
if attrib.max_val is not None and not _is_date(attrib.max_val):
|
||||
return f"max_val must be a valid date (YYYY-MM-DD) for attribute '{attrib.attrib_name}'."
|
||||
if attrib.min_val is not None and attrib.max_val is not None:
|
||||
min_date = datetime.strptime(attrib.min_val, "%Y-%m-%d")
|
||||
max_date = datetime.strptime(attrib.max_val, "%Y-%m-%d")
|
||||
if max_date < min_date:
|
||||
return f"max_val cannot be earlier than min_val for attribute '{attrib.attrib_name}'."
|
||||
if attrib.default_val is not None and not _is_date(attrib.default_val):
|
||||
return f"default_val must be a valid date (YYYY-MM-DD) for attribute '{attrib.attrib_name}'."
|
||||
if attrib.default_val is not None:
|
||||
default_date = datetime.strptime(attrib.default_val, "%Y-%m-%d")
|
||||
if attrib.min_val is not None:
|
||||
min_date = datetime.strptime(attrib.min_val, "%Y-%m-%d")
|
||||
if default_date < min_date:
|
||||
return f"default_val cannot be earlier than min_val for attribute '{attrib.attrib_name}'."
|
||||
if attrib.max_val is not None:
|
||||
max_date = datetime.strptime(attrib.max_val, "%Y-%m-%d")
|
||||
if default_date > max_date:
|
||||
return f"default_val cannot be later than max_val for attribute '{attrib.attrib_name}'."
|
||||
return None
|
||||
|
||||
def _validate_text(attrib: textAttribute) -> Optional[str]:
|
||||
"""Validate text-specific attributes. Returns an error message if invalid, otherwise None."""
|
||||
if attrib.default_val is not None and attrib.regex is not None:
|
||||
if not re.match(attrib.regex, str(attrib.default_val)):
|
||||
return f"default_val does not match the regex pattern for attribute '{attrib.attrib_name}'."
|
||||
return None
|
||||
|
||||
def _validate_select(attrib: selectAttribute) -> Optional[str]:
|
||||
"""Validate select-specific attributes. Returns an error message if invalid, otherwise None."""
|
||||
if not attrib.options:
|
||||
return f"options cannot be empty for attribute '{attrib.attrib_name}'."
|
||||
if attrib.default_val is not None and attrib.default_val not in attrib.options:
|
||||
return f"default_val must be one of the options for attribute '{attrib.attrib_name}'."
|
||||
return None
|
||||
|
||||
def validate_config(item_attributes: List) -> str:
|
||||
def validate_config(item_attributes: List[Attribute]) -> str:
|
||||
"""
|
||||
Validate the configuration file to ensure all attributes are properly defined.
|
||||
Returns "Ok" if everything is valid, otherwise returns an error message.
|
||||
"""
|
||||
for attrib in item_attributes:
|
||||
# Validate essential properties
|
||||
if not attrib.attrib_name:
|
||||
return f"Missing attribute name for the {item_attributes.index(attrib) + 1}th attribute."
|
||||
if not attrib.display_name:
|
||||
return f"Missing display name for attribute '{attrib.attrib_name}'."
|
||||
if not attrib.html_input_type:
|
||||
return f"Missing input type for attribute '{attrib.attrib_name}'."
|
||||
if attrib.html_input_type not in ["text", "number", "date", "select"]:
|
||||
return f"Invalid input type for attribute '{attrib.attrib_name}'."
|
||||
# Check for duplicate attribute names
|
||||
attrib_names = [attrib.attrib_name for attrib in item_attributes]
|
||||
if len(attrib_names) != len(set(attrib_names)):
|
||||
return "Duplicate attribute names are not allowed."
|
||||
|
||||
# Validate type-specific attributes
|
||||
if isinstance(attrib, intAttribute):
|
||||
error = _validate_number(attrib)
|
||||
if error:
|
||||
return error
|
||||
elif isinstance(attrib, dateAttribute):
|
||||
error = _validate_date(attrib)
|
||||
if error:
|
||||
return error
|
||||
elif isinstance(attrib, textAttribute):
|
||||
error = _validate_text(attrib)
|
||||
if error:
|
||||
return error
|
||||
elif isinstance(attrib, selectAttribute):
|
||||
error = _validate_select(attrib)
|
||||
# Validate each attribute
|
||||
for attrib in item_attributes:
|
||||
error = attrib.validate()
|
||||
if error:
|
||||
return error
|
||||
|
||||
# Validate comparison (if applicable)
|
||||
if attrib.compareto:
|
||||
if not isinstance(attrib.compareto, list) or not all(isinstance(pair, tuple) and len(pair) == 2 for pair in attrib.compareto):
|
||||
if not isinstance(attrib.compareto, list) or not all(
|
||||
isinstance(pair, tuple) and len(pair) == 2 for pair in attrib.compareto
|
||||
):
|
||||
return f"Invalid comparison format for attribute '{attrib.attrib_name}'. Expected a list of tuples."
|
||||
for cmp, ref_attr in attrib.compareto:
|
||||
if cmp not in ["lt", "gt", "le", "ge", "eq"]:
|
||||
return f"Invalid comparison operator '{cmp}' for attribute '{attrib.attrib_name}'."
|
||||
if ref_attr not in [a.attrib_name for a in item_attributes]:
|
||||
if ref_attr not in attrib_names:
|
||||
return f"Invalid reference attribute '{ref_attr}' for comparison in attribute '{attrib.attrib_name}'."
|
||||
|
||||
return "Ok"
|
@ -1,7 +1,7 @@
|
||||
from flask import Blueprint, redirect, session, request, jsonify
|
||||
from definitions.models import Asset, db
|
||||
import json
|
||||
from config import item_attributes # Import the configuration
|
||||
from config import item_attributes
|
||||
|
||||
confirm_save_bp = Blueprint('confirm_save', __name__)
|
||||
|
||||
@ -20,7 +20,7 @@ def confirm_save():
|
||||
for asset_data in edited_assets:
|
||||
# Dynamically create the Asset object using item_attributes
|
||||
asset = Asset(**{
|
||||
attrib: asset_data[attrib]
|
||||
attrib.attrib_name: asset_data[attrib.attrib_name]
|
||||
for attrib in item_attributes
|
||||
})
|
||||
db.session.add(asset)
|
||||
|
@ -2,6 +2,7 @@ from flask import Blueprint, request, render_template, redirect
|
||||
from definitions.models import Asset, db
|
||||
from config import item_attributes
|
||||
from sqlalchemy import exc # Import exc for database exceptions
|
||||
from definitions.attribute import selectAttribute, intAttribute # Import attribute classes
|
||||
|
||||
addasset_bp = Blueprint('addasset', __name__)
|
||||
|
||||
@ -9,17 +10,35 @@ addasset_bp = Blueprint('addasset', __name__)
|
||||
def create():
|
||||
if request.method == 'GET':
|
||||
# Render the "add item" form
|
||||
return render_template('create.html', item_attributes=item_attributes)
|
||||
return render_template(
|
||||
'create.html',
|
||||
item_attributes=item_attributes,
|
||||
isinstance=isinstance, # Pass isinstance to the template
|
||||
hasattr=hasattr, # Pass hasattr to the template
|
||||
selectAttribute=selectAttribute, # Pass selectAttribute to the template
|
||||
intAttribute=intAttribute # Pass intAttribute to the template
|
||||
)
|
||||
|
||||
# Process submitted form
|
||||
if request.method == 'POST':
|
||||
# Get data from form
|
||||
form_data = {attrib: request.form[attrib] for attrib in item_attributes}
|
||||
try:
|
||||
primary_attr = next((attrib_name for attrib_name, attrib in item_attributes.items() if attrib.primary), None)
|
||||
except ValueError:
|
||||
return render_template('create.html', item_attributes=item_attributes, exc=primary_attr)
|
||||
form_data = {attrib.attrib_name: request.form[attrib.attrib_name] for attrib in item_attributes}
|
||||
|
||||
# Validate status (if it's an option field)
|
||||
status_attrib = next((attrib for attrib in item_attributes if attrib.attrib_name == 'status'), None)
|
||||
if status_attrib and isinstance(status_attrib, selectAttribute):
|
||||
if form_data['status'] not in status_attrib.options:
|
||||
return render_template('create.html', item_attributes=item_attributes, exc='status')
|
||||
|
||||
# Convert staffnum to int (if it's a number field)
|
||||
staffnum_attrib = next((attrib for attrib in item_attributes if attrib.attrib_name == 'staffnum'), None)
|
||||
if staffnum_attrib and isinstance(staffnum_attrib, intAttribute):
|
||||
try:
|
||||
form_data['staffnum'] = int(form_data['staffnum'])
|
||||
except ValueError:
|
||||
return render_template('create.html', item_attributes=item_attributes, exc='staffnum')
|
||||
|
||||
# Create the Asset object
|
||||
item = Asset(**form_data)
|
||||
|
||||
try:
|
||||
|
@ -8,7 +8,7 @@ delete_bp = Blueprint('deleteasset', __name__)
|
||||
def delete(primary_value):
|
||||
# Identify the primary attribute
|
||||
primary_attrib = next(
|
||||
(attrib for attrib, config in item_attributes.items() if config.primary),
|
||||
(attrib for attrib in item_attributes if attrib.primary),
|
||||
None
|
||||
)
|
||||
|
||||
@ -16,7 +16,7 @@ def delete(primary_value):
|
||||
return "Primary attribute not defined in configuration."
|
||||
|
||||
# Fetch the item using the primary attribute
|
||||
item = Asset.query.filter_by(**{primary_attrib: primary_value}).first()
|
||||
item = Asset.query.filter_by(**{primary_attrib.attrib_name: primary_value}).first()
|
||||
if not item:
|
||||
abort(404) # Item not found
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
from flask import Blueprint, Response
|
||||
from definitions.models import Asset
|
||||
from config import item_attributes
|
||||
import csv
|
||||
import io
|
||||
|
||||
@ -14,12 +15,13 @@ def export_csv():
|
||||
# Fetch all records from the database
|
||||
records = Asset.query.all()
|
||||
|
||||
# Write headers
|
||||
writer.writerow([column.name for column in Asset.__mapper__.columns])
|
||||
# Write headers (use display names from item_attributes)
|
||||
headers = [attrib.display_name for attrib in item_attributes]
|
||||
writer.writerow(headers)
|
||||
|
||||
# Write data rows
|
||||
for record in records:
|
||||
writer.writerow([getattr(record, column.name) for column in Asset.__mapper__.columns])
|
||||
writer.writerow([getattr(record, attrib.attrib_name) for attrib in item_attributes])
|
||||
|
||||
# Prepare the response
|
||||
response = Response(
|
||||
|
@ -2,6 +2,7 @@ from flask import Blueprint, request, render_template, redirect
|
||||
from definitions.models import Asset, db
|
||||
from config import item_attributes
|
||||
from sqlalchemy import exc # Import exc for database exceptions
|
||||
from definitions.attribute import selectAttribute, intAttribute # Import attribute classes
|
||||
|
||||
update_bp = Blueprint('editasset', __name__)
|
||||
|
||||
@ -9,7 +10,7 @@ update_bp = Blueprint('editasset', __name__)
|
||||
def update(primary_value):
|
||||
# Identify the primary attribute
|
||||
primary_attrib = next(
|
||||
(attrib for attrib, config in item_attributes.items() if config.primary),
|
||||
(attrib for attrib in item_attributes if attrib.primary),
|
||||
None
|
||||
)
|
||||
|
||||
@ -17,32 +18,42 @@ def update(primary_value):
|
||||
return "Primary attribute not defined in configuration."
|
||||
|
||||
# Fetch the item using the primary attribute
|
||||
item = Asset.query.filter_by(**{primary_attrib: primary_value}).first()
|
||||
item = Asset.query.filter_by(**{primary_attrib.attrib_name: primary_value}).first()
|
||||
if not item:
|
||||
return f"{item_attributes[primary_attrib].display_name} '{primary_value}' not found." # Move this to a template
|
||||
return f"{primary_attrib.display_name} '{primary_value}' not found."
|
||||
|
||||
if request.method == 'GET':
|
||||
return render_template('update.html', item=item, item_attributes=item_attributes)
|
||||
return render_template(
|
||||
'update.html',
|
||||
item=item,
|
||||
item_attributes=item_attributes,
|
||||
isinstance=isinstance, # Pass isinstance to the template
|
||||
hasattr=hasattr, # Pass hasattr to the template
|
||||
selectAttribute=selectAttribute, # Pass selectAttribute to the template
|
||||
intAttribute=intAttribute # Pass intAttribute to the template
|
||||
)
|
||||
|
||||
if request.method == 'POST':
|
||||
# Dynamically get form data using item_attributes
|
||||
form_data = {attrib: request.form[attrib] for attrib in item_attributes}
|
||||
form_data = {attrib.attrib_name: request.form[attrib.attrib_name] for attrib in item_attributes}
|
||||
|
||||
# Validate status (if it's an option field)
|
||||
if 'status' in item_attributes and item_attributes['status'].html_input_type == "select":
|
||||
if form_data['status'] not in item_attributes['status'].options:
|
||||
status_attrib = next((attrib for attrib in item_attributes if attrib.attrib_name == 'status'), None)
|
||||
if status_attrib and isinstance(status_attrib, selectAttribute):
|
||||
if form_data['status'] not in status_attrib.options:
|
||||
return render_template('update.html', item=item, exc='status', item_attributes=item_attributes)
|
||||
|
||||
# Convert staffnum to int (if it's a number field)
|
||||
if 'staffnum' in item_attributes and item_attributes['staffnum'].html_input_type == "number":
|
||||
staffnum_attrib = next((attrib for attrib in item_attributes if attrib.attrib_name == 'staffnum'), None)
|
||||
if staffnum_attrib and isinstance(staffnum_attrib, intAttribute):
|
||||
try:
|
||||
form_data['staffnum'] = int(form_data['staffnum'])
|
||||
except ValueError:
|
||||
return render_template('update.html', item=item, exc='staffnum', item_attributes=item_attributes)
|
||||
|
||||
# Update the item with the new data
|
||||
for attrib, value in form_data.items():
|
||||
setattr(item, attrib, value)
|
||||
for attrib in item_attributes:
|
||||
setattr(item, attrib.attrib_name, form_data[attrib.attrib_name])
|
||||
|
||||
try:
|
||||
db.session.commit()
|
||||
|
@ -1,11 +1,8 @@
|
||||
from flask import Blueprint, request, render_template, redirect, session
|
||||
import csv
|
||||
from io import TextIOWrapper
|
||||
from definitions.models import Asset, db
|
||||
from functions.process_csv import get_csv_data # Import the CSV processing function
|
||||
from config import item_attributes # Import the configuration
|
||||
from functions.process_csv import get_csv_data
|
||||
from config import item_attributes
|
||||
|
||||
# Create a Blueprint for upload
|
||||
upload_bp = Blueprint('uploadcsv', __name__)
|
||||
|
||||
@upload_bp.route('/uploadcsv', methods=['GET', 'POST'])
|
||||
@ -27,7 +24,7 @@ def upload_file():
|
||||
|
||||
# Identify the primary attribute
|
||||
primary_attrib = next(
|
||||
(attrib for attrib, config in item_attributes.items() if config.primary),
|
||||
(attrib for attrib in item_attributes if attrib.primary),
|
||||
None
|
||||
)
|
||||
|
||||
@ -38,8 +35,8 @@ def upload_file():
|
||||
new_assets = []
|
||||
existing_assets = []
|
||||
for row in csvdata:
|
||||
primary_value = row[primary_attrib]
|
||||
asset_exists = Asset.query.filter_by(**{primary_attrib: primary_value}).first()
|
||||
primary_value = row[primary_attrib.attrib_name]
|
||||
asset_exists = Asset.query.filter_by(**{primary_attrib.attrib_name: primary_value}).first()
|
||||
if asset_exists:
|
||||
existing_assets.append(row)
|
||||
else:
|
||||
@ -48,7 +45,12 @@ def upload_file():
|
||||
session['assets'] = new_assets # Store new assets in session
|
||||
|
||||
# Redirect to preview page with the CSV data
|
||||
return render_template('csv_preview.html', new_assets=new_assets, existing=existing_assets, item_attributes=item_attributes)
|
||||
return render_template(
|
||||
'csv_preview.html',
|
||||
new_assets=new_assets,
|
||||
existing=existing_assets,
|
||||
item_attributes=item_attributes
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
# Handle errors during file processing
|
||||
|
@ -6,9 +6,18 @@ viewall_bp = Blueprint('viewall', __name__)
|
||||
|
||||
@viewall_bp.route('/viewall/', methods=['GET'])
|
||||
def view_list():
|
||||
# Fetch all items from the database
|
||||
items = Asset.query.all()
|
||||
primary_attrib = next(
|
||||
(attrib for attrib, config in item_attributes.items() if config.primary),
|
||||
None
|
||||
|
||||
# Identify the primary attribute
|
||||
primary_attrib = next((attrib for attrib in item_attributes if attrib.primary), None)
|
||||
if not primary_attrib:
|
||||
return "Primary attribute not defined in configuration."
|
||||
|
||||
# Render the template with items, attributes, and primary attribute
|
||||
return render_template(
|
||||
'viewList.html',
|
||||
items=items,
|
||||
item_attributes=item_attributes,
|
||||
primary_attrib=primary_attrib.attrib_name
|
||||
)
|
||||
return render_template('viewList.html', items=items, item_attributes=item_attributes, primary_attrib=primary_attrib)
|
@ -8,28 +8,28 @@
|
||||
<h2 align="center">Add new Item</h2>
|
||||
|
||||
<form method="POST">
|
||||
{% for attrib, properties in item_attributes.items() -%}
|
||||
{% for attrib in item_attributes -%}
|
||||
<p>
|
||||
<label for="{{ attrib }}">{{ properties.display_name }}:</label>
|
||||
{%- if properties.html_input_type == "select" %}
|
||||
<label for="{{ attrib.attrib_name }}">{{ attrib.display_name }}:</label>
|
||||
{%- if isinstance(attrib, selectAttribute) %}
|
||||
<select
|
||||
id="{{ attrib }}"
|
||||
name="{{ attrib }}"
|
||||
{%- if properties.required %} required {% endif -%}
|
||||
id="{{ attrib.attrib_name }}"
|
||||
name="{{ attrib.attrib_name }}"
|
||||
{%- if attrib.required %} required {% endif -%}
|
||||
>
|
||||
{% for option in properties.options -%}
|
||||
{% for option in attrib.options -%}
|
||||
<option value="{{ option }}">{{ option }}</option>
|
||||
{% endfor -%}
|
||||
</select>
|
||||
{% else %}
|
||||
<input
|
||||
id="{{ attrib }}"
|
||||
type="{{ properties.html_input_type }}"
|
||||
name="{{ attrib }}"
|
||||
{%- if properties.required %} required {% endif -%}
|
||||
{%- if properties.min is not none %} min="{{ properties.min }}" {% endif -%}
|
||||
{%- if properties.max is not none %} max="{{ properties.max }}" {% endif -%}
|
||||
{%- if properties.default_val %} value="{{ properties.default_val }}" {% endif -%}
|
||||
id="{{ attrib.attrib_name }}"
|
||||
type="{{ attrib.html_input_type }}"
|
||||
name="{{ attrib.attrib_name }}"
|
||||
{%- if attrib.required %} required {% endif -%}
|
||||
{%- if hasattr(attrib, 'min_val') and attrib.min_val is not none %} min="{{ attrib.min_val }}" {% endif -%}
|
||||
{%- if hasattr(attrib, 'max_val') and attrib.max_val is not none %} max="{{ attrib.max_val }}" {% endif -%}
|
||||
{%- if attrib.default_val is not none %} value="{{ attrib.default_val }}" {% endif -%}
|
||||
/>
|
||||
{% endif -%}
|
||||
</p>
|
||||
@ -50,4 +50,3 @@
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
@ -15,16 +15,16 @@
|
||||
<table border="1" class="table-new-assets">
|
||||
<thead>
|
||||
<tr>
|
||||
{% for attrib, config in item_attributes.items() %}
|
||||
<th data-attrib="{{ attrib }}">{{ config.display_name }}</th>
|
||||
{% for attrib in item_attributes %}
|
||||
<th data-attrib="{{ attrib.attrib_name }}">{{ attrib.display_name }}</th>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for asset in new_assets %}
|
||||
<tr>
|
||||
{% for attrib, config in item_attributes.items() %}
|
||||
<td contenteditable="true">{{ asset[attrib] }}</td>
|
||||
{% for attrib in item_attributes %}
|
||||
<td contenteditable="true">{{ asset[attrib.attrib_name] }}</td>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
{% endfor %}
|
||||
@ -37,16 +37,16 @@
|
||||
<table border="1" class="table-existing-assets">
|
||||
<thead>
|
||||
<tr>
|
||||
{% for attrib, config in item_attributes.items() %}
|
||||
<th >{{ config.display_name }}</th>
|
||||
{% for attrib in item_attributes %}
|
||||
<th>{{ attrib.display_name }}</th>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for asset in existing %}
|
||||
<tr>
|
||||
{% for attrib, config in item_attributes.items() %}
|
||||
<td>{{ asset[attrib] }}</td>
|
||||
{% for attrib in item_attributes %}
|
||||
<td>{{ asset[attrib.attrib_name] }}</td>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
{% endfor %}
|
||||
|
@ -7,29 +7,29 @@
|
||||
<body>
|
||||
<h2 align="center">Update Item</h2>
|
||||
|
||||
<form action='' method = "POST">
|
||||
{% for attrib, properties in item_attributes.items() -%}
|
||||
<form method="POST">
|
||||
{% for attrib in item_attributes -%}
|
||||
<p>
|
||||
<label for="{{ attrib }}">{{ properties.display_name }}:</label>
|
||||
{%- if properties.html_input_type == "select" %}
|
||||
<label for="{{ attrib.attrib_name }}">{{ attrib.display_name }}:</label>
|
||||
{%- if isinstance(attrib, selectAttribute) %}
|
||||
<select
|
||||
id="{{ attrib }}"
|
||||
name="{{ attrib }}"
|
||||
{%- if properties.required %} required {% endif -%}
|
||||
id="{{ attrib.attrib_name }}"
|
||||
name="{{ attrib.attrib_name }}"
|
||||
{%- if attrib.required %} required {% endif -%}
|
||||
>
|
||||
{% for option in properties.options -%}
|
||||
<option value="{{ option }}" {% if item[attrib] == option %}selected{% endif %}>{{ option }}</option>
|
||||
{% for option in attrib.options -%}
|
||||
<option value="{{ option }}" {% if item[attrib.attrib_name] == option %}selected{% endif %}>{{ option }}</option>
|
||||
{% endfor -%}
|
||||
</select>
|
||||
{% else %}
|
||||
<input
|
||||
id="{{ attrib }}"
|
||||
type="{{ properties.html_input_type }}"
|
||||
name="{{ attrib }}"
|
||||
{%- if properties.required %} required {% endif -%}
|
||||
{%- if properties.min is not none %} min="{{ properties.min }}" {% endif -%}
|
||||
{%- if properties.max is not none %} max="{{ properties.max }}" {% endif -%}
|
||||
{%- if item[attrib] %} value="{{ item[attrib] }}" {% endif -%}
|
||||
id="{{ attrib.attrib_name }}"
|
||||
type="{{ attrib.html_input_type }}"
|
||||
name="{{ attrib.attrib_name }}"
|
||||
{%- if attrib.required %} required {% endif -%}
|
||||
{%- if hasattr(attrib, 'min_val') and attrib.min_val is not none %} min="{{ attrib.min_val }}" {% endif -%}
|
||||
{%- if hasattr(attrib, 'max_val') and attrib.max_val is not none %} max="{{ attrib.max_val }}" {% endif -%}
|
||||
{%- if item[attrib.attrib_name] %} value="{{ item[attrib.attrib_name] }}" {% endif -%}
|
||||
/>
|
||||
{% endif -%}
|
||||
</p>
|
||||
|
@ -10,8 +10,8 @@
|
||||
<table border="1" align="center">
|
||||
<tr>
|
||||
<!-- Table headers -->
|
||||
{% for attrib, config in item_attributes.items() %}
|
||||
<th>{{ config.display_name }}</th>
|
||||
{% for attrib in item_attributes %}
|
||||
<th>{{ attrib.display_name }}</th>
|
||||
{% endfor %}
|
||||
<th colspan="2">Actions</th>
|
||||
</tr>
|
||||
@ -19,8 +19,8 @@
|
||||
<!-- Table rows -->
|
||||
{% for item in items %}
|
||||
<tr>
|
||||
{% for attrib, config in item_attributes.items() %}
|
||||
<td>{{ item[attrib] }}</td>
|
||||
{% for attrib in item_attributes %}
|
||||
<td>{{ item[attrib.attrib_name] }}</td>
|
||||
{% endfor %}
|
||||
<td>
|
||||
<form action="/update/{{ item[primary_attrib] }}" method="get">
|
||||
|
Loading…
Reference in New Issue
Block a user