Exemple #1
0
class TagAssociation(db.Model):
    __tablename__ = "tag_association"
    id = db.Column(db.Integer(), primary_key=True)
    rfw_id = db.Column(db.Integer, db.ForeignKey('rfw.id'))
    # can add more parent ids so different types can refer to same tags, ex:
    # proposal_id = db.Column(db.Integer, db.ForeignKey('proposal.id'))
    tag_id = db.Column(db.Integer, db.ForeignKey('tag.id'))
Exemple #2
0
class User(db.Model):
    __tablename__ = "user"

    id = db.Column(db.Integer(), primary_key=True)
    email_address = db.Column(db.String(255), unique=True, nullable=True)
    account_address = db.Column(db.String(255), unique=True, nullable=True)
    display_name = db.Column(db.String(255), unique=False, nullable=True)
    title = db.Column(db.String(255), unique=False, nullable=True)

    social_medias = db.relationship(SocialMedia, backref="user", lazy=True)
    comments = db.relationship(Comment, backref="user", lazy=True)
    avatar = db.relationship(Avatar, uselist=False, back_populates="user")
    email_verification = db.relationship(EmailVerification, uselist=False, back_populates="user", lazy=True)

    # TODO - add create and validate methods

    def __init__(self, email_address=None, account_address=None, display_name=None, title=None):
        if not email_address and not account_address:
            raise ValueError("Either email_address or account_address is required to create a user")

        self.email_address = email_address
        self.account_address = account_address
        self.display_name = display_name
        self.title = title

    @staticmethod
    def create(email_address=None, account_address=None, display_name=None, title=None, _send_email=True):
        user = User(
            account_address=account_address,
            email_address=email_address,
            display_name=display_name,
            title=title
        )
        db.session.add(user)
        db.session.flush()

        # Setup & send email verification
        ev = EmailVerification(user_id=user.id)
        db.session.add(ev)
        db.session.commit()

        if send_email:
            send_email(user.email_address, 'signup', {
                'display_name': user.display_name,
                'confirm_url': make_url(f'/email/verify?code={ev.code}')
            })

        return user

    @staticmethod
    def get_by_identifier(email_address: str = None, account_address: str = None):
        if not email_address and not account_address:
            raise ValueError("Either email_address or account_address is required to get a user")

        return User.query.filter(
            (func.lower(User.account_address) == func.lower(account_address)) |
            (func.lower(User.email_address) == func.lower(email_address))
        ).first()
Exemple #3
0
class Milestone(db.Model):
    __tablename__ = "milestone"

    id = db.Column(db.Integer(), primary_key=True)
    date_created = db.Column(db.DateTime, nullable=False)

    title = db.Column(db.String(255), nullable=False)
    content = db.Column(db.Text, nullable=False)
    stage = db.Column(db.String(255), nullable=False)
    payout_percent = db.Column(db.String(255), nullable=False)
    immediate_payout = db.Column(db.Boolean)

    date_estimated = db.Column(db.DateTime, nullable=False)

    proposal_id = db.Column(db.Integer, db.ForeignKey("proposal.id"), nullable=False)

    def __init__(
            self,
            title: str,
            content: str,
            date_estimated: datetime,
            payout_percent: str,
            immediate_payout: bool,
            stage: str = NOT_REQUESTED,
            proposal_id=int
    ):
        self.title = title
        self.content = content
        self.stage = stage
        self.date_estimated = date_estimated
        self.payout_percent = payout_percent
        self.immediate_payout = immediate_payout
        self.proposal_id = proposal_id
        self.date_created = datetime.datetime.now()
Exemple #4
0
class SocialMedia(db.Model):
    __tablename__ = "social_media"

    id = db.Column(db.Integer(), primary_key=True)
    # TODO replace this with something proper
    social_media_link = db.Column(db.String(255), unique=False, nullable=True)
    user_id = db.Column(db.Integer, db.ForeignKey("user.id"), nullable=False)

    def __init__(self, social_media_link, user_id):
        self.social_media_link = social_media_link
        self.user_id = user_id
Exemple #5
0
class Avatar(db.Model):
    __tablename__ = "avatar"

    id = db.Column(db.Integer(), primary_key=True)
    image_url = db.Column(db.String(255), unique=False, nullable=True)
    user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
    user = db.relationship("User", back_populates="avatar")

    def __init__(self, image_url, user_id):
        self.image_url = image_url
        self.user_id = user_id
Exemple #6
0
class SocialMedia(db.Model):
    __tablename__ = "social_media"

    id = db.Column(db.Integer(), primary_key=True)
    service = db.Column(db.String(255), unique=False, nullable=False)
    username = db.Column(db.String(255), unique=False, nullable=False)
    user_id = db.Column(db.Integer, db.ForeignKey("user.id"), nullable=False)

    def __init__(self, service: str, username: str, user_id):
        self.service = service.upper()[:255]
        self.username = username.lower()[:255]
        self.user_id = user_id
Exemple #7
0
class EmailVerification(db.Model):
    __tablename__ = "email_verification"

    user_id = db.Column(db.Integer, db.ForeignKey('user.id'), primary_key=True)
    code = db.Column(db.String(255), unique=True, nullable=False)
    has_verified = db.Column(db.Boolean)

    user = db.relationship("User", back_populates="email_verification")

    def __init__(self, user_id: int):
        self.user_id = user_id
        self.code = gen_random_code(32)
        self.has_verified = False
Exemple #8
0
class Task(db.Model):
    __tablename__ = 'task'

    id = db.Column(db.Integer(), primary_key=True)
    job_type = db.Column(db.Integer(), nullable=False)
    blob = db.Column(JsonEncodedDict, nullable=False)
    execute_after = db.Column(db.DateTime, nullable=False)
    completed = db.Column(db.Boolean, default=False)

    def __init__(self, job_type, blob, execute_after):
        assert job_type in list(JOBS.keys()), "Not a valid job"
        self.job_type = job_type
        self.blob = blob
        self.execute_after = execute_after
Exemple #9
0
class ProposalUpdate(db.Model):
    __tablename__ = "proposal_update"

    id = db.Column(db.Integer(), primary_key=True)
    date_created = db.Column(db.DateTime)

    proposal_id = db.Column(db.Integer, db.ForeignKey("proposal.id"), nullable=False)
    title = db.Column(db.String(255), nullable=False)
    content = db.Column(db.Text, nullable=False)

    def __init__(self, proposal_id: int, title: str, content: str):
        self.id = gen_random_id(ProposalUpdate)
        self.proposal_id = proposal_id
        self.title = title[:255]
        self.content = content
        self.date_created = datetime.datetime.now()
Exemple #10
0
class Comment(db.Model):
    __tablename__ = "comment"

    id = db.Column(db.Integer(), primary_key=True)
    date_created = db.Column(db.DateTime)
    content = db.Column(db.Text, nullable=False)

    proposal_id = db.Column(db.Integer,
                            db.ForeignKey("proposal.id"),
                            nullable=False)
    user_id = db.Column(db.Integer, db.ForeignKey("user.id"), nullable=False)

    def __init__(self, proposal_id, user_id, content):
        self.proposal_id = proposal_id
        self.user_id = user_id
        self.content = content
        self.date_created = datetime.datetime.now()
Exemple #11
0
class EmailRecovery(db.Model):
    __tablename__ = "email_recovery"

    user_id = db.Column(db.Integer, db.ForeignKey('user.id'), primary_key=True)
    code = db.Column(db.String(255), unique=True, nullable=False)
    date_created = db.Column(db.DateTime)

    user = db.relationship("User", back_populates="email_recovery")

    def __init__(self, user_id: int):
        self.user_id = user_id
        self.code = gen_random_code(32)
        self.date_created = datetime.now()

    def is_expired(self):
        time_diff = datetime.now() - self.date_created
        return time_diff > RECOVERY_EXPIRATION
Exemple #12
0
class Comment(db.Model):
    __tablename__ = "comment"

    id = db.Column(db.Integer(), primary_key=True)
    date_created = db.Column(db.DateTime)
    content = db.Column(db.Text, nullable=False)
    hidden = db.Column(db.Boolean,
                       nullable=False,
                       default=False,
                       server_default=db.text("FALSE"))
    reported = db.Column(db.Boolean,
                         nullable=True,
                         default=False,
                         server_default=db.text("FALSE"))

    parent_comment_id = db.Column(db.Integer,
                                  db.ForeignKey("comment.id"),
                                  nullable=True)
    proposal_id = db.Column(db.Integer,
                            db.ForeignKey("proposal.id"),
                            nullable=False)
    user_id = db.Column(db.Integer, db.ForeignKey("user.id"), nullable=False)

    user = db.relationship("User", back_populates="comments")

    author = db.relationship("User", back_populates="comments")
    replies = db.relationship("Comment")

    def __init__(self, proposal_id, user_id, parent_comment_id, content):
        self.id = gen_random_id(Comment)
        self.proposal_id = proposal_id
        self.user_id = user_id
        self.parent_comment_id = parent_comment_id
        self.content = content[:1000]
        self.date_created = datetime.datetime.now()

    @staticmethod
    def get_by_user(user):
        return Comment.query \
            .options(raiseload(Comment.replies)) \
            .filter(Comment.user_id == user.id) \
            .order_by(Comment.date_created.desc()) \
            .all()

    def report(self, reported: bool):
        self.reported = reported
        db.session.add(self)

    def hide(self, hidden: bool):
        self.hidden = hidden
        db.session.add(self)
Exemple #13
0
class UserSettings(db.Model):
    __tablename__ = "user_settings"

    id = db.Column(db.Integer(), primary_key=True)
    user_id = db.Column(db.Integer, db.ForeignKey("user.id"), nullable=False)
    _email_subscriptions = db.Column("email_subscriptions",
                                     db.Integer,
                                     default=0)  # bitmask
    refund_address = db.Column(db.String(255), unique=False, nullable=True)
    tip_jar_address = db.Column(db.String(255), unique=False, nullable=True)
    tip_jar_view_key = db.Column(db.String(255), unique=False, nullable=True)

    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
Exemple #14
0
class HistoryEvent(db.Model):
    __tablename__ = "history_event"

    id = db.Column(db.Integer(), primary_key=True)

    title = db.Column(db.String(255), nullable=False)
    content = db.Column(db.Text, nullable=False)
    date = db.Column(db.DateTime, nullable=False)
    user_id = db.Column(db.Integer, db.ForeignKey("user.id"), nullable=True)
    proposal_id = db.Column(db.Integer,
                            db.ForeignKey("proposal.id"),
                            nullable=True)

    user = db.relationship("User", lazy=True)
    proposal = db.relationship("Proposal", lazy=True)

    def __init__(
        self,
        title: str,
        content: str,
        date: datetime = None,
        user_id: int = None,
        proposal_id: int = None,
    ):
        self.id = gen_random_id(HistoryEvent)
        self.title = title[:120]
        self.content = content[:1000]
        self.user_id = user_id
        self.proposal_id = proposal_id
        self.date = date or datetime.datetime.now()
Exemple #15
0
class Tag(db.Model):
    __tablename__ = "tag"
    id = db.Column(db.Integer(), primary_key=True)
    text = db.Column(db.String(255), nullable=False)
    description = db.Column(db.Text, default='')
    color = db.Column(db.String(255), nullable=False)

    rfws = db.relationship('RFW',
                           secondary='tag_association',
                           back_populates="tags")

    def __init__(self, **kwargs):
        super().__init__(id=gen_random_id(Tag), **kwargs)

    @staticmethod
    def upsert(**kwargs):
        id = kwargs.get('id', False)
        if id:
            tag = Tag.query.get(id)
            if not tag:
                raise TagException(f'Attempted to update missing tag {id}')
            tag.update(**kwargs)
            return tag
        return Tag.create_tag(**kwargs)

    @staticmethod
    def create_tag(text: str, description: str, color: str):
        tag = Tag(text=text, description=description, color=color)
        db.session.add(tag)
        db.session.flush()
        return tag

    def update(self, **kwargs):
        for key, value in kwargs.items():
            setattr(self, key, value)
        db.session.flush()

    def delete(self):
        db.session.delete(self)
        db.session.flush()
Exemple #16
0
class ProposalTeamInvite(db.Model):
    __tablename__ = "proposal_team_invite"

    id = db.Column(db.Integer(), primary_key=True)
    date_created = db.Column(db.DateTime)

    proposal_id = db.Column(db.Integer, db.ForeignKey("proposal.id"), nullable=False)
    address = db.Column(db.String(255), nullable=False)
    accepted = db.Column(db.Boolean)

    def __init__(self, proposal_id: int, address: str, accepted: bool = None):
        self.proposal_id = proposal_id
        self.address = address[:255]
        self.accepted = accepted
        self.date_created = datetime.datetime.now()

    @staticmethod
    def get_pending_for_user(user):
        return ProposalTeamInvite.query.filter(
            ProposalTeamInvite.accepted == None,
            (func.lower(user.email_address) == func.lower(ProposalTeamInvite.address))
        ).all()
Exemple #17
0
class Avatar(db.Model):
    __tablename__ = "avatar"

    id = db.Column(db.Integer(), primary_key=True)
    _image_url = db.Column("image_url",
                           db.String(255),
                           unique=False,
                           nullable=True)
    user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
    user = db.relationship("User", back_populates="avatar")

    @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)

    def __init__(self, image_url, user_id):
        self.id = gen_random_id(Avatar)
        self.image_url = image_url
        self.user_id = user_id
Exemple #18
0
class Proposal(db.Model):
    __tablename__ = "proposal"

    id = db.Column(db.Integer(), primary_key=True)
    date_created = db.Column(db.DateTime)

    title = db.Column(db.String(255), nullable=False)
    proposal_address = db.Column(db.String(255), unique=True, nullable=False)
    stage = db.Column(db.String(255), nullable=False)
    content = db.Column(db.Text, nullable=False)
    category = db.Column(db.String(255), nullable=False)

    team = db.relationship("User", secondary=proposal_team)
    comments = db.relationship(Comment, backref="proposal", lazy=True)
    updates = db.relationship(ProposalUpdate, backref="proposal", lazy=True)
    milestones = db.relationship("Milestone", backref="proposal", lazy=True)

    def __init__(self, stage: str, proposal_address: str, title: str,
                 content: str, category: str):
        self.stage = stage
        self.proposal_address = proposal_address
        self.title = title
        self.content = content
        self.category = category
        self.date_created = datetime.datetime.now()

    @staticmethod
    def validate(stage: str, proposal_address: str, title: str, content: str,
                 category: str):
        if stage not in PROPOSAL_STAGES:
            raise ValidationException("{} not in {}".format(
                stage, PROPOSAL_STAGES))
        if category not in CATEGORIES:
            raise ValidationException("{} not in {}".format(
                category, CATEGORIES))

    @staticmethod
    def create(**kwargs):
        Proposal.validate(**kwargs)
        return Proposal(**kwargs)
Exemple #19
0
class AdminLog(db.Model):
    __tablename__ = "admin_log"

    id = db.Column(db.Integer(), primary_key=True)
    date_created = db.Column(db.DateTime, nullable=False)
    event = db.Column(db.String(255), nullable=False)
    message = db.Column(db.Text, nullable=False)
    user_id = db.Column(db.Integer, db.ForeignKey("user.id"), nullable=True)
    ip = db.Column(db.String(255), nullable=False)

    user = db.relationship("User")

    def __init__(self, **kwargs):
        super().__init__(
            id=gen_random_id(AdminLog),
            date_created=datetime.now(),
            **kwargs
        )
Exemple #20
0
class RFWMilestone(db.Model):
    __tablename__ = 'rfw_milestone'

    id = db.Column(db.Integer(), primary_key=True)
    index = db.Column(db.Integer(), nullable=False)
    date_created = db.Column(db.DateTime)

    title = db.Column(db.String(255), nullable=False)
    content = db.Column(db.Text, nullable=False)
    effort_from = db.Column(db.BigInteger, nullable=False)
    effort_to = db.Column(db.BigInteger, nullable=False)
    bounty = db.Column(db.BigInteger, nullable=False)

    rfw_id = db.Column(db.Integer(), db.ForeignKey('rfw.id'), nullable=False)
    rfw = db.relationship('RFW', back_populates='milestones')
    claims = db.relationship("RFWMilestoneClaim", cascade="all,delete")

    @hybrid_property
    def authed_claim(self):
        authed = get_authed_user()
        if not authed:
            return None
        for c in self.claims:
            if c.worker.user_id == authed.id:
                return c
        return None

    @hybrid_property
    def is_authed_active(self):
        aw = self.rfw.authed_worker
        if aw and aw.status == RFWWorkerStatus.ACCEPTED:
            accepted = [
                c.milestone_id for c in aw.claims
                if c.stage == RFWMilestoneClaimStage.ACCEPTED
            ]
            for ms in self.rfw.milestones:
                if ms.id not in accepted:  # active ms for athed user
                    if ms.id == self.id:
                        return True
                    else:
                        return False
        return False

    def __init__(self, bounty=0, **kwargs):
        if 'index' not in kwargs:
            raise RFWException('Must set index on RFWMilestone')
        super().__init__(id=gen_random_id(RFWMilestone),
                         date_created=datetime.now(),
                         bounty=bounty,
                         title=kwargs.pop('title', ''),
                         content=kwargs.pop('content', ''),
                         effort_from=kwargs.pop('effort_from', 0),
                         effort_to=kwargs.pop('effort_to', 0),
                         **kwargs)

    def update(self, **kwargs):
        if kwargs.pop('id', None):
            raise RFWException('Cannot update RFWMilestone IDs once created')
        for key, value in kwargs.items():
            setattr(self, key, value)
        db.session.flush()

    def get_claim_by_id(self, id):
        claim = get(self.claims, 'id', int(id))
        if claim is None:
            raise RFWException(f'Could not find RFWMilestone.claims[{id}]')
        return claim
Exemple #21
0
CORE_DEV = "CORE_DEV"
COMMUNITY = "COMMUNITY"
DOCUMENTATION = "DOCUMENTATION"
ACCESSIBILITY = "ACCESSIBILITY"
CATEGORIES = [
    DAPP, DEV_TOOL, CORE_DEV, COMMUNITY, DOCUMENTATION, ACCESSIBILITY
]


class ValidationException(Exception):
    pass


proposal_team = db.Table(
    'proposal_team', db.Model.metadata,
    db.Column('user_id', db.Integer, db.ForeignKey('user.id')),
    db.Column('proposal_id', db.Integer, db.ForeignKey('proposal.id')))


class ProposalUpdate(db.Model):
    __tablename__ = "proposal_update"

    id = db.Column(db.Integer(), primary_key=True)
    date_created = db.Column(db.DateTime)

    proposal_id = db.Column(db.Integer,
                            db.ForeignKey("proposal.id"),
                            nullable=False)
    title = db.Column(db.String(255), nullable=False)
    content = db.Column(db.Text, nullable=False)
Exemple #22
0
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'))
Exemple #23
0
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))
Exemple #24
0
class RFWMilestoneClaim(db.Model):
    __tablename__ = 'rfw_milestone_claim'

    id = db.Column(db.Integer(), primary_key=True)
    date_created = db.Column(db.DateTime)

    stage = db.Column(db.String(255), nullable=False)
    stage_message = db.Column(db.String, nullable=False)
    stage_change_date = db.Column(db.DateTime, nullable=False)
    stage_url = db.Column(db.String, nullable=False)

    worker_id = db.Column(db.Integer(), db.ForeignKey('rfw_worker.id'))
    milestone_id = db.Column(db.Integer(), db.ForeignKey('rfw_milestone.id'))
    worker = db.relationship("RFWWorker", back_populates="claims")
    milestone = db.relationship("RFWMilestone", back_populates="claims")

    @validates('stage')
    def validate_status(self, key, field):
        if not RFWMilestoneClaimStage.includes(field):
            raise RFWException(
                f'RFWMilestoneClaim stage must be in the RFWMilestoneClaimStageEnum, was [{field}]'
            )
        return field

    def __init__(self, **kwargs):
        super().__init__(id=gen_random_id(RFWMilestoneClaim),
                         date_created=datetime.now(),
                         stage=RFWMilestoneClaimStage.REQUESTED,
                         stage_message=kwargs.pop('stage_message', ''),
                         stage_change_date=datetime.now(),
                         stage_url=kwargs.pop('stage_url', ''),
                         **kwargs)

    def set_requested(self, message='', url=''):
        if self.stage in [
                RFWMilestoneClaimStage.ACCEPTED,
                RFWMilestoneClaimStage.REQUESTED
        ]:
            raise RFWException(
                f'Cannot request claim id {self.id} with status {self.stage}')

        self.stage = RFWMilestoneClaimStage.REQUESTED
        self.stage_message = message
        self.stage_change_date = datetime.now()
        self.stage_url = url
        db.session.flush()

    def set_accepted(self, message=''):
        if self.stage in [
                RFWMilestoneClaimStage.ACCEPTED,
                RFWMilestoneClaimStage.REJECTED
        ]:
            raise RFWException(
                f'Cannot accept claim id {self.id} with status {self.stage}')

        self.stage = RFWMilestoneClaimStage.ACCEPTED
        self.stage_message = message
        self.stage_change_date = datetime.now()
        db.session.flush()

    def set_rejected(self, message: str):
        if self.stage in [
                RFWMilestoneClaimStage.ACCEPTED,
                RFWMilestoneClaimStage.REJECTED
        ]:
            raise RFWException(
                f'Cannot reject claim id {self.id} with status {self.stage}')

        self.stage = RFWMilestoneClaimStage.REJECTED
        self.stage_message = message
        self.stage_change_date = datetime.now()
        self.stage_url = ''
        db.session.flush()
Exemple #25
0
class RFP(db.Model):
    __tablename__ = "rfp"

    id = db.Column(db.Integer(), primary_key=True)
    date_created = db.Column(db.DateTime)

    title = db.Column(db.String(255), nullable=False)
    brief = db.Column(db.String(255), nullable=False)
    content = db.Column(db.Text, nullable=False)
    category = db.Column(db.String(255), nullable=False)
    status = db.Column(db.String(255), nullable=False)
    matching = db.Column(db.Boolean, default=False, nullable=False)
    _bounty = db.Column("bounty", db.String(255), nullable=True)
    date_closes = db.Column(db.DateTime, nullable=True)
    date_opened = db.Column(db.DateTime, nullable=True)
    date_closed = db.Column(db.DateTime, nullable=True)

    # Relationships
    proposals = db.relationship(
        "Proposal",
        backref="rfp",
        lazy=True,
        cascade="all, delete-orphan",
    )
    accepted_proposals = db.relationship(
        "Proposal",
        lazy=True,
        primaryjoin="and_(Proposal.rfp_id==RFP.id, Proposal.status=='LIVE')",
        cascade="all, delete-orphan",
    )

    @hybrid_property
    def bounty(self):
        return self._bounty

    @bounty.setter
    def bounty(self, bounty: str):
        if bounty and Decimal(bounty) > 0:
            self._bounty = bounty
        else:
            self._bounty = None

    def __init__(
        self,
        title: str,
        brief: str,
        content: str,
        category: str,
        bounty: str,
        date_closes: datetime,
        matching: bool = False,
        status: str = RFPStatus.DRAFT,
    ):
        assert RFPStatus.includes(status)
        assert Category.includes(category)
        self.id = gen_random_id(RFP)
        self.date_created = datetime.now()
        self.title = title[:255]
        self.brief = brief[:255]
        self.content = content
        self.category = category
        self.bounty = bounty
        self.date_closes = date_closes
        self.matching = matching
        self.status = status
Exemple #26
0
class RFW(db.Model):
    __tablename__ = "rfw"

    id = db.Column(db.Integer(), primary_key=True)
    date_created = db.Column(db.DateTime)

    title = db.Column(db.String(255), nullable=False)
    brief = db.Column(db.String(255), nullable=False)
    content = db.Column(db.Text, nullable=False)
    status = db.Column(db.String(255), nullable=False)
    status_change_date = db.Column(db.DateTime, nullable=True)
    category = db.Column(db.String(255), nullable=False)

    # Relationships
    workers = db.relationship('RFWWorker',
                              back_populates='rfw',
                              cascade="all,delete")
    milestones = db.relationship('RFWMilestone',
                                 back_populates='rfw',
                                 order_by="RFWMilestone.index",
                                 cascade="all,delete,delete-orphan")
    tags = db.relationship('Tag',
                           secondary='tag_association',
                           back_populates="rfws")

    @hybrid_property
    def effort_from(self):
        return sum([ms.effort_from for ms in self.milestones])

    @hybrid_property
    def effort_to(self):
        return sum([ms.effort_to for ms in self.milestones])

    @hybrid_property
    def bounty(self):
        return sum([ms.bounty for ms in self.milestones])

    @hybrid_property
    def authed_worker(self):
        authed = get_authed_user()
        if not authed:
            return None
        return get(self.workers, 'user_id', authed.id, None)

    @validates('status')
    def validate_status(self, key, field):
        if not RFWStatus.includes(field):
            raise RFWException(
                f'RFW status must be in the RFWStatusEnum, was [{field}]')
        return field

    @validates('category')
    def validate_category(self, key, field):
        if not Category.includes(field):
            raise RFWException(
                f'RFW category must be in the CategoryEnum, was [{field}]')
        return field

    def create(**kwargs):
        milestones = kwargs.pop('milestones', [{'index': 0}])
        tags = kwargs.pop('tags', [])
        rfw = RFW(id=gen_random_id(RFW),
                  date_created=datetime.now(),
                  title=kwargs.pop('title', ''),
                  brief=kwargs.pop('brief', ''),
                  content=kwargs.pop('content', ''),
                  status=kwargs.pop('status', RFWStatus.DRAFT),
                  category=kwargs.pop('category', Category.COMMUNITY),
                  **kwargs)
        db.session.add(rfw)
        db.session.flush()
        # milestones
        for ms in milestones:
            ms.pop('is_new', None)
            rfw.create_milestone(**ms)
        # tags
        for tag_id in tags:
            rfw.add_tag_by_id(tag_id)
        db.session.flush()
        return rfw

    def check_live(self):
        if self.status != RFWStatus.LIVE:
            raise RFWException(
                f'RFW must be {RFWStatus.LIVE}, was {self.status}')

    def delete(self):
        db.session.delete(self)
        db.session.flush()

    def update(self, milestones=[], delete_milestones=[], tags=[], **kwargs):
        for key, value in kwargs.items():
            setattr(self, key, value)
        # ms sync
        for ms in milestones:
            if ms.pop('is_new', False):
                self.create_milestone(**ms)
            elif 'id' in ms:
                self.update_milestone_by_id(**ms)
        for ms_id in delete_milestones:
            self.delete_milestone_by_id(ms_id)
        self.check_milestone_integrity()
        # tags sync
        cur_tags = [x.id for x in self.tags]
        to_rem_tags = set(cur_tags) - set(tags)
        for tag_id in to_rem_tags:
            self.remove_tag_by_id(tag_id)
        for tag_id in tags:
            self.add_tag_by_id(tag_id)
        db.session.flush()

    def check_milestone_integrity(self):
        milestones = sorted(self.milestones, key=lambda x: x.index)
        for ind, ms in enumerate(milestones):
            if ind != ms.index:
                raise RFWException(
                    f'RFW has bad milestone index for id {ms.id}. Got {ms.index}, expected {ind}'
                )

    def get_milestone_by_id(self, id):
        ms = get(self.milestones, 'id', int(id))
        if ms is None:
            raise RFWException(f'Could not find RFWMilestone with id {id}')
        return ms

    def update_milestone_by_id(self, id, **kwargs):
        ms = self.get_milestone_by_id(id)
        ms.update(**kwargs)
        db.session.flush()
        return ms

    def delete_milestone_by_id(self, id):
        ms = self.get_milestone_by_id(id)
        self.milestones.remove(ms)  # delete-orphan, so ms is deleted as well
        db.session.flush()
        # re-order indexes
        milestones = sorted(self.milestones, key=lambda x: x.index)
        for ind, ms in enumerate(milestones):
            ms.index = ind
        db.session.flush()

    def create_milestone(self, **kwargs):
        self.milestones.append(RFWMilestone(**kwargs))
        db.session.flush()

    def create_next_milestone(self, **kwargs):
        next_index = max([x.index for x in self.milestones]) + 1
        next_milestone = RFWMilestone(index=next_index, **kwargs)
        self.milestones.append(next_milestone)
        db.session.flush()
        return next_milestone

    def create_worker_by_user_id_and_request(self, id, status_message):
        self.check_live()
        from grant.user.models import User
        user = User.query.get(int(id))
        if not user:
            raise RFWException(
                f'Could not create a worker for RFW because user {id} not found'
            )
        worker = get(self.workers, 'user_id', user.id)
        if not worker:
            worker = RFWWorker(user_id=user.id, rfw_id=self.id)
            self.workers.append(worker)
        worker.set_requested(message=status_message)
        db.session.flush()
        return worker

    def get_worker_by_id(self, id: int):
        worker = get(self.workers, 'id', int(id))
        if not worker:
            raise RFWException(
                f'Could not find worker with id {id} for RFW with id {self.id}'
            )
        return worker

    def accept_worker_by_id(self, id, message=''):
        worker = self.get_worker_by_id(id)
        worker.set_accepted(message)
        return worker

    def reject_worker_by_id(self, id, message=''):
        worker = self.get_worker_by_id(id)
        worker.set_rejected(message)
        return worker

    def get_existing_claim(self, worker_id, ms_id):
        worker = self.get_worker_by_id(worker_id)
        self.get_milestone_by_id(
            ms_id)  # throws if non-child/non-existing milestone
        existing_claim = next(
            (x for x in worker.claims if x.milestone_id == ms_id), None)
        return existing_claim

    def request_milestone_claim(self, worker_id, ms_id, msg, url):
        self.check_live()
        worker_id = int(worker_id)
        ms_id = int(ms_id)
        claim = self.get_existing_claim(worker_id, ms_id)
        ms = self.get_milestone_by_id(ms_id)
        if not claim:
            worker = self.get_worker_by_id(worker_id)
            # init with message and url
            claim = RFWMilestoneClaim(stage_message=msg, stage_url=url)
            ms.claims.append(claim)
            worker.claims.append(claim)
            db.session.flush()
        else:
            claim.set_requested(msg, url)
        return claim

    def accept_milestone_claim(self, ms_id, claim_id, msg):
        ms = self.get_milestone_by_id(int(ms_id))
        claim = ms.get_claim_by_id(int(claim_id))
        claim.set_accepted(msg)

    def reject_milestone_claim(self, ms_id, claim_id, msg):
        ms = self.get_milestone_by_id(int(ms_id))
        claim = ms.get_claim_by_id(int(claim_id))
        claim.set_rejected(msg)

    def add_tag_by_id(self, id):
        tag = Tag.query.get(int(id))
        if tag:
            self.add_tag(tag)

    def add_tag(self, tag: Tag):
        self.tags.append(tag)
        db.session.flush()

    def remove_tag_by_id(self, id):
        tag = Tag.query.get(int(id))
        if tag:
            self.tags.remove(tag)
        db.session.flush()

    def set_status(self, status: RFWStatus):
        self.status = status
        self.status_change_date = datetime.now()
        db.session.flush()

    def publish(self):
        self.set_status(RFWStatus.LIVE)

    def close(self):
        self.set_status(RFWStatus.CLOSED)
class RFP(db.Model):
    __tablename__ = "rfp"

    id = db.Column(db.Integer(), primary_key=True)
    date_created = db.Column(db.DateTime)

    title = db.Column(db.String(255), nullable=False)
    brief = db.Column(db.String(255), nullable=False)
    content = db.Column(db.Text, nullable=False)
    category = db.Column(db.String(255), nullable=True)
    status = db.Column(db.String(255), nullable=False)
    matching = db.Column(db.Boolean, default=False, nullable=False)
    _bounty = db.Column("bounty", db.String(255), nullable=True)
    date_closes = db.Column(db.DateTime, nullable=True)
    date_opened = db.Column(db.DateTime, nullable=True)
    date_closed = db.Column(db.DateTime, nullable=True)
    version = db.Column(db.String(255), nullable=True)

    ccr = db.relationship("CCR", uselist=False, back_populates="rfp")

    # Relationships
    proposals = db.relationship(
        "Proposal",
        backref="rfp",
        lazy=True,
        cascade="all, delete-orphan",
    )
    accepted_proposals = db.relationship(
        "Proposal",
        lazy=True,
        primaryjoin="and_(Proposal.rfp_id==RFP.id, Proposal.status=='LIVE')",
        cascade="all, delete-orphan",
    )

    likes = db.relationship("User",
                            secondary=rfp_liker,
                            back_populates="liked_rfps")
    likes_count = column_property(
        select([func.count(rfp_liker.c.rfp_id)
                ]).where(rfp_liker.c.rfp_id == id).correlate_except(rfp_liker))

    @hybrid_property
    def bounty(self):
        return self._bounty

    @bounty.setter
    def bounty(self, bounty: str):
        if bounty and Decimal(bounty) > 0:
            self._bounty = bounty
        else:
            self._bounty = None

    @hybrid_property
    def authed_liked(self):
        from grant.utils.auth import get_authed_user

        authed = get_authed_user()
        if not authed:
            return False
        res = (db.session.query(rfp_liker).filter_by(user_id=authed.id,
                                                     rfp_id=self.id).count())
        if res:
            return True
        return False

    def like(self, user, is_liked):
        if is_liked:
            self.likes.append(user)
        else:
            self.likes.remove(user)
        db.session.flush()

    def __init__(
        self,
        title: str,
        brief: str,
        content: str,
        bounty: str,
        date_closes: datetime,
        matching: bool = False,
        status: str = RFPStatus.DRAFT,
    ):
        assert RFPStatus.includes(status)
        self.id = gen_random_id(RFP)
        self.date_created = datetime.now()
        self.title = title[:255]
        self.brief = brief[:255]
        self.content = content
        self.bounty = bounty
        self.date_closes = date_closes
        self.matching = matching
        self.status = status
        self.version = '2'
from datetime import datetime
from decimal import Decimal
from grant.extensions import ma, db
from sqlalchemy.ext.hybrid import hybrid_property
from sqlalchemy import func, select
from sqlalchemy.orm import column_property
from grant.utils.enums import RFPStatus
from grant.utils.misc import dt_to_unix, gen_random_id
from grant.utils.enums import Category

rfp_liker = db.Table(
    "rfp_liker",
    db.Model.metadata,
    db.Column("user_id", db.Integer, db.ForeignKey("user.id")),
    db.Column("rfp_id", db.Integer, db.ForeignKey("rfp.id")),
)


class RFP(db.Model):
    __tablename__ = "rfp"

    id = db.Column(db.Integer(), primary_key=True)
    date_created = db.Column(db.DateTime)

    title = db.Column(db.String(255), nullable=False)
    brief = db.Column(db.String(255), nullable=False)
    content = db.Column(db.Text, nullable=False)
    category = db.Column(db.String(255), nullable=True)
    status = db.Column(db.String(255), nullable=False)
    matching = db.Column(db.Boolean, default=False, nullable=False)
    _bounty = db.Column("bounty", db.String(255), nullable=True)
class CCR(db.Model):
    __tablename__ = "ccr"

    id = db.Column(db.Integer(), primary_key=True)
    date_created = db.Column(db.DateTime)

    title = db.Column(db.String(255), nullable=True)
    brief = db.Column(db.String(255), nullable=True)
    content = db.Column(db.Text, nullable=True)
    status = db.Column(db.String(255), nullable=False)
    _target = db.Column("target", db.String(255), nullable=True)
    reject_reason = db.Column(db.String())

    user_id = db.Column(db.Integer, db.ForeignKey("user.id"), nullable=False)
    author = db.relationship("User", back_populates="ccrs")

    rfp_id = db.Column(db.Integer, db.ForeignKey("rfp.id"), nullable=True)
    rfp = db.relationship("RFP", back_populates="ccr")

    @staticmethod
    def get_by_user(user, statuses=[CCRStatus.LIVE]):
        status_filter = or_(CCR.status == v for v in statuses)
        return CCR.query \
            .filter(CCR.user_id == user.id) \
            .filter(status_filter) \
            .all()

    @staticmethod
    def create(**kwargs):
        ccr = CCR(
            **kwargs
        )
        db.session.add(ccr)
        db.session.flush()
        return ccr

    @hybrid_property
    def target(self):
        return self._target

    @target.setter
    def target(self, target: str):
        if target and Decimal(target) > 0:
            self._target = target
        else:
            self._target = None

    def __init__(
            self,
            user_id: int,
            title: str = '',
            brief: str = '',
            content: str = default_content(),
            target: str = '0',
            status: str = CCRStatus.DRAFT,
    ):
        assert CCRStatus.includes(status)
        self.id = gen_random_id(CCR)
        self.date_created = datetime.now()
        self.title = title[:255]
        self.brief = brief[:255]
        self.content = content
        self.target = target
        self.status = status
        self.user_id = user_id

    def update(
            self,
            title: str = '',
            brief: str = '',
            content: str = '',
            target: str = '0',
    ):
        self.title = title[:255]
        self.brief = brief[:255]
        self.content = content[:300000]
        self._target = target[:255] if target != '' and target else '0'

    # state: status (DRAFT || REJECTED) -> (PENDING || STAKING)
    def submit_for_approval(self):
        self.validate_publishable()
        allowed_statuses = [CCRStatus.DRAFT, CCRStatus.REJECTED]
        # specific validation
        if self.status not in allowed_statuses:
            raise ValidationException(f"CCR status must be draft or rejected to submit for approval")
        self.set_pending()

    def send_admin_email(self, type: str):
        from grant.user.models import User
        admins = User.get_admins()
        for a in admins:
            send_email(a.email_address, type, {
                'user': a,
                'ccr': self,
                'ccr_url': make_admin_url(f'/ccrs/{self.id}'),
            })

    # state: status DRAFT -> PENDING
    def set_pending(self):
        self.send_admin_email('admin_approval_ccr')
        self.status = CCRStatus.PENDING
        db.session.add(self)
        db.session.flush()

    def validate_publishable(self):
        # Require certain fields
        required_fields = ['title', 'content', 'brief', 'target']
        for field in required_fields:
            if not hasattr(self, field):
                raise ValidationException("Proposal must have a {}".format(field))

        # Stricter limits on certain fields
        if len(self.title) > 60:
            raise ValidationException("Proposal title cannot be longer than 60 characters")
        if len(self.brief) > 140:
            raise ValidationException("Brief cannot be longer than 140 characters")
        if len(self.content) > 250000:
            raise ValidationException("Content cannot be longer than 250,000 characters")

    # state: status PENDING -> (LIVE || REJECTED)
    def approve_pending(self, is_approve, reject_reason=None):
        from grant.rfp.models import RFP
        self.validate_publishable()
        # specific validation
        if not self.status == CCRStatus.PENDING:
            raise ValidationException(f"CCR must be pending to approve or reject")

        if is_approve:
            self.status = CCRStatus.LIVE
            rfp = RFP(
                title=self.title,
                brief=self.brief,
                content=self.content,
                bounty=self._target,
                date_closes=datetime.now() + timedelta(days=90),
            )
            db.session.add(self)
            db.session.add(rfp)
            db.session.flush()
            self.rfp_id = rfp.id
            db.session.add(rfp)
            db.session.flush()

            # for emails
            db.session.commit()

            send_email(self.author.email_address, 'ccr_approved', {
                'user': self.author,
                'ccr': self,
                'admin_note': f'Congratulations! Your Request has been accepted. There may be a delay between acceptance and final posting as required by the Zcash Foundation.'
            })
            return rfp.id
        else:
            if not reject_reason:
                raise ValidationException("Please provide a reason for rejecting the ccr")
            self.status = CCRStatus.REJECTED
            self.reject_reason = reject_reason
            # for emails
            db.session.add(self)
            db.session.commit()
            send_email(self.author.email_address, 'ccr_rejected', {
                'user': self.author,
                'ccr': self,
                'admin_note': reject_reason
            })
            return None
Exemple #30
0
class RFWWorker(db.Model):
    __tablename__ = 'rfw_worker'

    id = db.Column(db.Integer(), primary_key=True)
    date_created = db.Column(db.DateTime)

    status = db.Column(db.String(255), nullable=False)
    status_message = db.Column(db.String, nullable=False)
    status_change_date = db.Column(db.DateTime, nullable=True)

    # relations
    rfw_id = db.Column(db.Integer(), db.ForeignKey('rfw.id'))
    user_id = db.Column(db.Integer(), db.ForeignKey('user.id'))
    rfw = db.relationship('RFW', back_populates='workers')
    user = db.relationship('User', back_populates='rfws')
    claims = db.relationship("RFWMilestoneClaim")

    @staticmethod
    def get_work(user_id, is_self=False):
        if is_self:
            work = RFWWorker.query.filter_by(user_id=user_id) \
                .order_by(RFWWorker.status_change_date.desc()) \
                .all()
            work_dump = RFWWorkerSchema(many=True).dump(work)
        else:
            work = RFWWorker.query.filter_by(user_id=user_id, status=RFWWorkerStatus.ACCEPTED) \
                .order_by(RFWWorker.status_change_date.desc()) \
                .all()
            work_dump = RFWWorkerSchema(many=True,
                                        exclude=['status_message']).dump(work)
            for w in work_dump:
                w['claims'] = [
                    c for c in w['claims']
                    if c['stage'] == RFWMilestoneClaimStage.ACCEPTED
                ]
        return work_dump

    @hybrid_property
    def is_self(self):
        authed = get_authed_user()
        if authed:
            return authed.id == self.user.id
        return False

    @validates('status')
    def validate_status(self, key, field):
        if not RFWWorkerStatus.includes(field):
            raise RFWException(
                f'RFWWorker status must be in the RFWWorkerStatusEnum, was [{field}]'
            )
        return field

    def __init__(self, **kwargs):
        super().__init__(id=gen_random_id(RFWWorker),
                         date_created=datetime.now(),
                         status=RFWWorkerStatus.REQUESTED,
                         status_message=kwargs.pop('status_message', ''),
                         **kwargs)

    def set_requested(self, message=''):
        if self.status == RFWWorkerStatus.ACCEPTED:
            raise RFWException(
                f'Cannot request worker when already accepted, worker id {worker.id}'
            )
        if self.status == RFWWorkerStatus.REJECTED:
            pass
        self.status = RFWWorkerStatus.REQUESTED
        self.status_message = message
        self.status_change_date = datetime.now()
        db.session.flush()

    def set_accepted(self, message=''):
        self.status = RFWWorkerStatus.ACCEPTED
        self.status_message = message
        self.status_change_date = datetime.now()
        db.session.flush()

    def set_rejected(self, message: str):
        self.status = RFWWorkerStatus.REJECTED
        self.status_message = message
        self.status_change_date = datetime.now()
        db.session.flush()