class LoadMeter(db.Model): __bind_key__ = 'ami' __tablename__ = 'meter' id = db.Column(db.Integer, primary_key=True) hes_id = db.Column(db.Integer, index=True, nullable=False) serial_no = db.Column(db.String(255)) customer_seq_no = db.Column(db.String(255)) phone_number = db.Column(db.String(255)) customer_name = db.Column(db.String(255)) branch = db.Column(db.String(10), nullable=False, server_default='') zone = db.Column(db.String(10), nullable=False, server_default='') online = db.Column('online', db.Boolean(), nullable=False, server_default='1') power = db.Column('power', db.Boolean(), nullable=False, server_default='1') ct = db.Column('ct', db.Boolean(), nullable=False, server_default='1') ak_ek = db.Column(db.String(255), nullable=False, server_default='30303030303030303030303030303030') initial = db.Column(db.String(10), nullable=False, server_default='') latlong = db.Column(db.String(30), nullable=False, server_default='') current_data = db.Column(db.String(30), nullable=False, server_default='') def __init__(self, **kwargs): # Call Flask-SQLAlchemy's constructor. super(LoadMeter, self).__init__(**kwargs)
class Sim(ResourceMixin, db.Model): __tablename__ = 'sim' id = db.Column(db.Integer, primary_key=True) phone_number = db.Column(db.String(25), unique=True, index=True) sim_serial = db.Column(db.String(25), unique=True, index=True, default='') def __init__(self, **kwargs): # Call Flask-SQLAlchemy's constructor. super(Imei, self).__init__(**kwargs) def __str__(self): return str(self.phone_number) + " : " + str(self.sim_serial) @classmethod def find_by_identity(cls, identity): """ Find a meter by its sequence_number or serial_number or phone_number. :param identity: sequence_number or serial_number or phone_number :type identity: str :return: Meter instance """ return Imei.query.filter((Imei.phone_number == identity) | (Imei.sim_serial == identity)).first() @classmethod def search(cls, query): """ Search a resource by 1 or more fields. :param query: Search query :type query: str :return: SQLAlchemy filter """ if not query: return '' search_query = '%{0}%'.format(query) search_chain = (Imei.phone_number.ilike(search_query), Imei.sim_serial.ilike(search_query)) return or_(*search_chain) @property def serialize(self): return { 'id': self.id, 'phone_number': self.meter_identifier, 'sim_serial': self.modem_imei, 'created_on': self.created_on, 'updated_on': self.updated_on, }
class LoadDemand(db.Model): __bind_key__ = 'ami' __tablename__ = 'demand_data' id = db.Column(db.Integer, primary_key=True) amount = db.Column(db.DECIMAL) created_at = db.Column(db.TIMESTAMP) meter_id = db.Column(db.Integer) obis = db.Column(db.String(255)) name = db.Column(db.String(255)) scalar = db.Column(db.Integer) unit = db.Column(db.String(5)) unit_value = db.Column(db.Integer) value = db.Column(db.DECIMAL) data = db.Column(db.String(45)) def __init__(self, **kwargs): # Call Flask-SQLAlchemy's constructor. super(LoadDemand, self).__init__(**kwargs)
class User(UserMixin, ResourceMixin, db.Model): ROLE = OrderedDict([ ('member', 'Member'), ('admin', 'Admin') ]) __tablename__ = 'users' id = db.Column(db.Integer, primary_key=True) # Authentication. role = db.Column(db.Enum(*ROLE, name='role_types', native_enum=False), index=True, nullable=False, server_default='member') active = db.Column('is_active', db.Boolean(), nullable=False, server_default='1') username = db.Column(db.String(24), unique=True, index=True) email = db.Column(db.String(255), unique=True, index=True, nullable=False, server_default='') password = db.Column(db.String(128), nullable=False, server_default='') # Activity tracking. sign_in_count = db.Column(db.Integer, nullable=False, default=0) current_sign_in_on = db.Column(db.TIMESTAMP) # AwareDateTime()) current_sign_in_ip = db.Column(db.String(45)) last_sign_in_on = db.Column(db.TIMESTAMP) # last_sign_in_ip = db.Column(db.String(45)) def __init__(self, **kwargs): # Call Flask-SQLAlchemy's constructor. super(User, self).__init__(**kwargs) self.password = User.encrypt_password(kwargs.get('password', '')) @classmethod def find_by_identity(cls, identity): """ Find a user by their e-mail or username. :param identity: Email or username :type identity: str :return: User instance """ return User.query.filter( (User.email == identity) | (User.username == identity)).first() @classmethod def encrypt_password(cls, plaintext_password): """ Hash a plaintext string using PBKDF2. This is good enough according to the NIST (National Institute of Standards and Technology). In other words while bcrypt might be superior in practice, if you use PBKDF2 properly (which we are), then your passwords are safe. :param plaintext_password: Password in plain text :type plaintext_password: str :return: str """ if plaintext_password: return generate_password_hash(plaintext_password) return None @classmethod def deserialize_token(cls, token): """ Obtain a user from de-serializing a signed token. :param token: Signed token. :type token: str :return: User instance or None """ private_key = TimedJSONWebSignatureSerializer( current_app.config['SECRET_KEY']) try: decoded_payload = private_key.loads(token) return User.find_by_identity(decoded_payload.get('user_email')) except Exception: return None @classmethod def initialize_password_reset(cls, identity): """ Generate a token to reset the password for a specific user. :param identity: User e-mail address or username :type identity: str :return: User instance """ u = User.find_by_identity(identity) reset_token = u.serialize_token() # This prevents circular imports. from ami.blueprints.user.tasks import ( deliver_password_reset_email) deliver_password_reset_email.delay(u.id, reset_token) return u @classmethod def search(cls, query): """ Search a resource by 1 or more fields. :param query: Search query :type query: str :return: SQLAlchemy filter """ if not query: return '' search_query = '%{0}%'.format(query) search_chain = (User.email.ilike(search_query), User.username.ilike(search_query)) return or_(*search_chain) @classmethod def is_last_admin(cls, user, new_role, new_active): """ Determine whether or not this user is the last admin account. :param user: User being tested :type user: User :param new_role: New role being set :type new_role: str :param new_active: New active status being set :type new_active: bool :return: bool """ is_changing_roles = user.role == 'admin' and new_role != 'admin' is_changing_active = user.active is True and new_active is None if is_changing_roles or is_changing_active: admin_count = User.query.filter(User.role == 'admin').count() active_count = User.query.filter(User.is_active is True).count() if admin_count == 1 or active_count == 1: return True return False def is_active(self): """ Return whether or not the user account is active, this satisfies Flask-Login by overwriting the default value. :return: bool """ return self.active def get_auth_token(self): """ Return the user's auth token. Use their password as part of the token because if the user changes their password we will want to invalidate all of their logins across devices. It is completely fine to use md5 here as nothing leaks. This satisfies Flask-Login by providing a means to create a token. :return: str """ private_key = current_app.config['SECRET_KEY'] serializer = URLSafeTimedSerializer(private_key) data = [str(self.id), md5(self.password.encode('utf-8')).hexdigest()] return serializer.dumps(data) def authenticated(self, with_password=True, password=''): """ Ensure a user is authenticated, and optionally check their password. :param with_password: Optionally check their password :type with_password: bool :param password: Optionally verify this as their password :type password: str :return: bool """ if with_password: return check_password_hash(self.password, password) return True def serialize_token(self, expiration=3600): """ Sign and create a token that can be used for things such as resetting a password or other tasks that involve a one off token. :param expiration: Seconds until it expires, defaults to 1 hour :type expiration: int :return: JSON """ private_key = current_app.config['SECRET_KEY'] serializer = TimedJSONWebSignatureSerializer(private_key, expiration) return serializer.dumps({'user_email': self.email}).decode('utf-8') def update_activity_tracking(self, ip_address): """ Update various fields on the user that's related to meta data on their account, such as the sign in count and ip address, etc.. :param ip_address: IP address :type ip_address: str :return: SQLAlchemy commit results """ self.sign_in_count += 1 self.last_sign_in_on = self.current_sign_in_on self.last_sign_in_ip = self.current_sign_in_ip self.current_sign_in_on = datetime.datetime.now()#pytz.utc self.current_sign_in_ip = ip_address return self.save()
class Meter(ResourceMixin, db.Model): ROLE = OrderedDict([ ('dev', 'Development'), ('pro', 'Production') ]) __tablename__ = 'meters' id = db.Column(db.Integer, primary_key=True) sequence_number = db.Column(db.String(20), unique=True, index=True) serial_number = db.Column(db.String(20), unique=True, index=True, nullable=False, server_default='') phone_number = db.Column(db.String(20), unique=True, index=True, nullable=False, server_default='') customer_name = db.Column(db.String(255), nullable=False, server_default='') ak_ek = db.Column(db.String(255), nullable=False, server_default='30303030303030303030303030303030') branch = db.Column(db.String(10), nullable=False, server_default='') zone = db.Column(db.String(10), nullable=False, server_default='') initial_reading = db.Column(db.String(10), nullable=False, server_default='') lat_long = db.Column(db.String(30), nullable=False, server_default='') online = db.Column('is_online', db.Boolean(), nullable=False, server_default='1') power = db.Column('power_on', db.Boolean(), nullable=False, server_default='1') ct = db.Column('is_ct', db.Boolean(), nullable=False, server_default='1') active = db.Column('is_active', db.Boolean(), nullable=False, server_default='1') def __init__(self, **kwargs): # Call Flask-SQLAlchemy's constructor. super(Meter, self).__init__(**kwargs) @classmethod def find_by_identity(cls, identity): """ Find a meter by its sequence_number or serial_number or phone_number. :param identity: sequence_number or serial_number or phone_number :type identity: str :return: Meter instance """ return Meter.query.filter( (Meter.sequence_number == identity) | (Meter.phone_number == identity) | ( Meter.serial_number == identity)).first() def is_active(self): """ Return whether or not the user account is active, this satisfies Flask-Login by overwriting the default value. :return: bool """ return self.active @classmethod def search(cls, query): """ Search a resource by 1 or more fields. :param query: Search query :type query: str :return: SQLAlchemy filter """ if not query: return '' search_query = '%{0}%'.format(query) search_chain = (Meter.sequence_number.ilike(search_query), Meter.serial_number.ilike(search_query), Meter.phone_number.ilike(search_query)) return or_(*search_chain) @property def serialize(self): return { 'id': self.id, 'serial_number': self.serial_number, 'sequence_number': self.sequence_number, 'phone_number': self.phone_number, 'customer_name': self.customer_name, 'ak_ek': self.ak_ek, 'active': self.active, 'branch': self.branch, 'zone': self.zone, 'lat_long': self.lat_long, 'initial_reading': self.initial_reading, 'ct': self.ct, 'power': self.power, 'online': self.online, 'created_on': self.created_on, 'updated_on': self.updated_on, }
class DailyData(ResourceMixin, db.Model): __tablename__ = 'daily_data_' id = db.Column(db.Integer, primary_key=True) meter_id = db.Column(db.Integer, db.ForeignKey('meters.id', onupdate='CASCADE', ondelete='CASCADE'), unique=True, index=True, nullable=False) meter = db.relationship(Meter, uselist=False, backref='daily_data_', passive_deletes=True) capture_time = db.Column(db.TIMESTAMP, default=tzware_datetime, unique=True) active_increase = db.Column(db.String(25), nullable=False, server_default='') total_active = db.Column(db.String(25), nullable=False, server_default='') import_active = db.Column(db.String(25), nullable=False, server_default='') export_active = db.Column(db.String(25), nullable=False, server_default='') import_apparent = db.Column(db.String(25), nullable=False, server_default='') export_apparent = db.Column(db.String(25), nullable=False, server_default='') import_reactive = db.Column(db.String(25), nullable=False, server_default='') export_reactive = db.Column(db.String(25), nullable=False, server_default='') def __init__(self, **kwargs): # Call Flask-SQLAlchemy's constructor. super(DailyData, self).__init__(**kwargs) def set_value(self, obis, value): if obis == "1.0.15.8.0.255": # cumulative_total_active_energy self.total_active = value elif obis == "1.0.1.8.0.255": # cumulative_import_active_energy self.import_active = value elif obis == "1.0.2.8.0.255": # cumulative_export_active_energy self.export_active = value elif obis == "1.0.3.8.0.255": # cumulative_import_reactive_energy self.import_reactive = value elif obis == "1.0.4.8.0.255": # cumulative_import_reactive_energy self.export_reactive = value elif obis == "1.0.9.8.0.255": # cumulative_import_apparent_energy self.import_apparent = value elif obis == "1.0.10.8.0.255": # cumulative_export_apparent_energy self.export_apparent = value elif obis == "1.0.15.19.0.255": # current_day_active_energy_increase self.active_increase = value @classmethod def query_search(cls, query): """ Search a resource by 1 or more fields. :param query: Search query :type query: str :return: SQLAlchemy filter """ if query: search_query = '%{0}%'.format(query) query_chain = (DailyData.meter.property.mapper.class_. sequence_number.ilike(search_query), DailyData.meter.property.mapper.class_. serial_number.ilike(search_query)) return or_(*query_chain) return '' @classmethod def date_search(cls, start_date, end_date): """ Search a resource by 1 or more fields. :param end_date: :param start_date: :return: SQLAlchemy filter """ if start_date and end_date: date_chain = (DailyData.capture_time >= start_date, DailyData.capture_time <= end_date) return and_(*date_chain) return '' @property def serialize(self): return { 'id': self.id, 'meter_id': self.meter_id, 'active_increase': self.active_increase, 'total_active': self.total_active, 'import_active': self.import_active, 'export_active': self.export_active, 'sequence_number': self.meter.sequence_number, 'capture_time': self.capture_time, }
class AlertData(ResourceMixin, db.Model): __tablename__ = 'alerts' id = db.Column(db.Integer, primary_key=True) # Relationships. meter_id = db.Column(db.Integer, db.ForeignKey('meters.id', onupdate='CASCADE', ondelete='CASCADE'), index=True, nullable=False) meter = db.relationship(Meter, uselist=False, backref='alerts', passive_deletes=True) capture_time = db.Column(db.TIMESTAMP, default=tzware_datetime, unique=True, index=True) code = db.Column(db.Integer) name = db.Column(db.String(25), nullable=False, server_default='') def __init__(self, **kwargs): # Call Flask-SQLAlchemy's constructor. super(AlertData, self).__init__(**kwargs) @classmethod def query_search(cls, query): """ Search a resource by 1 or more fields. :param query: Search query :type query: str :return: SQLAlchemy filter """ if query: search_query = '%{0}%'.format(query) query_chain = (AlertData.meter.property.mapper.class_. sequence_number.ilike(search_query), AlertData.meter.property.mapper.class_. serial_number.ilike(search_query)) return or_(*query_chain) return '' @classmethod def date_search(cls, start_date, end_date): """ Search a resource by 1 or more fields. :param end_date: :param start_date: :return: SQLAlchemy filter """ if start_date and end_date: date_chain = (AlertData.capture_time >= start_date, AlertData.capture_time <= end_date) return and_(*date_chain) return '' @property def serialize(self): return { 'id': self.id, 'meter_id': self.meter_id, 'code': self.code, 'name': self.name, 'sequence_number': self.meter.sequence_number, 'capture_time': self.capture_time, }
class DemandData(ResourceMixin, db.Model): __tablename__ = 'demand_data_' id = db.Column(db.Integer, primary_key=True) meter_id = db.Column(db.Integer, db.ForeignKey('meters.id', onupdate='CASCADE', ondelete='CASCADE'), unique=True, index=True, nullable=False) meter = db.relationship(Meter, uselist=False, backref='demand_data', passive_deletes=True) capture_time = db.Column(db.TIMESTAMP, default=tzware_datetime, unique=True, index=True) total_active = db.Column(db.String(25), nullable=False, server_default='') import_active = db.Column(db.String(25), nullable=False, server_default='') export_active = db.Column(db.String(25), nullable=False, server_default='') total_import_apparent = db.Column(db.String(25), nullable=False, server_default='') total_export_apparent = db.Column(db.String(25), nullable=False, server_default='') power_on_duration = db.Column(db.String(25), nullable=False, server_default='') current_x = db.Column(db.String(255), nullable=False, server_default='') current_y = db.Column(db.String(255), nullable=False, server_default='') current_z = db.Column(db.String(255), nullable=False, server_default='') current_neutral = db.Column(db.String(255), nullable=False, server_default='') voltage_x = db.Column(db.String(255), nullable=False, server_default='') voltage_y = db.Column(db.String(255), nullable=False, server_default='') voltage_z = db.Column(db.String(255), nullable=False, server_default='') reactive_power = db.Column(db.String(25), nullable=False, server_default='') apparent_power = db.Column(db.String(25), nullable=False, server_default='') active_power = db.Column(db.String(25), nullable=False, server_default='') billing_count = db.Column(db.String(25), nullable=False, server_default='') output_state = db.Column(db.String(25), nullable=False, server_default='') power_down_count = db.Column(db.String(25), nullable=False, server_default='') program_count = db.Column(db.String(25), nullable=False, server_default='') tamper_count = db.Column(db.String(25), nullable=False, server_default='') def __init__(self, **kwargs): # Call Flask-SQLAlchemy's constructor. super(DemandData, self).__init__(**kwargs) @classmethod def query_search(cls, query): """ Search a resource by 1 or more fields. :param query: Search query :type query: str :return: SQLAlchemy filter """ if query: search_query = '%{0}%'.format(query) query_chain = (DemandData.meter.property.mapper.class_. sequence_number.ilike(search_query), DemandData.meter.property.mapper.class_. serial_number.ilike(search_query)) return or_(*query_chain) return '' @classmethod def date_search(cls, start_date, end_date): """ Search a resource by 1 or more fields. :param end_date: :param start_date: :return: SQLAlchemy filter """ if start_date and end_date: date_chain = (DemandData.capture_time >= start_date, DemandData.capture_time <= end_date) return and_(*date_chain) return '' def set_value(self, obis, value): if obis == "0.0.96.3.10.255": # OUTPUT_STATE: self.output_state = value elif obis == "1.0.0.1.0.255": # BILLING_COUNT self.billing_count = value elif obis == "0.0.96.7.0.255": # POWER_DOWN_COUNT self.power_down_count = value elif obis == "0.0.96.2.0.255": # PROGRAM_COUNT self.program_count = value elif obis == "0.0.96.91.0.255": # TAMPER_COUNT self.tamper_count = value elif obis == "1.0.32.7.0.255": # VOLTAGE_PHASE_ONE self.voltage_x = value elif obis == "1.0.31.7.0.255": # CURRENT_PHASE_ONE self.current_x = value elif obis == "1.0.91.7.0.255": # NEUTRAL self.current_neutral = value elif obis == "1.0.1.7.0.255": # ACTIVE_POWER self.active_power = value elif obis == "1.0.3.7.0.255": # REACTIVE_POWER self.reactive_power = value elif obis == "1.0.9.7.0.255": # APPARENT_POWER self.apparent_power = value elif obis == "0.0.96.91.14.255": # POWER_ON_DURATION self.power_on_duration = value elif obis == "1.0.13.7.0.255": # POWER_FACTOR self.power_factor = value elif obis == "1.0.14.7.0.255": # FREQUENCY self.frequency = value elif obis == "1.0.51.7.0.255": # CURRENT_PHASE_TWO self.current_y = value elif obis == "1.0.52.7.0.255": # VOLTAGE_PHASE_TWO self.voltage_y = value elif obis == "1.0.71.7.0.255": # CURRENT_PHASE_THREE self.current_z = value elif obis == "1.0.72.7.0.255": # VOLTAGE_PHASE_THREE self.voltage_z = value elif obis == "1.0.9.8.0.255": # TOTAL_IMPORT_APPARENT self.total_import_apparent = value elif obis == "1.0.1.8.0.255": # TOTAL_IMPORT_ACTIVE self.total_import_active = value elif obis == "1.0.10.8.0.255": # TOTAL_EXPORT_APPARENT self.total_export_apparent = value elif obis == "1.0.2.8.0.255": # EXPORT_ACTIVE self.total_export_active = value
class HourlyData(ResourceMixin, db.Model): __tablename__ = 'hourly_data_' id = db.Column(db.Integer, primary_key=True) meter_id = db.Column(db.Integer, db.ForeignKey('meters.id', onupdate='CASCADE', ondelete='CASCADE'), unique=True, index=True, nullable=False) meter = db.relationship(Meter, uselist=False, backref='hourly_data', passive_deletes=True) capture_time = db.Column(db.TIMESTAMP, default=tzware_datetime, unique=True, index=True) block_import_active = db.Column(db.String(25), nullable=False, server_default='') block_export_active = db.Column(db.String(25), nullable=False, server_default='') block_import_apparent = db.Column(db.String(25), nullable=False, server_default='') block_export_apparent = db.Column(db.String(25), nullable=False, server_default='') block_import_reactive = db.Column(db.String(25), nullable=False, server_default='') block_export_reactive = db.Column(db.String(25), nullable=False, server_default='') profile_status = db.Column(db.String(25), nullable=False, server_default='') def __init__(self, **kwargs): # Call Flask-SQLAlchemy's constructor. super(HourlyData, self).__init__(**kwargs) def set_value(self, obis, value): if obis == "1.0.1.29.0.255": # BLOCK_IMPORT_ACTIVE = "1.0.1.29.0.255" self.block_import_active = value elif obis == "1.0.2.29.0.255": # BLOCK_EXPORT_ACTIVE = "1.0.2.29.0.255" self.block_export_active = value elif obis == "1.0.3.29.0.255": # BLOCK_IMPORT_REACTIVE = "1.0.3.29.0.255" self.block_export_reactive = value elif obis == "1.0.4.29.0.255": # BLOCK_EXPORT_REACTIVE = "1.0.4.29.0.255" self.block_export_reactive = value elif obis == "1.0.9.29.0.255": # BLOCK_IMPORT_APPARENT = "1.0.9.29.0.255" self.block_import_apparent = value elif obis == "1.0.10.29.0.255": # BLOCK_EXPORT_APPARENT = "1.0.10.29.0.255" self.block_export_apparent = value elif obis == "0.0.96.10.1.255": # PROFILE_STATUS = "0.0.96.10.1.255" self.profile_status = value @classmethod def query_search(cls, query): """ Search a resource by 1 or more fields. :param query: Search query :type query: str :return: SQLAlchemy filter """ if query: search_query = '%{0}%'.format(query) query_chain = (HourlyData.meter.property.mapper.class_. sequence_number.ilike(search_query), HourlyData.meter.property.mapper.class_. serial_number.ilike(search_query)) return or_(*query_chain) return '' @classmethod def date_search(cls, start_date, end_date): """ Search a resource by 1 or more fields. :param end_date: :param start_date: :return: SQLAlchemy filter """ if start_date and end_date: date_chain = (HourlyData.capture_time >= start_date, HourlyData.capture_time <= end_date) return and_(*date_chain) return '' @property def serialize(self): return { 'id': self.id, 'meter_id': self.meter_id, 'import_active': self.block_import_active, 'export_active': self.block_export_active, 'import_reactive': self.block_import_reactive, 'export_reactive': self.block_export_reactive, 'import_apparent': self.block_import_apparent, 'bexport_apparent': self.block_export_apparent, 'sequence_number': self.meter.sequence_number, 'capture_time': self.capture_time, }