Adjusted to new class defs and config format

This commit is contained in:
Candifloss 2025-02-23 00:28:10 +05:30
parent 0b58ac5d27
commit 8c4ae8dd88
15 changed files with 323 additions and 293 deletions

View File

@ -1,137 +1,205 @@
from datetime import datetime from datetime import datetime
from typing import Optional, List, Tuple import re
from typing import List, Optional, Tuple
# Base Attribute class
class Attribute: class Attribute:
"""Base class for all attribute types."""
def __init__( def __init__(
self, self,
attrib_name: str, # Attribute name and id. attrib_name: str,
display_name: str, # Input label or table column header. display_name: str,
html_input_type: str = "text", # HTML form input type. Determines MySQL data type. html_input_type: str,
placeholder: str = "", # HTML form input placeholder. required: bool = False,
required: bool = False, # HTML form input "required" attribute and MySQL "Not Null" constraint unique: bool = False,
unique: bool = False, # MySQL "unique" constraint primary: bool = False,
primary: bool = False, # MySQL "primary key" constraint default_val: Optional[str] = None,
index: bool = False, # bool: MySQL index compareto: Optional[List[Tuple[str, str]]] = None,
compareto: Optional[List[Tuple[str, str]]] = None, # Compare to another attribute of the item for validation
title: str = None, # Description text, HTML "title" attribute
): ):
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.attrib_name = attrib_name
self.display_name = display_name self.display_name = display_name
self.html_input_type = html_input_type self.html_input_type = html_input_type
self.placeholder = placeholder
self.required = required self.required = required
self.unique = unique self.unique = unique
self.primary = primary self.primary = primary
self.index = index self.default_val = default_val
self.compareto = compareto self.compareto = compareto
self.title = title
# Validate compareto def validate(self) -> Optional[str]:
if self.compareto is not None: """Validate common attributes. Returns an error message if invalid, otherwise None."""
validate_comparison(self) 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 textAttribute(Attribute):
"""Class for text attributes."""
def __init__( def __init__(
self, self,
regex: Optional[str] = None, # Regex for value validation attrib_name: str,
default_val: str = None, # Default value display_name: str,
compareto: Optional[List[Tuple[str, str]]] = None, regex: Optional[str] = None,
**kwargs # Additional arguments for the base class **kwargs
): ):
super().__init__(html_input_type="text", **kwargs) super().__init__(attrib_name, display_name, html_input_type="text", **kwargs)
self.regex = regex 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 intAttribute(Attribute):
"""Class for integer attributes."""
def __init__( def __init__(
self, self,
min_val: Optional[int] = None, # Min value attrib_name: str,
max_val: Optional[int] = None, # Max value display_name: str,
step_val: Optional[int] = None, # Increment step min_val: Optional[int] = None,
default_val: Optional[int] = None, # Default value max_val: Optional[int] = None,
auto_increment: bool = False, # bool: MySQL autoincrement **kwargs
compareto: Optional[List[Tuple[str, str]]] = None,
**kwargs # Additional arguments for the base class
): ):
super().__init__(html_input_type="number", **kwargs) super().__init__(attrib_name, display_name, html_input_type="number", **kwargs)
self.min_val = min_val self.min_val = min_val
self.max_val = max_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: 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 floatAttribute(Attribute):
"""Class for float attributes."""
def __init__( def __init__(
self, self,
min_val: Optional[float] = None, # Min value attrib_name: str,
max_val: Optional[float] = None, # Max value display_name: str,
step_val: Optional[float] = None, # Increment step min_val: Optional[float] = None,
default_val: Optional[float] = None, # Default value max_val: Optional[float] = None,
auto_increment: bool = False, # bool: MySQL autoincrement **kwargs
compareto: Optional[List[Tuple[str, str]]] = None,
**kwargs # Additional arguments for the base class
): ):
super().__init__(html_input_type="number", **kwargs) super().__init__(attrib_name, display_name, html_input_type="number", **kwargs)
self.min_val = min_val self.min_val = min_val
self.max_val = max_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: 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 dateAttribute(Attribute):
"""Class for date attributes."""
def __init__( def __init__(
self, self,
min_val: Optional[str] = None, # Min value (string in "YYYY-MM-DD" format) attrib_name: str,
max_val: Optional[str] = None, # Max value (string in "YYYY-MM-DD" format) display_name: str,
default_val: Optional[str] = None, # Default value (string in "YYYY-MM-DD" format) min_val: Optional[str] = None,
compareto: Optional[List[Tuple[str, str]]] = None, max_val: Optional[str] = None,
**kwargs # Additional arguments for the base class **kwargs
): ):
super().__init__(html_input_type="date", **kwargs) super().__init__(attrib_name, display_name, html_input_type="date", **kwargs)
self.min_val = self._parse_date(min_val) if min_val else None self.min_val = min_val
self.max_val = self._parse_date(max_val) if max_val else None self.max_val = max_val
self.default_val = self._parse_date(default_val) if default_val else None
self.compareto = compareto
def _parse_date(self, date_str: str) -> datetime: def _is_date(self, value: str) -> bool:
"""Parse a date string into a datetime object.""" """Check if a value is a valid date in YYYY-MM-DD format."""
try: try:
return datetime.strptime(date_str, "%Y-%m-%d") datetime.strptime(value, "%Y-%m-%d")
except ValueError as e: return True
raise ValueError(f"Invalid date format for '{date_str}'. Expected 'YYYY-MM-DD'.") from e 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 selectAttribute(Attribute):
"""Class for select attributes."""
def __init__( def __init__(
self, self,
options: Optional[List[str]] = None, # List of options attrib_name: str,
default_val: Optional[str] = None, # Default value display_name: str,
**kwargs # Additional arguments for the base class options: List[str],
**kwargs
): ):
super().__init__(html_input_type="select", **kwargs) super().__init__(attrib_name, display_name, html_input_type="select", **kwargs)
self.options = options if options else [] self.options = options
self.default_val = default_val if default_val else (self.options[0] if self.options else None)
# Validate default_val def validate(self) -> Optional[str]:
if self.default_val and self.default_val not in self.options: """Validate select-specific attributes."""
raise ValueError(f"Default value '{self.default_val}' is not in the options list.") 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

View File

@ -1,31 +1,35 @@
from flask_sqlalchemy import SQLAlchemy from flask_sqlalchemy import SQLAlchemy
from config import item_attributes # Import the configuration
from sqlalchemy import Enum, Integer, String, Date, Column 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() db = SQLAlchemy()
# Dynamically create the Asset model
def create_asset_model(): def create_asset_model():
""" """
Dynamically creates the Asset model based on the configuration in item_attributes. Dynamically creates the Asset model based on the configuration in item_attributes.
""" """
# Define the table name and basic methods
attrs = { 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 '__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: for attrib in item_attributes:
# Determine the column type based on the configuration # Determine the column type
if attrib.html_input_type == "text": if isinstance(attrib, textAttribute):
column_type = String(50) column_type = String(50)
elif attrib.html_input_type == "date": elif isinstance(attrib, dateAttribute):
column_type = Date column_type = Date
elif attrib.html_input_type == "select": elif isinstance(attrib, selectAttribute):
column_type = Enum(*attrib.options) column_type = Enum(*attrib.options)
elif attrib.html_input_type == "number": elif isinstance(attrib, (intAttribute, floatAttribute)):
column_type = Integer column_type = Integer if isinstance(attrib, intAttribute) else Float
else:
raise ValueError(f"Unsupported attribute type: {type(attrib)}")
# Define the column with additional properties # Define the column with additional properties
attrs[attrib.attrib_name] = Column( attrs[attrib.attrib_name] = Column(
@ -33,8 +37,7 @@ def create_asset_model():
primary_key=attrib.primary, primary_key=attrib.primary,
unique=attrib.unique, unique=attrib.unique,
nullable=not attrib.required, nullable=not attrib.required,
default=attrib.default_val, default=attrib.default_val
#title=attrib.title
) )
# Create the Asset class dynamically # Create the Asset class dynamically

View File

@ -7,16 +7,22 @@ def get_csv_data(file):
reader = csv.DictReader(csv_file, delimiter='|') reader = csv.DictReader(csv_file, delimiter='|')
# Validate CSV headers based on config # Validate CSV headers based on config
required_headers = set(item_attributes.keys()) # Get required headers as per configuration 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 csv_headers = set(reader.fieldnames) # Get headers present in the CSV file
# Check if the required headers exist # 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)}.") raise ValueError(f"CSV file must include headers: {', '.join(required_headers)}.")
# Check for invalid headers # Check for invalid headers
if extra_headers := csv_headers - required_headers: if extra_headers := csv_headers - required_headers:
raise ValueError(f"Unexpected headers found: {', '.join(extra_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 # 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
]

View File

@ -1,121 +1,32 @@
from datetime import datetime from typing import List
import re from definitions.attribute import Attribute
from typing import List, Optional
from definitions.attribute import textAttribute, intAttribute, dateAttribute, selectAttribute
def _is_int(value) -> bool: def validate_config(item_attributes: List[Attribute]) -> str:
"""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:
""" """
Validate the configuration file to ensure all attributes are properly defined. Validate the configuration file to ensure all attributes are properly defined.
Returns "Ok" if everything is valid, otherwise returns an error message. Returns "Ok" if everything is valid, otherwise returns an error message.
""" """
for attrib in item_attributes: # Check for duplicate attribute names
# Validate essential properties attrib_names = [attrib.attrib_name for attrib in item_attributes]
if not attrib.attrib_name: if len(attrib_names) != len(set(attrib_names)):
return f"Missing attribute name for the {item_attributes.index(attrib) + 1}th attribute." return "Duplicate attribute names are not allowed."
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}'."
# Validate type-specific attributes # Validate each attribute
if isinstance(attrib, intAttribute): for attrib in item_attributes:
error = _validate_number(attrib) error = attrib.validate()
if error: if error:
return 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)
if error:
return error
# Validate comparison (if applicable) # Validate comparison (if applicable)
if attrib.compareto: 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." return f"Invalid comparison format for attribute '{attrib.attrib_name}'. Expected a list of tuples."
for cmp, ref_attr in attrib.compareto: for cmp, ref_attr in attrib.compareto:
if cmp not in ["lt", "gt", "le", "ge", "eq"]: if cmp not in ["lt", "gt", "le", "ge", "eq"]:
return f"Invalid comparison operator '{cmp}' for attribute '{attrib.attrib_name}'." 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 f"Invalid reference attribute '{ref_attr}' for comparison in attribute '{attrib.attrib_name}'."
return "Ok" return "Ok"

View File

@ -1,7 +1,7 @@
from flask import Blueprint, redirect, session, request, jsonify from flask import Blueprint, redirect, session, request, jsonify
from definitions.models import Asset, db from definitions.models import Asset, db
import json import json
from config import item_attributes # Import the configuration from config import item_attributes
confirm_save_bp = Blueprint('confirm_save', __name__) confirm_save_bp = Blueprint('confirm_save', __name__)
@ -20,7 +20,7 @@ def confirm_save():
for asset_data in edited_assets: for asset_data in edited_assets:
# Dynamically create the Asset object using item_attributes # Dynamically create the Asset object using item_attributes
asset = Asset(**{ asset = Asset(**{
attrib: asset_data[attrib] attrib.attrib_name: asset_data[attrib.attrib_name]
for attrib in item_attributes for attrib in item_attributes
}) })
db.session.add(asset) db.session.add(asset)

View File

@ -2,6 +2,7 @@ from flask import Blueprint, request, render_template, redirect
from definitions.models import Asset, db from definitions.models import Asset, db
from config import item_attributes from config import item_attributes
from sqlalchemy import exc # Import exc for database exceptions from sqlalchemy import exc # Import exc for database exceptions
from definitions.attribute import selectAttribute, intAttribute # Import attribute classes
addasset_bp = Blueprint('addasset', __name__) addasset_bp = Blueprint('addasset', __name__)
@ -9,17 +10,35 @@ addasset_bp = Blueprint('addasset', __name__)
def create(): def create():
if request.method == 'GET': if request.method == 'GET':
# Render the "add item" form # 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 # Process submitted form
if request.method == 'POST': if request.method == 'POST':
# Get data from form # Get data from form
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}
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)
# 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) item = Asset(**form_data)
try: try:

View File

@ -8,7 +8,7 @@ delete_bp = Blueprint('deleteasset', __name__)
def delete(primary_value): def delete(primary_value):
# Identify the primary attribute # Identify the primary attribute
primary_attrib = next( primary_attrib = next(
(attrib for attrib, config in item_attributes.items() if config.primary), (attrib for attrib in item_attributes if attrib.primary),
None None
) )
@ -16,7 +16,7 @@ def delete(primary_value):
return "Primary attribute not defined in configuration." return "Primary attribute not defined in configuration."
# Fetch the item using the primary attribute # 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: if not item:
abort(404) # Item not found abort(404) # Item not found

View File

@ -1,5 +1,6 @@
from flask import Blueprint, Response from flask import Blueprint, Response
from definitions.models import Asset from definitions.models import Asset
from config import item_attributes
import csv import csv
import io import io
@ -14,12 +15,13 @@ def export_csv():
# Fetch all records from the database # Fetch all records from the database
records = Asset.query.all() records = Asset.query.all()
# Write headers # Write headers (use display names from item_attributes)
writer.writerow([column.name for column in Asset.__mapper__.columns]) headers = [attrib.display_name for attrib in item_attributes]
writer.writerow(headers)
# Write data rows # Write data rows
for record in records: 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 # Prepare the response
response = Response( response = Response(

View File

@ -2,6 +2,7 @@ from flask import Blueprint, request, render_template, redirect
from definitions.models import Asset, db from definitions.models import Asset, db
from config import item_attributes from config import item_attributes
from sqlalchemy import exc # Import exc for database exceptions from sqlalchemy import exc # Import exc for database exceptions
from definitions.attribute import selectAttribute, intAttribute # Import attribute classes
update_bp = Blueprint('editasset', __name__) update_bp = Blueprint('editasset', __name__)
@ -9,7 +10,7 @@ update_bp = Blueprint('editasset', __name__)
def update(primary_value): def update(primary_value):
# Identify the primary attribute # Identify the primary attribute
primary_attrib = next( primary_attrib = next(
(attrib for attrib, config in item_attributes.items() if config.primary), (attrib for attrib in item_attributes if attrib.primary),
None None
) )
@ -17,32 +18,42 @@ def update(primary_value):
return "Primary attribute not defined in configuration." return "Primary attribute not defined in configuration."
# Fetch the item using the primary attribute # 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: 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': 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': if request.method == 'POST':
# Dynamically get form data using item_attributes # 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) # Validate status (if it's an option field)
if 'status' in item_attributes and item_attributes['status'].html_input_type == "select": status_attrib = next((attrib for attrib in item_attributes if attrib.attrib_name == 'status'), None)
if form_data['status'] not in item_attributes['status'].options: 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) return render_template('update.html', item=item, exc='status', item_attributes=item_attributes)
# Convert staffnum to int (if it's a number field) # 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: try:
form_data['staffnum'] = int(form_data['staffnum']) form_data['staffnum'] = int(form_data['staffnum'])
except ValueError: except ValueError:
return render_template('update.html', item=item, exc='staffnum', item_attributes=item_attributes) return render_template('update.html', item=item, exc='staffnum', item_attributes=item_attributes)
# Update the item with the new data # Update the item with the new data
for attrib, value in form_data.items(): for attrib in item_attributes:
setattr(item, attrib, value) setattr(item, attrib.attrib_name, form_data[attrib.attrib_name])
try: try:
db.session.commit() db.session.commit()

View File

@ -1,11 +1,8 @@
from flask import Blueprint, request, render_template, redirect, session from flask import Blueprint, request, render_template, redirect, session
import csv
from io import TextIOWrapper
from definitions.models import Asset, db from definitions.models import Asset, db
from functions.process_csv import get_csv_data # Import the CSV processing function from functions.process_csv import get_csv_data
from config import item_attributes # Import the configuration from config import item_attributes
# Create a Blueprint for upload
upload_bp = Blueprint('uploadcsv', __name__) upload_bp = Blueprint('uploadcsv', __name__)
@upload_bp.route('/uploadcsv', methods=['GET', 'POST']) @upload_bp.route('/uploadcsv', methods=['GET', 'POST'])
@ -27,7 +24,7 @@ def upload_file():
# Identify the primary attribute # Identify the primary attribute
primary_attrib = next( primary_attrib = next(
(attrib for attrib, config in item_attributes.items() if config.primary), (attrib for attrib in item_attributes if attrib.primary),
None None
) )
@ -38,8 +35,8 @@ def upload_file():
new_assets = [] new_assets = []
existing_assets = [] existing_assets = []
for row in csvdata: for row in csvdata:
primary_value = row[primary_attrib] primary_value = row[primary_attrib.attrib_name]
asset_exists = Asset.query.filter_by(**{primary_attrib: primary_value}).first() asset_exists = Asset.query.filter_by(**{primary_attrib.attrib_name: primary_value}).first()
if asset_exists: if asset_exists:
existing_assets.append(row) existing_assets.append(row)
else: else:
@ -48,7 +45,12 @@ def upload_file():
session['assets'] = new_assets # Store new assets in session session['assets'] = new_assets # Store new assets in session
# Redirect to preview page with the CSV data # 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: except Exception as e:
# Handle errors during file processing # Handle errors during file processing

View File

@ -6,9 +6,18 @@ viewall_bp = Blueprint('viewall', __name__)
@viewall_bp.route('/viewall/', methods=['GET']) @viewall_bp.route('/viewall/', methods=['GET'])
def view_list(): def view_list():
# Fetch all items from the database
items = Asset.query.all() items = Asset.query.all()
primary_attrib = next(
(attrib for attrib, config in item_attributes.items() if config.primary), # Identify the primary attribute
None 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)

View File

@ -7,34 +7,34 @@
<body> <body>
<h2 align="center">Add new Item</h2> <h2 align="center">Add new Item</h2>
<form method = "POST"> <form method="POST">
{% for attrib, properties in item_attributes.items() -%} {% for attrib in item_attributes -%}
<p> <p>
<label for="{{ attrib }}">{{ properties.display_name }}:</label> <label for="{{ attrib.attrib_name }}">{{ attrib.display_name }}:</label>
{%- if properties.html_input_type == "select" %} {%- if isinstance(attrib, selectAttribute) %}
<select <select
id="{{ attrib }}" id="{{ attrib.attrib_name }}"
name="{{ attrib }}" name="{{ attrib.attrib_name }}"
{%- if properties.required %} required {% endif -%} {%- if attrib.required %} required {% endif -%}
> >
{% for option in properties.options -%} {% for option in attrib.options -%}
<option value="{{ option }}">{{ option }}</option> <option value="{{ option }}">{{ option }}</option>
{% endfor -%} {% endfor -%}
</select> </select>
{% else %} {% else %}
<input <input
id="{{ attrib }}" id="{{ attrib.attrib_name }}"
type="{{ properties.html_input_type }}" type="{{ attrib.html_input_type }}"
name="{{ attrib }}" name="{{ attrib.attrib_name }}"
{%- if properties.required %} required {% endif -%} {%- if attrib.required %} required {% endif -%}
{%- if properties.min is not none %} min="{{ properties.min }}" {% endif -%} {%- if hasattr(attrib, 'min_val') and attrib.min_val is not none %} min="{{ attrib.min_val }}" {% endif -%}
{%- if properties.max is not none %} max="{{ properties.max }}" {% endif -%} {%- if hasattr(attrib, 'max_val') and attrib.max_val is not none %} max="{{ attrib.max_val }}" {% endif -%}
{%- if properties.default_val %} value="{{ properties.default_val }}" {% endif -%} {%- if attrib.default_val is not none %} value="{{ attrib.default_val }}" {% endif -%}
/> />
{% endif -%} {% endif -%}
</p> </p>
{% endfor %} {% endfor %}
<p><input type = "submit" value = "Submit" /></p> <p><input type="submit" value="Submit" /></p>
</form> </form>
<p align="center"> <p align="center">
@ -50,4 +50,3 @@
</p> </p>
</body> </body>
</html> </html>

View File

@ -15,16 +15,16 @@
<table border="1" class="table-new-assets"> <table border="1" class="table-new-assets">
<thead> <thead>
<tr> <tr>
{% for attrib, config in item_attributes.items() %} {% for attrib in item_attributes %}
<th data-attrib="{{ attrib }}">{{ config.display_name }}</th> <th data-attrib="{{ attrib.attrib_name }}">{{ attrib.display_name }}</th>
{% endfor %} {% endfor %}
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{% for asset in new_assets %} {% for asset in new_assets %}
<tr> <tr>
{% for attrib, config in item_attributes.items() %} {% for attrib in item_attributes %}
<td contenteditable="true">{{ asset[attrib] }}</td> <td contenteditable="true">{{ asset[attrib.attrib_name] }}</td>
{% endfor %} {% endfor %}
</tr> </tr>
{% endfor %} {% endfor %}
@ -37,16 +37,16 @@
<table border="1" class="table-existing-assets"> <table border="1" class="table-existing-assets">
<thead> <thead>
<tr> <tr>
{% for attrib, config in item_attributes.items() %} {% for attrib in item_attributes %}
<th >{{ config.display_name }}</th> <th>{{ attrib.display_name }}</th>
{% endfor %} {% endfor %}
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{% for asset in existing %} {% for asset in existing %}
<tr> <tr>
{% for attrib, config in item_attributes.items() %} {% for attrib in item_attributes %}
<td>{{ asset[attrib] }}</td> <td>{{ asset[attrib.attrib_name] }}</td>
{% endfor %} {% endfor %}
</tr> </tr>
{% endfor %} {% endfor %}

View File

@ -7,34 +7,34 @@
<body> <body>
<h2 align="center">Update Item</h2> <h2 align="center">Update Item</h2>
<form action='' method = "POST"> <form method="POST">
{% for attrib, properties in item_attributes.items() -%} {% for attrib in item_attributes -%}
<p> <p>
<label for="{{ attrib }}">{{ properties.display_name }}:</label> <label for="{{ attrib.attrib_name }}">{{ attrib.display_name }}:</label>
{%- if properties.html_input_type == "select" %} {%- if isinstance(attrib, selectAttribute) %}
<select <select
id="{{ attrib }}" id="{{ attrib.attrib_name }}"
name="{{ attrib }}" name="{{ attrib.attrib_name }}"
{%- if properties.required %} required {% endif -%} {%- if attrib.required %} required {% endif -%}
> >
{% for option in properties.options -%} {% for option in attrib.options -%}
<option value="{{ option }}" {% if item[attrib] == option %}selected{% endif %}>{{ option }}</option> <option value="{{ option }}" {% if item[attrib.attrib_name] == option %}selected{% endif %}>{{ option }}</option>
{% endfor -%} {% endfor -%}
</select> </select>
{% else %} {% else %}
<input <input
id="{{ attrib }}" id="{{ attrib.attrib_name }}"
type="{{ properties.html_input_type }}" type="{{ attrib.html_input_type }}"
name="{{ attrib }}" name="{{ attrib.attrib_name }}"
{%- if properties.required %} required {% endif -%} {%- if attrib.required %} required {% endif -%}
{%- if properties.min is not none %} min="{{ properties.min }}" {% endif -%} {%- if hasattr(attrib, 'min_val') and attrib.min_val is not none %} min="{{ attrib.min_val }}" {% endif -%}
{%- if properties.max is not none %} max="{{ properties.max }}" {% endif -%} {%- if hasattr(attrib, 'max_val') and attrib.max_val is not none %} max="{{ attrib.max_val }}" {% endif -%}
{%- if item[attrib] %} value="{{ item[attrib] }}" {% endif -%} {%- if item[attrib.attrib_name] %} value="{{ item[attrib.attrib_name] }}" {% endif -%}
/> />
{% endif -%} {% endif -%}
</p> </p>
{% endfor %} {% endfor %}
<p><input type = "submit" value = "Update" /></p> <p><input type="submit" value="Update" /></p>
</form> </form>
<p align="center"> <p align="center">

View File

@ -10,8 +10,8 @@
<table border="1" align="center"> <table border="1" align="center">
<tr> <tr>
<!-- Table headers --> <!-- Table headers -->
{% for attrib, config in item_attributes.items() %} {% for attrib in item_attributes %}
<th>{{ config.display_name }}</th> <th>{{ attrib.display_name }}</th>
{% endfor %} {% endfor %}
<th colspan="2">Actions</th> <th colspan="2">Actions</th>
</tr> </tr>
@ -19,8 +19,8 @@
<!-- Table rows --> <!-- Table rows -->
{% for item in items %} {% for item in items %}
<tr> <tr>
{% for attrib, config in item_attributes.items() %} {% for attrib in item_attributes %}
<td>{{ item[attrib] }}</td> <td>{{ item[attrib.attrib_name] }}</td>
{% endfor %} {% endfor %}
<td> <td>
<form action="/update/{{ item[primary_attrib] }}" method="get"> <form action="/update/{{ item[primary_attrib] }}" method="get">