class Client(db.Model, OAuth2ClientMixin): id = db.Column(db.Integer, primary_key=True) user_id = db.Column(db.Integer, db.ForeignKey('users.id', ondelete='CASCADE')) user = db.relationship('User') #overide token_endpoint_auth_method = db.Column(db.String(48), default='client_secret_post') grant_type = db.Column(db.Text, nullable=False, default='client_credentials') client_id = db.Column(db.String(24), index=True, default=gen_salt(24)) client_secret = db.Column(db.String(48), default=gen_salt(48)) def signup_client(self, u): self.user_id = u.id self.client_name = u.username self.scope = u.role db.session.add(self) db.session.commit() def make_secret_response(self): return { 'client_id': self.client_id, 'client_secret': self.client_secret, }
class ItemSimilarity(ResourceMixin, db.Model): #TODO use hybrid property __tablename__ = 'item_similarity' id = db.Column(db.Integer, primary_key=True) article_id1 = db.Column(db.Integer, nullable=False) article_id2 = db.Column(db.Integer, nullable=False) similarity_doc = db.Column(db.Float, nullable=False) def __init__(self, id1, id2, similarity_doc): self.article_id1 = id1 self.article_id2 = id2 self.similarity_doc = similarity_doc @classmethod def check_exist(cls, id1, id2): if cls.query \ .filter( or_( and_(cls.article_id1 == id1 \ ,cls.article_id2 == id2), and_(cls.article_id1 == id2 \ ,cls.article_id2 == id1) ) ).first(): return True else: return False @classmethod def delete_by_article_id(cls, article_id): delete_count = cls.query.filter( or_(cls.article_id1 == article_id, cls.article_id2 == article_id)).delete( synchronize_session=False) db.session.commit() return True
class Token(db.Model, OAuth2TokenMixin): id = db.Column(db.Integer, primary_key=True) user_id = db.Column(db.Integer, db.ForeignKey('users.id', ondelete='CASCADE')) user = db.relationship('User')
class User(ResourceMixin, db.Model, UserMixin): 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), 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='') #relationship client = db.relationship('Client', backref='users', uselist=False) # Activity tracking. current_sign_in_on = db.Column(db.DateTime()) last_sign_in_on = db.Column(db.DateTime()) def __init__(self, email, password, username=None, role='member'): self.username = username if role in self.ROLE.keys(): self.role = role else: raise BadRequest( description='Only accept admin or member in role parameter ') self.email = email self.password = User.encrypt_password(password) c = Client() c.signup_client(self) self.client = c @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 find_by_uid(cls, uid): return User.query.filter(User.id == uid).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 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 authenticated(self, password): return check_password_hash(self.password, password) 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 = tzware_datetime() return self.save() def get_user_id(self): return self.id
class ResourceMixin(object): # Keep track when records are created and updated. created_on = db.Column(db.DateTime(), default=tzware_datetime) updated_on = db.Column(db.DateTime(), default=tzware_datetime, onupdate=tzware_datetime) # @classmethod # def sort_by(cls, field, direction): # """ # Validate the sort field and direction. # :param field: Field name # :type field: str # :param direction: Direction # :type direction: str # :return: tuple # """ # if field not in cls.__table__.columns: # field = 'created_on' # if direction not in ('asc', 'desc'): # direction = 'asc' # return field, direction @classmethod def get_bulk_action_ids(cls, scope, ids, omit_ids=[], query=''): """ Determine which IDs are to be modified. :param scope: Affect all or only a subset of items :type scope: str :param ids: List of ids to be modified :type ids: list :param omit_ids: Remove 1 or more IDs from the list :type omit_ids: list :param query: Search query (if applicable) :type query: str :return: list """ omit_ids = map(str, omit_ids) if scope == 'all_search_results': # Change the scope to go from selected ids to all search results. ids = cls.query.with_entities(cls.id).filter(cls.search(query)) # SQLAlchemy returns back a list of tuples, we want a list of strs. ids = [str(item[0]) for item in ids] # Remove 1 or more items from the list, this could be useful in spots # where you may want to protect the current user from deleting themself # when bulk deleting user accounts. if omit_ids: ids = [id for id in ids if id not in omit_ids] return ids @classmethod def bulk_delete(cls, ids): """ Delete 1 or more model instances. :param ids: List of ids to be deleted :type ids: list :return: Number of deleted instances """ delete_count = cls.query.filter( cls.id.in_(ids)).delete(synchronize_session=False) db.session.commit() return delete_count def save(self): """ Save a model instance. :return: Model instance """ db.session.add(self) db.session.commit() return self def delete(self): """ Delete a model instance. :return: db.session.commit()'s result """ db.session.delete(self) return db.session.commit() def __str__(self): """ Create a human readable version of a class instance. :return: self """ obj_id = hex(id(self)) columns = self.__table__.c.keys() values = ', '.join("%s=%r" % (n, getattr(self, n)) for n in columns) return '<%s %s(%s)>' % (obj_id, self.__class__.__name__, values)
class Article(db.Model, ResourceMixin): __tablename__ = 'articles' id = db.Column(db.Integer, primary_key=True, index=True) title = db.Column(db.String(100), nullable=False, unique=True) content = db.Column(db.Text(collation='utf8mb4_bin')) ask = db.Column(db.Text(collation='utf8mb4_bin')) answer = db.Column(db.Text(collation='utf8mb4_bin')) type = db.Column(db.String(100)) division = db.Column(db.String(100)) tag = db.Column(db.Text) is_delete = db.Column(db.Boolean(), nullable=False, server_default='0') def __init__(self, title, type, division, ask='', answer='', **kwargs): self.title = title self.ask = ask self.answer = answer self.type = type self.division = division self.tag = kwargs.get('tag') self.key = gen_salt(15) _content = f'民眾提問:\n{ask}\n醫師回答:\n{answer}' self.content = kwargs.get('content') or _content @property def info(self): return { 'id': self.id, 'type': self.type, 'division': self.division, 'title': self.title, 'content': self.content, 'ask': self.ask, 'answer': self.answer, 'created_on': self.created_on, 'updated_on': self.updated_on } @classmethod def get_all(cls): return cls.query.filter(cls.is_delete == 0).all() def update(self, **kwargs): from recommender.blueprints.article.tasks import del_vector_and_similarity, cache_vector fix_contnet = False if kwargs.get('title'): self.title = kwargs.get('title') if kwargs.get('ask'): self.ask = kwargs.get('ask') fix_contnet = True if kwargs.get('asnwer'): self.asnwer = kwargs.get('asnwer') fix_contnet = True if kwargs.get('type'): self.type = kwargs.get('type') if kwargs.get('division'): self.division = kwargs.get('division') if kwargs.get('tag'): self.tag = kwargs.get('tag') if fix_contnet: _content = f'民眾提問:\n{self.ask}\n醫師回答:\n{self.answer}' self.content = _content if kwargs.get('content'): self.content = kwargs.get('content') del_vector_and_similarity(self.id) cache_vector(self) self.save()