class Discount(BaseMixin, db.Model, ReprMixin): name = db.Column(db.String(55), nullable=True) value = db.Column(db.Float(precision=2), nullable=False) type = db.Column(db.Enum('PERCENTAGE', 'FIXED', name='varchar'), nullable=False, default='PERCENTAGE') orders = db.relationship('Order', secondary='order_discount')
class HistorySyncDetailModel(ModelBase): __tablename__ = 'history_sync_details' id = db.Column(db.Integer(), primary_key=True, autoincrement=True) type = db.Column(db.Enum(HistorySyncType), nullable=False, default=HistorySyncType.POSTGRES) details = db.Column(db.String(320), nullable=True) __table_args__ = (UniqueConstraint('type'), ) def __repr__(self): return f"HistorySyncDetailModel(type = {self.type})" @classmethod def update_history_sync_details(cls, history_sync_detail: dict): detail: HistorySyncDetailModel = cls.query.filter_by( type=history_sync_detail.get('type')).first() if detail: detail.update(**history_sync_detail) else: detail = HistorySyncDetailModel(**history_sync_detail) detail.save_to_db() @classmethod def find_details_by_type(cls, _type: str) -> str: detail: HistorySyncDetailModel = cls.query.filter_by( type=_type).first() if detail: return detail.details return ''
class Transaction(db.Model): """ Class for Transactions table """ __tablename__ = 'transactions' id = db.Column(db.Integer, primary_key=True) amount = db.Column(db.Numeric(), nullable=False) paid_on = db.Column(db.DateTime) payment_status = db.Column(db.Enum(PaymentStatus)) user_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False) expense_id = db.Column(db.Integer, db.ForeignKey('expenses.id'), nullable=False) updated_at = db.Column(db.DateTime) user = db.relationship('User', back_populates='transaction') expense = db.relationship('Expense', back_populates='transaction') def __init__(self, expense_id, user_id, amount): self.amount = Decimal(amount) self.user_id = user_id self.expense_id = expense_id self.payment_status = PaymentStatus.PENDING self.update_at = datetime.datetime.now() def __repr__(self): return f'<Transaction {self.id}>'
class PrinterConfig(BaseMixin, db.Model, ReprMixin): header = db.Column(db.Text, nullable=True) footer = db.Column(db.Text, nullable=True) bill_template = db.Column(db.Text, nullable=True) receipt_template = db.Column(db.Text, nullable=True) bill_printer_type = db.Column( db.Enum('thermal', 'dot_matrix', 'laser', name='varchar')) receipt_printer_type = db.Column( db.Enum('thermal', 'dot_matrix', 'laser', name='varchar')) label_printer_type = db.Column( db.Enum('1x1', '2x1', '3x1', '4x1', name='varchar')) have_receipt_printer = db.Column(db.Boolean(), default=False) have_bill_printer = db.Column(db.Boolean(), default=False) store_id = db.Column(db.ForeignKey('store.id')) stores = db.relationship('Store', back_populates='printer_config', foreign_keys=[store_id])
class ForwardEntity(db.Model): __tablename__ = 'forward' timestamp = db.Column(UtcDateTime, nullable=False, index=True, primary_key=True) ticker = db.Column(db.String(10), db.ForeignKey(StockEntity.ticker), nullable=False, primary_key=True) action = db.Column(db.Enum(ActionEnum)) price = db.Column(db.DECIMAL) number = db.Column(db.DECIMAL) cash = db.Column(db.DECIMAL)
class User(UserMixin, db.Model): __tablename__ = 'user' id = db.Column(db.Integer, primary_key=True) email = db.Column(db.String(100), nullable=False) login_method = db.Column(db.Enum('signup', 'google', 'facebook'), default='signup') name = db.Column(db.String(100), nullable=True) avatar = db.Column(db.String(200)) active = db.Column(db.Boolean, default=False) tokens = db.Column(db.Text) created_on = db.Column(db.DateTime, default=datetime.datetime.utcnow()) posts = db.relationship('Post', backref='author', lazy='dynamic') def __repr__(self): return '<User %r>' % (self.email)
class UserModel(ModelBase): __tablename__ = 'users' uuid = db.Column(db.String(80), primary_key=True, nullable=False) first_name = db.Column(db.String(80), nullable=False) last_name = db.Column(db.String(80), nullable=False) username = db.Column(db.String(80), unique=True, nullable=False) password = db.Column(db.String(80), nullable=False) email = db.Column(db.String(80), unique=True, nullable=False) state = db.Column(db.Enum(StateType), nullable=False, default=StateType.UNVERIFIED) devices = db.relationship('DeviceModel', cascade="all,delete", backref='user', lazy=True) sites = db.relationship('UserSiteModel', cascade="all,delete", backref="user", lazy=True) __table_args__ = ( UniqueConstraint('username'), UniqueConstraint('email'), ) @validates('email') def validate_email(self, _, value): if not re.search(r"(^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$)", value): raise ValueError("Invalid email") return value @validates('username') def validate_username(self, _, value): if not re.match("^([A-Za-z0-9_-])+$", value): raise ValueError( "username should be alphanumeric and can contain '_', '-'") return value @classmethod def find_by_username(cls, username: str): return cls.query.filter_by(username=username).first() @classmethod def find_by_email(cls, email: str): return cls.query.filter_by(email=email).first()
class UserProfile(BaseMixin, db.Model): first_name = db.Column(db.String(255)) last_name = db.Column(db.String(255)) gender = db.Column(db.Enum('male', 'female', 'na', name='gender'), default='na') dob = db.Column(db.DateTime, default=db.func.current_timestamp(), nullable=True) profile_picture = db.Column(db.Text(), nullable=True) user_id = db.Column(db.Integer, db.ForeignKey('user.id', ondelete='CASCADE'), unique=True) user = db.relationship('User', back_populates="user_profile", single_parent=True)
class UserProfile(db.Model, BaseMixin): first_name = db.Column(db.String(255)) last_name = db.Column(db.String(255)) gender = db.Column(db.Enum('male', 'female', 'ns'), default='ns') dob = db.Column(db.DateTime, default=db.func.current_timestamp(), nullable=True) profile_picture = db.Column(db.String(512), nullable=True) address = db.Column(db.Integer) user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False) user = db.relationship('User', lazy='subquery', backref='user_profile') @hybrid_property def age(self): if self.dob: return datetime.now().year - self.dob.year else: return 0
class DgfObject(db.Model): """ Data gouv fr objects Model """ id = db.Column(db.Integer, primary_key=True, autoincrement=True) suspicious = db.Column(db.Boolean()) read = db.Column(db.Boolean()) deleted = db.Column(db.Boolean()) dgf_type = db.Column( db.Enum('user', 'community_resource', 'organization', 'dataset', 'reuse', name='dgf_type')) dgf_id = db.Column(db.String(255), nullable=False) comments = db.relationship('Comment', backref='dgf_object', lazy='dynamic') def __repr__(self): return f"<Dgf {self.dgf_type} with id {self.dgf_id}>"
class User(UserMixin, db.Model): id = db.Column(db.Integer, primary_key=True) username = db.Column(db.String(64), unique=True, index=True) email = db.Column(db.String(120), unique=True, index=True) password = db.Column(db.String(255)) account_type = db.Column(db.Enum(UserType)) avatar_url = db.Column(db.String(255)) html_url = db.Column(db.String(255)) answers = db.relationship("Answer", backref="user", lazy="dynamic") answer_votes = db.relationship("AnswerVote", backref="user", lazy="dynamic") answer_flags = db.relationship("AnswerFlag", backref="user", lazy="dynamic") comments = db.relationship("Comment", backref="user", lazy="dynamic") comment_flags = db.relationship("CommentFlag", backref="user", lazy="dynamic") questions = db.relationship("Question", backref="user", lazy="dynamic") question_votes = db.relationship("QuestionVote", backref="user", lazy="dynamic") question_flags = db.relationship("QuestionFlag", backref="user", lazy="dynamic") @staticmethod def verify_token(token): id = jwt.decode(token, app.config.get("SECRET_KEY"), algorithms=["HS256"])[ "reset_password" ] return User.query.get(id) def set_password(self, password): self.password = generate_password_hash(password) def check_password(self, password): return check_password_hash(self.password, password) def get_token(self, expires=600): token = jwt.encode( {"reset_password": self.id, "exp": time() + expires}, app.config.get("SECRET_KEY"), algorithm="HS256", ).decode("utf-8") return token def __repr__(self): return "User {}".format(self.username)
class User(db.Model, BaseMixin, UserMixin, ReprMixin): email = db.Column(db.String(127), unique=True, nullable=False) password = db.Column(db.String(255), default='', nullable=False) username = db.Column(db.String(127), nullable=True) user_type = db.Column(db.Enum('student', 'counsellor'), default='counsellor') school_id = db.Column(db.Integer, db.ForeignKey('school.id')) active = db.Column(db.Boolean()) confirmed_at = db.Column(db.DateTime()) last_login_at = db.Column(db.DateTime()) current_login_at = db.Column(db.DateTime()) last_login_ip = db.Column(db.String(45)) current_login_ip = db.Column(db.String(45)) login_count = db.Column(db.Integer) roles = db.relationship('Role', secondary=roles_users, backref=db.backref('users', lazy='dynamic')) @staticmethod def hash_md5(data): return md5(data.encode('utf-8')).hexdigest() def get_auth_token(self): pass def generate_auth_token(self): token = serialize_data([str(self.id), self.hash_md5(self.password)]) return token @hybrid_property def authentication_token(self): return self.generate_auth_token() @hybrid_property def name(self): if self.user_profile and self.user_profile.first_name: if self.user_profile.last_name: return self.user_profile.first_name + self.user_profile.last_name return self.user_profile.first_name
class HistorySyncLogModel(ModelBase): __tablename__ = 'history_sync_logs' id = db.Column(db.Integer(), primary_key=True, autoincrement=True) type = db.Column(db.Enum(HistorySyncType), nullable=False, default=HistorySyncType.POSTGRES) point_uuid = db.Column(db.String, db.ForeignKey('points.uuid'), nullable=False) last_sync_id = db.Column(db.Integer(), nullable=False, default=0) __table_args__ = (UniqueConstraint('type', 'point_uuid'), ) def __repr__(self): return f"HistorySyncLogModel(point_uuid = {self.point_uuid})" @classmethod def update_history_sync_logs(cls, history_sync_log_list: List[dict]): for history_sync_log in history_sync_log_list: log: HistorySyncLogModel = cls.query.filter_by( type=history_sync_log.get('type'), point_uuid=history_sync_log.get('point_uuid')).first() if log: log.update(**history_sync_log) else: log = HistorySyncLogModel(**history_sync_log) log.save_to_db() @classmethod def find_last_sync_id_by_type_point_uuid(cls, _type: str, point_uuid: str) -> int: log: HistorySyncLogModel = cls.query.filter_by( type=_type, point_uuid=point_uuid).first() if log: return log.last_sync_id return 0
class User(db.Model): __tablename__ = "user" id = db.Column(db.Integer, primary_key=True, autoincrement=True) username = db.Column(db.String(255), unique=True, nullable=False, index=True) password = db.Column(db.String(255), nullable=False) role = db.Column(db.Enum(Roles)) def __init__(self, username, password, role=Roles.pleb) -> None: self.username = username self.role = role self.set_password(password) def set_password(self, password) -> None: self.password = bcrypt.generate_password_hash( password=password).decode() def check_password(self, password) -> bool: return bcrypt.check_password_hash(self.password, password) def is_authorized(self, claims) -> bool: return self.is_admin() or claims['identity'] in self.id def is_admin(self) -> bool: return Roles.admin in self.role @classmethod def find_by_id(cls, user_id): return cls.query.filter_by(id=user_id).first() @classmethod def find_by_username(cls, username): return cls.query.filter_by(username=username).first()
class LPGBPointMapping(ModelBase): """ lora_point <> generic_point | bacnet_point """ __tablename__ = 'mappings_lp_gbp' uuid = db.Column(db.String, primary_key=True) point_uuid = db.Column(db.String, db.ForeignKey('points.uuid'), nullable=False) mapped_point_uuid = db.Column(db.String(80), nullable=True, unique=True) point_name = db.Column(db.String(80), nullable=False) mapped_point_name = db.Column(db.String(80), nullable=True) type = db.Column(db.Enum(MapType), nullable=True) mapping_state = db.Column(db.Enum(MappingState), default=MappingState.MAPPED) @validates('type') def validate_type(self, _, value): if not value: raise ValueError("type should not be null or blank") return value def check_self(self) -> (bool, any): super().check_self() if self.point_uuid: self.__set_point_name() if self.mapped_point_uuid: self.__set_mapped_point_name() if not self.point_uuid: self.__set_point_uuid() if not self.mapped_point_uuid: self.__set_mapped_point_uuid() def set_uuid_with_name(self): self.__set_point_uuid() self.__set_mapped_point_uuid() def __set_point_name(self): from src.models.model_point import PointModel if not self.point_uuid: raise ValueError(f"point_uuid should not be null or blank") point: PointModel = PointModel.find_by_uuid(self.point_uuid) if not point: raise ValueError(f"Does not exist point_uuid {self.point_uuid}") self.point_name = f"{point.device.name}:{point.name}" def __set_mapped_point_name(self): if not self.mapped_point_uuid: raise ValueError("mapped_point_uuid should not be null or blank") if self.type in (MapType.GENERIC.name, MapType.GENERIC): response: Response = gw_request( f'/ps/api/generic/points/get_name/uuid/{self.mapped_point_uuid}' ) if response.status_code != 200: raise ValueError( f"Does not exist mapped_point_uuid {self.mapped_point_uuid}" ) self.mapped_point_name = json.loads(response.data).get('name') elif self.type in (MapType.BACNET.name, MapType.BACNET): response: Response = gw_request( f'/bacnet/api/bacnet/points/uuid/{self.mapped_point_uuid}') if response.status_code != 200: raise ValueError( f"Does not exist mapped_point_uuid {self.mapped_point_uuid}" ) self.mapped_point_name = json.loads( response.data).get('object_name') def __set_point_uuid(self): from src.models.model_point import PointModel point_names = self.point_name.split(":") if len(point_names) != 2: raise ValueError( "point_name should be colon (:) delimited device_name:point_name" ) device_name, point_name = point_names point: PointModel = PointModel.find_by_name(device_name, point_name) if not point: raise ValueError(f"Does not exist point_name {self.point_name}") self.point_uuid = point.uuid def __set_mapped_point_uuid(self): if not self.mapped_point_name: raise ValueError("mapped_point_name should not be null or blank") if self.type in (MapType.GENERIC.name, MapType.GENERIC): mapped_point_names = self.mapped_point_name.split(":") if len(mapped_point_names) != 3: raise ValueError( "mapped_point_names should be colon (:) delimited network_name:device_name:point_name" ) network_name, device_name, point_name = mapped_point_names response: Response = gw_request( f'/ps/api/generic/points/name/{network_name}/{device_name}/{point_name}' ) if response.status_code != 200: raise ValueError( f"Does not exit mapped_point_name {self.mapped_point_name}" ) self.mapped_point_uuid = json.loads(response.data).get('uuid') elif self.type in (MapType.BACNET.name, MapType.BACNET): response: Response = gw_request( api=f'/bacnet/api/bacnet/points/name/{self.mapped_point_name}') if response.status_code != 200: raise ValueError( f"Does not exist mapped_point_name {self.mapped_point_name}" ) self.mapped_point_uuid = json.loads(response.data).get('uuid') else: raise ValueError(f"Invalid type {self.type}") @classmethod def find_by_point_uuid(cls, point_uuid): return cls.query.filter_by(point_uuid=point_uuid).first() @classmethod def find_mapped_point_uuid_type(cls, mapped_point_uuid, map_type): return cls.query.filter_by(mapped_point_uuid=mapped_point_uuid, type=map_type).first()
class Product(BaseMixin, db.Model, ReprMixin): name = db.Column(db.String(127), unique=False, nullable=False, index=True) min_stock = db.Column(db.SmallInteger, nullable=False) auto_discount = db.Column(db.FLOAT(precision=2), default=0, nullable=False) description = db.Column(db.JSON(), nullable=True) sub_description = db.Column(db.Text(), nullable=True) is_disabled = db.Column(db.Boolean(), default=False) default_quantity = db.Column(db.Float(precision=2), default=1) quantity_label = db.Column(db.Enum('KG', 'GM', 'MG', 'L', 'ML', 'TAB', 'SYRUP', 'OTH', 'TAB', 'ML', 'CAP', 'INJ', 'BOTTLE', 'VAIL', 'KIT', 'STRIP', 'OTHER', 'PACK', 'SET', 'LTR', 'SACHET', 'PILLS', 'SYRINGE', 'SYRUP', 'ROLL', name='varchar'), default='OTH', nullable=True) is_loose = db.Column(db.Boolean(), default=False) barcode = db.Column(db.String(13), nullable=True) retail_shop_id = db.Column(UUID, db.ForeignKey('retail_shop.id', ondelete='CASCADE'), index=True, nullable=False) brand_id = db.Column(UUID, db.ForeignKey('brand.id'), index=True, nullable=False) retail_shop = db.relationship('RetailShop', foreign_keys=[retail_shop_id], uselist=False, back_populates='products') taxes = db.relationship('Tax', back_populates='products', secondary='product_tax') tags = db.relationship('Tag', back_populates='products', secondary='product_tag') brand = db.relationship('Brand', foreign_keys=[brand_id], uselist=False, back_populates='products') stocks = db.relationship('Stock', uselist=True, cascade="all, delete-orphan", lazy='dynamic') # distributors = db.relationship('Distributor', back_populates='products', secondary='product_distributor') combos = db.relationship('Combo', back_populates='products', secondary='combo_product') salts = db.relationship('Salt', back_populates='products', secondary='product_salt') add_ons = db.relationship('AddOn', back_populates='products', secondary='product_add_on') UniqueConstraint('barcode', 'retail_shop_id', 'bar_retail_un') @hybrid_property def available_stock(self): return self.stocks.filter(Stock.is_sold != True, Stock.expired == False)\ .with_entities(func.coalesce(func.Sum(Stock.units_purchased), 0)-func.coalesce(func.Sum(Stock.units_sold), 0)).scalar() @hybrid_property def available_stocks(self): return self.stocks.filter(and_(or_(Stock.is_sold != True), Stock.expired == False)).all() @available_stock.expression def available_stock(cls): return select([func.coalesce(func.Sum(Stock.units_purchased), 0)-func.coalesce(func.Sum(Stock.units_sold), 0)])\ .where(and_(or_(Stock.is_sold != True), Stock.product_id == cls.id)).as_scalar() @hybrid_property def mrp(self): mrp = self.stocks.filter(or_(Stock.is_sold != True))\ .with_entities(Stock.selling_amount).order_by(Stock.id).first() return mrp[0] if mrp else 0 @hybrid_property def similar_products(self): if len(self.salts): return [i[0] for i in Product.query.with_entities(Product.id) .join(ProductSalt, and_(ProductSalt.product_id == Product.id)) .filter(ProductSalt.salt_id.in_([i.id for i in self.salts])).group_by(Product.id) .having(func.Count(func.Distinct(ProductSalt.salt_id)) == len(self.salts)).all()] return [] @hybrid_property def last_purchase_amount(self): return self.stocks.order_by(desc(Stock.purchase_date)).first().purchase_amount @hybrid_property def last_selling_amount(self): return self.stocks.order_by(desc(Stock.purchase_date)).first().selling_amount @hybrid_property def stock_required(self): return abs(self.min_stock - self.available_stock) @stock_required.expression def stock_required(self): return self.min_stock - self.available_stock @hybrid_property def is_short(self): return self.min_stock >= self.available_stock @hybrid_property def product_name(self): return self.name @hybrid_property def distributors(self): return self.brand.distributors.all() @hybrid_property def brand_name(self): return self.brand.name @brand_name.expression def brand_name(self): return select([Brand.name]).where(Brand.id == self.brand_id).as_scalar()
class DeviceModel(ModelBase): __tablename__ = 'devices' uuid = db.Column(db.String(80), primary_key=True, nullable=False) name = db.Column(db.String(80), nullable=False, unique=True) device_id = db.Column(db.String(8), nullable=False, unique=True) enable = db.Column(db.Boolean, nullable=False, default=True) device_type = db.Column(db.Enum(DeviceTypes), nullable=False) device_model = db.Column(db.Enum(DeviceModels), nullable=False) description = db.Column(db.String(120), nullable=True) ai_1_config = db.Column(db.Enum(MicroEdgeConfig), nullable=True) ai_2_config = db.Column(db.Enum(MicroEdgeConfig), nullable=True) ai_3_config = db.Column(db.Enum(MicroEdgeConfig), nullable=True) fault = db.Column(db.Integer, nullable=True) points = db.relationship('PointModel', cascade="all,delete", backref='device', lazy=True) def __repr__(self): return "DeviceModel({})".format(self.uuid) @validates('name') def validate_name(self, _, value): if not re.match("^([A-Za-z0-9_-])+$", value): raise ValueError( "name should be alphanumeric and can contain '_', '-'") return value @validates('device_id') def validate_device_id(self, _, value): if value != value.upper(): raise ValueError("device_id characters should be in upper case") if len(value) != 8: raise ValueError("device_id should be of 8 characters") if not all(c in string.hexdigits for c in value): raise ValueError("device_id is not hex characters") return value @classmethod def find_by_name(cls, name: str): return cls.query.filter_by(name=name).first() @classmethod def find_by_id(cls, device_id: str): return cls.query.filter_by(device_id=device_id).first() def save_to_db(self): self.save_to_db_no_commit() super().save_to_db() def save_to_db_no_commit(self): if not self.points or not len(self.points): from src.models.device_point_presets import get_device_points device_points = get_device_points(self.device_model) for point in device_points: point.uuid = shortuuid.uuid() point.device_point_name = point.name # to match decoder key point.device_uuid = self.uuid point.save_to_db_no_commit() super().save_to_db_no_commit() def delete_from_db(self): super().delete_from_db() @validates('device_model', 'device_type') def validate_device_model(self, key, value): if key == 'device_type': if isinstance(value, DeviceTypes): return value if not value or value not in DeviceTypes.__members__: raise ValueError("Invalid Device Type") value = DeviceTypes[value] else: if not isinstance(value, DeviceModels): if not value or value not in DeviceModels.__members__: raise ValueError("Invalid Device Model") value = DeviceModels[value] # handle for both cases depending on which field is validated first if key == 'device_model' and self.device_type is not None: if not verify_device_model(self.device_type, value): raise ValueError('Invalid device model for device type') elif key == 'device_type' and self.device_model is not None: if not verify_device_model(value, self.device_model): raise ValueError('Invalid device model for device type') return value def update_mqtt(self): output: dict = {} for point in self.points: output[point.device_point_name] = point.point_store.value logger.debug(f'Publish payload: {json.dumps(output)}') MqttClient().publish_value((self.device_id, self.name), json.dumps(output))
class QuestionFlag(db.Model): id = db.Column(db.Integer, primary_key=True) question_id = db.Column(db.Integer, db.ForeignKey("question.id")) user_id = db.Column(db.Integer, db.ForeignKey("user.id")) flag = db.Column(db.Enum(Flag))
class NetworkModel(ModelBase): __tablename__ = 'network' uuid = db.Column(db.String(80), primary_key=True, nullable=False) name = db.Column(db.String(80), nullable=False) port = db.Column(db.String(40), nullable=False, unique=True) baud_rate = db.Column(db.Integer, default=9600) stop_bits = db.Column(db.Integer, default=1) parity = db.Column(db.Enum(SerialParity), default=SerialParity.N) byte_size = db.Column(db.Integer, default=8) timeout = db.Column(db.Integer, default=5) firmware_version = db.Column(db.Enum(SupportedFirmwareVersion), nullable=False) encryption_key = db.Column(db.String(32)) @validates('name') def validate_name(self, _, value): if not re.match("^([A-Za-z0-9_-])+$", value): raise ValueError( "name should be alphanumeric and can contain '_', '-'") return value @classmethod def filter_one(cls): return cls.query.filter() @classmethod def find_one(cls): driver = cls.query.first() if driver: db.session.refresh(driver) return driver def save_to_db(self): db.session.add(self) db.session.commit() @classmethod def create_network(cls, config: SerialSetting): serial_driver = NetworkModel.find_one() if not serial_driver: serial_driver = NetworkModel( uuid=shortuuid.uuid(), name=config.port, port=config.port, baud_rate=config.baud_rate, stop_bits=config.stop_bits, parity=config.parity, byte_size=config.byte_size, timeout=config.timeout, firmware_version=config.firmware_version, encryption_key=config.encryption_key) serial_driver.save_to_db() else: db.session.refresh(serial_driver) return serial_driver @validates('port') def validate_name(self, _, value): self.name = value return value @validates('firmware_version') def validate_data_endian(self, _, value): if isinstance(value, SupportedFirmwareVersion): return value if not value or value not in SupportedFirmwareVersion.__members__: raise ValueError("Invalid Firmware Version") return SupportedFirmwareVersion[value] @validates('encryption_key') def validate_encryption_key(self, _, value): # TODO: handle self.firmware_version is None when this is validated first if self.firmware_version == SupportedFirmwareVersion.v1 or \ self.firmware_version == SupportedFirmwareVersion.v2_encryption: return None else: assert len(value) == 32 return value
class PointModel(ModelBase): __tablename__ = 'points' uuid = db.Column(db.String(80), primary_key=True, nullable=False) name = db.Column(db.String(80), nullable=False) device_uuid = db.Column(db.String, db.ForeignKey('devices.uuid'), nullable=False) enable = db.Column(db.Boolean(), nullable=False, default=True) history_enable = db.Column(db.Boolean(), nullable=False, default=True) history_type = db.Column(db.Enum(HistoryType), nullable=False, default=HistoryType.INTERVAL) history_interval = db.Column(db.Integer, nullable=False, default=15) writable = db.Column(db.Boolean, nullable=False, default=True) priority_array_write = db.relationship('PriorityArrayModel', backref='point', lazy=True, uselist=False, cascade="all,delete") cov_threshold = db.Column(db.Float, nullable=False, default=0) value_round = db.Column(db.Integer(), nullable=False, default=2) value_operation = db.Column(db.String, nullable=True, default="x + 0") input_min = db.Column(db.Float()) input_max = db.Column(db.Float()) scale_min = db.Column(db.Float()) scale_max = db.Column(db.Float()) tags = db.Column(db.String(320), nullable=True) point_store = db.relationship('PointStoreModel', backref='point', lazy=True, uselist=False, cascade="all,delete") point_store_history = db.relationship('PointStoreHistoryModel', backref='point', lazy=True, cascade="all,delete") history_sync_log = db.relationship('HistorySyncLogModel', backref='hsl', lazy=True, cascade="all,delete") fallback_value = db.Column(db.Float(), nullable=True) disable_mqtt = db.Column(db.Boolean, nullable=False, default=True) type = db.Column(db.Enum(GenericPointType), nullable=False, default=GenericPointType.FLOAT) unit = db.Column(db.String, nullable=True) __table_args__ = (UniqueConstraint('name', 'device_uuid'), ) def __repr__(self): return f"Point(uuid = {self.uuid})" @validates('name') def validate_name(self, _, value): if not re.match("^([A-Za-z0-9_-])+$", value): raise ValueError( "name should be alphanumeric and can contain '_', '-'") return value @validates('value_operation') def validate_value_operation(self, _, value): try: if value and value.strip(): eval_arithmetic_expression(value.lower().replace( 'x', str(random.randint(1, 9)))) except Exception: raise ValueError( "Invalid value_operation, must be a valid arithmetic expression" ) return value @classmethod def find_by_name(cls, network_name: str, device_name: str, point_name: str): results = cls.query.filter_by(name=point_name) \ .join(DeviceModel).filter_by(name=device_name) \ .join(NetworkModel).filter_by(name=network_name) \ .first() return results def save_to_db(self): self.point_store = PointStoreModel.create_new_point_store_model( self.uuid) super().save_to_db() def update_point_value( self, point_store: PointStoreModel, cov_threshold: float = None, priority_array_write_obj: PriorityArrayModel = None) -> bool: if not point_store.fault: if cov_threshold is None: cov_threshold = self.cov_threshold value = point_store.value_original if value is not None: value = self.apply_scale(value, self.input_min, self.input_max, self.scale_min, self.scale_max) value = self.apply_value_operation(value, self.value_operation) value = round(value, self.value_round) point_store.value = self.apply_point_type(value) return point_store.update(cov_threshold, priority_array_write_obj) @validates('tags') def validate_tags(self, _, value): """ Rules for tags: - force all tags to be lower case - if there is a gap add an underscore - no special characters """ if value is not None: try: return validate_json(value) except ValueError: raise ValueError('tags needs to be a valid JSON') return value @validates('history_interval') def validate_history_interval(self, _, value): if self.history_type == HistoryType.INTERVAL and value is not None and value < 1: raise ValueError( "history_interval needs to be at least 1, default is 15 (in minutes)" ) return value @validates('input_min') def validate_input_min(self, _, value): if value is not None and self.input_max is not None and value > self.input_max: raise ValueError("input_min cannot be greater than input_max") return value @validates('input_max') def validate_input_max(self, _, value): if self.input_min is not None and value is not None and self.input_min > value: raise ValueError("input_min cannot be greater than input_max") return value @validates('scale_min') def validate_scale_min(self, _, value): if value is not None and self.scale_max is not None and value > self.scale_max: raise ValueError("scale_min cannot be greater than scale_max") return value @validates('scale_max') def validate_scale_max(self, _, value): if self.scale_min is not None and value is not None and self.scale_min > value: raise ValueError("scale_min cannot be greater than scale_max") return value def update(self, **kwargs) -> bool: publish_cov: bool = self.disable_mqtt != kwargs.get('disable_mqtt') changed: bool = super().update(**kwargs) updated: bool = self.update_point_value(self.point_store, 0) if updated or publish_cov: self.publish_cov(self.point_store) return changed def update_point_store(self, value: float, priority: int, priority_array_write: dict): priority_array_write_obj, highest_priority_value = self.update_priority_value_without_commit( value, priority, priority_array_write) point_store = PointStoreModel(point_uuid=self.uuid, value_original=highest_priority_value) updated = self.update_point_value( point_store, priority_array_write_obj=priority_array_write_obj) if updated: self.publish_cov(point_store) def update_priority_value_without_commit(self, value: float, priority: int, priority_array_write: dict) -> \ (Union[PriorityArrayModel, None], Union[float, None]): if priority_array_write: priority_array: PriorityArrayModel = PriorityArrayModel.find_by_point_uuid( self.uuid) if priority_array: highest_priority_value: float = priority_array.update_with_no_commit( **priority_array_write) return priority_array, highest_priority_value return None, None if not priority: priority = 16 if priority not in range(1, 17): raise ValueError('priority should be in range(1, 17)') if priority: priority_array: PriorityArrayModel = PriorityArrayModel.find_by_point_uuid( self.uuid) if priority_array: highest_priority_value: float = priority_array.update_with_no_commit( **{f"_{priority}": value}) return priority_array, highest_priority_value return None, None def update_priority_value(self, value: float, priority: int, priority_array_write: dict): self.update_priority_value_without_commit(value, priority, priority_array_write) db.session.commit() @classmethod def apply_value_operation(cls, original_value, value_operation: str) -> float or None: """Do calculations on original value with the help of point details""" if original_value is None or value_operation is None or not value_operation.strip( ): return original_value return eval_arithmetic_expression(value_operation.lower().replace( 'x', str(original_value))) @classmethod def apply_scale(cls, value: float, input_min: float, input_max: float, output_min: float, output_max: float) \ -> float or None: if value is None or input_min is None or input_max is None or output_min is None or output_max is None: return value if input_min == input_max or output_min == output_max: return value scaled = ( (value - input_min) / (input_max - input_min)) * (output_max - output_min) + output_min if scaled > max(output_max, output_min): return max(output_max, output_min) elif scaled < min(output_max, output_min): return min(output_max, output_min) else: return scaled def apply_point_type(self, value: float): if value is not None: if self.type == GenericPointType.STRING: value = None elif self.type == GenericPointType.INT: value = round(value, 0) elif self.type == GenericPointType.BOOL: value = float(bool(value)) return value def publish_cov(self, point_store: PointStoreModel, device: DeviceModel = None, network: NetworkModel = None, force_clear: bool = False): if point_store is None: raise Exception('Point.publish_cov point_store cannot be None') if device is None: device = DeviceModel.find_by_uuid(self.device_uuid) if network is None: network = NetworkModel.find_by_uuid(device.network_uuid) if device is None or network is None: raise Exception( f'Cannot find network or device for point {self.uuid}') priority = self._get_highest_priority_field() if self.history_enable \ and (self.history_type == HistoryType.COV or self.history_type == HistoryType.COV_AND_INTERVAL) \ and network.history_enable \ and device.history_enable: PointStoreHistoryModel.create_history(point_store) db.session.commit() if not self.disable_mqtt: from src.services.mqtt_client import MqttClient MqttClient.publish_point_cov(Drivers.GENERIC.name, network, device, self, point_store, force_clear, priority) def _get_highest_priority_field(self): for i in range(1, 17): value = getattr(self.priority_array_write, f'_{i}', None) if value is not None: return i return 16
class CommentFlag(db.Model): id = db.Column(db.Integer, primary_key=True) comment_id = db.Column(db.Integer, db.ForeignKey("comment.id")) user_id = db.Column(db.Integer, db.ForeignKey("user.id")) flag = db.Column(db.Enum(Flag))
class AnswerFlag(db.Model): id = db.Column(db.Integer, primary_key=True) answer_id = db.Column(db.Integer, db.ForeignKey("answer.id")) user_id = db.Column(db.Integer, db.ForeignKey("user.id")) flag = db.Column(db.Enum(Flag))
class BACnetPointModel(db.Model): __tablename__ = 'bac_points' uuid = db.Column(db.String(80), primary_key=True, nullable=False) object_type = db.Column(db.Enum(PointType), nullable=False) object_name = db.Column(db.String(80), nullable=False, unique=True) use_next_available_address = db.Column(db.Boolean(), nullable=False, default=False) address = db.Column(db.Integer(), nullable=True, unique=False) relinquish_default = db.Column(db.Float(), nullable=False) priority_array_write = db.relationship('PriorityArrayModel', backref='point', lazy=False, uselist=False, cascade="all,delete") event_state = db.Column(db.Enum(BACnetEventState), nullable=False) units = db.Column(db.Enum(Units), nullable=False) description = db.Column(db.String(120), nullable=False) enable = db.Column(db.Boolean(), nullable=False) fault = db.Column(db.Boolean(), nullable=True) data_round = db.Column(db.Integer(), nullable=True) data_offset = db.Column(db.Float(), nullable=True) cov = db.Column(db.Float(), nullable=True) source = db.Column(db.Enum(Sources), default=Sources.OWN) point_store = db.relationship('BACnetPointStoreModel', backref='point', lazy=False, uselist=False, cascade="all,delete") bp_gp_mapping = db.relationship('BPGPointMapping', backref='point', lazy=True, uselist=False, cascade="all,delete") created_on = db.Column(db.DateTime, server_default=db.func.now()) updated_on = db.Column(db.DateTime, server_default=db.func.now(), onupdate=db.func.now()) def __repr__(self): return f"BACnetPointModel({self.uuid})" @validates('object_name') def validate_object_name(self, _, value): if not re.match("^([A-Za-z0-9_-])+$", value): raise ValueError( "object_name should be alphanumeric and can contain '_', '-'") return value @classmethod def find_all(cls, *args, **kwargs): if 'source' in kwargs: return cls.query.filter_by(source=kwargs['source']).all() return cls.query.all() @classmethod def find_by_uuid(cls, uuid): return cls.query.filter_by(uuid=uuid).first() @classmethod def filter_by_uuid(cls, uuid): return cls.query.filter_by(uuid=uuid) @classmethod def find_by_object_id(cls, object_type, address): return cls.query.filter((BACnetPointModel.object_type == object_type) & (BACnetPointModel.address == address)).first() @classmethod def find_by_object_name(cls, object_name): return cls.query.filter( BACnetPointModel.object_name == object_name).first() @classmethod def get_next_available_address(cls, current_address): addresses = cls.query.filter().with_entities( BACnetPointModel.address).all() sorted_addresses = sorted({address[0] for address in addresses} - {current_address}) available_address = 1 for address in sorted_addresses: if address == available_address: available_address += 1 else: break return available_address @classmethod def delete_all_from_db(cls): cls.query.delete() db.session.commit() def save_to_db(self, priority_array_write: dict): self.priority_array_write = PriorityArrayModel(point_uuid=self.uuid, **priority_array_write) self.point_store = BACnetPointStoreModel.create_new_point_store_model( self.uuid) db.session.add(self) db.session.commit() def delete_from_db(self): db.session.delete(self) db.session.commit()