2018-12-14 11:36:22 -08:00
|
|
|
from flask_security import UserMixin, RoleMixin
|
|
|
|
from flask_security.core import current_user
|
|
|
|
from flask_security.utils import hash_password, verify_and_update_password, login_user, logout_user
|
|
|
|
from sqlalchemy.ext.hybrid import hybrid_property
|
|
|
|
|
2019-01-16 14:26:45 -08:00
|
|
|
from grant.utils.exceptions import ValidationException
|
2018-09-25 13:09:25 -07:00
|
|
|
from grant.comment.models import Comment
|
2018-12-17 10:33:33 -08:00
|
|
|
from grant.email.models import EmailVerification, EmailRecovery
|
2018-12-14 11:36:22 -08:00
|
|
|
from grant.email.send import send_email
|
|
|
|
from grant.extensions import ma, db, security
|
2018-11-02 09:07:06 -07:00
|
|
|
from grant.utils.misc import make_url
|
2019-01-02 10:23:02 -08:00
|
|
|
from grant.utils.social import generate_social_url
|
2018-12-14 11:36:22 -08:00
|
|
|
from grant.utils.upload import extract_avatar_filename, construct_avatar_url
|
2019-01-16 14:26:45 -08:00
|
|
|
from grant.email.subscription_settings import (
|
|
|
|
get_default_email_subscriptions,
|
|
|
|
email_subscriptions_to_bits,
|
|
|
|
email_subscriptions_to_dict
|
|
|
|
)
|
2018-12-14 11:36:22 -08:00
|
|
|
|
|
|
|
|
|
|
|
def is_current_authed_user_id(user_id):
|
|
|
|
return current_user.is_authenticated and \
|
2018-12-17 10:33:33 -08:00
|
|
|
current_user.id == user_id
|
2018-12-14 11:36:22 -08:00
|
|
|
|
|
|
|
|
|
|
|
class RolesUsers(db.Model):
|
|
|
|
__tablename__ = 'roles_users'
|
|
|
|
id = db.Column(db.Integer(), primary_key=True)
|
|
|
|
user_id = db.Column('user_id', db.Integer(), db.ForeignKey('user.id'))
|
|
|
|
role_id = db.Column('role_id', db.Integer(), db.ForeignKey('role.id'))
|
|
|
|
|
|
|
|
|
|
|
|
class Role(db.Model, RoleMixin):
|
|
|
|
__tablename__ = 'role'
|
|
|
|
id = db.Column(db.Integer(), primary_key=True)
|
|
|
|
name = db.Column(db.String(80), unique=True)
|
|
|
|
description = db.Column(db.String(255))
|
2018-09-25 13:09:25 -07:00
|
|
|
|
|
|
|
|
|
|
|
class SocialMedia(db.Model):
|
|
|
|
__tablename__ = "social_media"
|
|
|
|
|
|
|
|
id = db.Column(db.Integer(), primary_key=True)
|
2019-01-02 10:23:02 -08:00
|
|
|
service = db.Column(db.String(255), unique=False, nullable=False)
|
|
|
|
username = db.Column(db.String(255), unique=False, nullable=False)
|
2018-09-25 13:09:25 -07:00
|
|
|
user_id = db.Column(db.Integer, db.ForeignKey("user.id"), nullable=False)
|
|
|
|
|
2019-01-02 10:23:02 -08:00
|
|
|
def __init__(self, service: str, username: str, user_id):
|
|
|
|
self.service = service.upper()
|
|
|
|
self.username = username.lower()
|
2018-09-25 13:09:25 -07:00
|
|
|
self.user_id = user_id
|
|
|
|
|
|
|
|
|
2019-01-16 14:26:45 -08:00
|
|
|
class UserSettings(db.Model):
|
|
|
|
__tablename__ = "user_settings"
|
|
|
|
|
|
|
|
id = db.Column(db.Integer(), primary_key=True)
|
|
|
|
_email_subscriptions = db.Column("email_subscriptions", db.Integer, default=0) # bitmask
|
|
|
|
user_id = db.Column(db.Integer, db.ForeignKey("user.id"), nullable=False)
|
|
|
|
user = db.relationship("User", back_populates="settings")
|
|
|
|
|
|
|
|
@hybrid_property
|
|
|
|
def email_subscriptions(self):
|
|
|
|
return email_subscriptions_to_dict(self._email_subscriptions)
|
|
|
|
|
|
|
|
@email_subscriptions.setter
|
|
|
|
def email_subscriptions(self, subs):
|
|
|
|
self._email_subscriptions = email_subscriptions_to_bits(subs)
|
|
|
|
|
|
|
|
def __init__(self, user_id):
|
|
|
|
self.email_subscriptions = get_default_email_subscriptions()
|
|
|
|
self.user_id = user_id
|
|
|
|
|
|
|
|
def unsubscribe_emails(self):
|
|
|
|
es = self.email_subscriptions
|
|
|
|
for k in es:
|
|
|
|
es[k] = False
|
|
|
|
self.email_subscriptions = es
|
|
|
|
|
|
|
|
|
2018-09-25 13:09:25 -07:00
|
|
|
class Avatar(db.Model):
|
|
|
|
__tablename__ = "avatar"
|
|
|
|
|
|
|
|
id = db.Column(db.Integer(), primary_key=True)
|
2018-12-14 11:36:22 -08:00
|
|
|
_image_url = db.Column("image_url", db.String(255), unique=False, nullable=True)
|
2018-09-25 13:09:25 -07:00
|
|
|
user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
|
|
|
|
user = db.relationship("User", back_populates="avatar")
|
|
|
|
|
2018-12-14 11:36:22 -08:00
|
|
|
@hybrid_property
|
|
|
|
def image_url(self):
|
|
|
|
return construct_avatar_url(self._image_url)
|
|
|
|
|
|
|
|
@image_url.setter
|
|
|
|
def image_url(self, image_url):
|
|
|
|
self._image_url = extract_avatar_filename(image_url)
|
|
|
|
|
2018-09-25 13:09:25 -07:00
|
|
|
def __init__(self, image_url, user_id):
|
|
|
|
self.image_url = image_url
|
|
|
|
self.user_id = user_id
|
|
|
|
|
|
|
|
|
2018-12-14 11:36:22 -08:00
|
|
|
class User(db.Model, UserMixin):
|
2018-09-25 13:09:25 -07:00
|
|
|
__tablename__ = "user"
|
|
|
|
|
|
|
|
id = db.Column(db.Integer(), primary_key=True)
|
2018-12-14 11:36:22 -08:00
|
|
|
email_address = db.Column(db.String(255), unique=True, nullable=False)
|
|
|
|
password = db.Column(db.String(255), unique=False, nullable=False)
|
2018-09-25 13:09:25 -07:00
|
|
|
display_name = db.Column(db.String(255), unique=False, nullable=True)
|
|
|
|
title = db.Column(db.String(255), unique=False, nullable=True)
|
2018-12-14 11:36:22 -08:00
|
|
|
active = db.Column(db.Boolean, default=True)
|
2018-09-25 13:09:25 -07:00
|
|
|
|
2018-11-14 13:12:24 -08:00
|
|
|
social_medias = db.relationship(SocialMedia, backref="user", lazy=True, cascade="all, delete-orphan")
|
2018-09-25 13:09:25 -07:00
|
|
|
comments = db.relationship(Comment, backref="user", lazy=True)
|
2018-11-14 13:12:24 -08:00
|
|
|
avatar = db.relationship(Avatar, uselist=False, back_populates="user", cascade="all, delete-orphan")
|
2019-01-16 14:26:45 -08:00
|
|
|
settings = db.relationship(UserSettings, uselist=False, back_populates="user",
|
|
|
|
lazy=True, cascade="all, delete-orphan")
|
2018-12-14 11:36:22 -08:00
|
|
|
email_verification = db.relationship(EmailVerification, uselist=False,
|
|
|
|
back_populates="user", lazy=True, cascade="all, delete-orphan")
|
2018-12-17 10:33:33 -08:00
|
|
|
email_recovery = db.relationship(EmailRecovery, uselist=False, back_populates="user",
|
|
|
|
lazy=True, cascade="all, delete-orphan")
|
2018-12-14 11:36:22 -08:00
|
|
|
roles = db.relationship('Role', secondary='roles_users',
|
|
|
|
backref=db.backref('users', lazy='dynamic'))
|
2018-09-25 13:09:25 -07:00
|
|
|
|
|
|
|
# TODO - add create and validate methods
|
|
|
|
|
2018-12-14 11:36:22 -08:00
|
|
|
def __init__(
|
|
|
|
self,
|
|
|
|
email_address,
|
|
|
|
password,
|
|
|
|
active,
|
|
|
|
roles,
|
|
|
|
display_name=None,
|
|
|
|
title=None,
|
|
|
|
):
|
2018-09-25 13:09:25 -07:00
|
|
|
self.email_address = email_address
|
|
|
|
self.display_name = display_name
|
|
|
|
self.title = title
|
2018-12-14 11:36:22 -08:00
|
|
|
self.password = password
|
2018-09-25 13:09:25 -07:00
|
|
|
|
2018-11-02 09:07:06 -07:00
|
|
|
@staticmethod
|
2018-12-14 11:36:22 -08:00
|
|
|
def create(email_address=None, password=None, display_name=None, title=None, _send_email=True):
|
|
|
|
user = security.datastore.create_user(
|
2018-11-02 09:07:06 -07:00
|
|
|
email_address=email_address,
|
2018-12-14 11:36:22 -08:00
|
|
|
password=hash_password(password),
|
2018-11-02 09:07:06 -07:00
|
|
|
display_name=display_name,
|
|
|
|
title=title
|
|
|
|
)
|
2018-12-14 11:36:22 -08:00
|
|
|
security.datastore.commit()
|
2018-11-02 09:07:06 -07:00
|
|
|
|
2019-01-16 14:26:45 -08:00
|
|
|
# user settings
|
|
|
|
us = UserSettings(user_id=user.id)
|
|
|
|
db.session.add(us)
|
|
|
|
|
2018-11-02 09:07:06 -07:00
|
|
|
# Setup & send email verification
|
|
|
|
ev = EmailVerification(user_id=user.id)
|
|
|
|
db.session.add(ev)
|
|
|
|
db.session.commit()
|
|
|
|
|
2018-11-27 11:07:09 -08:00
|
|
|
if _send_email:
|
2018-11-13 05:58:02 -08:00
|
|
|
send_email(user.email_address, 'signup', {
|
|
|
|
'display_name': user.display_name,
|
|
|
|
'confirm_url': make_url(f'/email/verify?code={ev.code}')
|
|
|
|
})
|
2018-11-02 09:07:06 -07:00
|
|
|
|
|
|
|
return user
|
|
|
|
|
2018-10-19 22:18:27 -07:00
|
|
|
@staticmethod
|
2018-12-14 11:36:22 -08:00
|
|
|
def get_by_id(user_id: int):
|
|
|
|
return security.datastore.get_user(user_id)
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def get_by_email(email_address: str):
|
|
|
|
return security.datastore.get_user(email_address)
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def logout_current_user():
|
|
|
|
logout_user() # logs current user out
|
|
|
|
|
|
|
|
def check_password(self, password: str):
|
|
|
|
return verify_and_update_password(password, self)
|
|
|
|
|
|
|
|
def set_password(self, password: str):
|
|
|
|
self.password = hash_password(password)
|
|
|
|
db.session.commit()
|
|
|
|
|
|
|
|
def login(self):
|
|
|
|
login_user(self)
|
2018-10-19 22:18:27 -07:00
|
|
|
|
2018-12-17 10:33:33 -08:00
|
|
|
def send_recovery_email(self):
|
|
|
|
existing = self.email_recovery
|
|
|
|
if existing:
|
|
|
|
db.session.delete(existing)
|
|
|
|
er = EmailRecovery(user_id=self.id)
|
|
|
|
db.session.add(er)
|
|
|
|
db.session.commit()
|
|
|
|
send_email(self.email_address, 'recover', {
|
|
|
|
'display_name': self.display_name,
|
|
|
|
'recover_url': make_url(f'/email/recover?code={er.code}'),
|
|
|
|
})
|
|
|
|
|
2018-10-19 22:18:27 -07:00
|
|
|
|
2018-09-25 13:09:25 -07:00
|
|
|
class UserSchema(ma.Schema):
|
|
|
|
class Meta:
|
|
|
|
model = User
|
|
|
|
# Fields to expose
|
2018-09-26 12:42:40 -07:00
|
|
|
fields = (
|
|
|
|
"title",
|
|
|
|
"email_address",
|
|
|
|
"social_medias",
|
|
|
|
"avatar",
|
|
|
|
"display_name",
|
|
|
|
"userid"
|
|
|
|
)
|
|
|
|
|
|
|
|
social_medias = ma.Nested("SocialMediaSchema", many=True)
|
|
|
|
avatar = ma.Nested("AvatarSchema")
|
2018-09-25 13:09:25 -07:00
|
|
|
userid = ma.Method("get_userid")
|
|
|
|
|
|
|
|
def get_userid(self, obj):
|
|
|
|
return obj.id
|
|
|
|
|
2018-12-14 11:36:22 -08:00
|
|
|
|
2018-09-25 13:09:25 -07:00
|
|
|
user_schema = UserSchema()
|
|
|
|
users_schema = UserSchema(many=True)
|
2018-09-26 12:42:40 -07:00
|
|
|
|
2018-12-14 11:36:22 -08:00
|
|
|
|
2018-09-26 12:42:40 -07:00
|
|
|
class SocialMediaSchema(ma.Schema):
|
|
|
|
class Meta:
|
|
|
|
model = SocialMedia
|
|
|
|
# Fields to expose
|
2018-11-16 15:05:17 -08:00
|
|
|
fields = (
|
2018-11-19 12:23:56 -08:00
|
|
|
"url",
|
2018-11-16 15:05:17 -08:00
|
|
|
"service",
|
|
|
|
"username",
|
|
|
|
)
|
2018-11-19 12:23:56 -08:00
|
|
|
url = ma.Method("get_url")
|
2018-11-16 15:05:17 -08:00
|
|
|
|
2018-11-19 12:23:56 -08:00
|
|
|
def get_url(self, obj):
|
2019-01-02 10:23:02 -08:00
|
|
|
return generate_social_url(obj.service, obj.username)
|
2018-09-26 12:42:40 -07:00
|
|
|
|
2018-10-19 22:18:27 -07:00
|
|
|
|
2018-09-26 12:42:40 -07:00
|
|
|
social_media_schema = SocialMediaSchema()
|
|
|
|
social_media_schemas = SocialMediaSchema(many=True)
|
|
|
|
|
|
|
|
|
|
|
|
class AvatarSchema(ma.Schema):
|
|
|
|
class Meta:
|
|
|
|
model = SocialMedia
|
|
|
|
# Fields to expose
|
|
|
|
fields = ("image_url",)
|
|
|
|
|
|
|
|
|
|
|
|
avatar_schema = AvatarSchema()
|
|
|
|
avatar_schemas = AvatarSchema(many=True)
|
2019-01-16 14:26:45 -08:00
|
|
|
|
|
|
|
|
|
|
|
class UserSettingsSchema(ma.Schema):
|
|
|
|
class Meta:
|
|
|
|
model = UserSettings
|
|
|
|
fields = ("email_subscriptions",)
|
|
|
|
|
|
|
|
|
|
|
|
user_settings_schema = UserSettingsSchema()
|
|
|
|
user_settings_schemas = UserSettingsSchema(many=True)
|