Initial commit: Shop Database Flask Application

Flask backend with Vue 3 frontend for shop floor machine management.
Includes database schema export for MySQL shopdb_flask database.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
cproudlock
2026-01-13 16:07:34 -05:00
commit 1196de6e88
188 changed files with 19921 additions and 0 deletions

View File

@@ -0,0 +1,49 @@
"""Core SQLAlchemy models."""
from .base import BaseModel, SoftDeleteMixin, AuditMixin
from .machine import Machine, MachineType, MachineStatus, PCType
from .vendor import Vendor
from .model import Model
from .businessunit import BusinessUnit
from .location import Location
from .operatingsystem import OperatingSystem
from .relationship import MachineRelationship, RelationshipType
from .communication import Communication, CommunicationType
from .user import User, Role
from .application import Application, AppVersion, AppOwner, SupportTeam, InstalledApp
from .knowledgebase import KnowledgeBase
__all__ = [
# Base
'BaseModel',
'SoftDeleteMixin',
'AuditMixin',
# Machine
'Machine',
'MachineType',
'MachineStatus',
'PCType',
# Reference
'Vendor',
'Model',
'BusinessUnit',
'Location',
'OperatingSystem',
# Relationships
'MachineRelationship',
'RelationshipType',
# Communication
'Communication',
'CommunicationType',
# Auth
'User',
'Role',
# Applications
'Application',
'AppVersion',
'AppOwner',
'SupportTeam',
'InstalledApp',
# Knowledge Base
'KnowledgeBase',
]

View File

@@ -0,0 +1,130 @@
"""Application tracking models."""
from shopdb.extensions import db
from .base import BaseModel
class AppOwner(BaseModel):
"""Application owner/contact."""
__tablename__ = 'appowners'
appownerid = db.Column(db.Integer, primary_key=True)
appowner = db.Column(db.String(100), nullable=False)
sso = db.Column(db.String(50))
email = db.Column(db.String(100))
# Relationships
supportteams = db.relationship('SupportTeam', back_populates='owner', lazy='dynamic')
def __repr__(self):
return f"<AppOwner {self.appowner}>"
class SupportTeam(BaseModel):
"""Application support team."""
__tablename__ = 'supportteams'
supportteamid = db.Column(db.Integer, primary_key=True)
teamname = db.Column(db.String(100), nullable=False)
teamurl = db.Column(db.String(255))
appownerid = db.Column(db.Integer, db.ForeignKey('appowners.appownerid'))
# Relationships
owner = db.relationship('AppOwner', back_populates='supportteams')
applications = db.relationship('Application', back_populates='supportteam', lazy='dynamic')
def __repr__(self):
return f"<SupportTeam {self.teamname}>"
class Application(BaseModel):
"""Application catalog."""
__tablename__ = 'applications'
appid = db.Column(db.Integer, primary_key=True)
appname = db.Column(db.String(100), unique=True, nullable=False)
appdescription = db.Column(db.String(255))
supportteamid = db.Column(db.Integer, db.ForeignKey('supportteams.supportteamid'))
isinstallable = db.Column(db.Boolean, default=False)
applicationnotes = db.Column(db.Text)
installpath = db.Column(db.String(255))
applicationlink = db.Column(db.String(512))
documentationpath = db.Column(db.String(512))
ishidden = db.Column(db.Boolean, default=False)
isprinter = db.Column(db.Boolean, default=False)
islicenced = db.Column(db.Boolean, default=False)
image = db.Column(db.String(255))
# Relationships
supportteam = db.relationship('SupportTeam', back_populates='applications')
versions = db.relationship('AppVersion', back_populates='application', lazy='dynamic')
installed_on = db.relationship('InstalledApp', back_populates='application', lazy='dynamic')
def __repr__(self):
return f"<Application {self.appname}>"
class AppVersion(BaseModel):
"""Application version tracking."""
__tablename__ = 'appversions'
appversionid = db.Column(db.Integer, primary_key=True)
appid = db.Column(db.Integer, db.ForeignKey('applications.appid'), nullable=False)
version = db.Column(db.String(50), nullable=False)
releasedate = db.Column(db.Date)
notes = db.Column(db.String(255))
dateadded = db.Column(db.DateTime, default=db.func.now())
# Relationships
application = db.relationship('Application', back_populates='versions')
installations = db.relationship('InstalledApp', back_populates='appversion', lazy='dynamic')
# Unique constraint on app + version
__table_args__ = (
db.UniqueConstraint('appid', 'version', name='uq_app_version'),
)
def __repr__(self):
return f"<AppVersion {self.application.appname if self.application else self.appid} v{self.version}>"
class InstalledApp(db.Model):
"""Junction table for applications installed on machines (PCs)."""
__tablename__ = 'installedapps'
id = db.Column(db.Integer, primary_key=True)
machineid = db.Column(db.Integer, db.ForeignKey('machines.machineid'), nullable=False)
appid = db.Column(db.Integer, db.ForeignKey('applications.appid'), nullable=False)
appversionid = db.Column(db.Integer, db.ForeignKey('appversions.appversionid'))
isactive = db.Column(db.Boolean, default=True, nullable=False)
installeddate = db.Column(db.DateTime, default=db.func.now())
# Relationships
machine = db.relationship('Machine', back_populates='installedapps')
application = db.relationship('Application', back_populates='installed_on')
appversion = db.relationship('AppVersion', back_populates='installations')
# Unique constraint - one app per machine (can have different versions over time)
__table_args__ = (
db.UniqueConstraint('machineid', 'appid', name='uq_machine_app'),
)
def to_dict(self):
"""Convert to dictionary."""
return {
'id': self.id,
'machineid': self.machineid,
'appid': self.appid,
'appversionid': self.appversionid,
'isactive': self.isactive,
'installeddate': self.installeddate.isoformat() + 'Z' if self.installeddate else None,
'application': {
'appid': self.application.appid,
'appname': self.application.appname,
'appdescription': self.application.appdescription,
} if self.application else None,
'version': self.appversion.version if self.appversion else None
}
def __repr__(self):
return f"<InstalledApp machine={self.machineid} app={self.appid}>"

View File

@@ -0,0 +1,66 @@
"""Base model class with common fields."""
from datetime import datetime
from shopdb.extensions import db
class BaseModel(db.Model):
"""
Abstract base model with common fields.
All models should inherit from this.
"""
__abstract__ = True
createddate = db.Column(
db.DateTime,
default=datetime.utcnow,
nullable=False
)
modifieddate = db.Column(
db.DateTime,
default=datetime.utcnow,
onupdate=datetime.utcnow,
nullable=False
)
isactive = db.Column(db.Boolean, default=True, nullable=False)
def to_dict(self):
"""Convert model to dictionary."""
result = {}
for c in self.__table__.columns:
value = getattr(self, c.name)
if isinstance(value, datetime):
value = value.isoformat() + 'Z'
result[c.name] = value
return result
def update(self, **kwargs):
"""Update model attributes."""
for key, value in kwargs.items():
if hasattr(self, key):
setattr(self, key, value)
@classmethod
def get_active(cls):
"""Return query for active records only."""
return cls.query.filter_by(isactive=True)
class SoftDeleteMixin:
"""Mixin for soft delete functionality."""
deleteddate = db.Column(db.DateTime, nullable=True)
deletedby = db.Column(db.String(100), nullable=True)
def soft_delete(self, deleted_by: str = None):
"""Mark record as deleted."""
self.isactive = False
self.deleteddate = datetime.utcnow()
self.deletedby = deleted_by
class AuditMixin:
"""Mixin for audit fields."""
createdby = db.Column(db.String(100), nullable=True)
modifiedby = db.Column(db.String(100), nullable=True)

View File

@@ -0,0 +1,31 @@
"""Business Unit model."""
from shopdb.extensions import db
from .base import BaseModel
class BusinessUnit(BaseModel):
"""Business unit / department model."""
__tablename__ = 'businessunits'
businessunitid = db.Column(db.Integer, primary_key=True)
businessunit = db.Column(db.String(100), unique=True, nullable=False)
code = db.Column(db.String(20), unique=True, comment='Short code')
description = db.Column(db.Text)
# Optional parent for hierarchy
parentid = db.Column(
db.Integer,
db.ForeignKey('businessunits.businessunitid'),
nullable=True
)
# Self-referential relationship for hierarchy
parent = db.relationship(
'BusinessUnit',
remote_side=[businessunitid],
backref='children'
)
def __repr__(self):
return f"<BusinessUnit {self.businessunit}>"

View File

@@ -0,0 +1,90 @@
"""Communication/network interface models."""
from shopdb.extensions import db
from .base import BaseModel
class CommunicationType(BaseModel):
"""Types of communication interfaces."""
__tablename__ = 'communicationtypes'
comtypeid = db.Column(db.Integer, primary_key=True)
comtype = db.Column(db.String(50), unique=True, nullable=False)
description = db.Column(db.Text)
# Types: IP, Serial, USB, VNC, FTP, DNC, Parallel, Network Interface
def __repr__(self):
return f"<CommunicationType {self.comtype}>"
class Communication(BaseModel):
"""
Communication interface for a machine.
Stores network config, serial settings, etc.
"""
__tablename__ = 'communications'
communicationid = db.Column(db.Integer, primary_key=True)
machineid = db.Column(
db.Integer,
db.ForeignKey('machines.machineid'),
nullable=False
)
comtypeid = db.Column(
db.Integer,
db.ForeignKey('communicationtypes.comtypeid'),
nullable=False
)
# Network configuration (for IP type)
ipaddress = db.Column(db.String(50))
subnetmask = db.Column(db.String(50))
gateway = db.Column(db.String(50))
dns1 = db.Column(db.String(50))
dns2 = db.Column(db.String(50))
macaddress = db.Column(db.String(50))
isdhcp = db.Column(db.Boolean, default=False)
# Serial configuration (for Serial type)
comport = db.Column(db.String(20))
baudrate = db.Column(db.Integer)
databits = db.Column(db.Integer)
stopbits = db.Column(db.String(10))
parity = db.Column(db.String(20))
flowcontrol = db.Column(db.String(20))
# VNC/FTP configuration
port = db.Column(db.Integer)
username = db.Column(db.String(100))
# Note: passwords should not be stored here - use secure vault
# DNC configuration
pathname = db.Column(db.String(255))
pathname2 = db.Column(db.String(255), comment='Secondary path for dualpath')
# Flags
isprimary = db.Column(
db.Boolean,
default=False,
comment='Primary communication method'
)
ismachinenetwork = db.Column(
db.Boolean,
default=False,
comment='On machine network vs office network'
)
notes = db.Column(db.Text)
# Relationships
comtype = db.relationship('CommunicationType', backref='communications')
__table_args__ = (
db.Index('idx_comm_machine', 'machineid'),
db.Index('idx_comm_ip', 'ipaddress'),
)
def __repr__(self):
return f"<Communication {self.machineid}:{self.comtype.comtype if self.comtype else 'Unknown'}>"

View File

@@ -0,0 +1,27 @@
"""Knowledge Base models."""
from shopdb.extensions import db
from .base import BaseModel
class KnowledgeBase(BaseModel):
"""Knowledge Base article linking to external resources."""
__tablename__ = 'knowledgebase'
linkid = db.Column(db.Integer, primary_key=True)
appid = db.Column(db.Integer, db.ForeignKey('applications.appid'))
shortdescription = db.Column(db.String(500), nullable=False)
linkurl = db.Column(db.String(2000))
keywords = db.Column(db.String(500))
clicks = db.Column(db.Integer, default=0)
lastupdated = db.Column(db.DateTime, default=db.func.now(), onupdate=db.func.now())
# Relationships
application = db.relationship('Application', backref=db.backref('knowledgebase_articles', lazy='dynamic'))
def __repr__(self):
return f"<KnowledgeBase {self.linkid}: {self.shortdescription[:50] if self.shortdescription else 'No desc'}>"
def increment_clicks(self):
"""Increment click counter."""
self.clicks = (self.clicks or 0) + 1

View File

@@ -0,0 +1,24 @@
"""Location model."""
from shopdb.extensions import db
from .base import BaseModel
class Location(BaseModel):
"""Physical location model."""
__tablename__ = 'locations'
locationid = db.Column(db.Integer, primary_key=True)
locationname = db.Column(db.String(100), unique=True, nullable=False)
building = db.Column(db.String(100))
floor = db.Column(db.String(50))
room = db.Column(db.String(50))
description = db.Column(db.Text)
# Map configuration
mapimage = db.Column(db.String(500), comment='Path to floor map image')
mapwidth = db.Column(db.Integer)
mapheight = db.Column(db.Integer)
def __repr__(self):
return f"<Location {self.locationname}>"

View File

@@ -0,0 +1,252 @@
"""Unified Machine model - combines equipment and PCs."""
from shopdb.extensions import db
from .base import BaseModel, SoftDeleteMixin, AuditMixin
class MachineType(BaseModel):
"""
Machine type classification.
Categories: Equipment, PC, Network, Printer
"""
__tablename__ = 'machinetypes'
machinetypeid = db.Column(db.Integer, primary_key=True)
machinetype = db.Column(db.String(100), unique=True, nullable=False)
category = db.Column(
db.String(50),
nullable=False,
default='Equipment',
comment='Equipment, PC, Network, or Printer'
)
description = db.Column(db.Text)
icon = db.Column(db.String(50), comment='Icon name for UI')
def __repr__(self):
return f"<MachineType {self.machinetype}>"
class MachineStatus(BaseModel):
"""Machine status options."""
__tablename__ = 'machinestatuses'
statusid = db.Column(db.Integer, primary_key=True)
status = db.Column(db.String(50), unique=True, nullable=False)
description = db.Column(db.Text)
color = db.Column(db.String(20), comment='CSS color for UI')
def __repr__(self):
return f"<MachineStatus {self.status}>"
class PCType(BaseModel):
"""
PC type classification for more specific PC categorization.
Examples: Shopfloor PC, Engineer Workstation, CMM PC, etc.
"""
__tablename__ = 'pctypes'
pctypeid = db.Column(db.Integer, primary_key=True)
pctype = db.Column(db.String(100), unique=True, nullable=False)
description = db.Column(db.Text)
def __repr__(self):
return f"<PCType {self.pctype}>"
class Machine(BaseModel, SoftDeleteMixin, AuditMixin):
"""
Unified machine model for all asset types.
Machine types can be:
- CNC machines, CMMs, EDMs, etc. (manufacturing equipment)
- PCs (shopfloor PCs, engineer workstations, etc.)
- Network devices (servers, switches, etc.) - if network_devices plugin not used
The machinetype.category field distinguishes between types.
"""
__tablename__ = 'machines'
machineid = db.Column(db.Integer, primary_key=True)
# Identification
machinenumber = db.Column(
db.String(50),
unique=True,
nullable=False,
index=True,
comment='Business identifier (e.g., CMM01, G5QX1GT3ESF)'
)
alias = db.Column(
db.String(100),
comment='Friendly name'
)
hostname = db.Column(
db.String(100),
index=True,
comment='Network hostname (for PCs)'
)
serialnumber = db.Column(
db.String(100),
index=True,
comment='Hardware serial number'
)
# Classification
machinetypeid = db.Column(
db.Integer,
db.ForeignKey('machinetypes.machinetypeid'),
nullable=False
)
pctypeid = db.Column(
db.Integer,
db.ForeignKey('pctypes.pctypeid'),
nullable=True,
comment='Set for PCs, NULL for equipment'
)
businessunitid = db.Column(
db.Integer,
db.ForeignKey('businessunits.businessunitid'),
nullable=True
)
modelnumberid = db.Column(
db.Integer,
db.ForeignKey('models.modelnumberid'),
nullable=True
)
vendorid = db.Column(
db.Integer,
db.ForeignKey('vendors.vendorid'),
nullable=True
)
# Status
statusid = db.Column(
db.Integer,
db.ForeignKey('machinestatuses.statusid'),
default=1,
comment='In Use, Spare, Retired, etc.'
)
# Location and mapping
locationid = db.Column(
db.Integer,
db.ForeignKey('locations.locationid'),
nullable=True
)
mapleft = db.Column(db.Integer, comment='X coordinate on floor map')
maptop = db.Column(db.Integer, comment='Y coordinate on floor map')
islocationonly = db.Column(
db.Boolean,
default=False,
comment='Virtual location marker (not actual machine)'
)
# PC-specific fields (nullable for non-PC machines)
osid = db.Column(
db.Integer,
db.ForeignKey('operatingsystems.osid'),
nullable=True
)
loggedinuser = db.Column(db.String(100), nullable=True)
lastreporteddate = db.Column(db.DateTime, nullable=True)
lastboottime = db.Column(db.DateTime, nullable=True)
# Features/flags
isvnc = db.Column(db.Boolean, default=False, comment='VNC remote access enabled')
iswinrm = db.Column(db.Boolean, default=False, comment='WinRM enabled')
isshopfloor = db.Column(db.Boolean, default=False, comment='Shopfloor PC')
requiresmanualconfig = db.Column(
db.Boolean,
default=False,
comment='Multi-PC machine needs manual configuration'
)
# Notes
notes = db.Column(db.Text, nullable=True)
# Relationships
machinetype = db.relationship('MachineType', backref='machines')
pctype = db.relationship('PCType', backref='machines')
businessunit = db.relationship('BusinessUnit', backref='machines')
model = db.relationship('Model', backref='machines')
vendor = db.relationship('Vendor', backref='machines')
status = db.relationship('MachineStatus', backref='machines')
location = db.relationship('Location', backref='machines')
operatingsystem = db.relationship('OperatingSystem', backref='machines')
# Communications (one-to-many)
communications = db.relationship(
'Communication',
backref='machine',
cascade='all, delete-orphan',
lazy='dynamic'
)
# Installed applications (for PCs)
installedapps = db.relationship(
'InstalledApp',
back_populates='machine',
cascade='all, delete-orphan',
lazy='dynamic'
)
# Indexes
__table_args__ = (
db.Index('idx_machine_type_bu', 'machinetypeid', 'businessunitid'),
db.Index('idx_machine_location', 'locationid'),
db.Index('idx_machine_active', 'isactive'),
db.Index('idx_machine_hostname', 'hostname'),
)
def __repr__(self):
return f"<Machine {self.machinenumber}>"
@property
def display_name(self):
"""Get display name (alias if set, otherwise machinenumber)."""
return self.alias or self.machinenumber
@property
def derived_machinetype(self):
"""Get machinetype from model (single source of truth)."""
if self.model and self.model.machinetype:
return self.model.machinetype
return None
@property
def is_pc(self):
"""Check if this machine is a PC type."""
mt = self.derived_machinetype
return mt.category == 'PC' if mt else False
@property
def is_equipment(self):
"""Check if this machine is equipment."""
mt = self.derived_machinetype
return mt.category == 'Equipment' if mt else False
@property
def is_network_device(self):
"""Check if this machine is a network device."""
mt = self.derived_machinetype
return mt.category == 'Network' if mt else False
@property
def is_printer(self):
"""Check if this machine is a printer."""
mt = self.derived_machinetype
return mt.category == 'Printer' if mt else False
@property
def primary_ip(self):
"""Get primary IP address from communications."""
comm = self.communications.filter_by(
isprimary=True,
comtypeid=1 # IP type
).first()
if comm:
return comm.ipaddress
# Fall back to any IP
comm = self.communications.filter_by(comtypeid=1).first()
return comm.ipaddress if comm else None

View File

@@ -0,0 +1,43 @@
"""Model (equipment model number) model."""
from shopdb.extensions import db
from .base import BaseModel
class Model(BaseModel):
"""Equipment/device model information."""
__tablename__ = 'models'
modelnumberid = db.Column(db.Integer, primary_key=True)
modelnumber = db.Column(db.String(100), nullable=False)
# Link to machine type (what kind of equipment this model is for)
machinetypeid = db.Column(
db.Integer,
db.ForeignKey('machinetypes.machinetypeid'),
nullable=True
)
# Link to vendor/manufacturer
vendorid = db.Column(
db.Integer,
db.ForeignKey('vendors.vendorid'),
nullable=True
)
description = db.Column(db.Text)
imageurl = db.Column(db.String(500), comment='URL to product image')
documentationurl = db.Column(db.String(500), comment='URL to documentation')
notes = db.Column(db.Text)
# Relationships
machinetype = db.relationship('MachineType', backref='models')
vendor = db.relationship('Vendor', backref='models')
# Unique constraint on modelnumber + vendor
__table_args__ = (
db.UniqueConstraint('modelnumber', 'vendorid', name='uq_model_vendor'),
)
def __repr__(self):
return f"<Model {self.modelnumber}>"

View File

@@ -0,0 +1,22 @@
"""Operating System model."""
from shopdb.extensions import db
from .base import BaseModel
class OperatingSystem(BaseModel):
"""Operating system model."""
__tablename__ = 'operatingsystems'
osid = db.Column(db.Integer, primary_key=True)
osname = db.Column(db.String(100), nullable=False)
osversion = db.Column(db.String(50))
architecture = db.Column(db.String(20), comment='x86, x64, ARM')
endoflife = db.Column(db.Date, comment='End of support date')
__table_args__ = (
db.UniqueConstraint('osname', 'osversion', name='uq_os_name_version'),
)
def __repr__(self):
return f"<OperatingSystem {self.osname} {self.osversion}>"

View File

@@ -0,0 +1,77 @@
"""Machine relationship models."""
from shopdb.extensions import db
from .base import BaseModel
class RelationshipType(BaseModel):
"""Types of relationships between machines."""
__tablename__ = 'relationshiptypes'
relationshiptypeid = db.Column(db.Integer, primary_key=True)
relationshiptype = db.Column(db.String(50), unique=True, nullable=False)
description = db.Column(db.Text)
# Example types:
# - "Controls" (PC controls Equipment)
# - "Dualpath" (Redundant path partner)
# - "Backup" (Backup machine)
def __repr__(self):
return f"<RelationshipType {self.relationshiptype}>"
class MachineRelationship(BaseModel):
"""
Relationships between machines.
Examples:
- PC controls CNC machine
- Two CNCs are dualpath partners
"""
__tablename__ = 'machinerelationships'
relationshipid = db.Column(db.Integer, primary_key=True)
parentmachineid = db.Column(
db.Integer,
db.ForeignKey('machines.machineid'),
nullable=False
)
childmachineid = db.Column(
db.Integer,
db.ForeignKey('machines.machineid'),
nullable=False
)
relationshiptypeid = db.Column(
db.Integer,
db.ForeignKey('relationshiptypes.relationshiptypeid'),
nullable=False
)
notes = db.Column(db.Text)
# Relationships
parent_machine = db.relationship(
'Machine',
foreign_keys=[parentmachineid],
backref='child_relationships'
)
child_machine = db.relationship(
'Machine',
foreign_keys=[childmachineid],
backref='parent_relationships'
)
relationship_type = db.relationship('RelationshipType', backref='relationships')
__table_args__ = (
db.UniqueConstraint(
'parentmachineid',
'childmachineid',
'relationshiptypeid',
name='uq_machine_relationship'
),
)
def __repr__(self):
return f"<MachineRelationship {self.parentmachineid} -> {self.childmachineid}>"

View File

@@ -0,0 +1,73 @@
"""User and authentication models."""
from datetime import datetime
from shopdb.extensions import db
from .base import BaseModel
# Association table for user roles (many-to-many)
userroles = db.Table(
'userroles',
db.Column('userid', db.Integer, db.ForeignKey('users.userid'), primary_key=True),
db.Column('roleid', db.Integer, db.ForeignKey('roles.roleid'), primary_key=True)
)
class Role(BaseModel):
"""User role model."""
__tablename__ = 'roles'
roleid = db.Column(db.Integer, primary_key=True)
rolename = db.Column(db.String(50), unique=True, nullable=False)
description = db.Column(db.Text)
def __repr__(self):
return f"<Role {self.rolename}>"
class User(BaseModel):
"""User model for authentication."""
__tablename__ = 'users'
userid = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(100), unique=True, nullable=False, index=True)
email = db.Column(db.String(255), unique=True, nullable=False)
passwordhash = db.Column(db.String(255), nullable=False)
# Profile
firstname = db.Column(db.String(100))
lastname = db.Column(db.String(100))
# Status
lastlogindate = db.Column(db.DateTime)
failedlogins = db.Column(db.Integer, default=0)
lockeduntil = db.Column(db.DateTime)
# Relationships
roles = db.relationship(
'Role',
secondary=userroles,
backref=db.backref('users', lazy='dynamic')
)
def __repr__(self):
return f"<User {self.username}>"
@property
def islocked(self):
"""Check if account is locked."""
if self.lockeduntil:
return datetime.utcnow() < self.lockeduntil
return False
def hasrole(self, rolename: str) -> bool:
"""Check if user has a specific role."""
return any(r.rolename == rolename for r in self.roles)
def getpermissions(self) -> list:
"""Get list of permission names from roles."""
# Simple role-based permissions
perms = []
for role in self.roles:
perms.append(role.rolename)
return perms

View File

@@ -0,0 +1,20 @@
"""Vendor model."""
from shopdb.extensions import db
from .base import BaseModel
class Vendor(BaseModel):
"""Vendor/Manufacturer model."""
__tablename__ = 'vendors'
vendorid = db.Column(db.Integer, primary_key=True)
vendor = db.Column(db.String(100), unique=True, nullable=False)
description = db.Column(db.Text)
website = db.Column(db.String(255))
supportphone = db.Column(db.String(50))
supportemail = db.Column(db.String(100))
notes = db.Column(db.Text)
def __repr__(self):
return f"<Vendor {self.vendor}>"