class News(db.Model, AccountDb): id_field = "news_id" unique_field = "subject" news_id = Column(db.Integer, primary_key=True) subject = Column(db.String(78)) body = Column(db.Text) published = Column(db.DateTime()) deleted = Column(db.DateTime()) display_fields = frozenset( ['news_id', 'subject', 'body', 'published', 'deleted']) @classmethod @duplicate_handle(errors.NewsAlreadyExist) def create_news(cls, subject, body): news = cls() news.subject = subject news.body = body news.published = None news.deleted = None db.session.add(news) return news def update(self, new_parameters): if self.published: new_parameters['published'] = utcnow().datetime super().update(new_parameters) def publish(self, publish): self.published = utcnow().datetime if publish else None
class Consulta(db.Model): __tablename__ = 'consulta_medica_consulta' id = db.Column(db.String(64), primary_key=True) start_date = db.Column(db.DateTime(), nullable=False) end_date = db.Column(db.DateTime()) physician_id = db.Column(db.String(64), nullable=False) patient_id = db.Column(db.String(64), nullable=False) price = db.Column(db.Numeric(17, 4), nullable=False)
class TimeState(db.Model, AccountDb): time_state_id = Column(db.Integer, primary_key=True) name = Column(db.String(64), nullable=False) customer_id = Column(db.Integer, ForeignKey("customer.customer_id", ondelete="CASCADE"), nullable=False) scheduled_at = Column(db.DateTime(), index=True, nullable=False) action = Column(db.String(64), nullable=False) step = Column(db.Integer) customer = relationship("Customer") __table_args__ = (UniqueConstraint('customer_id', 'name'),) def __str__(self): return "<TimeState %s of %s. %s scheduled at %s>" % (self.name, self.customer, self.action, self.scheduled_at) def __init__(self, name, customer_id, scheduled_at, action): self.name = name self.customer_id = customer_id self.scheduled_at = scheduled_at self.action = action self.step = 0 @classmethod def get_actual_actions(cls, now=None): now = now or utcnow().datetime return cls.query.filter(cls.scheduled_at < now) def remove(self): db.session.delete(self) @classmethod def get_by_customer(cls, customer_id, name): return cls.query.filter(cls.customer_id == customer_id, cls.name == name).first()
class Tenant(db.Model, AccountDb): """Model for storage of metadata related to a tenant.""" # ID is a uuid tenant_id = Column(String(32), primary_key=True, nullable=False) name = Column(Text, nullable=False) created = Column(DateTime, nullable=False) last_collected = Column(DateTime) deleted = Column(db.DateTime()) @classmethod def create(cls, tenant_id, tenant_name, created_at=None): created_at = created_at or utcnow().datetime tenant = cls() tenant.tenant_id = tenant_id tenant.name = tenant_name tenant.created = created_at or utcnow().datetime tenant.last_collected = created_at db.session.add(tenant) db.session.flush() # can't assume deferred constraints. return tenant def __str__(self): return "<Tenant %s %s>" % (self.tenant_id, self.name) @classmethod def all_active_tenants(cls): return cls.query.filter_by(deleted=None) def mark_removed(self): logbook.info("Remove {} from db", self) self.deleted = utcnow().datetime
class telegramGroup(db.Model): __tablename__ = 'telegramGroup' __table_args__ = {'mysql_engine': 'InnoDB', 'mysql_charset': 'utf8mb4'} id = db.Column(db.Integer, primary_key=True) title = db.Column(db.VARCHAR(255)) photo = db.Column(db.VARCHAR(255)) username = db.Column(db.VARCHAR(255)) date = db.Column(db.DateTime(6)) def __init__(self, id, title, photo, username, date): self.id = id self.title = title self.photo = photo self.username = username self.date = date def __repr__(self): return "<teleGramGroup %r>" % self.username def to_json(self): item = self.__dict__ if "_sa_instance_state" in item: del item["_sa_instance_state"] item['photo'] = 'http://localhost:5000/download?filepath=' + item[ 'photo'] item['date'] = item['date'].strftime("%Y-%m-%d-%H") return item
class Message(db.Model): __table_name__ = 'message' msgId = db.Column(db.Integer, primary_key=True) isSend = db.Column(db.Integer) senderId = db.Column(db.Integer) content = db.Column(db.VARCHAR(1000)) createTime = db.Column(db.DateTime(6)) intent = db.Column(db.VARCHAR(255)) def __init__(self, msgId, isSend, senderId, content, createTime, intent): self.senderId = senderId self.msgId = msgId self.isSend = isSend self.content = content self.intent = intent self.createTime = createTime def __repr__(self): return "<Message %r>" % self.msgId def to_json(self): item = self.__dict__ if "_sa_instance_state" in item: del item["_sa_instance_state"] item['createTime'] = item['createTime'].strftime("%Y-%m-%d-%H") return item
class Tariff(db.Model, AccountDb): id_field = "tariff_id" unique_field = "localized_name" tariff_id = Column(db.Integer, primary_key=True) localized_name = relationship("TariffLocalization", cascade="all") description = Column(db.Text()) currency = Column(db.String(3)) parent_id = Column(db.Integer, ForeignKey('tariff.tariff_id', ondelete='CASCADE')) parent = relationship('Tariff', remote_side=[tariff_id]) deleted = Column(db.DateTime()) created = Column(db.DateTime()) modified = Column(db.DateTime()) services = relationship("ServicePrice", cascade="save-update, merge, delete, delete-orphan") mutable = Column(db.Boolean()) default = Column(db.Boolean(), index=True) history = relationship('TariffHistory', remote_side=[tariff_id], lazy="dynamic", cascade="all") display_fields = frozenset(["description", "created", "deleted", "tariff_id", "parent_id", "mutable", "default", "currency", "modified"]) def __str__(self): return "<Tariff %s>" % self.name @property def name(self): return self.localized_name_as_dict()[DEFAULT_LANGUAGE].localized_name def localized_name_as_dict(self): return {localization.language: localization for localization in self.localized_name} def update_localized_name(self, localized_name): current = self.localized_name_as_dict() for language, value in localized_name.items(): if language in current: current[language].localized_name = value else: self.localized_name.append(TariffLocalization.create_localization(language, value)) def get_localized_name(self, language): localized_name = self.localized_name_as_dict() return (localized_name.get(language) or localized_name[DEFAULT_LANGUAGE]).localized_name @classmethod @duplicate_handle(errors.TariffAlreadyExists) def create_tariff(cls, localized_name, description, currency, parent_id=None, services=None): tariff = cls() tariff.description = description tariff.currency = currency.upper() tariff.parent_id = parent_id now = utcnow().datetime tariff.created = now tariff.modified = now tariff.deleted = None tariff.update_localized_name(localized_name) tariff.mutable = True if parent_id and not services: tariff.update_services(Tariff.get_by_id(parent_id).services) if services: tariff.update_services(services) db.session.add(tariff) db.session.flush() return tariff @duplicate_handle(errors.TariffAlreadyExists) def update(self, localized_name=None, description=None, services=None, currency=None): if not self.mutable: if services and self.services_to_change(): return self.update_new_vm_services(services) raise errors.ImmutableTariff() if self.deleted: raise errors.RemovedTariff() if localized_name: self.update_localized_name(localized_name) if description: self.description = description if services: self.update_services(services) if currency: self.currency = currency.upper() if db.session.is_modified(self): self.modified = utcnow().datetime return True return False def services_as_dict(self, lower=False): lower_func = (lambda x: x.lower()) if lower else lambda x: x return {lower_func(service.service_id): service for service in self.services} def service_price(self, service_id): service_id = str(service_id) sp = self.services_as_dict(lower=True).get(service_id.lower()) if not sp: logbook.warning("Tariff {} don't have service {}", self, service_id) return None return sp.price def services_to_change(self): return {service.service_id for service in self.services if service.need_changing} def service_ids(self): return {service.service_id for service in self.services} def flavors(self): services = set() for service_price in self.services: service = service_price.service if service.category_id == Category.VM: services.add(service.flavor.flavor_id) return frozenset(services) def update_new_vm_services(self, services): current_services = self.services_as_dict() services_to_update = self.services_to_change() for service in services: service_id = service['service_id'] if service_id in services_to_update: current_services[service_id].price = service['price'] current_services[service_id].need_changing = False def update_services(self, services): current_services = self.services_as_dict() new_services = set() for service in services: if isinstance(service, dict): service_id = service["service_id"] price = service["price"] else: service_id = service.service_id price = service.price if service_id in current_services: current_services[service_id].price = price else: self.services.append(ServicePrice(service_id, price)) new_services.add(service_id) removed_services = set(current_services.keys()) - new_services if removed_services: logbook.debug("Remove services {} from tariff {}", removed_services, self) for service_id in removed_services: self.services.remove(current_services[service_id]) def display(self, short=True): res = super().display(short) res["localized_name"] = {loc.language: loc.localized_name for loc in self.localized_name} if not short: res["services"] = [s.display() for s in self.services] # HACK to show used customers by request if isinstance(short, list): res["used"] = self.used() return res def display_for_customer(self): tariff_info = { "services": [s.display() for s in self.services], "localized_name": {loc.language: loc.localized_name for loc in self.localized_name} } return tariff_info def remove(self): if self.deleted: return False if self.used() > 0 or self.deferred_changes(): raise errors.RemoveUsedTariff() self.deleted = utcnow().datetime return True def mark_immutable(self): if self.mutable: self.mutable = False self.modified = utcnow().datetime return True return False @classmethod def delete_by_prefix(cls, prefix, field=None): field = field or cls.unique_field if not field: raise Exception("Field for removing is not set") member = getattr(cls, field) if field == "localized_name": query = cls.query.join(Tariff.localized_name).\ filter(TariffLocalization.localized_name.ilike("%{}%".format(prefix + "%"))) else: query = cls.query.filter(member.like(prefix + "%")) query = query.filter(or_(cls.default == None, cls.default == False)) ids = [tariff_id for tariff_id, in query.with_entities(cls.id_field)] if not ids: return 0 ServicePrice.query.filter(ServicePrice.tariff_id.in_(ids)).delete(False) TariffHistory.query.filter(TariffHistory.tariff_id.in_(ids)).delete(False) TariffLocalization.query.filter(TariffLocalization.parent_id.in_(ids)).delete(False) return cls.query.filter(cls.tariff_id.in_(ids)).delete("fetch") @classmethod def get_default(cls): return cls.query.filter(cls.default == True) def make_default(self): if self.default: return if self.deleted: raise errors.RemovedTariff() if self.mutable: raise errors.MutableTariffCantBeDefault() self.get_default().update({"default": False}) db.session.flush() self.default = True def used(self): from model import Customer """ Returns number of customer who use this tariff """ return Customer.query.filter(Customer.tariff_id == self.tariff_id, Customer.deleted == None).count() def deferred_changes(self): from model import Deferred """ Returns number of usages in deferred changes """ return Deferred.query.filter(Deferred.tariff_id == self.tariff_id).count() def get_history(self, date_before, date_after): history = self.history.filter(TariffHistory.tariff_id == self.tariff_id) if date_after: history = history.filter(TariffHistory.date > date_after) if date_before: history = history.filter(TariffHistory.date < date_before) return history.order_by(desc(TariffHistory.history_id))
class TariffHistory(db.Model, AccountDb): EVENT_CREATE = "create" EVENT_UPDATE = "update" EVENT_DELETE = "delete" EVENT_ASSIGN = "assign" EVENT_UNASSIGN = "unassign" EVENT_IMMUTABLE = "immutable" EVENTS = {EVENT_CREATE, EVENT_UPDATE, EVENT_DELETE} MAX_AGE_OF_UPDATES_TO_AUTO_COLLAPSE = timedelta(seconds=conf.backend.tariff.max_age_of_updates_to_auto_collapse) history_id = Column(db.Integer, primary_key=True) event = Column(db.String(16)) user_id = Column(db.Integer, ForeignKey('user.user_id', ondelete="set null")) user = relationship("User") tariff_id = Column(db.Integer, ForeignKey('tariff.tariff_id')) tariff = relationship("Tariff") customer_id = Column(db.Integer, ForeignKey('customer.customer_id')) customer = relationship("Customer") date = Column(db.DateTime()) snapshot = deferred(Column(db.Text())) display_fields = frozenset(("history_id", "user", "event", "date", "localized_name", "snapshot")) display_fields_short = frozenset(("history_id", "user", "event", "date", "localized_name")) extract_fields = {"user": User.display} def __str__(self): return "<TariffHistory '%s' %s by %s>" % (self.tariff.name, self.event, self.date) def __repr__(self): return str(self) @classmethod def create(cls, event, user_id, tariff, customer=None): history_item = cls() history_item.event = event history_item.user_id = user_id history_item.tariff = tariff history_item.customer = customer history_item.date = tariff.modified history_item.snapshot = json.dumps(tariff.display(short=True), cls=DateTimeJSONEncoder) db.session.add(history_item) db.session.flush() if event == cls.EVENT_UPDATE: history_item._reduce_history_of_update_operations() return history_item def _reduce_history_of_update_operations(self): query = self.query.filter(TariffHistory.history_id != self.history_id, TariffHistory.tariff_id == self.tariff_id, TariffHistory.event == self.EVENT_UPDATE, TariffHistory.user_id == self.user_id, TariffHistory.date <= self.date + timedelta(seconds=1), TariffHistory.date >= self.date - self.MAX_AGE_OF_UPDATES_TO_AUTO_COLLAPSE) n = query.delete(False) logbook.debug("Reduced {} history records for tariff {}", n, self.tariff) return n @property def localized_name(self): return conf.tariff.events.get(self.event)
class User(db.Model, AccountDb): id_field = "user_id" unique_field = "email" user_id = Column(db.Integer, primary_key=True) email = Column(db.String(254), nullable=False, unique=True) password = Column(db.String(100), nullable=False) role = Column(db.String(16), nullable=False) name = Column(db.String(256)) deleted = Column(db.DateTime()) created = Column(db.DateTime()) display_fields = frozenset(("user_id", "email", "role", "name", "created", "deleted")) display_fields_short = frozenset(("user_id", "name")) extract_fields = {"role": Role.display} def __str__(self): return "<User {0.email} ({0.role})>".format(self) @classmethod def password_hashing(cls, raw_password): return pbkdf2_sha256.encrypt(raw_password, rounds=conf.user.salt_rounds, salt_size=10) def check_password(self, raw_password): return pbkdf2_sha256.verify(raw_password, self.password) @classmethod @duplicate_handle(errors.UserAlreadyExists) def new_user(cls, email, password, role, name=None): user = cls.get_by_email(email) if user: if user.deleted is None: raise errors.UserAlreadyExists() existed = True else: user = cls() existed = False user.email = email user.password = cls.password_hashing(password) if password else "" user.role = role user.name = name user.deleted = None user.created = utcnow().datetime if not existed: db.session.add(user) return user @classmethod def get_by_email(cls, email, include_deleted=False): query = cls.query.filter_by(email=email) if not include_deleted: query.filter_by(deleted=None) return query.first() @classmethod def login(cls, email, password): user = User.get_by_email(email) if user is None: logbook.info("User {} is not found", email) raise errors.UserUnauthorized() if user.deleted: logbook.info("User {} is deleted at {}", email, user.deleted) raise errors.UserUnauthorized() if not user.check_password(password): logbook.info("Password mismatch for user {}", email) raise errors.UserUnauthorized() token = UserToken.create(user) return token, user def update(self, new_parameters): password = new_parameters.get("password") if password: new_parameters["password"] = self.password_hashing(password) return super().update(new_parameters) def mark_removed(self): res = super().mark_removed() UserToken.remove_by(self.user_id) return res def password_reset(self, password): self.password = self.password_hashing(password)
class Customer(db.Model, AccountDb): id_field = "customer_id" unique_field = "email" CUSTOMER_TYPE_PRIVATE_PERSON = "private" CUSTOMER_TYPE_LEGAL_ENTITY = "entity" CUSTOMER_PRODUCTION_MODE = "production" CUSTOMER_PENDING_PRODUCTION_MODE = "pending_prod" CUSTOMER_TEST_MODE = "test" OPENSTACK_DASHBOARD_HORIZON = "horizon" OPENSTACK_DASHBOARD_SKYLINE = "skyline" OPENSTACK_DASHBOARD_BOTH = "both" ALL_MODES = [CUSTOMER_TEST_MODE, CUSTOMER_PENDING_PRODUCTION_MODE, CUSTOMER_PRODUCTION_MODE] ALL_TYPES = [CUSTOMER_TYPE_PRIVATE_PERSON, CUSTOMER_TYPE_LEGAL_ENTITY] ALL_PANELS = [OPENSTACK_DASHBOARD_HORIZON, OPENSTACK_DASHBOARD_SKYLINE, OPENSTACK_DASHBOARD_BOTH] CUSTOMER_MODE = Enum(*ALL_MODES) CUSTOMER_TYPE = Enum(*ALL_TYPES) CUSTOMER_PANEL = Enum(*ALL_PANELS) customer_id = Column(db.Integer, primary_key=True) email = Column(db.String(254), nullable=False, unique=True) password = Column(db.String(100), nullable=False) deleted = Column(db.DateTime()) created = Column(db.DateTime()) email_confirmed = Column(db.Boolean) tariff_id = Column(db.Integer, ForeignKey("tariff.tariff_id"), index=True) blocked = Column(db.Boolean) customer_type = Column(CUSTOMER_TYPE, nullable=False, default=CUSTOMER_TYPE_PRIVATE_PERSON) customer_mode = Column(CUSTOMER_MODE, nullable=False) withdraw_period = Column(db.String(16)) balance_limit = Column(db.DECIMAL(precision=conf.backend.decimal.precision, scale=conf.backend.decimal.scale)) auto_withdraw_enabled = Column(db.Boolean) auto_withdraw_balance_limit = Column(db.Integer) auto_withdraw_amount = Column(db.Integer) locale = Column(db.String(32)) tariff = relationship("Tariff") # Each customer has his own OpenStack tenant_id os_tenant_id = Column(db.String(32), ForeignKey("tenant.tenant_id"), nullable=True, unique=True, default=None) # Each customer has his own OpenStack User account os_user_id = Column(db.String(32), nullable=True, unique=True, default=None) # Each customer has his own OpenStack Password os_user_password = Column(db.String(100), nullable=True, default=None) os_username = Column(db.String(64), nullable=True, default=None) # OpenStack dashboard view os_dashboard = Column(CUSTOMER_PANEL, nullable=False, default=OPENSTACK_DASHBOARD_HORIZON) subscriptions = relationship("Subscription", lazy='dynamic') switches = relationship("SubscriptionSwitch", lazy='dynamic') quota = relationship("Quote", lazy='dynamic') display_fields = frozenset(["customer_id", "email", "created", "deleted", "email_confirmed", "tariff_id", "blocked", "customer_mode", "customer_type", "withdraw_period", "balance_limit", "os_tenant_id", "os_user_id", "locale", "os_username", "os_dashboard"]) accounts = relationship("Account", cascade="all, delete-orphan") accounts_history = relationship("AccountHistory", cascade="all, delete-orphan", lazy='dynamic') history = relationship("CustomerHistory", cascade="all, delete-orphan", lazy='dynamic') private_info = relationship("PrivateCustomerInfo", uselist=False, backref="customer", cascade="all, delete-orphan") entity_info = relationship("EntityCustomerInfo", uselist=False, backref="customer", cascade="all, delete-orphan") tenant = relationship("Tenant") tasks = relationship("ScheduledTask", cascade="all, delete-orphan") AUTO_REPORT_TASK = "auto_report" role = "customer" # stab for logging def __str__(self): return "<Customer %s blocked:%s, deleted:%s>" % (self.email, self.blocked, self.deleted) @property def info(self): return self.private_info if self.customer_type == self.CUSTOMER_TYPE_PRIVATE_PERSON else self.entity_info @classmethod def password_hashing(cls, raw_password): return pbkdf2_sha256.encrypt(raw_password, rounds=conf.customer.salt_rounds, salt_size=10) def check_password(self, raw_password): if not self.password: return False return pbkdf2_sha256.verify(raw_password, self.password) @classmethod @duplicate_handle(errors.CustomerAlreadyExists) def new_customer(cls, email, password, creator_id, detailed_info=None, comment="New customer", withdraw_period=None, make_prod=False, customer_type=None, promo_code=None, locale=None): from model import Tariff, Account, CustomerHistory customer = cls() customer.email = email customer.password = cls.password_hashing(password) if password else "" customer.deleted = None customer.created = utcnow().datetime customer.email_confirmed = False customer.blocked = False customer.customer_mode = cls.CUSTOMER_PRODUCTION_MODE if make_prod else cls.CUSTOMER_TEST_MODE customer.customer_type = customer_type if customer_type else cls.CUSTOMER_TYPE_PRIVATE_PERSON customer.withdraw_period = withdraw_period or conf.customer.default_withdraw_period customer.auto_withdraw_enabled = conf.payments.auto_withdraw_enable customer.auto_withdraw_balance_limit = conf.payments.auto_withdraw_balance_limit customer.auto_withdraw_amount = conf.payments.auto_withdraw_amount customer.locale = locale or conf.customer.default_locale customer.os_dashboard = conf.customer.default_openstack_dashboard default_tariff = Tariff.get_default().first() if not default_tariff: default_tariff = Tariff.query.filter_by(mutable=False).first() or Tariff.query.filter_by().first() if not default_tariff: raise errors.TariffNotFound() customer.tariff_id = default_tariff.tariff_id customer.balance_limit = conf.customer.balance_limits.get(default_tariff.currency, conf.customer.balance_limits.default) db.session.add(customer) db.session.flush() customer.init_subscriptions() template = 'customer' if make_prod else 'test_customer' customer.quota_init(template) customer.accounts.append(Account.create(default_tariff.currency, customer, creator_id, comment)) if promo_code is not None: PromoCode.new_code(promo_code, customer.customer_id) CustomerHistory.new_customer(customer, creator_id, comment) if detailed_info: customer.create_info(customer.customer_type, detailed_info) auto_report_task = ScheduledTask(cls.AUTO_REPORT_TASK, customer.customer_id, customer.withdraw_period) db.session.add(auto_report_task) logbook.info("New customer created: {}", customer) if not make_prod: currency = customer.tariff.currency.upper() initial_balance = conf.customer.test_customer.balance.get(currency) logbook.debug("Initial balance for customer {}: {} {}", customer, initial_balance, currency) if initial_balance: customer.modify_balance(Decimal(initial_balance), currency, None, "Initial test balance") return customer def update_os_credentials(self, tenant_id, tenant_name, user_id, username, password): from model import Tenant """Update os_tenant_id and os_user_id for customer with given email address.""" if self.os_tenant_id is not None and self.os_tenant_id != tenant_id and tenant_id: logbook.warning("Customer {} already has tenant in open stack {}. New value: {}", self, self.os_tenant_id, tenant_id) old_tenant = Tenant.get_by_id(self.os_tenant_id) if old_tenant: old_tenant.mark_removed() if self.os_user_id is not None and self.os_user_id != user_id and user_id: logbook.warning("Customer {} already has user in open stack {}. New value: {}", self, self.os_user_id, user_id) self.os_user_password = password if tenant_id is not None and self.os_tenant_id != tenant_id: logbook.info("Create tenant in db {} {}", tenant_id, tenant_name) Tenant.create(tenant_id, tenant_name) self.os_tenant_id = tenant_id self.os_user_id = user_id self.os_username = username def update_os_password(self, password): self.os_user_password = password db.session.flush() @classmethod def get_by_email(cls, email, include_deleted=False): query = cls.query.filter_by(email=email) if not include_deleted: query.filter_by(deleted=None) return query.first() @classmethod def get_by_id(cls, customer_id, include_deleted=False): query = cls.query.filter_by(customer_id=customer_id) if not include_deleted: query.filter_by(deleted=None) return query.first() @classmethod def login(cls, email, password): customer = cls.get_by_email(email) if customer is None: logbook.info("Customer {} is not found", email) raise errors.CustomerUnauthorized() if customer.deleted: logbook.info("Customer {} is deleted at {}", email, customer.deleted) raise errors.CustomerUnauthorized() if not customer.check_password(password): logbook.info("Password mismatch for customer {}", email) raise errors.CustomerUnauthorized() token = CustomerToken.create(customer) return token, customer @classmethod def filter_by_customer_type(cls, query_parameters, customer_info, query): from model import PrivateCustomerInfo customer_type = cls.CUSTOMER_TYPE_PRIVATE_PERSON if customer_info is PrivateCustomerInfo \ else cls.CUSTOMER_TYPE_LEGAL_ENTITY query = query.outerjoin(customer_info).filter(cls.customer_type == customer_type) for k, v in query_parameters.items(): if hasattr(cls, k): query = query.filter(getattr(cls, k) == v) elif k.endswith('_before'): query = query.filter(getattr(cls, k.partition('_before')[0]) < v) elif k.endswith('_after'): query = query.filter(getattr(cls, k.partition('_after')[0]) > v) elif hasattr(customer_info, k): query = query.filter_by(**{k: v}) return query @classmethod def filter(cls, query_parameters, visibility=None): from model import PrivateCustomerInfo, EntityCustomerInfo limit = query_parameters.pop("limit") page = query_parameters.pop("page") sort = query_parameters.pop("sort", None) query = cls.query if visibility == "all": pass elif visibility == "visible": query = cls.query.filter(cls.deleted.is_(None)) elif visibility == "deleted": query = cls.query.filter(cls.deleted.isnot(None)) if query_parameters: tariff_ids = query_parameters.pop("tariff_ids", None) if tariff_ids: query = query.filter(cls.tariff_id.in_(tariff_ids)) customer_type = query_parameters.get('customer_type') if customer_type == cls.CUSTOMER_TYPE_PRIVATE_PERSON: result_query = cls.filter_by_customer_type(query_parameters, PrivateCustomerInfo, query) elif customer_type == cls.CUSTOMER_TYPE_LEGAL_ENTITY: result_query = cls.filter_by_customer_type(query_parameters, EntityCustomerInfo, query) else: private_query = cls.filter_by_customer_type(query_parameters, PrivateCustomerInfo, query).all() entity_query = cls.filter_by_customer_type(query_parameters, EntityCustomerInfo, query).all() result_query = private_query + entity_query customer_ids = {item.customer_id for item in result_query} query = cls.query.filter(cls.customer_id.in_(customer_ids)) if sort: query = cls.sort_query(query, sort) return query.paginate(page, limit) def update(self, new_common_params=None, new_info_params=None, user_id=None, comment=None): from model import CustomerHistory if self.deleted: raise errors.CustomerRemoved() if not (new_common_params or new_info_params): return if new_common_params: assert "tariff_id" not in new_common_params assert "tariff" not in new_common_params password = new_common_params.get("password") if password: new_common_params["password"] = self.password_hashing(password) CustomerHistory.change_password(self, comment) withdraw_period = new_common_params.get("withdraw_period") if withdraw_period: task = ScheduledTask.get_by_customer(self.customer_id, self.AUTO_REPORT_TASK) task.update_period(withdraw_period) super().update(new_common_params) if new_info_params: if not self.info: self.create_info(self.customer_type, new_info_params) else: self.info.update(new_info_params) CustomerHistory.change_info(self, user_id, comment) def update_tariff(self, new_tariff_id, user_id, comment=None): from model import Tariff, CustomerHistory from task.customer import change_flavors if self.deleted: raise errors.CustomerRemoved() new_tariff = Tariff.get_by_id(new_tariff_id) if new_tariff.mutable: raise errors.AssignMutableTariff() if new_tariff.deleted: raise errors.RemovedTariff() if self.tariff.currency != new_tariff.currency: # TODO implement logic pass logbook.info("Change tariff from {} to {} for customer {}", self.tariff, new_tariff, self) self.tariff_id = new_tariff_id if self.os_tenant_id: change_flavors.delay(self.os_tenant_id, new_tariff.flavors()) CustomerHistory.tariff_changed(self, user_id, comment) def mark_removed(self): res = super().mark_removed() CustomerToken.remove_by(self.customer_id) ScheduledTask.remove_by_customer_id(self.customer_id) return res def password_reset(self, password): from model import CustomerHistory self.password = self.password_hashing(password) CustomerHistory.reset_password(self, "password was reset ed") def confirm_email(self): from model import CustomerHistory from task.openstack import task_os_create_tenant_and_user self.email_confirmed = True logbook.info("Email for customer {} confirmed", self) CustomerHistory.email_confirmed(self, "Email is confirmed") if self.customer_mode == self.CUSTOMER_TEST_MODE: TestPeriodOver.create(self.customer_id) db.session.commit() logbook.info("Creating tenants for customer {}", self) task_os_create_tenant_and_user.delay(self.customer_id, self.email) def get_deferred(self): from model.account.deferred import Deferred return Deferred.get_by_customer(self.customer_id) def account_dict(self): return {account.currency: { "balance": account.balance, "withdraw": account.withdraw, "current": account.current} for account in self.accounts} def balance_dict(self): return {account.currency: account.balance for account in self.accounts} def display(self, short=True): res = super().display(short) res["account"] = self.account_dict() res["currency"] = self.tariff.currency res["tariff_id"] = self.tariff_id auto_report_task = ScheduledTask.get_by_customer(self.customer_id, self.AUTO_REPORT_TASK) res["withdraw_date"] = auto_report_task.next_scheduled if auto_report_task else None res["detailed_info"] = self.info.display(short) if self.info else {} res["promo_code"] = display(PromoCode.get_by_customer_id(self.customer_id)) return res def report_info(self): report_fields = ["email", "locale"] report_info = {field: getattr(self, field, None) for field in report_fields} report_info["name"] = self.get_name() entity_info = self.entity_info if entity_info: report_info["entity_info"] = entity_info.display() return report_info def get_account(self, currency): currency = currency.upper() for account in self.accounts: if account.currency == currency: return account return None def get_or_create_account(self, currency, user_id): from model import Account account = self.get_account(currency) if account: return account account = Account.create(currency, self, user_id) self.accounts.append(account) db.session.flush() return account def get_name(self): return self.info.name if self.info and self.info.name else self.email def check_balance(self, account, currency): if currency == self.tariff.currency: if not self.blocked and account.balance - account.withdraw < self.balance_limit: self.block(blocked=True, user_id=None, message='insufficient funds') return from model import CustomerHistory block_event = CustomerHistory.get_last_block_event(self) if self.blocked and account.balance - account.withdraw > self.balance_limit and not block_event.user_id: self.block(blocked=False, user_id=None, message='balance is positive') def is_test_mode(self): return self.customer_mode == self.CUSTOMER_TEST_MODE def modify_balance(self, account_delta, currency, user_id, comment, transaction_id=None, cleanup=False): logbook.info("Changing balance of {}: {} {} by user {} with comment {}", self, account_delta, currency, user_id, comment) if Decimal(account_delta) > 0 and not cleanup: self.check_wait_production() if not currency: currency = self.tariff.currency currency = currency.upper() account = self.get_or_create_account(currency, user_id) account.modify(self, account_delta, user_id, comment, transaction_id=transaction_id) PaymentService.send_email_about_balance_modifying(self, account_delta, currency, account.balance - account.withdraw, comment) self.check_balance(account, currency) db.session.flush() def withdraw(self, delta, currency=None): assert isinstance(delta, Decimal) assert delta >= 0 currency = (currency or self.tariff.currency).upper() logbook.debug("Withdraw {} {} of {}", delta, currency, self) account = self.get_account(currency) if account is None: raise Exception("Customer %s doesn't have account in %s, but withdraw %s %s is required" % (self, currency, delta, currency)) account.charge(delta) self.check_balance(account, currency) db.session.flush() def change_auto_withdraw(self, enabled, balance_limit, payment_amount): if enabled is not None: self.auto_withdraw_enabled = enabled if payment_amount is not None: assert payment_amount > 0 self.auto_withdraw_amount = payment_amount if balance_limit is not None: self.auto_withdraw_balance_limit = balance_limit def display_auto_withdraw(self): return { 'enabled': self.auto_withdraw_enabled, 'balance_limit': self.auto_withdraw_balance_limit, 'payment_amount': self.auto_withdraw_amount } def get_account_history(self, after=None, before=None, limit=1000): from model import AccountHistory query = self.accounts_history.filter(AccountHistory.customer_id == self.customer_id) if after: query = query.filter(AccountHistory.date >= after) if before: query = query.filter(AccountHistory.date < before) query = query.order_by(desc(AccountHistory.account_history_id)).limit(limit) return query def check_account_history_transaction(self, transaction_id): from model import AccountHistory return self.accounts_history.filter(AccountHistory.customer_id == self.customer_id, AccountHistory.transaction_id == transaction_id).count() @classmethod def get_customers_by_prefix_info_field(cls, prefix, field): from model import PrivateCustomerInfo, EntityCustomerInfo if not field: raise Exception("Field for is not set") customers = [] if getattr(PrivateCustomerInfo, field, None): query = cls.query.join(PrivateCustomerInfo).filter(cls.customer_type == cls.CUSTOMER_TYPE_PRIVATE_PERSON, getattr(PrivateCustomerInfo, field).ilike(prefix + "%")) customers.extend(query.all()) if getattr(EntityCustomerInfo, field, None): query = cls.query.join(EntityCustomerInfo).filter(cls.customer_type == cls.CUSTOMER_TYPE_LEGAL_ENTITY, getattr(EntityCustomerInfo, field).ilike(prefix + "%")) customers.extend(query.all()) return customers @classmethod def delete_by_prefix(cls, prefix, field=None): from model import Account, AccountHistory, Tenant from task.openstack import final_delete_from_os field = field or cls.unique_field if not field: raise Exception("Field for removing is not set") if getattr(cls, field, None): query = cls.query.filter(getattr(cls, field).like(prefix + "%")) ids = [customer_id for customer_id, in query.with_entities(cls.id_field)] else: ids = [item.customer_id for item in cls.get_customers_by_prefix_info_field(prefix, field)] if not ids: return 0 logbook.info("Remove customers by prefix: {}", prefix) Subscription.query.filter(Subscription.customer_id.in_(ids)).delete(False) SubscriptionSwitch.query.filter(SubscriptionSwitch.customer_id.in_(ids)).delete(False) AccountHistory.query.filter(AccountHistory.customer_id.in_(ids)).delete(False) Account.query.filter(Account.customer_id.in_(ids)).delete(False) Quote.query.filter(Quote.customer_id.in_(ids)).delete(False) CustomerCard.query.filter(CustomerCard.customer_id.in_(ids)).delete(False) customers = cls.query.filter(cls.customer_id.in_(ids)) if conf.devel.debug: logbook.debug("Remove customers {}", customers.all()) tenants_ids = [] for customer in customers: if customer.os_tenant_id: logbook.info("Final remove tenant {} for customer {}", customer.os_tenant_id, customer) final_delete_from_os.delay(customer.os_tenant_id, customer.os_user_id) tenants_ids.append(customer.os_tenant_id) customer.os_tenant_id = None customer.os_user_id = None if tenants_ids: Tenant.query.filter(Tenant.tenant_id.in_(tenants_ids)).delete(False) return customers.delete(False) def send_email_about_blocking(self, blocked, user_id): subscription_info = self.subscription_info()['status'] if subscription_info['enable']: block_date = utcnow().datetime if blocked: if user_id: template_id = MessageTemplate.CUSTOMER_BLOCKED_BY_MANAGER subject, body = MessageTemplate.get_rendered_message(template_id, language=self.locale_language(), block_date=block_date, email=self.email) else: template_id = MessageTemplate.CUSTOMER_BLOCKED recommended_payment = conf.customer.minimum_recommended_payment.get(self.tariff.currency) recommended_payment = {'money': Decimal(recommended_payment), 'currency': self.tariff.currency} subject, body = MessageTemplate.get_rendered_message(template_id, language=self.locale_language(), block_date=block_date, email=self.email, minimum_recomended_payment=recommended_payment) else: template_id = MessageTemplate.CUSTOMER_UNBLOCKED subject, body = MessageTemplate.get_rendered_message(template_id, language=self.locale_language()) send_email.delay(subscription_info['email'], subject, body) def block(self, blocked, user_id, message=""): from model import CustomerHistory from task.openstack import block_user if self.blocked == blocked: return False logbook.debug("Customer {} is marked as {} because '{}'", self, "blocked" if blocked else "unblocked", message) self.blocked = blocked CustomerHistory.blocked(blocked, self, user_id, message) if self.os_user_id: block_user.delay(self.os_user_id, blocked) if self.customer_mode != self.CUSTOMER_TEST_MODE: if blocked: BlockCustomer.create(self.customer_id) else: BlockCustomer.stop(self.customer_id) self.send_email_about_blocking(blocked, user_id) def remove(self, user_id, comment): from model import CustomerHistory, Tenant from task.openstack import final_delete_from_os if not self.mark_removed(): raise errors.CustomerRemoved() CustomerHistory.remove_customer(self, user_id, comment) if self.os_tenant_id or self.os_user_id: final_delete_from_os.delay(self.os_tenant_id, self.os_user_id) tenant_id = self.os_tenant_id self.update_os_credentials(None, None, None, None, None) Tenant.query.filter_by(tenant_id=tenant_id).delete(False) def get_history(self, after=None, before=None, limit=1000): from model import CustomerHistory query = self.history.filter(CustomerHistory.customer_id == self.customer_id) if after: query = query.filter(CustomerHistory.date >= after) if before: query = query.filter(CustomerHistory.date < before) query = query.order_by(desc(CustomerHistory.customer_history_id)).limit(limit) return query @classmethod def news_subscribers(cls): return cls.query.filter(cls.switches.any(name='news', enable=True)) def subscription_info(self): subscriptions = self.subscriptions switches = self.switches result = {} for name in conf.subscription: subscription = subscriptions.filter(Subscription.name == name).all() switch = switches.filter(SubscriptionSwitch.name == name).one() emails = [s.email for s in subscription] result[name] = {'enable': switch.enable, 'email': emails} return result def subscriptions_update(self, data, user_id, comment=None): from model import CustomerHistory if self.deleted: raise errors.CustomerRemoved() subscriptions = self.subscriptions switches = self.switches for key, value in data.items(): subscription = subscriptions.filter(Subscription.name == key) new_emails = set(value['email']) current_emails = {s.email for s in subscription} emails_to_add = new_emails - current_emails emails_to_delete = current_emails - new_emails if emails_to_add: for email in emails_to_add: Subscription.new_subscription(name=key, email=email, customer_id=self.customer_id) if emails_to_delete: subscription.filter(Subscription.email.in_(emails_to_delete)).delete(False) switches.filter(SubscriptionSwitch.name == key).update({'enable': value["enable"]}) CustomerHistory.change_info(self, user_id, comment) def init_subscriptions(self): for name in conf.subscription: if name in conf.customer.automatic_subscriptions: Subscription.new_subscription(name=name, email=self.email, customer_id=self.customer_id) SubscriptionSwitch.new_switch(name=name, enable=True, customer_id=self.customer_id) else: SubscriptionSwitch.new_switch(name=name, enable=False, customer_id=self.customer_id) def quota_info(self): quotas = self.quota return [quota.display() for quota in quotas] def quota_update(self, data, user_id): from task.customer import update_quota for name, value in data.items(): self.quota.filter(Quote.name == name).update({'value': value}) update_quota.delay(self.customer_id, user_id, data) def quota_init(self, template='test_customer'): for name, value in conf.template[template].items(): Quote.new_quota(name=name, value=value, customer_id=self.customer_id) def make_production(self, user_id, comment): if not self.email_confirmed: raise errors.CustomerEmailIsNotConfirmed() account = self.get_account(self.tariff.currency) if account.current > self.balance_limit: self._make_prod(user_id, comment) else: self._make_pending_prod(user_id, comment) def check_wait_production(self): if self.customer_mode == self.CUSTOMER_PENDING_PRODUCTION_MODE: self._make_prod() def send_email_make_production(self): template_id = MessageTemplate.CUSTOMER_MAKE_PRODUCTION subject, body = MessageTemplate.get_rendered_message(template_id, language=self.locale_language()) send_email.delay(self.email, subject, body) def _make_prod(self, user_id=None, comment=''): from model import CustomerHistory from task.customer import clean_up_customer_service_usage TestPeriodOver.stop(self.customer_id) account = self.get_account(self.tariff.currency) if account.current < 0: self.clean_up_balance(user_id) time_end = utcnow().datetime self.clean_up_service_usage(time_end) clean_up_customer_service_usage.apply_async((self.customer_id, time_end), eta=time_end + timedelta(hours=2)) self.quota_update(conf.template['customer'], user_id) if self.blocked: self.block(blocked=False, user_id=user_id) openstack.change_tenant_quotas(self.os_tenant_id, **conf.template['customer']) self.update({'customer_mode': self.CUSTOMER_PRODUCTION_MODE}) CustomerHistory.make_prod(self, user_id, comment) self.send_email_make_production() def _make_pending_prod(self, user_id, comment): from model import CustomerHistory TestPeriodOver.stop(self.customer_id) self.update({'customer_mode': self.CUSTOMER_PENDING_PRODUCTION_MODE}) CustomerHistory.make_pending_prod(self, user_id, comment) def clean_up_balance(self, user_id=None): for currency, balance in self.balance_dict().items(): self.modify_balance(-balance, currency, user_id, 'Customer balance is reset to 0.0 by system during switching to production mode', cleanup=True) db.session.flush() def clean_up_service_usage(self, time_end): from model import ServiceUsage service_usages_to_clean = ServiceUsage.query.filter(ServiceUsage.tenant_id == self.os_tenant_id, ServiceUsage.end <= time_end) total_cost = self.calculate_usage_cost(service_usages_to_clean.all()) self.get_account(self.tariff.currency).charge(-total_cost) service_usages_to_clean.delete(False) @classmethod def get_by_tenant_id(cls, tenant_id): return cls.query.filter_by(os_tenant_id=tenant_id).first() def calculate_usage_cost(self, usages): from model import Service, Category, ServicePrice from task.notifications import notify_managers_about_new_service_in_tariff total_cost = Decimal() tariff = self.tariff if not tariff: raise Exception("Tariff is not set for customer %s" % self) services = tariff.services_as_dict(lower=True) for usage in usages: db.session.add(usage) service_id = usage.service_id.lower() if isinstance(usage.service_id, str) else str(usage.service_id) service_price = services.get(service_id) service = Service.get_by_id(service_id) usage.tariff_id = tariff.tariff_id usage.customer_mode = self.customer_mode if service is None: logbook.error("Not found declaration service {} during calculate usage for {}", usage.service_id, self) continue usage_volume = service.calculate_volume_usage(usage) usage.usage_volume = usage_volume if service_price is None: if service.category_id == Category.VM: if service.deleted: logbook.error("Service {} not found in {} for {}. But this service is archived", service_id, tariff, self) else: service_price = ServicePrice(service_id=service_id, price=Decimal(0), need_changing=True) self.tariff.services.append(service_price) services = tariff.services_as_dict(lower=True) flavor_name = Service.get_by_id(service_id).flavor.flavor_id notify_managers_about_new_service_in_tariff.delay(self.customer_id, flavor_name) else: logbook.warning("Service {} not found in {} for {}. Allowed services: {}", service_id, tariff, self, services.keys()) if service_price: usage_cost = usage_volume * service_price.price / service.hours else: usage_cost = Decimal(0) total_cost += usage_cost usage.cost = usage_cost logbook.info("Found {} usages for customer {}. Total cost of used resources is: {}", len(usages), self, total_cost) return total_cost def create_info(self, info_type: str, detailed_info): from model import PrivateCustomerInfo, EntityCustomerInfo if info_type == self.CUSTOMER_TYPE_PRIVATE_PERSON: self.private_info = PrivateCustomerInfo.create(self.customer_id, detailed_info) elif info_type == self.CUSTOMER_TYPE_LEGAL_ENTITY: self.entity_info = EntityCustomerInfo.create(self.customer_id, detailed_info) def locale_language(self): return language_from_locale(self.locale) or DEFAULT_LANGUAGE @classmethod def auto_withdraw_query(cls, email_prefix=None, now=None): query = None if email_prefix: query = cls.query.filter(cls.email.ilike("{}%".format(email_prefix))) query = ScheduledTask.scheduled_tasks(cls.AUTO_REPORT_TASK, now=now, query=query) return query @staticmethod def fake_usage(customer, start, finish, service_id, resource_id, volume, resource_name=None): from model import ServiceUsage from fitter.aggregation.timelabel import TimeLabel if customer.os_tenant_id is None: raise errors.TenantIsnotCreated() time_label = TimeLabel(start) finish_time_label = TimeLabel(finish) total_cost = Decimal(0) while time_label <= finish_time_label: st, fn = time_label.datetime_range() st = max(st, start) fn = min(fn, finish) service_usage = ServiceUsage(customer.os_tenant_id, service_id, time_label, resource_id, customer.tariff, volume, st, fn, resource_name=resource_name) db.session.add(service_usage) cost = customer.calculate_usage_cost([service_usage]) customer.withdraw(cost) total_cost += cost time_label = time_label.next() return total_cost def pdf_invoice(self, amount, date, currency=None, number=None): from report.pdf_render import PdfRender entity_info = self.entity_info if entity_info is None: raise errors.CustomerIsNotEntity() number_str = str(number) if not number: number_str = "______" number = '<xpre>%s</xpre>' % number_str data = {"number": number, "number_str": number_str, "date": date, "currency": currency or self.tariff.currency, "amount": amount, "entity_info": entity_info, "nds": amount / Decimal(1.18) * Decimal(0.18) } data.update(conf.report.invoice) return PdfRender().render(data, "invoice", locale="ru_ru") @classmethod def customers_stat(cls): result = {"total": cls.query.count()} total_deleted = 0 total_by_mode = Counter() total_blocked = 0 for customer_type in cls.ALL_TYPES: query_by_type = cls.query.filter_by(customer_type=customer_type) for mode in cls.ALL_MODES: query = cls.query.filter_by(customer_mode=mode, customer_type=customer_type) metric_name = "%s_%s" % (mode, customer_type) count = query.count() total_by_mode[mode] += count result[metric_name] = count blocked = query.filter(cls.blocked.is_(True)).count() result[metric_name + "_blocked"] = blocked total_blocked += blocked deleted = query_by_type.filter(cls.deleted.isnot(None)).count() result[customer_type + "_deleted"] = deleted total_deleted += deleted result["total_deleted"] = total_deleted result["total_blocked"] = total_blocked for mode, count in total_by_mode.items(): result["total_" + mode] = count return result def get_used_quotas(self): logbook.debug("Getting used quotas from openstack for {}", self) if self.os_tenant_id and not self.blocked: try: quotas = openstack.get_limits(self.os_tenant_id, self.os_username, self.os_user_password) except Unauthorized as e: logbook.warning("Customer {} is not blocked but can't sign in OpenStack account. Error message: {}", self, e) quotas = {} else: quotas = {} if quotas: quota_cache.set(self, quotas) return quotas def used_quotas(self, force=False): from task.customer import get_used_quotas if not self.os_tenant_id: logbook.debug("Quotas are empty because tenant is not configured for {}", self) return {}, None if self.blocked: logbook.debug("Quotas are empty because {} is blocked", self) return {}, None quota = quota_cache.get(self) if not quota: logbook.debug("Quotas are missed in cache for {}", self) get_used_quotas.delay(self.customer_id) return None, None if quota.fresh: if force and quota.live_time > conf.customer.quota.min_live_time: get_used_quotas.delay(self.customer_id) return quota.used, quota.live_time get_used_quotas.delay(self.customer_id) return quota.used, quota.live_time @classmethod def active_tenants(cls): return [c.os_tenant_id for c in cls.query.filter(cls.os_tenant_id.isnot(None))]
class CustomerCard(db.Model, AccountDb): STATUS_ACTIVE = 0 STATUS_DISABLED = 1 STATUS_INVALID = 2 status_info = { STATUS_ACTIVE: 'active', STATUS_INVALID: 'invalid', STATUS_DISABLED: 'disabled'} card_id = Column(db.Integer, primary_key=True) last_four = Column(db.String(4)) card_type = Column(db.String(16)) status = Column(db.Integer) token = Column(db.String(64)) customer_id = Column(db.Integer, ForeignKey("customer.customer_id")) deleted = Column(db.DateTime()) display_fields = frozenset(["card_id", "last_four", "card_type"]) def __str__(self): return "<Customer card id:%s %s %s deleted:%s>" % (self.card_id, self.card_type, self.last_four, self.deleted) def display(self, short=True): res = super().display(short) res["status"] = self.status_info[self.status] return res @classmethod @duplicate_handle(errors.CustomerPaymentCardAlreadyExists) def add_card(cls, customer_id, last_four, card_type, token, active=False): new_card = cls() new_card.last_four = last_four new_card.card_type = card_type new_card.token = token new_card.customer_id = customer_id new_card.deleted = None if active: new_card.status = cls.STATUS_ACTIVE card = cls.get_active_card(customer_id) if card: card.status = cls.STATUS_DISABLED else: new_card.status = cls.STATUS_DISABLED db.session.add(new_card) return new_card @classmethod def delete_card(cls, card_id): card = cls.get_by_id(card_id) if not card: raise errors.NotFound if card.deleted is not None: raise errors.PaymentCardRemoved card.deleted = utcnow().datetime @classmethod def get_all_cards(cls, customer_id, include_deleted=False): query = CustomerCard.query.filter_by(customer_id=customer_id) if not include_deleted: query = query.filter_by(deleted=None) return query @classmethod def get_one(cls, card_id, customer_id=None, include_deleted=False): query = CustomerCard.query.filter_by(card_id=card_id) if customer_id: query = query.filter_by(customer_id=customer_id) if not include_deleted: query = query.filter_by(deleted=None) return query.first() @classmethod def get_active_card(cls, customer_id, include_deleted=False): query = cls.get_all_cards(customer_id, include_deleted).filter_by(status=CustomerCard.STATUS_ACTIVE) return query.first() def enable(self): if self.status != self.STATUS_ACTIVE: self.status = self.STATUS_ACTIVE return self def change_status(self, new_status): if self.deleted: raise errors.PaymentCardRemoved if new_status not in [CustomerCard.STATUS_ACTIVE, CustomerCard.STATUS_DISABLED, CustomerCard.STATUS_INVALID]: return self.status = new_status
class Deferred(db.Model, AccountDb): id_field = "deferred_id" deferred_id = Column(db.Integer, primary_key=True) user_id = Column(db.Integer, ForeignKey("user.user_id")) customer_id = Column(db.Integer, ForeignKey("customer.customer_id"), unique=True) tariff_id = Column(db.Integer, ForeignKey("tariff.tariff_id")) date = Column(db.DateTime(), index=True) comment = Column(db.Text()) tariff = relationship("Tariff") user = relationship("User") customer = relationship("Customer") def __str__(self): return "<Deferred %s %s>" % (self.tariff.name, self.date) def __repr__(self): return str(self) @classmethod def create(cls, customer_id, tariff_id, user_id, date, comment): deferred = cls.query.filter_by(customer_id=customer_id).first() if not deferred: deferred = cls() deferred.customer_id = customer_id db.session.add(deferred) deferred.tariff_id = tariff_id deferred.user_id = user_id deferred.date = date deferred.comment = comment return deferred @classmethod def find_deferred(cls, now=None): now = now or utcnow() return cls.query.filter(cls.date <= now) def display(self, short=True): return { "tariff": self.tariff.display(short=True), "date": self.date, "user": self.user.display(), "comment": self.comment } @classmethod def delete_by_customer(cls, customer_id): return cls.query.filter_by(customer_id=customer_id).delete(False) @classmethod def get_by_customer(cls, customer_id): return cls.query.filter_by(customer_id=customer_id).first() @classmethod def process_pending_deferred_changes(cls, time_now=None, name_prefix=""): db.session.rollback() logbook.debug( "Process pending deferred changes task for customer prefix {} and time {}", name_prefix, time_now) time_now = time_now or utcnow().datetime query = cls.find_deferred(time_now) if name_prefix: customer_ids = [ c.customer_id for c in Customer.get_customers_by_prefix_info_field( name_prefix, "name") ] query = query.filter( cls.customer_id.in_(customer_ids)) if customer_ids else [] count = 0 for deferred in query: cls.do_deferred_changes(deferred) count += 1 logbook.debug("Processed {} pending deferred changes", count) db.session.commit() return count @classmethod @autocommit def do_deferred_changes(cls, deferred_changes): logbook.info("Process pending deferred change {}", deferred_changes) deferred_changes.customer.update_tariff(deferred_changes.tariff_id, deferred_changes.user_id, deferred_changes.comment) Deferred.delete_by_customer(deferred_changes.customer_id)
class Service(db.Model, AccountDb, BaseService): service_id = Column(db.Integer, primary_key=True) localized_name = relationship("ServiceLocalization", cascade="all") description = relationship("ServiceDescription", cascade="all") measure_id = Column(db.String(32)) category_id = Column(Enum(Category.CUSTOM, Category.VM)) mutable = Column(db.Boolean()) deleted = Column(db.DateTime()) flavor = relationship("Flavor", uselist=False, backref="service", cascade="all, delete-orphan") display_fields = ("service_id", "mutable", "deleted", "fixed") unique_field = "localized_name" id_field = "service_id" @classmethod @duplicate_handle(errors.ServiceAlreadyExisted) def create_service(cls, localized_name, category, measure, description=None, mutable=True): assert isinstance(measure, Measure) if measure.measure_type != Measure.TIME: raise Exception("Custom services can have only time measure") service = cls() service.update_localized_name(localized_name) if description: service.update_description(description) service.measure_id = measure.measure_id service.mutable = mutable service.category_id = category db.session.add(service) db.session.flush() return service @classmethod def create_vm(cls, localized_name, description=None, flavor_info=None, mutable=True): measure = Measure('hour') service = Service.create_service(localized_name, Category.VM, measure, description, mutable) service.flavor = Flavor.new_flavor(service.service_id, **flavor_info) return service @classmethod def create_custom( cls, localized_name, measure, description=None, ): service = Service.create_service(localized_name, Category.CUSTOM, measure, description) return service def localized_name_as_dict(self): return { localization.language: localization for localization in self.localized_name } def description_as_dict(self): return { description.language: description for description in self.description } def get_localized_name(self, language): localized_name = self.localized_name_as_dict() return localized_name.get( language).localized_name or localized_name[DEFAULT_LANGUAGE] def update_localized_name(self, localized_name): current = self.localized_name_as_dict() for language, value in localized_name.items(): if language in current: current[language].localized_name = value else: self.localized_name.append( ServiceLocalization.create_localization(language, value)) def update_description(self, localized_description): current = self.description_as_dict() for language, value in localized_description.items(): if language in current: current[language].localized_description = value else: self.description.append( ServiceDescription.create_localization(language, value)) @classmethod def list(cls, only_categories=None, visibility=None): result = [] query = cls.query if visibility == "all": pass elif visibility == "visible": query = cls.query.filter(cls.deleted == None) elif visibility == "deleted": query = cls.query.filter(cls.deleted != None) if not only_categories: result.extend(query) else: if Category.CUSTOM in only_categories: result.extend(query.filter(cls.category_id == Category.CUSTOM)) if Category.VM in only_categories: result.extend(query.filter(cls.category_id == Category.VM)) if visibility != 'deleted': result.extend(FixedService.list(only_categories)) return result @classmethod def get_by_id(cls, service_id): try: # if service_id is int service_id = int(service_id) return super().get_by_id(service_id) except ValueError: pass return FixedService.get_by_id(service_id) def display(self, short=True): res = super().display(short) res["category"] = self.category.display() res["measure"] = self.measure.display() res["localized_name"] = { loc.language: loc.localized_name for loc in self.localized_name } res["description"] = { description.language: description.localized_description for description in self.description } if self.category_id == Category.VM: res["flavor"] = self.flavor.display() return res @duplicate_handle(errors.ServiceAlreadyExisted) def update_names(self, localized_name=None, description=None): if self.deleted: raise errors.RemovedService() if localized_name: self.update_localized_name(localized_name) if description: self.update_description(description) return True def update_custom(self, localized_name=None, description=None, measure=None): if not self.mutable and measure: raise errors.ImmutableService() self.update_names(localized_name, description) if measure: if measure.measure_type != Measure.TIME: raise Exception("Custom services can have only time measure") self.measure_id = measure.measure_id def update_vm(self, localized_name=None, description=None, flavor_info=None): if not self.mutable and flavor_info: raise errors.ImmutableService() self.update_names(localized_name=localized_name, description=description) if flavor_info: self.flavor.update( {k: v for k, v in flavor_info.items() if v is not None}) @property def measure(self): return Measure(self.measure_id) @property def category(self): return Category.get_by_id(self.category_id) def mark_immutable(self): from task.openstack import create_flavor if self.deleted: raise errors.RemovedService() if self.mutable: self.mutable = False if self.category_id == Category.VM: vm = self.flavor try: flavor = openstack.get_nova_flavor(vm.flavor_id) logbook.info("Flavor {} already exists in OpenStack", vm.flavor_id) if vm.vcpus != flavor.vcpus: logbook.info( "Flavor {} has different vcpus value(DB: {}, OS: {}). Changing DB value.", vm.flavor_id, vm.vcpus, flavor.vcpus) self.flavor.vcpus = flavor.vcpus if vm.ram != flavor.ram: logbook.info( "Flavor {} has different ram value(DB: {}, OS: {}). Changing DB value.", vm.flavor_id, vm.ram, flavor.ram) self.flavor.ram = flavor.ram if vm.disk != flavor.disk: logbook.info( "Flavor {} has different disk value(DB: {}, OS: {}). Changing DB value.", vm.flavor_id, vm.disk, flavor.disk) self.flavor.disk = flavor.disk except NotFound: create_flavor.delay(vm.flavor_id, vm.vcpus, vm.ram, vm.disk, is_public=False) return True return False def used(self): from model import Tariff, ServicePrice """ Returns number of tariffs with this service. """ return Tariff.query.filter( ServicePrice.service_id == self.service_id, Tariff.deleted == None, Tariff.tariff_id == ServicePrice.tariff_id).count() def remove(self): if self.deleted: raise errors.RemovedService() if self.used() > 0: raise errors.RemovingUsedService() self.deleted = utcnow().datetime return True @classmethod def delete_by_prefix(cls, prefix, field=None): field = field or cls.unique_field if not field: raise Exception("Field for removing is not set") member = getattr(cls, field, None) if member: if field == "localized_name": query = cls.query.join(Service.localized_name).\ filter(ServiceLocalization.localized_name.ilike("%{}%".format(prefix + "%"))) else: query = cls.query.filter(member.like(prefix + "%")) ids = [ service_id for service_id, in query.with_entities(cls.service_id) ] else: ids = [ flavor.service_id for flavor in Flavor.query.filter( getattr(Flavor, field).ilike(prefix + "%")) ] if not ids: return 0 ServiceLocalization.query.filter( ServiceLocalization.service_id.in_(ids)).delete(False) ServiceDescription.query.filter( ServiceDescription.service_id.in_(ids)).delete(False) Flavor.query.filter(Flavor.service_id.in_(ids)).delete(False) return cls.query.filter(cls.service_id.in_(ids)).delete(False)
class ScheduledTask(db.Model, AccountDb): id_field = "customer_id" unique_field = "email" task_id = Column(db.Integer, primary_key=True) task_name = Column(db.String(64), index=True) customer_id = Column(db.Integer, ForeignKey("customer.customer_id", ondelete="CASCADE"), index=True) started = Column(db.DateTime(), index=True) next_scheduled = Column(db.DateTime(timezone=True), index=True) frequency = Column(db.String(64), nullable=False) last = Column(db.DateTime(), index=True) default_cron_data = {"MIN": 0, "HOUR": 0, "DAY": 1, "WEEK_DAY": 1} def __init__(self, task_name, customer_id, frequency, cron_data=None, now=None): self.task_name = task_name self.customer_id = customer_id cron_frequency = self.cron_frequency(frequency, cron_data) logbook.debug("Setup task {} for customer {} with frequency: {}", task_name, customer_id, cron_frequency) now = now or utcnow().datetime try: cron = croniter(cron_frequency, start_time=now) except ValueError as e: logbook.error("Invalid frequency format {}: {}", cron_frequency, e) raise self.frequency = cron_frequency self.started = None self.next_scheduled = cron.get_next(datetime) self.last = now def cron_frequency(self, frequency, cron_data=None): data = self.default_cron_data.copy() conf_data = conf.event.event[self.task_name].get("periods", {}).get(frequency) if conf_data: data.update(conf_data) if cron_data: data.update(cron_data) cron_frequency = conf.event.period[frequency]["cron"] return cron_frequency.format(**data) def update_period(self, frequency, cron_data=None, now=None): cron_frequency = self.cron_frequency(frequency, cron_data=cron_data) now = now or utcnow().datetime try: cron = croniter(cron_frequency, start_time=now) except ValueError as e: logbook.error("Invalid frequency format {}: {}", cron_frequency, e) raise self.frequency = cron_frequency self.next_scheduled = cron.get_next(datetime) @classmethod def scheduled_tasks(cls, task_name, now=None, query=None): from model import Customer task_config = conf.event.event[task_name] now = now or utcnow().datetime now = now.replace(microsecond=0) query = query or cls.query query = query.filter(cls.task_name == task_name, cls.next_scheduled < now, or_(cls.started == None, cls.started < now - timedelta(seconds=task_config["task_hang"]))) query = query.filter(cls.customer_id == Customer.customer_id).limit(task_config["limit"]) return query @classmethod def get_by_customer(cls, customer_id, task_name): return cls.query.filter_by(customer_id=customer_id, task_name=task_name).first() def start(self, now=None, autocommit=True): self.started = now or utcnow().datetime if autocommit: db.session.commit() def completed(self, move_ahead=True, now=None): now = now or utcnow().datetime task_str = str(self) self.started = None if move_ahead: _, next_send = self.task_range(now) self.next_scheduled = next_send self.last = now logbook.info("Completed task {} and scheduled next at {}", task_str, next_send) else: logbook.info("Task {} failed and it will be rescheduled", task_str) db.session.flush() logbook.info("Task after prolongation {}", self) db.session.commit() def task_range(self, base_time=None, previous_interval=False, next_interval=False): if base_time is None: base_time = utcnow().datetime # Because croniter doesn't take seconds into account, add 1 minute base_time += timedelta(minutes=1) cron = croniter(self.frequency, base_time) if previous_interval: cron.get_prev(datetime) elif next_interval: cron.get_next(datetime) cron.get_next(datetime) return cron.get_prev(datetime), cron.get_next(datetime) @classmethod def remove_by_customer_id(cls, customer_id): return cls.query.filter_by(customer_id=customer_id).delete(False)