def followed_posts(self): return Meal.query.join(followers, (followers.c.followed_id == Meal.user_id)).filter(followers.c.follower_id == self.id).order_by(Meal.timestamp.desc()) id = db.Column(db.Integer, primary_key=True) username = db.Column(db.String(64), index=True, unique=True) email = db.Column(db.String(120), index=True, unique=True) password = db.Column(db.String(128)) meals = db.relationship('Meal', backref='author', lazy='dynamic') authenticated = db.Column(db.Boolean, default=False) followed = db.relationship('User', secondary=followers, primaryjoin=(followers.c.follower_id == id), secondaryjoin=(followers.c.followed_id == id), backref=db.backref('followers', lazy='dynamic'), lazy='dynamic') def __repr__(self): return '<User %r>' % (self.username) def follow(self, user): if not self.is_following(user): self.followed.append(user) return self def unfollow(self, user): if self.is_following(user): self.followed.remove(user) return self def is_following(self, user): return self.followed.filter(followers.c.followed_id == user.id).count() def followed_posts(self): return Meal.query.join(followers, (followers.c.followed_id == Meal.user_id)).filter(followers.c.follower_id == self.id).order_by(Meal.timestamp.desc())
class Carnet(db.Model): __tablename__ = 'carnet' id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String(64), index=True, unique=False) id_parent_carnet = db.Column(db.Integer, db.ForeignKey('carnet.id'), nullable=True) children_carnets = db.relationship('Carnet', backref=db.backref('parent', remote_side=[id])) answers = db.relationship('Answer', backref='carnet', lazy='dynamic') def __repr__(self): return f'<Carnet{self.id} {self.name} [parent={Carnet.query.get(self.id_parent_carnet).name if self.id_parent_carnet else ""}]>' def eraze(self): for a in self.answers: a.eraze() for child in self.children_carnets: child.eraze() db.session.delete(self) db.session.commit() logger.info(f"Carnet erazed: {self}") def move(self, id_carnet): self.id_parent_carnet = id_carnet print(self.id_parent_carnet) db.session.commit() logger.info(f"Carnet moved: {self}") def add_answer(self, text_content): db.session.add(Answer(text_content=text_content, carnet=self)) db.session.commit() logger.info(f"Answer added to carnet {self}") def add_carnet(self, name): db.session.add(Carnet(name=name, parent=self)) db.session.commit() logger.info(f"carnet added to carnet {self}") def get_questions(self): questions = [] for a in self.answers: questions += a.questions return questions def get_all_questions(self): questions = self.get_questions() for c in self.children_carnets: questions += c.get_all_questions() return questions def get_knowledge_composition(self): questions = self.get_all_questions() knowledge = [0, 0, 0, 0, 0] if len(questions) == 0: return knowledge for q in questions: last_eval = q.last_evaluation() if last_eval: knowledge[last_eval.result.value] += 1 else: knowledge[0] += 1 print(list(map(lambda x: int(x * 100 / len(questions)), knowledge))) return list(map(lambda x: int(x * 100 / len(questions)), knowledge))
class User(UserMixin, db.Model): __tablename__ = 'users' id = db.Column(db.Integer, primary_key=True) email = db.Column(db.String(64), unique=True, index=True) username = db.Column(db.String(64), unique=True, index=True) password_hash = db.Column(db.String(128)) role_id = db.Column(db.Integer, db.ForeignKey('roles.id')) #role = db.relationship(Role) avatar_hash = db.Column(db.String(32)) confirmed = db.Column(db.Boolean, default=False) name = db.Column(db.String(64)) location = db.Column(db.String(64)) about_me = db.Column(db.Text()) member_since = db.Column(db.DateTime(), default=datetime.utcnow) last_seen = db.Column(db.DateTime(), default=datetime.utcnow) posts = db.relationship('Post', backref='author', lazy='dynamic') followed = db.relationship('Follow', foreign_keys=[Follow.follower_id], backref=db.backref('follower', lazy='joined'), lazy='dynamic', cascade='all, delete-orphan') followers = db.relationship('Follow', foreign_keys=[Follow.followed_id], backref=db.backref('followed', lazy='joined'), lazy='dynamic', cascade='all, delete-orphan') comments = db.relationship('Comment', backref='author', lazy='dynamic') def __init__(self, **kwargs): super(User, self).__init__(**kwargs) if self.role is None: if self.email == current_app.config['FLASKY_ADMIN']: self.role = Role.query.filter_by(name='Administrator').first() if self.role is None: self.role = Role.query.filter_by(default=True).first() #db.session.add(self) #db.session.commit() if self.email is not None and self.avatar_hash is None: self.avatar_hash = self.gravatar_hash() self.follow(self) def to_json(self): json_user = { 'url': url_for('api.get_user', id=self.id), 'username': self.username, 'member_since': self.member_since, 'last_seen': self.last_seen, 'posts_url': url_for('api.get_user_posts', id=self.id, _external=True), 'followed_posts_url': url_for('api.get_user_followed_posts', id=self.id, _external=True), 'post_count': self.posts.count() } return json_user @staticmethod def add_self_follows(): for user in User.query.all(): if not user.is_following(user): user.follow(user) db.session.add(user) db.session.commit() @property def followed_posts(self): return Post.query.join(Follow, Follow.followed_id == Post.author_id).filter(Follow.follower_id==self.id) def follow(self, user): if not self.is_following(user): f = Follow(follower=self, followed=user) db.session.add(f) db.session.commit() def unfollow(self, user): f = self.followed.filter_by(followed_id=user.id).first() if f: db.session.delete(f) db.session.commit() def is_following(self, user): return self.followed.filter_by(followed_id=user.id).first() is not None def is_followed_by(self, user): return self.followers.filter_by(follower_id=user.id).first() is not None def gravatar(self, size=100, default='identicon', rating='g'): url = 'https://secure.gravatar.com/avatar' hash = self.avatar_hash or self.gravatar_hash() return '{url}/{hash}?s={size}&d={default}&r={rating}'.format(url=url, hash=hash, size=size, default=default, rating=rating) def can(self, perm): return self.role is not None and self.role.has_permission(perm) def is_administrator(self): return self.can(Permission.ADMIN) def ping(self): self.last_seen = datetime.utcnow() db.session.add(self) db.session.commit() def generate_confirmation_token(self, expiration=3600): s = Serializer(current_app.config['SECRET_KEY'], expiration) return s.dumps({'confirm': self.id}).decode('utf-8') def confirm(self, token): s = Serializer(current_app.config['SECRET_KEY']) try: data = s.loads(token.encode('utf-8')) except: return False if data.get('confirm') != self.id: return False self.confirmed = True db.session.add(self) db.session.commit() return True def __repr__(self): return '<User %r>' % self.username @property def password(self): raise AttributeError('password is not a readable attribute') @password.setter def password(self, password): self.password_hash = generate_password_hash(password) def verify_password(self, password): return check_password_hash(self.password_hash, password) def generate_reset_token(self, expiration=3600): s = Serializer(current_app.config['SECRET_KEY'], expiration) return s.dumps({'reset': self.id}).decode('utf-8') @staticmethod def reset_password(token, new_password): s = Serializer(current_app.config['SECRET_KEY']) try: data = s.loads(token.encode('utf-8')) except: return False user = User.query.get(data.get('reset')) if user is None: return False user.password = new_password db.session.add(user) db.session.commit() return True def generate_email_change_token(self, new_email, expiration=3600): s = Serializer(current_app.config['SECRET_KEY'], expiration) return s.dumps({'change_email': self.id, 'new_email': new_email}).decode('utf-8') def generate_auth_token(self, expiration): s = Serializer(current_app.config['SECRET_KEY'], expires_in = expiration) return s.dumps({'id': self.id}).decode('utf-8') @staticmethod def verify_auth_token(token): s = Serializer(current_app.config['SECRET_KEY']) try: data = s.loads(token.encode('utf-8')) except: return None return User.query.get(data['id']) def change_email(self, token): s = Serializer(current_app.config['SECRET_KEY']) try: data = s.loads(token.encode('utf-8')) except: return False if data.get('change_email') != self.id: return False new_email = data.get('new_email') if new_email is None: return False if self.query.filter_by(email=new_email).first() is not None: return False self.email = new_email self.avatar_hash = self.gravatar_hash() db.session.add(self) db.session.commit() return True def gravatar_hash(self): return hashlib.md5(self.email.lower().encode('utf-8')).hexdigest() '''@staticmethod
class User(UserMixin, db.Model): id = db.Column(db.Integer, primary_key=True) username = db.Column(db.String(64), index=True, unique=True) email = db.Column(db.String(120), index=True, unique=True) password_hash = db.Column(db.String(128)) posts = db.relationship('Post', backref='author', lazy='dynamic') about_me = db.Column(db.String(140)) type = db.Column(db.String(20)) last_seen = db.Column(db.DateTime, default=datetime.utcnow) name = db.Column(db.String(50)) phone = db.Column(db.String(10)) experience = db.Column(db.String(1000)) departments = db.Column(db.String(100)) why = db.Column(db.String(1000)) applied = db.relationship('User', secondary=applicants, primaryjoin=(applicants.c.applicant_id == id), secondaryjoin=(applicants.c.applied_id == id), backref=db.backref('applicants', lazy='dynamic'), lazy='dynamic') followed = db.relationship('User', secondary=followers, primaryjoin=(followers.c.follower_id == id), secondaryjoin=(followers.c.followed_id == id), backref=db.backref('followers', lazy='dynamic'), lazy='dynamic') def __repr__(self): return '<User {}>'.format(self.username) def set_password(self, password): self.password_hash = generate_password_hash(password) def check_password(self, password): return check_password_hash(self.password_hash, password) def avatar(self, size): digest = md5(self.email.lower().encode('utf-8')).hexdigest() return 'https://www.gravatar.com/avatar/{}?d=identicon&s={}'.format( digest, size) def follow(self, user): if not self.is_following(user): self.followed.append(user) def unfollow(self, user): if self.is_following(user): self.followed.remove(user) def is_following(self, user): return self.followed.filter( followers.c.followed_id == user.id).count() > 0 def apply(self, user): if not self.is_applied(user): self.applied.append(user) def unapply(self, user): if self.is_applied(user): self.applied.remove(user) def is_applied(self, user): return self.applied.filter( applicants.c.applied_id == user.id).count() > 0 def followed_posts(self): followed = Post.query.join( followers, (followers.c.followed_id == Post.user_id)).filter( followers.c.follower_id == self.id) own = Post.query.filter_by(user_id=self.id) return followed.union(own).order_by(Post.timestamp.desc()) def applied_applicants(self): applied = User.query.join(applicants, (applicants.c.applied_id == User.id)).filter( applicants.c.applicant_id == self.id) return applied def get_reset_password_token(self, expires_in=600): return jwt.encode( { 'reset_password': self.id, 'exp': time() + expires_in }, app.config['SECRET_KEY'], algorithm='HS256').decode('utf-8') @staticmethod def verify_reset_password_token(token): try: id = jwt.decode(token, app.config['SECRET_KEY'], algorithms=['HS256'])['reset_password'] except: return return User.query.get(id)
class User(UserMixin, db.Model): id = db.Column(db.Integer, primary_key=True) username = db.Column(db.String(64), index=True, unique=True) email = db.Column(db.String(120), index=True, unique=True) password_hash = db.Column(db.String(128)) posts = db.relationship('Post', backref='author', lazy='dynamic') about_me = db.Column(db.String(140)) last_seen = db.Column(db.DateTime, default=datetime.utcnow) followed = db.relationship( 'User', secondary=followers, primaryjoin=(followers.c.follower_id == id), secondaryjoin=(followers.c.followed_id == id), backref=db.backref('followers', lazy='dynamic'), lazy='dynamic') # This method tells python how to print objects of this class, which # is going to be useful for debugging. def __repr__(self): return '<User {}>'.format(self.username) # With these two methods of generate and check password hashes, the # user is now able to do secure password generation and store passwords # Example: u = User(username = '******', email = '*****@*****.**') # u.check_password('mypassword') # u.set_password('mypassword') def set_password(self, password): self.password_hash = generate_password_hash(password) def check_password(self, password): return check_password_hash(self.password_hash, password) # The new avatar() method returns the URL of the user's avatar image, # scaled to the requestsed size in pixels. For users that don't have an # avatar registered, an "identicon" image will be generated. def avatar(self, size): # To generate the MD5 hash, we first have to convert the emails to # lower case. then encode the string into bytes before passing it on # to the hash function. digest = md5(self.email.lower().encode('utf-8')).hexdigest() return 'https://www.gravatar.com/avatar/{}?d=identicon&s={}'.format(digest, size) # Declare many to many relationships in the users table followed = db.relationship( # User is the right side entity of the relationship, left side entity # is the parent class. we use the same class on both sides since this is # a self referential relationship 'User', secondary=followers, # Primaryjoin indicates the condition that links the left side entity with # the association table. The followers.c.follower_id expression reference the # follower_id column on the association table primaryjoin=(followers.c.follower_id == id), # Similar for primary join where we join followed id secondaryjoin=(followers.c.followed_id == id), # Backref defines how this relationship will be access from the right side entity. # From the left side, the relationship is named followed so from the right side # we are going to use the name followers to reperesent al the left side users # that are linked to the target user in the right side. backref=db.backref('followers', lazy='dynamic'), lazy='dynamic' ) def follow(self, user): if not self.is_following(user): self.followed.append(user) def unfollow(self, user): if self.is_following(user): self.followed.remove(user) # The is_following method isses a query on the followed relationship to # check if a link between two users already exists. It looks for items in # the association table that have the left side foreign key set to the self user, # and the right side set to the user argument. # The argument will either return a 0 or a 1 def is_following(self, user): return self.followed.filter( followers.c.followed_id == user.id).count() > 0 # def followed_posts(self): # In the join operation on the posts table, the first argument is the # followers association table, and the second argument is the join # condition. This call creates a temp table that combines data from # posts and followers data tables. The data is going to be merged according # to the condition that we have passed as argument # The followed_id field must equal to the user_id of the posts table. followed = Post.query.join( followers, (followers.c.followed_id == Post.user_id)).filter( followers.c.follower_id == self.id) # This does not apply to your own posts, so we query the posts table # and find id's for ourselves, then we union the table and order the # timestamp by time descending own = Post.query.filter_by(user_id=self.id) return followed.union(own).order_by(Post.timestamp.desc()) # Generate a JWT token as a string, we need to decode the string in UTF-8 because # the encode function returns as a byte sequence, but its more convenient to have # the token as a string def get_reset_password_token(self, expires_in=600): return jwt.encode( {'reset-password': self.id, 'exp': time() + expires_in}, app.config['SECRET_KEY'], algorithm='HS256').decode('utf-8') # A static method can be invoked direct from the class. A static method is similar # to a class method, with the only difference being static methods do not receive # class as a first argument. @staticmethod def verify_reset_password_token(token): try: id = jwt.decode(token, app.config['SECRET_KEY'], algorithms=['HS256'])['reset_password'] except: return return User.query.get(id)
class User(UserMixin, db.Model): __tablename__ = 'user' id = db.Column(db.Integer, primary_key=True) username = db.Column(db.String(64), index=True, unique=True) email = db.Column(db.String(120), index=True, unique=True) password_hash = db.Column(db.String(128)) posts = db.relationship("Post", backref='author', lazy="dynamic") about_me = db.Column(db.String(140)) last_seen = db.Column(db.DateTime, default=datetime.datetime.now()) followed = db.relationship( 'User', secondary=followers, primaryjoin=(followers.c.follower_id == id), secondaryjoin=(followers.c.followed_id == id), backref=db.backref('followers', lazy='dynamic'), lazy='dynamic') message_sent = db.relationship('Message', foreign_keys='Message.sender_id', backref='author', lazy='dynamic') message_received = db.relationship('Message', foreign_keys='Message.recipient_id', backref='recipient', lazy='dynamic') last_message_read_time = db.Column(db.DateTime) notifications = db.relationship('Notification', backref='user', lazy='dynamic') def __repr__(self): return '<User {}>'.format(self.username) def set_password(self, password): """设置密码""" self.password_hash = generate_password_hash(password) def check_password(self, password): """验证密码""" return check_password_hash(self.password_hash, password) def avatar(self, size): """头像设置""" digest = md5(self.email.lower().encode('utf-8')).hexdigest() return 'https://www.gravatar.com/avatar/{}?d=identicon&s={}'.format(digest, size) def follow(self, user): if not self.is_following(user): self.followed.append(user) def unfollow(self, user): if self.is_following(user): self.followed.remove(user) def is_following(self, user): return self.followed.filter( followers.c.followed_id == user.id ).count() > 0 def followed_posts(self): followed = Post.query.join( followers, (followers.c.followed_id == Post.user_id)).filter( followers.c.follower_id == self.id) own = Post.query.filter_by(user_id=self.id) return followed.union(own).order_by(Post.timestamp.desc()) def get_reset_password_token(self, expires_in=600): return jwt.encode( {'reset_password': self.id, 'exp': time() + expires_in}, current_app.config['SECRET_KEY'], algorithm='HS256').decode('utf-8') @staticmethod def verify_reset_password_token(token): try: id = jwt.decode(token, current_app.config['SECRET_KEY'], algorithms=['HS256'])['reset_password'] except: return return User.query.get(id) def new_message(self): last_read_time = self.last_message_read_time or datetime.datetime(1900, 1, 1) return Message.query.filter_by(recipient=self).filter( Message.timestamp > last_read_time ).count() def add_notification(self, name, data): self.notifications.filter_by(name=name).delete() n = Notification(name=name, payload_json=json.dumps(data), user=self) db.session.commit() return n
class User(UserMixin, PaginatedAPIMixin, db.Model): id = db.Column(db.Integer, primary_key=True) username = db.Column(db.String(64), index=True, unique=True) email = db.Column(db.String(120), index=True, unique=True) password_hash = db.Column(db.String(128)) posts = db.relationship('Post', backref='author', lazy='dynamic') longposts = db.relationship('LP', backref='author', lazy='dynamic') about_me = db.Column(db.String(140)) last_seen = db.Column(db.DateTime, default=datetime.utcnow) token = db.Column(db.String(32), index=True, unique=True) verified = db.Column(db.Boolean) darkmode = db.Column(db.Boolean) Staff = db.Column(db.Boolean) hindi = db.Column(db.Boolean) token_expiration = db.Column(db.DateTime) followed = db.relationship('User', secondary=followers, primaryjoin=(followers.c.follower_id == id), secondaryjoin=(followers.c.followed_id == id), backref=db.backref('followers', lazy='dynamic'), lazy='dynamic') messages_sent = db.relationship('Message', foreign_keys='Message.sender_id', backref='author', lazy='dynamic') messages_received = db.relationship('Message', foreign_keys='Message.recipient_id', backref='recipient', lazy='dynamic') last_message_read_time = db.Column(db.DateTime) notifications = db.relationship('Notification', backref='user', lazy='dynamic') tasks = db.relationship('Task', backref='user', lazy='dynamic') def __repr__(self): return '<User {}>'.format(self.username) def set_password(self, password): self.password_hash = generate_password_hash(password) def check_password(self, password): return check_password_hash(self.password_hash, password) def avatar(self, size): digest = md5(self.email.lower().encode('utf-8')).hexdigest() return 'https://www.gravatar.com/avatar/{}?d=identicon&s={}'.format( digest, size) def follow(self, user): if not self.is_following(user): self.followed.append(user) def unfollow(self, user): if self.is_following(user): self.followed.remove(user) def is_following(self, user): return self.followed.filter( followers.c.followed_id == user.id).count() > 0 def followed_posts(self): followed = Post.query.join( followers, (followers.c.followed_id == Post.user_id)).filter( followers.c.follower_id == self.id) own = Post.query.filter_by(user_id=self.id) return followed.union(own).order_by(Post.timestamp.desc()) def get_reset_password_token(self, expires_in=600): return jwt.encode( { 'reset_password': self.id, 'exp': time() + expires_in }, current_app.config['SECRET_KEY'], algorithm='HS256').decode('utf-8') @staticmethod def verify_reset_password_token(token): try: id = jwt.decode(token, current_app.config['SECRET_KEY'], algorithms=['HS256'])['reset_password'] except: return return User.query.get(id) def new_messages(self): last_read_time = self.last_message_read_time or datetime(1900, 1, 1) return Message.query.filter_by(recipient=self).filter( Message.timestamp > last_read_time).count() def add_notification(self, name, data): self.notifications.filter_by(name=name).delete() n = Notification(name=name, payload_json=json.dumps(data), user=self) db.session.add(n) return n def launch_task(self, name, description, *args, **kwargs): rq_job = current_app.task_queue.enqueue('app.tasks.' + name, self.id, *args, **kwargs) task = Task(id=rq_job.get_id(), name=name, description=description, user=self) db.session.add(task) return task def get_tasks_in_progress(self): return Task.query.filter_by(user=self, complete=False).all() def get_task_in_progress(self, name): return Task.query.filter_by(name=name, user=self, complete=False).first() def to_dict(self, include_email=False): data = { 'id': self.id, 'username': self.username, 'last_seen': self.last_seen.isoformat() + 'Z', 'about_me': self.about_me, 'post_count': self.posts.count(), 'follower_count': self.followers.count(), 'followed_count': self.followed.count(), 'verified?': self.verified, 'staff': self.Staff, 'darkmode': self.darkmode, 'hindi': self.hindi, '_links': { 'self': url_for('api.get_user', id=self.id), 'followers': url_for('api.get_followers', id=self.id), 'followed': url_for('api.get_followed', id=self.id), 'avatar': self.avatar(128) } } if include_email: data['email'] = self.email return data def from_dict(self, data, new_user=False): for field in ['username', 'email', 'about_me']: if field in data: setattr(self, field, data[field]) if new_user and 'password' in data: self.set_password(data['password']) def get_token(self, expires_in=3600): now = datetime.utcnow() if self.token and self.token_expiration > now + timedelta(seconds=60): return self.token self.token = base64.b64encode(os.urandom(24)).decode('utf-8') self.token_expiration = now + timedelta(seconds=expires_in) db.session.add(self) return self.token def revoke_token(self): self.token_expiration = datetime.utcnow() - timedelta(seconds=1) @staticmethod def check_token(token): user = User.query.filter_by(token=token).first() if user is None or user.token_expiration < datetime.utcnow(): return None return user
class Post(db.Model, BaseMixin, DateTimeMixin): __tablename__ = 'posts' __searchable__ = ['title'] __analyzer__ = SimpleAnalyzer() id = db.Column(db.Integer, primary_key=True) title = db.Column(db.String(64)) body = db.Column(db.Text) author_id = db.Column(db.Integer, db.ForeignKey('users.id')) favorites = db.relationship('PostFavorite', backref='post', lazy='dynamic', foreign_keys=[PostFavorite.post_id], cascade='all,delete-orphan') tags = db.relationship('PostTag', backref='post', foreign_keys=[PostTag.post_id], lazy='dynamic') # 标签 disable_comment = db.Column(db.Boolean, default=False) # 是否禁止评论 comments = db.relationship('Comment', backref='post', lazy='dynamic') # 属于文章的评论 browse_count = db.Column(db.Integer, default=0) # 文章被浏览了多少次 liked_posts = db.relationship('LikePost', backref=db.backref('post_liked', lazy='joined'), lazy='dynamic', foreign_keys=[LikePost.post_liked_id], cascade='all,delete-orphan') liked_count = db.Column(db.Integer, default=0) # 以点赞数排序 tag_count = db.Column(db.Integer) # 标签数量 comments_count = db.Column(db.Integer) def disable(self): self.disable_comment = True db.session.add(self) def browsed(self): count = self.browse_count count += 1 self.browse_count = count db.session.add(self) def add_tag(self, tag): if not self.has_tag(tag): PostTag.create(post=self, tag=tag) return True return False def remove_tag(self, tag): f = self.tags.filter_by(tag_id=tag.id).first() if f: db.session.delete(f) return True return False def has_tag(self, tag): return self.tags.filter_by(tag_id=tag.id).first() is not None def is_liked_by(self, user): return self.liked_posts.filter_by( like_post_id=user.id).first() is not None @property def undelete_comments(self): return self.comments.filter(Comment.was_delete == False)
class Favorite(db.Model, BaseMixin, DateTimeMixin): """收藏夹""" __tablename__ = 'favorites' id = db.Column(db.Integer, primary_key=True) title = db.Column(db.String(64)) # 收藏夹的名称 description = db.Column(db.Text) # 描述 public = db.Column(db.Boolean, default=True) # 是否公开 user_id = db.Column(db.Integer, db.ForeignKey('users.id')) # 用户 questions = db.relationship('QuestionFavorite', backref='favorite', lazy='dynamic', foreign_keys=[QuestionFavorite.favorite_id], cascade='all,delete-orphan') answers = db.relationship('AnswerFavorite', backref='favorite', lazy='dynamic', foreign_keys=[AnswerFavorite.favorite_id], cascade='all,delete-orphan') posts = db.relationship('PostFavorite', backref='favorite', lazy='dynamic', foreign_keys=[PostFavorite.favorite_id], cascade='all,delete-orphan') comments = db.relationship('Comment', backref='favorite', lazy='dynamic') followers = db.relationship('FollowFavorite', backref=db.backref('followed', lazy='joined'), lazy='dynamic', foreign_keys=[FollowFavorite.followed_id], cascade='all,delete-orphan') answers_count = db.Column(db.Integer) posts_count = db.Column(db.Integer) comments_count = db.Column(db.Integer) followers_count = db.Column(db.Integer) def collect(self, item): if isinstance(item, Answer): self.collect_answer(item) elif isinstance(item, Question): self.collect_question(item) elif isinstance(item, Post): self.collect_post(item) def uncollect(self, item): if isinstance(item, Answer): self.uncollect_answer(item) elif isinstance(item, Question): self.uncollect_question(item) elif isinstance(item, Post): self.uncollect_post(item) def collect_question(self, question): if not self.has_collect_question(question): QuestionFavorite.create(favorite=self, question=question) def uncollect_question(self, question): f = self.questions.filter_by(question_id=question.id).first() if f: db.session.delete(f) db.session.commit() def has_collect_question(self, question): return self.questions.filter_by( question_id=question.id).first() is not None def collect_answer(self, answer): if not self.has_collect_answer(answer): AnswerFavorite.create(favorite=self, answer=answer) def uncollect_answer(self, answer): f = self.answers.filter_by(answer_id=answer.id).first() if f: db.session.delete(f) db.session.commit() def has_collect_answer(self, answer): return self.answers.filter_by(answer_id=answer.id).first() is not None def collect_post(self, post): if not self.has_collect_post(post): PostFavorite.create(favorite=self, post=post) def uncollect_post(self, post): f = self.posts.filter_by(post_id=post.id).first() if f: db.session.delete(f) db.session.commit() def has_collect_post(self, post): return self.posts.filter_by(post_id=post.id).first() is not None def is_followed_by(self, user): return self.followers.filter_by( follower_id=user.id).first() is not None @property def undelete_comments(self): return self.comments.filter(Comment.was_delete == False)
class User(ModelMixin, db.Model): __tablename__ = 'user' # FIXME unique? public_name = db.Column(db.String(80), unique=True) email = db.Column(db.String(80), unique=True) password = db.Column(db.String(255)) active = db.Column(db.Boolean(), default=False) confirmed_at = db.Column(db.DateTime()) last_login_at = db.Column(db.DateTime()) current_login_at = db.Column(db.DateTime()) last_login_ip = db.Column(db.String(80)) # FIXME set to IP type? current_login_ip = db.Column(db.String(80)) # FIXME set to IP type? login_count = db.Column(db.Integer(), default=0) roles = db.relationship('Role', secondary=roles_users, backref=db.backref('users', lazy='dynamic')) originating_shares = db.relationship('Share', backref='originator', lazy='dynamic', foreign_keys='Share.originator_id') receiving_shares = db.relationship('Share', backref='receiver', lazy='dynamic', foreign_keys='Share.receiver_id') def __init__(self, email, password=None, active=False, roles=[]): super(User, self).__init__() self.email = email if password: self.set_password(password, False) self.active = active self.roles += roles def get_name(self): return self.email def set_password(self, password, save=True): pwd_hash = pwd_context.encrypt(password) self.password = pwd_hash if save: self.save() def check_password(self, password, request=None): ok = pwd_context.verify(password, self.password) if not ok: return False if request: self.last_login_at = self.current_login_at self.current_login_at = datetime.utcnow() self.last_login_ip = self.current_login_ip self.current_login_ip = request.remote_addr self.login_count += 1 self.save() return True def is_active(self): return self.active def get_id(self): return self.id @classmethod def get_by_username(cls, username): return cls.query.filter(cls.email == username).first() def is_authenticated(self): return True def is_anonymous(self): return False
class Role(db.Model): id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String(255), default='normal') users = db.relationship('Role', secondary=user_roles, backref=db.backref('User'))
class User(db.Model, UserMixin): __tablename__ = 'user' id = db.Column(db.Integer, primary_key=True) loginname = db.Column(db.String(10)) username = db.Column(db.String(10)) password = db.Column(db.String(16)) active = db.Column(db.Boolean()) roles = db.relationship('Role', secondary=roles_users, backref=db.backref('users', lazy='dynamic')) def __init__(self, loginname, username, password, active, roles): self.loginname = loginname self.username = username self.password = password self.active = active self.roles = roles def __repr__(self): return '<User %r>' % self.username def to_json(self): return { 'id': self.id, 'username': self.username, 'loginname': self.loginname, 'active': self.active, 'roles': self.get_roles(), } def get_roles(self): roleids = [] if self.roles: for role in self.roles: role.to_json() roleids.append(role.id) return roleids def to_dict(self): return dict([(k, getattr(self, k)) for k in self.__dict__.keys() if not k.startswith("_")]) def verify_password(self, password): if self.password == password: return True return False @property def Is_Admin(self): if 'Administrator' in self.roles: return True return False @property def permissions(self): permissions = [] if self.roles: for role in self.roles: if role.get_permissionIds(): permissions += role.get_permissionIds() return permissions # @property # def password_hash(self): # raise AttributeError('password is not a readable attribute') # # @password_hash.setter # def password_hash(self, password): # self.password = generate_password_hash(password) # def get_id(self): # return unicode(self.id) @login_manager.user_loader def load_user(user_id): return User.query.get(int(user_id))
class Organization(db.Model): __tablename__ = "organizations" id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String(200)) active = db.Column(db.Boolean, default=False, server_default="0", nullable=False) created_at = db.Column(db.DateTime(), default=datetime.utcnow, nullable=False) enable_shiftplanning_export = db.Column(db.Boolean, default=False, server_default="0", nullable=False) enable_timeclock_default = db.Column(db.Boolean, default=False, server_default="0", nullable=False) enable_time_off_requests_default = db.Column(db.Boolean, default=False, server_default="0", nullable=False) day_week_starts = db.Column(db.String(200), db.Enum("monday", "tuesday", "wednesday", "thursday", "friday", "saturday", "sunday"), default="monday", server_default="monday", nullable=False) shifts_assigned_days_before_start = db.Column(db.Integer, default=4, server_default="4", nullable=False) workers_can_claim_shifts_in_excess_of_max = db.Column(db.Boolean, default=False, server_default="0", nullable=False) early_access = db.Column(db.Boolean, default=False, server_default="0", nullable=False) enterprise_access = db.Column(db.Boolean, default=False, server_default="0", nullable=False) # # Billing # # Override service and let org be paid forever. Use this for contracts, demo accounts, etc # When does the org get locked out? paid_until = db.Column(db.DateTime(), default=None) # Who's the one paying billing_user_id = db.Column("billing_user_id", db.Integer, db.ForeignKey("users.id"), index=True) # Stripe unique subscription id (for that user) stripe_customer_id = db.Column(db.String(200)) # The id of the plan, both here in the app and on stripe plan = db.Column(db.String(200), default="boss-v2", server_default="per-seat-v1", nullable=False) paid_labs_subscription_id = db.Column(db.String(200), nullable=True) # Can extend trial for certain people trial_days = db.Column(db.Integer, default=30, server_default="30", nullable=False) def in_trial(self): if self.paid(): return False return self.created_at + timedelta( days=self.trial_days) > datetime.now() def trial_days_remaining(self): if self.paid(): return 0 delta = self.created_at + timedelta( days=self.trial_days) - datetime.now() # This takes floor, so add 1 to round up days = delta.days + 1 if days < 0: return 0 return days def active_billing_plan(self): """ Keep it simple """ return self.paid_labs_subscription_id is not None def paid(self): """ Return whether the account is in good standings """ if self.paid_until is None: return False return self.paid_until >= datetime.now() def worker_count(self): """ Return the number of workers in the account """ query = select([func.count(distinct( user_model.User.id))])\ .where(Organization.id == self.id)\ .where(RoleToUser.archived == False)\ .where(Organization.id == Location.organization_id)\ .where(Location.id == Role.location_id)\ .where(Role.id == RoleToUser.role_id)\ .where(RoleToUser.user_id == user_model.User.id)\ .select_from(RoleToUser)\ .select_from(Role)\ .select_from(Location)\ .select_from(Organization)\ .select_from(user_model.User) # Filter out demo account and Staffjoy emails for email in current_app.config.get("KPI_EMAILS_TO_EXCLUDE"): query = query.where(not_(user_model.User.email.like(email))) workers = db.session.execute(query).fetchone()[0] return workers def set_paid_days(self, days): if self.paid_until is None: start = datetime.now() elif self.paid_until < datetime.now(): start = datetime.now() else: start = self.paid_until self.paid_until = start + timedelta(days=days) admins = db.relationship("User", secondary=organization_admins, backref=db.backref("admin_of", lazy="dynamic"), lazy="dynamic") locations = db.relationship("Location", backref=db.backref("organization")) def intercom_settings(self): """ Data for Intercom to pass to user model IN BROWSER""" data = { "company_id": str(self.id), "name": self.name, "remote_created_at": int(self.created_at.strftime("%s")), "custom_attributes": { "active": self.active, "paid": self.paid(), "plan": self.plan, "enterprise_access": self.enterprise_access, } } return data def get_ordered_week(self): """returns a list of the days of the week where day_week_starts is first""" wrap_index = DAYS_OF_WEEK.index(self.day_week_starts) return DAYS_OF_WEEK[wrap_index:] + DAYS_OF_WEEK[:wrap_index] def is_plan_boss(self): """determines if the current plan is part of Staffjoy Boss""" return self.plan in boss_plans def is_plan_flex(self): """determines if the current plan is part of Staffjoy Flex""" return self.plan in flex_plans def get_week_start_from_datetime(self, current_day): """ takes current_day and the org_id and returns a datetime for the start of the week DISCLAIMER - this does not normalize to midnight or pay attention to tzinfo """ week_start_index = DAYS_OF_WEEK.index(self.day_week_starts) adjust_days = (WEEK_LENGTH - week_start_index + current_day.weekday()) % WEEK_LENGTH return current_day - timedelta(days=adjust_days)
class User(db.Model, UserMixin, ModeloComIdMixin): username = db.Column(db.String(64), index=True, unique=True) email = db.Column(db.String(120), index=True, unique=True) password_hash = db.Column(db.String(128)) # Password criptografado posts = db.relationship("Post", backref="author", lazy="dynamic") about_me = db.Column(db.String(140)) last_seen = db.Column(db.DateTime, default=datetime.utcnow) # Followed -> Sendo seguido # Follower -> Seguindo alguém followed = db.relationship( "User", secondary=followers, primaryjoin=("followers.c.follower_id == User.id"), secondaryjoin=("followers.c.followed_id == User.id"), backref=db.backref("followers", lazy="dynamic"), lazy="dynamic", ) def __repr__(self): return "<User {}>".format(self.username) def set_password(self, password): self.password_hash = generate_password_hash(password) def check_password(self, password): return check_password_hash(self.password_hash, password) def avatar(self, size): digest = md5(self.email.lower().encode("utf-8")).hexdigest() return "https://www.gravatar.com/avatar/{}?d=identicon&s={}".format( digest, size ) def follow(self, user): if not self.is_following(user): self.followed.append(user) def unfollow(self, user): if self.is_following(user): self.followed.remove(user) def is_following(self, user) -> bool: return self.followed.filter(followers.c.followed_id == user.id).count() > 0 def followed_posts(self): followed = Post.query.join( followers, (followers.c.followed_id == Post.user_id) ).filter(followers.c.follower_id == self.id) own = Post.query.filter_by(user_id=self.id) return followed.union(own).order_by(Post.timestamp.desc()) def followed_posts_v2(self): query = """ select * from post p join followers f on f.followed_id == p.user_id join user u on u.id == p.user_id where f.follower_id == user.id order by p.timestamp desc; """ resultado = db.session.execute(query) return resultado def get_reset_password_token(self, expires_in=600): return jwt.encode( {"reset_password": self.id, "exp": time() + expires_in}, app.config["SECRET_KEY"], algorithm="HS256", ).decode("utf-8") @staticmethod def verify_reset_password_token(token): try: id = jwt.decode(token, app.config["SECRET_KEY"], algorithms=["HS256"])[ "reset_password" ] except Exception: # InvalidSignatureError: return return User.query.get(id)
class Tournament(db.Model): id = db.Column(db.Integer, primary_key=True) name = db.Column(db.VARCHAR(64), unique=True) tasks = db.relationship('Task', secondary=association_task_table, backref=db.backref('tasks', lazy='dynamic'))
class User(db.Model, UserMixin, BaseMixin): __tablename__ = 'users' id = db.Column(db.Integer, primary_key=True) username = db.Column(db.String(64)) email = db.Column(db.String(64)) password_hash = db.Column(db.String(64)) name = db.Column(db.String(64)) location = db.Column(db.String(64)) job = db.Column(db.String(64)) about_me = db.Column(db.String(64)) avatar_hash = db.Column(db.String(32)) # 默认头像 avatar_url_sm = db.Column(db.String(256)) # 自定义头像缩略图 avatar_url_nm = db.Column(db.String(256)) # 自定义头像正常图 last_seen = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow) # 最近登录 member_since = db.Column(db.DateTime, default=datetime.utcnow) # 注册日期 visited_count = db.Column(db.Integer, default=0) # 个人主页被浏览次数 confirmed = db.Column(db.Boolean, default=False) # 是否已经认证 role_id = db.Column(db.Integer, db.ForeignKey('roles.id')) questions = db.relationship('Question', backref='author', lazy='dynamic') # 提出的问题 answers = db.relationship('Answer', backref='author', lazy='dynamic') # 用户的回答 topics = db.relationship('Topic', backref='author', lazy='dynamic') # 创建的话题 posts = db.relationship('Post', backref='author', lazy='dynamic') # 我的文章 comments = db.relationship('Comment', backref='author', lazy='dynamic', foreign_keys=[Comment.author_id]) # 我的评论 replies = db.relationship('Reply', backref='author', lazy='dynamic', foreign_keys=[Reply.author_id]) # 我的回复 received_replies = db.relationship('Reply', backref='user', lazy='dynamic', foreign_keys=[Reply.user_id]) # 回复我的 private_messages = db.relationship('Message', backref='sender', lazy='dynamic', foreign_keys=[Message.sender_id ]) # 我发送的私信 private_messages_from = db.relationship('Message', backref='receiver', lazy='dynamic', foreign_keys=[Message.receiver_id ]) # 我接收的私信 favorites = db.relationship('Favorite', backref='user', lazy='dynamic') # 收藏夹 followers = db.relationship('Follow', backref=db.backref('followed', lazy='joined'), foreign_keys=[Follow.followed_id], lazy='dynamic', cascade='all,delete-orphan') # 关注者 followed = db.relationship('Follow', backref=db.backref('follower', lazy='joined'), foreign_keys=[Follow.follower_id], lazy='dynamic', cascade='all,delete-orphan') # 被关注者 followed_questions = db.relationship( 'FollowQuestion', backref=db.backref('follower', lazy='joined'), foreign_keys=[FollowQuestion.follower_id], lazy='dynamic', cascade='all,delete-orphan') # 关注的问题 followed_favorites = db.relationship( 'FollowFavorite', backref=db.backref('follower', lazy='joined'), foreign_keys=[FollowFavorite.follower_id], lazy='dynamic', cascade='all,delete-orphan') # 关注的收藏夹 followed_topics = db.relationship('FollowTopic', backref=db.backref('follower', lazy='joined'), foreign_keys=[FollowTopic.follower_id], lazy='dynamic', cascade='all,delete-orphan') # 关注的收藏夹 answer_likes = db.relationship('LikeAnswer', backref=db.backref('like_answer', lazy='joined'), lazy='dynamic', foreign_keys=[LikeAnswer.like_answer_id], cascade='all,delete-orphan') # 赞过的答案 post_likes = db.relationship('LikePost', backref=db.backref('like_post', lazy='joined'), lazy='dynamic', foreign_keys=[LikePost.like_post_id], cascade='all,delete-orphan') comment_likes = db.relationship('LikeComment', backref=db.backref('like_comment', lazy='joined'), lazy='dynamic', foreign_keys=[LikeComment.like_comment_id], cascade='all,delete-orphan') reply_likes = db.relationship('LikeReply', backref=db.backref('like_reply', lazy='joined'), lazy='dynamic', foreign_keys=[LikeReply.like_reply_id], cascade='all,delete-orphan') def __init__(self, **kwargs): super(User, self).__init__(**kwargs) if self.role is None: if self.email == current_app.config['ZHIDAO_ADMIN']: self.role = Role.query.filter_by(permission=0xff).first() if self.role is None: self.role = Role.query.filter_by(default=True).first() if self.email is not None and self.avatar_hash is None: self.avatar_hash = hashlib.md5( self.email.encode('utf-8')).hexdigest() User.add_self_follows() def follow(self, item): if isinstance(item, User): self.follow_user(item) elif isinstance(item, Question): self.follow_question(item) elif isinstance(item, Favorite): self.follow_favorite(item) elif isinstance(item, Topic): self.follow_topic(item) else: raise TypeError('参数错误') def unfollow(self, item): if isinstance(item, User): self.unfollow_user(item) elif isinstance(item, Question): self.unfollow_question(item) elif isinstance(item, Favorite): self.unfollow_favorite(item) elif isinstance(item, Topic): self.unfollow_topic(item) else: raise TypeError('参数错误') @staticmethod def add_self_follows(): for user in User.query.all(): if not user.is_following_user(user): user.follow(user) db.session.add(user) def follow_user(self, user): if not self.is_following_user(user): Follow.create(follower=self, followed=user) def unfollow_user(self, user): f = self.followed.filter_by(followed_id=user.id).first() if f: db.session.delete(f) db.session.commit() def is_following_user(self, user): return self.followed.filter_by(followed_id=user.id).first() is not None def is_followed_by_user(self, user): return self.followers.filter_by( follower_id=user.id).first() is not None def is_friend(self, user): return self.is_following_user(user) and self.is_followed_by_user(user) @property def friends(self): followers = [i.follower for i in self.followers] followed = [i.followed for i in self.followed] users = list(set(followers + followed)) friends = [i for i in users if self.is_friend(i)] return friends def follow_question(self, question): if not self.is_following_question(question): FollowQuestion.create(follower=self, followed=question) def unfollow_question(self, question): f = self.followed_questions.filter_by(followed_id=question.id).first() if f: db.session.delete(f) db.session.commit() def is_following_question(self, question): return self.followed_questions.filter_by( followed_id=question.id).first() is not None def follow_favorite(self, favorite): if not self.is_following_favorite(favorite): FollowFavorite.create(follower=self, followed=favorite) def unfollow_favorite(self, favorite): f = self.followed_favorites.filter_by(followed_id=favorite.id).first() if f: db.session.delete(f) db.session.commit() def is_following_favorite(self, favorite): return self.followed_favorites.filter_by( followed_id=favorite.id).first() is not None def follow_topic(self, topic): if not self.is_following_topic(topic): FollowTopic.create(follower=self, followed=topic) def unfollow_topic(self, topic): f = self.followed_topics.filter_by(followed_id=topic.id).first() if f: db.session.delete(f) db.session.commit() def is_following_topic(self, topic): return self.followed_topics.filter_by( followed_id=topic.id).first() is not None @property def followed_user_questions(self): """除了自己的关注的人的提问""" return Question.query.join(Follow, db.and_(Follow.followed_id == Question.author_id, Follow.followed_id != self.id)). \ filter(Follow.follower_id == self.id) @property def followed_user_answers(self): """ 除了自己的关注的人的回答 :return: """ return Answer.query.join(Follow, db.and_(Follow.followed_id == Answer.author_id, Follow.followed_id != self.id)). \ filter(Follow.follower_id == self.id) @property def followed_user_posts(self): return Post.query.join(Follow, db.and_(Follow.followed_id == Post.author_id, Follow.followed_id != self.id)). \ filter(Follow.follower_id == self.id) @property def followed_user_favorites(self): return Favorite.query.join(Follow, db.and_(Follow.followed_id == Favorite.user_id, Follow.followed_id != self.id)). \ filter(Follow.follower_id == self.id) def who_is_following_current_user_in_my_followed(self, user): """ 除自己之外我关注的人里面谁也关注了他,返回两条数据 :param user: :return: """ return [ i.followed for i in self.followed.order_by(Follow.timestamp.desc()).all() if i.followed.is_following_user(user) and user != i.followed ][:2] def like(self, item): if isinstance(item, Answer): self.like_answer(item) elif isinstance(item, Post): self.like_post(item) elif isinstance(item, Comment): self.like_comment(item) elif isinstance(item, Reply): self.like_reply(item) def unlike(self, item): if isinstance(item, Answer): self.unlike_answer(item) elif isinstance(item, Post): self.unlike_post(item) elif isinstance(item, Comment): self.unlike_comment(item) elif isinstance(item, Reply): self.unlike_reply(item) def like_answer(self, answer): if not self.is_like_answer(answer): f = LikeAnswer.create(like_answer=self, answer_liked=answer) def like_post(self, post): if not self.is_like_post(post): f = LikePost.create(like_post=self, post_liked=post) def like_comment(self, comment): if not self.is_like_comment(comment): LikeComment.create(like_comment=self, comment_liked=comment) def like_reply(self, reply): if not self.is_like_reply(reply): LikeReply.create(like_reply=self, reply_liked=reply) def unlike_answer(self, answer): f = self.answer_likes.filter_by(answer_liked_id=answer.id).first() if f: db.session.delete(f) def unlike_post(self, post): f = self.post_likes.filter_by(post_liked_id=post.id).first() if f: db.session.delete(f) def unlike_comment(self, comment): f = self.comment_likes.filter_by(comment_liked_id=comment.id).first() if f: db.session.delete(f) def unlike_reply(self, reply): f = self.reply_likes.filter_by(reply_liked_id=reply.id).first() if f: db.session.delete(f) def is_like_answer(self, answer): return self.answer_likes.filter_by( answer_liked_id=answer.id).first() is not None def is_like_post(self, post): return self.post_likes.filter_by( post_liked_id=post.id).first() is not None def is_like_comment(self, comment): return self.comment_likes.filter_by( comment_liked_id=comment.id).first() is not None def is_like_reply(self, reply): return self.reply_likes.filter_by( reply_liked_id=reply.id).first() is not None def has_collect_question(self, question): return any(quest.question == question for favorite in self.favorites for quest in favorite.questions.all()) def has_collect_answer(self, answer): return any(ans.answer == answer for favorite in self.favorites for ans in favorite.answers.all()) def has_collect_post(self, post): return any(pos.post == post for favorite in self.favorites for pos in favorite.posts.all()) def has_collect(self, item): if isinstance(item, Answer): return self.has_collect_answer(item) elif isinstance(item, Question): return self.has_collect_question(item) elif isinstance(item, Post): return self.has_collect_post(item) else: raise TypeError('参数类型错误') def collect(self, item, favorite): if isinstance(item, Answer): fa = self.favorites.filter_by(id=favorite.id).first() if fa: fa.collect_answer(item) if isinstance(item, Question): fa = self.favorites.filter_by(id=favorite.id).first() if fa: fa.collect_question(item) if isinstance(item, Post): fa = self.favorites.filter_by(id=favorite.id).first() if fa: fa.collect_post(item) def uncollect(self, item, favorite): if isinstance(item, Answer): fa = self.favorites.filter_by(id=favorite.id).first() if fa: fa.uncollect_answer(item) if isinstance(item, Question): fa = self.favorites.filter_by(id=favorite.id).first() if fa: fa.uncollect_question(item) if isinstance(item, Post): fa = self.favorites.filter_by(id=favorite.id).first() if fa: fa.uncollect_post(item) def gravatar(self, size=100, default='identicon', rating='g'): if request.is_secure: url = 'https://secure.gravatar.com/avatar' else: url = 'http://secure.gravatar.com/avatar' hash = self.avatar_hash or hashlib.md5( self.email.encode('utf-8')).hexdigest() return '{url}/{hash}?s={size}&d={default}&r={rating}'.format( url=url, hash=hash, size=size, default=default, rating=rating) def send_standard_message_to(self, content, to): Message.create(sender=self, receiver=to, message_content=content) def send_system_message(self, content): for user in User.query.all(): if user.id == self.id: Message.create(sender=self, receiver=user, message_content=content, message_type=MessageType.system) Message.create(sender=self, receiver=user, message_content=content, message_type=MessageType.system, delete_status=DeleteStatus.author_delete) def delete_send_box_msg(self, msg): """ 如果删除状态为标准,设置为author_delete 如果删除状态为user_delete,说明接收者已经将其删除,可以真正删除 :param msg: :return: """ message = self.private_messages.filter_by(id=msg.id).first() if message.delete_status == DeleteStatus.standard: message.delete_status = DeleteStatus.author_delete db.session.add(message) elif message.delete_status == DeleteStatus.user_delete: db.session.delete(message) db.session.commit() def delete_in_box_msg(self, msg): """ 如果删除状态为标准,设置为user_delete 如果删除状态为author_delete,说明发送者已经删除该消息,可以将其真正删除 :param msg: :return: """ message = self.private_messages_from.filter_by(id=msg.id).first() if message.delete_status == DeleteStatus.standard: message.delete_status = DeleteStatus.user_delete db.session.add(message) elif message.delete_status == DeleteStatus.author_delete: db.session.delete(message) db.session.commit() @property def send_box_messages(self): """ :return: """ return self.private_messages.filter( Message.delete_status != DeleteStatus.author_delete) @property def in_box_messages(self): """ 收件箱全部信息,包括未读的 :return: """ return self.private_messages_from.filter( Message.delete_status != DeleteStatus.user_delete) def set_messages_read(self): for message in self.in_box_messages.all(): message.status = MessageStatus.read db.session.add(message) db.session.commit() @property def in_box_message_unread_count(self): return self.private_messages_from.filter( Message.delete_status != DeleteStatus.user_delete, Message.status == MessageStatus.unread).count() def dialogue_with(self, whom): """ 双方都删除也会保留对话内容 :param whom:对话人物 :return: """ return Message.query.filter( db.or_(db.and_(Message.sender == self, Message.receiver == whom), db.and_(Message.sender == whom, Message.receiver == self)), Message.message_type == MessageType.standard) @property def total_liked_answers_count(self): """ 所有答案的总赞同 :return: """ return sum(i.liked_answers.count() for i in self.answers) def delete_comment(self, comment): if comment.replies.count() == 0: db.session.delete(comment) else: comment.was_delete = True db.session.add(comment) db.session.commit() def delete_reply(self, reply): if reply.comment.was_delete == True and reply.comment.replies.count( ) == 1: db.session.delete(reply.comment) else: db.session.delete(reply) db.session.commit() @hybrid_property def visited(self): return self.visited_count @visited.setter def visited(self, val): self.visited_count = val db.session.add(self) db.session.commit() def can(self, permissions): return self.role is not None and (self.role.permissions & permissions) == permissions def is_administrator(self): return self.can(Permission.ADMINISTER) @property def password(self): raise AttributeError('禁止访问') @password.setter def password(self, password): self.password_hash = bcrypt.generate_password_hash(password) def verify_password(self, password): return bcrypt.check_password_hash(self.password_hash, password) def generate_confirm_token(self, experition=3600): """生成token""" s = Serializer(current_app.config['SECRET_KEY'], expires_in=experition) return s.dumps({'confirm.txt': self.id}) def generate_reset_token(self, experition=60 * 60): """生成token""" s = Serializer(current_app.config['SECRET_KEY'], expires_in=experition) return s.dumps({'reset': self.id}) def generate_email_change_token(self, new_email, expiration=3600): s = Serializer(current_app.config['SECRET_KEY'], expiration) return s.dumps({'change_email': self.id, 'new_email': new_email}) def reset_password(self, token, password): s = Serializer(current_app.config['SECRET_KEY']) try: data = s.loads(token) except: return False if data and data.get('reset') == self.id: self.password = password db.session.add(self) return True else: return False def change_email(self, token): s = Serializer(current_app.config['SECRET_KEY']) try: data = s.loads(token) except: return False if data.get('change_email') != self.id: return False new_email = data.get('new_email') if new_email is None: return False if self.query.filter_by(email=new_email).first() is not None: return False self.email = new_email self.avatar_hash = hashlib.md5(self.email.encode('utf-8')).hexdigest() db.session.add(self) return True def confirm(self, token): """令牌验证,反序列化,数据存在并且令牌没被篡改,返回True""" s = Serializer(current_app.config['SECRET_KEY']) try: data = s.loads(token) except: return False if data and data.get('confirm.txt') == self.id: self.confirmed = True db.session.add(self) return True else: return False @staticmethod def verify_api_auth_token(token): s = Serializer(current_app.config['SECRET_KEY']) try: data = s.loads(token) except SignatureExpired: return None except BadSignature: return None user = User.query.get(data.get('id')) return user def __repr__(self): return '<User{}>'.format(self.username)
class User(db.Model): id = db.Column(db.Integer, primary_key=True) nickname = db.Column(db.String(64), index=True, unique=True) email = db.Column(db.String(120), index=True, unique=True) role = db.Column(db.SmallInteger, default=ROLE_USER) posts = db.relationship('Post', backref='author', lazy='dynamic') about_me = db.Column(db.String(1024)) last_seen = db.Column(db.DateTime) followed = db.relationship('User', secondary=followers, primaryjoin=(followers.c.follower_id == id), secondaryjoin=(followers.c.followed_id == id), backref=db.backref('followers', lazy='dynamic'), lazy='dynamic') def __repr__(self): return '<User %r>' % (self.nickname) def is_authenticated(self): return True def is_active(self): return True def is_anonymous(self): return False def get_id(self): return unicode(self.id) def avatar(self, size): return 'http://www.gravatar.com/avatar/' + md5( self.email).hexdigest() + '?d=mm&s=' + str(size) @staticmethod def make_unique_nickname(nickname): if User.query.filter_by(nickname=nickname).first() == None: return nickname version = 2 while True: new_nickname = nickname + str(version) if User.query.filter_by(nickname=new_nickname).first() == None: break version += 1 return new_nickname def follow(self, user): if not self.is_following(user): self.followed.append(user) return self def unfollow(self, user): if self.is_following(user): self.followed.remove(user) return self def is_following(self, user): return self.followed.filter( followers.c.followed_id == user.id).count() > 0 def followed_posts(self): return Post.query.join( followers, (followers.c.followed_id == Post.user_id)).filter( followers.c.follower_id == self.id).order_by( Post.timestamp.desc())
class User(UserMixin, db.Model): """ Represetns a system User """ id = db.Column(db.Integer, primary_key=True) username = db.Column(db.String(64), index=True, unique=True) email = db.Column(db.String(120), index=True, unique=True) password_hash = db.Column(db.String(128)) about_me = db.Column(db.String(140)) last_seen = db.Column(db.DateTime, default=datetime.utcnow) posts = db.relationship('Post', backref='author', lazy='dynamic') followed = db.relationship('User', secondary=followers, primaryjoin=(followers.c.follower_id == id), secondaryjoin=(followers.c.followed_id == id), backref=db.backref('followers', lazy='dynamic'), lazy='dynamic') def followed_posts(self): """ followed posts """ followed = Post.query.join( followers, (followers.c.followed_id == Post.user_id)).filter( followers.c.follower_id == self.id) own = Post.query.filter_by(user_id=self.id) return followed.union(own).order_by(Post.timestamp.desc()) def follow(self, user): """ follow """ if not self.is_following(user): self.followed.append(user) def unfollow(self, user): """ unfollow """ if self.is_following(user): self.followed.remove(user) def is_following(self, user): """ is following """ return self.followed.filter( followers.c.followed_id == user.id).count() > 0 def __repr__(self): """ repr """ return '<User {}, {}>'.format(self.username, self.email) def set_password(self, password): """ Set password to generated password hash """ self.password_hash = generate_password_hash(password) def check_password(self, password): """ Check if password hash matches set password """ current_app.logger.debug("Checking password {}".format(password)) return check_password_hash(self.password_hash, password) @staticmethod @login.user_loader def load_user(id_): """ Return user base on id. """ return User.query.get(int(id_)) def avatar(self, size="80"): """ Return Gravatar URL based on email """ digest = md5(self.email.lower().encode('utf-8')).hexdigest() url = 'https://www.gravatar.com/avatar/{}?d=retro&s={}'.format( digest, size) current_app.logger.debug("Get gravatar {}".format(url)) return url
class User(UserMixin, db.Model): id = db.Column(db.Integer, primary_key=True) username = db.Column(db.String(62), index=True, unique=True) email = db.Column(db.String(120), index=True, unique=True) password_hash = db.Column(db.String(128)) image = db.Column(db.String(120)) # relationship with posts posts = db.relationship('Post', backref='author', lazy='dynamic') about_me = db.Column(db.String(140)) last_seen = db.Column(db.DateTime, default=datetime.utcnow) # for followers followed = db.relationship('User', secondary=followers, primaryjoin=(followers.c.follower_id == id), secondaryjoin=(followers.c.followed_id == id), backref=db.backref('followers', lazy='dynamic'), lazy='dynamic') def follow(self, user): """for follow user""" if not self.is_following(user): self.followed.append(user) def unfollow(self, user): """for unfollow user""" if self.is_following(user): self.followed.remove(user) def is_following(self, user): """check user is following or not""" return self.followed.filter( followers.c.followed_id == user.id).count() > 0 def followed_posts(self): """to get all the followed user posts and own posts""" followed = Post.query.join( followers, (followers.c.followed_id == Post.user_id)).filter( followers.c.follower_id == self.id) own = Post.query.filter_by(user_id=self.id) return followed.union(own).order_by(Post.timestamp.desc()) # for password reset def get_reset_password_token(self, expires_in=600): """for generating reset token""" return jwt.encode( { 'reset_password': self.id, 'exp': time() + expires_in }, app.config['SECRET_KEY'], algorithm='HS256').decode('utf-8') @staticmethod def verify_reset_password_token(token): """for verifying reset token""" try: id = jwt.decode(token, app.config['SECRET_KEY'], algorithms=['HS256'])['reset_password'] except: return return User.query.get(id) def avatar(self, size): """getting default avtar for every new user""" digest = md5(self.email.lower().encode('utf-8')).hexdigest() return 'https://www.gravatar.com/avatar/{}?d=identicon&s={}'.format( digest, size) def set_password(self, password): """hashing password""" self.password_hash = generate_password_hash(password) def check_password(self, password): """checking password""" return check_password_hash(self.password_hash, password) def __repr__(self): return "{0.id} - {0.email}".format(self)
class Node(SearchableMixin,db.Model): __tablename__ = 'node' __searchable__ = { 'name': {"type": "text", "analyzer": "ik_max_word", "search_analyzer": "ik_smart"} } id = db.Column(db.Integer, primary_key=True) # 对外id uuid = db.Column(db.String(64), default=shortuuid.uuid, unique=True) # 名称 name = db.Column(db.String(32)) # 创建时间 create_at = db.Column(db.DateTime(), default=datetime.utcnow()) # 类型 0正常型 1热搜型[正常型:按照发布时间排序。热搜型:首页显示条目按照posiniton显示,历史数据按照update时间排序] type = db.Column(db.Integer, default=0) # 首页显示条目数 num = db.Column(db.Integer, default=15) # 来源 source_id = db.Column(db.Integer, db.ForeignKey('source.id')) # 爬虫内容 tops = db.relationship('Top', backref=db.backref('node')) # 归属topic topic_id = db.Column(db.Integer, db.ForeignKey('topic.id')) # 联合唯一 __table_args__ = ( db.UniqueConstraint('name', 'source_id'), ) def admin_to_json(self): return { "topic_id": self.topic.id, "source_id": self.source.id, "name": self.name, "type": self.type, "num": self.num, } def tiny_to_json(self): return { "topic_name": self.topic.name, "id": self.uuid, "source_name": self.source.name, "source_img": self.source.img_url, "name": self.name, } def to_json(self, need_history=False): json_node = { "topic_name":self.topic.name, "id": self.uuid, "source_name": self.source.name, "source_img": self.source.img_url, "name": self.name, "tops": get_tops(self.id, self.num, self.type, need_history) } return json_node def __repr__(self): return '<Node id={0},name={1}>'.format(self.uuid, self.name)
class User(UserMixin, db.Model): id = db.Column(db.Integer, primary_key=True) username = db.Column(db.String(64), index=True, unique=True) email = db.Column(db.String(120), index=True, unique=True) password_hash = db.Column(db.String(128)) posts = db.relationship('Post', backref='author', lazy='dynamic') about_me = db.Column(db.String(140)) last_seen = db.Column(db.DateTime, default=datetime.utcnow) followed = db.relationship("User", secondary=followers, primaryjoin=(followers.c.follower_id == id), secondaryjoin=(followers.c.followed_id == id), backref=db.backref("followers", lazy="dynamic"), lazy="dynamic") def set_password(self, password): self.password_hash = generate_password_hash(password) def check_password(self, password): return check_password_hash(self.password_hash, password) def avatar(self, size): digest = md5(self.email.lower().encode("utf-8")).hexdigest() return 'https://www.gravatar.com/avatar/{}?d=identicon&s={}'.format( digest, size) def __repr__(self): return '<User {}>'.format(self.username) def follow(self, user): if not self.is_following(user): self.followed.append(user) def unfollow(self, user): if self.is_following(user): self.followed.remove(user) def is_following(self, user): return self.followed.filter( followers.c.followed_id == user.id).count() > 0 def followed_posts(self): followed = Post.query.join( followers, (followers.c.followed_id == Post.user_id)).filter( followers.c.follower_id == self.id) own = Post.query.filter_by(user_id=self.id) return followed.union(own).order_by(Post.timestamp.desc()) def get_reset_password_token(self, expires_in=600): return jwt.encode( { "reset_password": self.id, "exp": time() + expires_in }, app.config["SECRET_KEY"], algorithm="HS256").decode("utf-8") @staticmethod def verify_reset_password_token(token): try: id = jwt.decode(token, app.config["SECRET_KEY"], algorithms=["HS256"])["reset_password"] except: return return User.query.get(id)
class User(db.Model): id = db.Column(db.Integer, primary_key=True) nickname = db.Column(db.String(64), index=True, unique=True) email = db.Column(db.String(120), index=True, unique=True) posts = db.relationship('Post', backref='author', lazy='dynamic') about_me = db.Column(db.String(140)) last_seen = db.Column(db.DateTime) followed = db.relationship('User', secondary=followers, primaryjoin=(followers.c.follower_id == id), secondaryjoin=(followers.c.followed_id == id), backref=db.backref('followers', lazy='dynamic'), lazy='dynamic') @staticmethod def make_valid_nickname(nickname): return re.sub('[^a-zA-Z0-9_\.]', '', nickname) @staticmethod def make_unique_nickname(nickname): if User.query.filter_by(nickname=nickname).first() is None: return nickname version = 2 while True: new_nickname = nickname + str(version) if User.query.filter_by(nickname=new_nickname).first() is None: break version += 1 return new_nickname @property def is_authenticated(self): return True @property def is_active(self): return True @property def is_anonymous(self): return False def get_id(self): try: return unicode(self.id) # python 2 except NameError: return str(self.id) # python 3 def avatar(self, size): return 'http://www.gravatar.com/avatar/%s?d=mm&s=%d' % \ (md5(self.email.encode('utf-8')).hexdigest(), size) def follow(self, user): if not self.is_following(user): self.followed.append(user) return self def unfollow(self, user): if self.is_following(user): self.followed.remove(user) return self def is_following(self, user): return self.followed.filter( followers.c.followed_id == user.id).count() > 0 def followed_posts(self): return Post.query.join( followers, (followers.c.followed_id == Post.user_id)).filter( followers.c.follower_id == self.id).order_by( Post.timestamp.desc()) def __repr__(self): # pragma: no cover return '<User %r>' % (self.nickname)
class ItemModel(db.Model): """ Item model """ __tablename__ = 'item' id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String(80), nullable=False) description = db.Column(db.TEXT) category_id = db.Column(db.Integer, db.ForeignKey('category.id')) category = db.relationship(CategoryModel, backref=db.backref('items', lazy=True)) created_date = db.Column(db.DateTime, default=datetime.datetime.utcnow) slug = db.Column(db.String(80)) user_id = db.Column(db.Integer, db.ForeignKey('user.id')) user = db.relationship(UserModel, backref=db.backref('items', lazy=True)) __table_args__ = (db.UniqueConstraint('slug', name='slug_unique_1'), ) @staticmethod def find(item_id): """ Find an item by its id :param item_id: item id :return: an Item or None """ return db.session.query(ItemModel).filter_by(id=item_id).one_or_none() @staticmethod def get_user_item(item_id, user_id): """ Find an item by its it and its user id :param item_id: item id :param user_id: user id :return: an Item or None """ return db.session.query(ItemModel).filter_by( id=item_id, user_id=user_id).one_or_none() @staticmethod def validate_slug(slug): """ Validate an item by its slug :param slug: item slug :return: True if valid and False if invalid """ item = db.session.query(ItemModel) \ .filter_by(slug=slug).one_or_none() if item is not None: return False else: return True @staticmethod def get_last_n_items(n=10): return db.session.query(ItemModel).order_by( ItemModel.created_date.desc()).limit(n).all() @staticmethod def get_all_items(): return db.session.query(ItemModel).all()
class User(db.Model): id = db.Column(db.Integer, primary_key=True) nickname = db.Column(db.String(64), index=True, unique=True) email = db.Column(db.String(120), index=True, unique=True) pwdhash = db.Column(db.String(54)) activation_status = db.Column(db.Boolean, index=True) posts = db.relationship('Post', backref='author', lazy='dynamic') flwrs = db.Column(db.Integer, index=True) tokens = db.Column(db.Text) followed = db.relationship('User', secondary=followers, primaryjoin=(followers.c.follower_id == id), secondaryjoin=(followers.c.followed_id == id), backref=db.backref('followers', lazy='dynamic'), lazy='dynamic') def __init__(self, nickname, email, password): self.nickname = nickname self.email = email.lower() self.set_password(password) self.activation_status = False self.flwrs = -1 def follow(self, user): if not self.is_following(user): self.followed.append(user) user.flwrs = user.flwrs + 1 db.session.commit() return self def unfollow(self, user): if self.is_following(user): self.followed.remove(user) user.flwrs = user.flwrs - 1 db.session.commit() return self def is_following(self, user): return self.followed.filter( followers.c.followed_id == user.id).count() > 0 def has_liked(self, post_id): post = Post.query.filter_by(id=post_id).first() res = Like.query.filter_by(post_id=post_id).all() for r in res: if r.user_id == self.id: return True else: return False def set_password(self, password): self.pwdhash = generate_password_hash(password) def check_password(self, password): return check_password_hash(self.pwdhash, password) def make_dirs(self): os.makedirs('/home/manan/Programs/EduTech/app/static/userdata/' + str(self.nickname), exist_ok=True) def avatar(self): for root, dirs, files in os.walk( "/home/manan/Programs/EduTech/app/static/userdata/" + str(self.nickname)): for file in files: if file.endswith(".jpg") or file.endswith(".png"): return "/static/userdata/" + str( self.nickname) + '/' + str(file) return "/static/userdata/avatar.png" def __repr__(self): return '<User %s, Email %s, Activation %s>' % ( self.nickname, self.email, self.activation_status)
class Image(db.Model): __tablename__ = 'image' id = db.Column(db.Integer, primary_key=True) filename = db.Column(db.String(200), nullable=False) uploader_id = db.Column(db.Integer, db.ForeignKey('user.id')) uploader = db.relationship('User', backref=db.backref('images'))
class User(PaginatedAPIMixin, UserMixin, db.Model): id = db.Column(db.Integer, primary_key=True) username = db.Column(db.String(64), index=True, unique=True) email = db.Column(db.String(120), index=True, unique=True) password_hash = db.Column(db.String(128)) # backref is how the relationship will be accessed from the RS entity # lazy="dynamic" -> NO run until explicitly requested posts = db.relationship("Post", backref="author", lazy="dynamic") about_me = db.Column(db.String(140)) last_seen = db.Column(db.DateTime, default=datetime.utcnow) # secondary configures the association table for this relationship # primaryjoin links the LS entity(follower) with the association table # followers.c(olumn).follower_id references the follower_id column # secondaryjoin links the RS entity(followed) with the association table # relationship from LS entity - followed, from RS entity - followers followed = db.relationship( "User", secondary=followers, primaryjoin=(followers.c.follower_id == id), secondaryjoin=(followers.c.followed_id == id), backref=db.backref("followers", lazy="dynamic"), lazy="dynamic", ) notifications = db.relationship( "Notification", backref="user", lazy="dynamic" ) messages_sent = db.relationship( "Message", foreign_keys="Message.sender_id", backref="author", lazy="dynamic", ) messages_received = db.relationship( "Message", foreign_keys="Message.recipient_id", backref="recipient", lazy="dynamic", ) last_message_read_time = db.Column(db.DateTime) tasks = db.relationship("Task", backref="user", lazy="dynamic") token = db.Column(db.String(32), index=True, unique=True) token_expiration = db.Column(db.DateTime) def __repr__(self): return f"<User {self.username}>" def set_password(self, password): self.password_hash = generate_password_hash(password) def check_password(self, password): return check_password_hash(self.password_hash, password) def avatar(self, size): # MD5 support works on bytes and not strings, encode the string digest = md5(self.email.lower().encode("utf-8")).hexdigest() return f"https://www.gravatar.com/avatar/{digest}?d=identicon&s={size}" def is_following(self, user): return ( self.followed.filter(followers.c.followed_id == user.id).count() > 0 ) def follow(self, user): if not self.is_following(user): self.followed.append(user) def unfollow(self, user): if self.is_following(user): self.followed.remove(user) def followed_posts(self): followed = Post.query.join( followers, (followers.c.followed_id == Post.user_id) ).filter(followers.c.follower_id == self.id) own = Post.query.filter_by(user_id=self.id) return followed.union(own).order_by(Post.timestamp.desc()) def get_reset_password_token(self, expires_in=600): return jwt.encode( {"reset_password": self.id, "exp": time() + expires_in}, current_app.config["SECRET_KEY"], algorithm="HS256", ).decode("utf-8") @staticmethod def verify_reset_password_token(token): try: id = jwt.decode( token, current_app.config["SECRET_KEY"], algorithms=["HS256"] )["reset_password"] except Exception: return return User.query.get(id) def new_messages(self): last_read_time = self.last_message_read_time or datetime(1900, 1, 1) return ( Message.query.filter_by(recipient=self) .filter(Message.timestamp > last_read_time) .count() ) def add_notification(self, name, data): self.notifications.filter_by(name=name).delete() n = Notification(name=name, payload_json=json.dumps(data), user=self) db.session.add(n) return n def launch_task(self, name, description, *args, **kwargs): rq_job = current_app.task_queue.enqueue( "app.tasks." + name, self.id, *args, **kwargs ) task = Task( id=rq_job.get_id(), name=name, description=description, user=self ) db.session.add(task) return task def get_tasks_in_progress(self): return Task.query.filter_by(user=self, complete=False).all() def get_task_in_progress(self, name): return Task.query.filter_by( name=name, user=self, complete=False ).first() def to_dict(self, include_email=False): data = { "id": self.id, "username": self.username, "last_seen": (self.last_seen.isoformat() + "Z") if self.last_seen else "no_log", "about_me": self.about_me, "post_count": self.posts.count(), "follower_count": self.followers.count(), "followed_count": self.followed.count(), "_links": { "self": url_for("api.get_user", id=self.id), "followers": url_for("api.get_followers", id=self.id), "followed": url_for("api.get_followed", id=self.id), "avatar": self.avatar(128), }, } if include_email: data["email"] = self.email return data def from_dict(self, data, new_user=False): for field in ["username", "email", "about_me"]: if field in data: setattr(self, field, data[field]) if new_user and "password" in data: self.set_password(data["password"]) def get_token(self, expires_in=3600): now = datetime.utcnow() if self.token and self.token_expiration > now + timedelta(seconds=60): return self.token self.token = base64.b64encode(os.urandom(24)).decode("utf-8") self.token_expiration = now + timedelta(seconds=expires_in) db.session.add(self) return self.token def revoke_token(self): self.token_expiration = datetime.utcnow() - timedelta(seconds=1) @staticmethod def check_token(token): user = User.query.filter_by(token=token).first() if user is None or user.token_expiration < datetime.utcnow(): return None return user
class User( UserMixin, db.Model ): #User class inherits from db.Model, a base class for all models from Flask-SQLAlchemy id = db.Column(db.Integer, primary_key=True) username = db.Column(db.String(64), index=True, unique=True) #indexing allows efficient search email = db.Column(db.String(120), index=True, unique=True) password_hash = db.Column(db.String(128)) posts = db.relationship( 'Post', backref='author', lazy='dynamic' ) #not a db field, but a high-level representatn btw users and posts. about_me = db.Column(db.String(140)) last_seen = db.Column(db.DateTime, default=datetime.utcnow) def __repr__( self ): #tells Python how to print objects of this class, and useful for debugging return '<User {}>'.format(self.username) def avatar(self, size): digest = md5(self.email.lower().encode('utf-8')).hexdigest() return 'https://www.gravatar.com/avatar/{}?d=identicons&s={}'.format( digest, size) followed = db.relationship('User', secondary=followers, primaryjoin=(followers.c.follower_id), secondaryjoin=(followers.c.followed_id), backref=db.backref('followers', lazy='dynamic'), lazy='dynamic') #many-to-many relationship: followed = db.relationship('User', secondary=followers, primaryjoin=(followers.c.follower_id == id), secondaryjoin=(followers.c.followed_id == id), backref=db.backref('followers', lazy='dynamic'), lazy='dynamic') #password hashing mechanism: user can now do secure password verification, without a need to store original passwords def set_password(self, password): self.password_hash = generate_password_hash(password) def check_password(self, password): return check_password_hash(self.password_hash, password) #to add(follow) and remove relationships(unfollow) def follow(self, user): if not self.is_following(user): self.followed.append(user) def unfollow(self, user): if self.is_following(user): self.followed.remove(user) def is_following(self, user): return self.followed.filter( followers.c.followed_id == user.id).count() > 0 def followed_posts(self): followed = Post.query.join( followers, (followers.c.followed_id == Post.user_id)).filter( followers.c.follower_id == self.id) own = Post.query.filter_by(user_id=self.id) return followed.union(own).order_by(Post.timestamp.desc()) def get_reset_password_token(self, expires_in=600): return jwt.encode( { 'reset_password': self.id, 'exp': time() + expires_in }, app.config['SECRET_KEY'], algorithm='HS256').decode('utf-8') @staticmethod #does not need an object, and so can be invoked directly from d class def verify_reset_password_token(token): try: id = jwt.decode(token, app.config['SECRET_KEY'], algorithms=['HS256'])['reset_password'] #load user except: return return User.query.get(id)
class User(UserMixin, db.Model): __tablename__ = 'user' id = db.Column(db.Integer, primary_key=True) username = db.Column(db.String(64), index=True, unique=True) email = db.Column(db.String(128), index=True, unique=True) password_hash = db.Column(db.String(128)) posts = db.relationship('Purchase', backref='author', lazy='dynamic') purchases = db.relationship( 'Purchase', secondary=purchases_table, backref='user_purchase', lazy='dynamic' ) last_seen = db.Column(db.DateTime, default=datetime.utcnow) remindings = db.Column(db.String(140)) followed = db.relationship( 'User', secondary=followers, primaryjoin=(followers.c.follower_id == id), secondaryjoin=(followers.c.followed_id == id), backref=db.backref('followers', lazy='dynamic'), lazy='dynamic' ) def set_password(self, password): self.password_hash = generate_password_hash(password) def check_password(self, password): return check_password_hash(self.password_hash, password) def avatar(self, size): digest = md5(self.email.lower().encode('utf-8')).hexdigest() return 'https://www.gravatar.com/avatar/{}?d=identicon&s={}'.format( digest, size ) def add_purchase(self, purchase): if not self.bought(purchase): self.purchases.append(purchase) def rm_purchase(self, purchase): if self.bought(purchase): self.purchases.remove(purchase) def bought(self, purchase): return self.purchases.filter( purchases_table.c.purchase_id == purchase.id ).count() > 0 def bought_purchases(self): purchased = Purchase.query.join( purchases_table, (purchases_table.c.purchase_id == Purchase.id)).filter( purchases_table.c.purchaser_id == self.id ) return purchased.order_by(Purchase.timestamp.desc()) def follow(self, user): if not self.is_following(user): self.followed.append(user) def unfollow(self, user): if self.is_following(user): self.followed.remove(user) def is_following(self, user): return self.followed.filter( followers.c.followed_id == user.id ).count() > 0 def followed_purchases(self): followed = Purchase.query.join( followers, (followers.c.followed_id == Purchase.user_id) ).filter( followers.c.follower_id == self.id ) own = Purchase.query.filter_by(user_id=self.id) return followed.union(own).order_by(Purchase.timestamp.desc()) def get_reset_password_token(self, expires_in=600): return jwt.encode( { 'reset_password': self.id, 'exp': time() + expires_in }, current_app.config['SECRET_KEY'], algorithm='HS256' ).decode('utf-8') @staticmethod def verify_reset_password_token(token): try: id = jwt.decode( token, current_app.config['SECRET_KEY'], algorithms='HS256' )['reset_password'] except: return return User.query.get(id) @classmethod def get_user_list(cls): return User.query.order_by(User.username) def __repr__(self): return "<User {}>".format(self.username)
class User(UserMixin, db.Model): id = db.Column(db.Integer, primary_key=True) username = db.Column(db.String(64), index=True, unique=True) email = db.Column(db.String(120), index=True, unique=True) password_hash = db.Column(db.String(128)) posts = db.relationship('Post', backref='author', lazy='dynamic') about_me = db.Column(db.String(140)) last_seen = db.Column(db.DateTime, default=datetime.utcnow) followed = db.relationship('User', secondary=followers, primaryjoin=(followers.c.follower_id == id), secondaryjoin=(followers.c.followed_id == id), backref=db.backref('followers', lazy='dynamic'), lazy='dynamic') def set_password(self, password): self.password_hash = generate_password_hash(password) def check_password(self, password): return check_password_hash(self.password_hash, password) def get_reset_password_token(self, expires_in=600): payload = { 'reset_password': self.id, 'exp': time() + expires_in, } return jwt.encode(payload, current_app.config['SECRET_KEY'], algorithm='HS256').decode('utf-8') @staticmethod def verify_reset_password_token(token): try: user_id = jwt.decode(token, current_app.config['SECRET_KEY'], algorithms=['HS256'])['reset_password'] except (jwt.exceptions.DecodeError, jwt.exceptions.ExpiredSignatureError): return return User.query.get(user_id) def avatar(self, size): digest = md5(self.email.lower().encode('utf-8')).hexdigest() return f'https://www.gravatar.com/avatar/{digest}?d=identicon&s={size}' def follow(self, user): if not self.is_following(user): self.followed.append(user) def unfollow(self, user): if self.is_following(user): self.followed.remove(user) def is_following(self, user): return self.followed.filter( followers.c.followed_id == user.id).count() > 0 def followed_posts(self): followed = Post.query.join( followers, (followers.c.followed_id == Post.user_id)).filter( followers.c.follower_id == self.id) own = Post.query.filter_by(user_id=self.id) return followed.union(own).order_by(Post.timestamp.desc()) def __repr__(self): return f'<User id={self.id} username={self.username} email={self.email} ' \ f'password_hash={self.password_hash}>'
class User(UserMixin, db.Model): __tablename__ = "users" id = db.Column(db.Integer, primary_key=True) email = db.Column(db.String(120), index=True, unique=True) username = db.Column(db.String(120), index=True, unique=True) password_hash = db.Column(db.String(128)) streak = db.Column(db.Integer, default=0) daily_meditations = db.relationship("DailyMeditation", backref="user", lazy="dynamic") daily_meditations = db.relationship( "DailyMeditation", secondary=user_daily_meditation, back_populates="users", lazy="dynamic" ) followed = db.relationship( 'User', secondary=followers, primaryjoin=(followers.c.follower_id == id), secondaryjoin=(followers.c.followed_id == id), backref=db.backref('followers', lazy='dynamic'), lazy='dynamic' ) def __repr__(self): return "<User id: {} email: {}>".format(self.id, self.email) def set_password(self, password): self.password_hash = generate_password_hash(password) def check_password(self, password): return check_password_hash(self.password_hash, password) def is_streak(self): yesterday = datetime.date.today()-datetime.timedelta(days=1) yesterdays_meditation = DailyMeditation.query.filter_by(date=yesterday) if yesterdays_meditation in self.daily_meditations: return True return False def follow(self, user): if not self.is_following(user): self.followed.append(user) def unfollow(self, user): if self.is_following(user): self.followed.remove(user) def is_following(self, user): return self.followed.filter( followers.c.followed_id == user.id).count() > 0 def get_highscore(self): friends = [friend for friend in self.followed] friends.append(self) friends.sort(key=lambda current_friend: current_friend.streak, reverse=True) return friends
def sample_annotations(self): return db.relationship('ProjectSampleAnnotations', backref=db.backref('project'), lazy='dynamic', cascade='save-update, merge, delete, expunge')
class User(UserMixin, db.Model): __tablename__ = "users" id = db.Column(db.Integer, primary_key=True) email = db.Column(db.String(256), unique=True, index=True) username = db.Column(db.String(64), unique=True, index=True) confirmed = db.Column(db.Boolean, default="0") sudo = db.Column(db.Boolean, default=False, server_default="0") name = db.Column(db.String(256)) password_hash = db.Column(db.String(128)) member_since = db.Column(db.DateTime(), default=datetime.utcnow) # The "ping" function updates this last_seen = db.Column(db.DateTime(), default=datetime.utcnow) active = db.Column(db.Boolean, default=True, server_default="1") # used for invitation roles = db.relationship("RoleToUser", backref=db.backref("user")) enable_notification_emails = db.Column(db.Boolean, nullable=False, default=True, server_default="1") enable_timeclock_notification_sms = db.Column(db.Boolean, nullable=False, default=True, server_default="1") phone_country_code = db.Column(db.String(3), index=True) phone_national_number = db.Column(db.String(15), index=True) SMS_VERIFICATION_PIN_LENGTH = 6 def __init__(self, **kwargs): super(User, self).__init__(**kwargs) self.session_id = None @property def password(self): raise AttributeError("password is not a readable attribute") def __eq__(self, other): """Checks equality of user objects. Necessary to override UserMixin one.""" return self.id == other.id @property def first_name(self): """Guess first name for friendly notifications""" name_list = self.name.partition(" ") return name_list[0] @password.setter def password(self, password): """Store plaintext password securely on the model, and send alerts + flush sessions""" # See whether there was a password before existing_password = self.password_hash is None # Actually update the password self.password_hash = generate_password_hash(password) db.session.commit() if self.id is not None: # This is used before user is committed current_app.logger.info( "Password for user id %s (%s) has been updated" % (self.id, self.email)) # If password is changed, lot user out of other if not existing_password: # Email users for security purposes self.send_email("[Alert] Your Password Has Changed", render_template( "email/password-changed.html", user=self, ), force_send=True) # Logout all sessions self.logout_all_sessions() def ping(self, org_id=None): """Update the last-seen time, and send tracking info to intercom""" if not PingLimiter.allowed_to_send(self): return if not self.active or not self.confirmed: current_app.logger.debug( "Did not send tracking for inactive user %s" % self.id) return PingLimiter.mark_sent(self) try: self.last_seen = datetime.utcnow() db.session.add(self) db.session.commit() except: db.session.rollback() # Now update intercom if current_app.config.get("ENV") == "dev": return @copy_current_request_context def async_callback(resp): if resp.code != 200: current_app.logger.info("Failed intercom update - header %s" % (resp.code)) headers = { "Accept": "application/json", "Content-Type": "application/json", } # Stuff we want to tack on, so decode then reencode data = { "user_id": str(self.id), "name": self.name, "custom_attributes": { "sudo": self.sudo, "username": self.username, "phone_number": self.pretty_phone_number, }, "email": self.email, "created_at": int(self.member_since.strftime("%s")), "last_request_at": int(time.time()), "new_session": True, "companies": [], } if org_id is not None and not self.is_sudo(): # Don't add users to any org org = organization_model.Organization.query.get(org_id) if org is not None: data["companies"].append(org.intercom_settings()) unirest.post( "https://api.intercom.io/users", params=json.dumps(data), headers=headers, auth=( current_app.config.get("INTERCOM_ID"), current_app.config.get("INTERCOM_API_KEY"), ), callback=async_callback, ) return def track_event(self, event): """ Send events to intercom """ if event is None: current_app.logger.warning("Null event") return if not self.active or not self.confirmed: current_app.logger.debug( "Did not track event %s for inactive user %s" % (event, self.id)) if current_app.config.get("ENV") == "dev": current_app.logger.debug("Intercepted event for user %s - %s" % (self.id, event)) return @copy_current_request_context def async_callback(resp): if resp.code != 202: current_app.logger.info("bad intercom event - header %s" % (resp.code)) headers = { "Accept": "application/json", "Content-Type": "application/json", } # Stuff we want to tack on, so decode then reencode data = { "user_id": str(self.id), "email": self.email, "created_at": int(time.time()), "event_name": event, } unirest.post( "https://api.intercom.io/events", params=json.dumps(data), headers=headers, auth=( current_app.config.get("INTERCOM_ID"), current_app.config.get("INTERCOM_API_KEY"), ), callback=async_callback, ) return def verify_password(self, password): return check_password_hash(self.password_hash, password) def generate_confirmation_token(self, expiration=SECONDS_PER_DAY, trial=False): s = Serializer(current_app.config["SECRET_KEY"], expiration) return s.dumps({"confirm": self.id, "trial": trial}) def generate_reset_token(self, expiration=SECONDS_PER_HOUR): s = Serializer(current_app.config["SECRET_KEY"], expiration) return s.dumps({"reset": self.id}) def generate_activation_token(self, expiration=SECONDS_PER_DAY): """ Used for account invitations """ s = Serializer(current_app.config["SECRET_KEY"], expiration) return s.dumps({"activation": self.id}) def reset_password(self, token, new_password): s = Serializer(current_app.config["SECRET_KEY"]) try: data = s.loads(token) except: return False if data.get("reset") != self.id: return False self.password = new_password self.logout_all_sessions() db.session.add(self) self.track_event("reset_password") return True @staticmethod def get_id_from_activate_token(token): s = Serializer(current_app.config["SECRET_KEY"]) try: data = s.loads(token) except: return False user_id = data.get("activation") if user_id is None: return False return user_id def activate_account(self, token, name, password, username): s = Serializer(current_app.config["SECRET_KEY"]) try: data = s.loads(token) except: return False if data.get("activation") != self.id: return False self.password = password self.name = name self.username = username self.confirmed = True self.active = True db.session.add(self) current_app.logger.info("User account activated: user id %s (%s)" % (self.id, self.email)) self.track_event("activated_account") return True def __repr__(self): return "<User %r - %s - id %r>" % (self.email, self.name, self.id) def confirm(self, token): s = Serializer(current_app.config["SECRET_KEY"]) try: data = s.loads(token) except: return False if data.get("confirm") != self.id: return False self.confirmed = True self.active = True db.session.add(self) db.session.commit() current_app.logger.info("User account confirmed: user id %s (%s)" % (self.id, self.email)) self.track_event("confirmed_account") if data.get("trial") is True: self.track_event("started_free_trial") return True def generate_email_change_token(self, new_email, expiration=SECONDS_PER_HOUR): s = Serializer(current_app.config["SECRET_KEY"], expiration) return s.dumps({"change_email": self.id, "new_email": new_email}) def change_email(self, token): s = Serializer(current_app.config["SECRET_KEY"]) try: data = s.loads(token) except: return False if data.get("change_email") != self.id: return False new_email = data.get("new_email") if new_email is None: return False if self.query.filter_by(email=new_email).first() is not None: return False self.email = new_email try: db.session.add(self) db.session.commit() except: db.session.rollback() raise Exception("Dirty session") self.track_event("changed_email") return True def is_sudo(self): """checks if sudo""" return self.sudo is True def is_org_admin_or_location_manager(self, org_id, location_id): """checks if an admin or manager in the org/location""" return self.is_org_admin(org_id) or self.is_location_manager( location_id) def is_org_admin(self, org_id): """checks if an org admin""" admin = User.query \ .join(User.admin_of) \ .filter( organization_model.Organization.id == org_id, User.id == self.id ) \ .first() return admin is not None def is_manager_in_org(self, org_id): """checks if current user manages a location in the org""" manager = User.query \ .join(User.manager_of) \ .filter( User.id == self.id, Location.organization_id == org_id ) \ .first() return manager is not None def is_location_manager(self, location_id): """checks if current user managers location in kwargs""" manager = User.query \ .join(User.manager_of) \ .filter( User.id == self.id, Location.id == location_id ) \ .first() return manager is not None def is_location_worker(self, location_id): """checks if the user is a worker of a role in the location""" assoc = User.query \ .join(User.roles) \ .join(Role) \ .filter( RoleToUser.user_id == self.id, RoleToUser.archived == False, RoleToUser.role_id == Role.id, Role.location_id == location_id ) \ .first() return assoc is not None def is_active(self): return self.active def manager_accounts(self): """ returns a list of all organizations that the user has Manager access to """ # start with org admins result = self.admin_of.all() # add in additional location admins, but avoid duplicates for location in self.manager_of.all(): org = location.organization if org not in result: result.append(org) return result def memberships(self): """ Return org ids that this user is an active member of """ memberships = db.session.execute( select([organization_model.Organization.id, organization_model.Organization.name, Location.id, Location.name, RoleToUser.role_id, Role.name]).\ where(RoleToUser.user_id == self.id).\ where(RoleToUser.archived == False).\ where(RoleToUser.role_id == Role.id).\ where(Role.location_id == Location.id).\ where(Location.organization_id == organization_model.Organization.id).\ select_from(RoleToUser).\ select_from(Role).\ select_from(Location).\ select_from(organization_model.Organization) ).fetchall() result = [] for entry in memberships: # order is defined in select statement result.append({ "organization_id": entry[0], "organization_name": entry[1], "location_id": entry[2], "location_name": entry[3], "role_id": entry[4], "role_name": entry[5], }) return result def membership_ids(self): memberships = db.session.execute( select([organization_model.Organization.id, Location.id, RoleToUser.role_id]).\ where(RoleToUser.user_id == self.id).\ where(RoleToUser.archived == False).\ where(RoleToUser.role_id == Role.id).\ where(Role.location_id == Location.id).\ where(Location.organization_id == organization_model.Organization.id).\ select_from(RoleToUser).\ select_from(Role).\ select_from(Location).\ select_from(organization_model.Organization) ).fetchall() result = [] for entry in memberships: # order is defined in select statement result.append({ "organization_id": entry[0], "location_id": entry[1], "role_id": entry[2], "user_id": self.id, }) return result def intercom_settings(self, org_id=None): """ Data for Intercom, json-encoded """ data = { "user_id": str(self.id), "name": self.name, "sudo": self.sudo, "username": self.username, "email": self.email, "created_at": int(self.member_since.strftime("%s")), "app_id": current_app.config.get("INTERCOM_ID"), "user_hash": hmac.new( current_app.config.get("INTERCOM_SECRET"), str(self.id), hashlib.sha256, ).hexdigest(), "is_org_admin": (len(self.admin_of.all()) > 0), "is_location_manager": (len(self.manager_of.all()) > 0), "is_org_member": (len(self.memberships()) > 0), } if org_id is not None and not self.is_sudo(): # Don't add users to any org org = organization_model.Organization.query.get(org_id) if org is not None: data["companies"] = [ org.intercom_settings(), ] return json.dumps(data) def flush_associated_shift_caches(self): schedules2 = schedule2_model.Schedule2.query.join(Role).join( RoleToUser).filter(RoleToUser.user_id == self.id, RoleToUser.archived == False).all() for schedule in schedules2: Shifts2Cache.delete(schedule.id) @staticmethod def create_and_invite(email, name="", inviter_name=None, silent=False): """ Create a new user and return that account """ if "@" not in email: raise Exception("Invalid email %s passed to create function." % email) user = User( email=email.lower().strip(), name=name, active=False, confirmed=False, ) try: db.session.add(user) db.session.commit() except: db.session.rollback() raise Exception("Dirty session") if not silent: User.send_activation_email(user, inviter_name) return user @staticmethod def send_activation_reminder(user, inviter_name=None): if user.active: raise Exception("User %s is alredy active" % user.id) if UserActivationReminderLimiter.allowed_to_send(user): User.send_activation_email(user, inviter_name) @staticmethod def send_activation_email(user, inviter_name=None): if inviter_name is None or len(inviter_name) == 0: subject = "[Action Required] Activate your Staffjoy Account" else: subject = "[Action Required] Set up your %s shift scheduling account" % ( inviter_name) token = user.generate_activation_token() user.send_email( subject, render_template("email/activate-account.html", user=user, token=token), True) UserActivationReminderLimiter.mark_sent(user) def generate_api_token(self, expiration=None): """ Create a time-based token for single page apps""" if not self.is_authenticated: raise Exception("User not authenticated") s = Serializer(current_app.config["SECRET_KEY"], current_app.config.get("SESSION_EXPIRATION")) session_id = self.session_id return s.dumps({ "id": self.id, "session_id": session_id }).decode("ascii") @staticmethod def verify_api_token(token): """Validate a time-based token (from single page apps)""" s = Serializer(current_app.config["SECRET_KEY"]) try: data = s.loads(token) except: return None if not data.get("id") or not data.get("session_id"): return None if SessionCache.validate_session(data.get("id"), data.get("session_id")): user = User.query.get(data.get("id")) if user is not None: user.set_session_id(data.get("session_id")) return user return None def send_email(self, subject, html_body, force_send=False): """ Send the user an email if they are active Allow overriding for things like account activation """ if self is None: current_app.logger.error( "Could not send email becuase could not find user - subject '%s'" % (subject)) return if not (self.active and self.confirmed) and not force_send: current_app.logger.info( "Did not send email to id %s (%s) because account inactive - subject '%s'" % (self.id, self.email, subject)) return if not self.enable_notification_emails and not force_send: current_app.logger.info( "Did not send email to id %s (%s) because account disabled notification emails - subject '%s'" % (self.id, self.email, subject)) return send_email(self.email, subject, html_body) @staticmethod @login_manager.token_loader def load_session_token(token): """Load cookie session""" s = Serializer(current_app.config["SECRET_KEY"], current_app.config.get("SESSION_EXPIRATION")) try: data = s.loads(token) except: return None if SessionCache.validate_session(data.get("user_id", -1), data.get("session_id", "-1")): user = User.query.get(data["user_id"]) user.set_session_id(data["session_id"]) current_app.logger.debug("Loading user %s from cookie session %s" % (user.id, user.session_id)) return user return None def get_auth_token(self): """Cookie info. Must be secure.""" s = Serializer(current_app.config["SECRET_KEY"], current_app.config["COOKIE_EXPIRATION"]) current_app.logger.debug("Generating auth token for user %s" % self.id) if not self.is_authenticated: raise Exception("User not authenticated") return s.dumps({ "user_id": self.id, "session_id": SessionCache.create_session( self.id, expiration=current_app.config["COOKIE_EXPIRATION"]) }) def get_id(self, expiration=None): """Returns the id and session id used to identify a logged-in user""" # Used exclusively by flask-login if not expiration: expiration = current_app.config.get("SESSION_EXPIRATION") if not self.is_authenticated: current_app.logger.info( "Cannot generate token because user not authenticated") return None try: session_id = self.session_id if not session_id: session_id = SessionCache.create_session( self.id, expiration=current_app.config.get("SESSION_EXPIRATION")) except: session_id = SessionCache.create_session( self.id, expiration=current_app.config.get("SESSION_EXPIRATION")) # flask-login handles encryption of this token = "%(user_id)s-%(session_id)s" % { "user_id": self.id, "session_id": session_id } return token @staticmethod @login_manager.user_loader def load_session_id(token): """Load user session as opposite of get_id function""" # used exclusively by flask-login try: user_id, session_id = token.split("-") except: return None if not user_id or not session_id: return None if SessionCache.validate_session(user_id, session_id): user = User.query.get(user_id) user.set_session_id(session_id) return user return None def logout_session(self): """Delete user's current session and cookie""" # Delete the session from Redis try: if self.session_id: SessionCache.delete_session(self.id, self.session_id) except: current_app.logger.warning("User without session_id") # If there is a cookie, try deleting the session in redis cookie_data = request.cookies.get( current_app.config.get("REMEMBER_COOKIE_NAME")) if cookie_data: s = Serializer(current_app.config["SECRET_KEY"]) try: data = s.loads(cookie_data) user_id = data["user_id"] session_id = data["session_id"] except: current_app.logger.info("Corrupt cookie for user %s" % self.id) return if user_id and session_id: SessionCache.delete_session(user_id, session_id) current_app.logger.debug("Deleted cookie session %s" % session_id) def logout_target_session(self, session_id): """Delete user's current session and cookie""" SessionCache.delete_session(self.id, session_id) def get_target_session(self, session_id): """Returns info from a target session key""" return SessionCache.get_session_info(self.id, session_id) def get_all_sessions(self): # {session_id => {"remote_ip": "str", "last_used": utctimestamp}} # Get keys keys = SessionCache.get_all_sessions(self.id) output = {} for key in keys: # Key not guaranteed to be unique across users output[key] = SessionCache.get_session_info(self.id, key) return output def logout_all_sessions(self): """Log out all user sessions""" current_app.logger.info("Logged user %s out of all sessions" % self.id) SessionCache.delete_all_sessions(self.id) def set_session_id(self, session_id): self.session_id = session_id @property def phone_number(self): """Return phone number for API and such""" if not (self.phone_country_code and self.phone_national_number): return None p = phonenumbers.parse("+" + self.phone_country_code + self.phone_national_number) # Default to show it for computers - E164 format. return phonenumbers.format_number(p, phonenumbers.PhoneNumberFormat.E164) @property def pretty_phone_number(self): """Return phone number in a readable format""" if not self.phone_number: return None p = phonenumbers.parse(self.phone_number) return phonenumbers.format_number( p, phonenumbers.PhoneNumberFormat.INTERNATIONAL) @phone_number.setter def phone_number(self): raise Exception( "Use verify_phone_number instead of directly setting the phone number" ) def send_sms(self, message): """Send an SMS to the user""" if not self.active: # Stop communications to inactive users return if not self.phone_country_code and self.phone_national_number: raise Exception("User lacks a verified phone number") current_app.logger.info("Sending sms to user %s : %s" % (self, message)) send_sms(self.phone_country_code, self.phone_national_number, message) def get_phone_data_pending_verification(self): return PhoneVerificationCache.get(self.id) def set_phone_number(self, phone_country_code, phone_national_number): """Send a verification pin for a new phone number""" if not self.active: raise Exception( "Cannot confirm a phone number for an inactive user") if self.phone_number: # This prevents race conditions raise Exception( "Remove existing phone number before setting a new one") # Filter to pure digits phone_country_code = ''.join(i for i in phone_country_code if i.isdigit()) phone_national_number = ''.join(i for i in phone_national_number if i.isdigit()) # First, verify that it's a valid phone number format p = phonenumbers.parse("+" + str(phone_country_code) + str(phone_national_number)) if not phonenumbers.is_possible_number(p): raise Exception("Invalid phone number") # check if we registered this number with somebody else. if User.get_user_from_phone_number(phone_country_code, phone_national_number): current_app.logger.info( "Unable to verify user %s (%s) phone number because it already belongs to another user" % (self.id, self.email)) raise Exception("Phone number already belongs to a user") current_app.logger.info( "User %s (%s) is attempting to verify phone number %s" % (self.id, self.email, phonenumbers.format_number(p, phonenumbers.PhoneNumberFormat.E164))) # At this point, we think we can send a message to the number # Generate a verification pin # (comes as a string due to leading zeros) verification_pin = ''.join( random.choice(string.digits) for x in range(self.SMS_VERIFICATION_PIN_LENGTH)) # Set cache (before sms) PhoneVerificationCache.set( self.id, { "verification_pin": verification_pin, "phone_country_code": phone_country_code, "phone_national_number": phone_national_number, }) # Send verification pin message = "Hi %s! Your Staffjoy verification pin is %s" % ( self.first_name, verification_pin) send_sms(phone_country_code, phone_national_number, message) def verify_phone_number(self, pin): """Return true if new phone set, otherwise returns false.""" data = self.get_phone_data_pending_verification() if not data: # Probably an error - might be a race condition where # cache decided to drop the data after too long raise Exception("No data pending verification") if data["verification_pin"] != pin: current_app.logger.info( "User %s (%s) entered incorrect phone verification pin" % (self.id, self.email)) return False # check if we registered this number with somebody else. if User.get_user_from_phone_number(data["phone_country_code"], data["phone_national_number"]): current_app.logger.info( "Unable to verify user %s (%s) phone number because it already belongs to another user" % (self.id, self.email)) # Pin confirmed! Put it in the database self.phone_national_number = data["phone_national_number"] self.phone_country_code = data["phone_country_code"] db.session.commit() PhoneVerificationCache.delete(self.id) current_app.logger.info("User %s (%s) verified their phone number" % (self.id, self.email)) return True def remove_phone_number(self): """Null out phone number fields""" self.phone_national_number = None self.phone_country_code = None PhoneVerificationCache.delete(self.id) db.session.commit() @classmethod def get_user_from_phone_number(cls, phone_country_code, phone_national_number): return cls.query.filter_by( phone_country_code=phone_country_code, phone_national_number=phone_national_number, ).first()