def test_set_v2_date_estimates_deadline_recalculation(self):
        proposal_data = test_proposal.copy()
        proposal = self.init_proposal(proposal_data)

        first_ms = proposal.milestones[0]
        second_ms = proposal.milestones[1]

        first_ms.stage = MilestoneStage.PAID
        first_ms.date_paid = datetime.datetime.now()

        expected_base_date = datetime.datetime.now() + datetime.timedelta(
            days=42)
        second_ms.stage = MilestoneStage.PAID
        second_ms.date_paid = expected_base_date

        db.session.add(proposal)
        db.session.commit()

        Milestone.set_v2_date_estimates(proposal)

        proposal = Proposal.query.get(proposal.id)
        third_ms = proposal.milestones[2]
        expected_date_estimated = expected_base_date + datetime.timedelta(
            days=int(third_ms.days_estimated))

        # ensure `date_estimated` was recalculated as expected
        self.assertEqual(third_ms.date_estimated, expected_date_estimated)
def paid_milestone_payout_request(id, mid, tx_id):
    proposal = Proposal.query.filter_by(id=id).first()
    if not proposal:
        return {"message": "No proposal matching id"}, 404
    if not proposal.is_funded:
        return {"message": "Proposal is not fully funded"}, 400
    for ms in proposal.milestones:
        if ms.id == int(mid):
            is_final_milestone = False
            ms.mark_paid(tx_id)
            db.session.add(ms)
            db.session.flush()
            # check if this is the final ms, and update proposal.stage
            num_paid = reduce(
                lambda a, x: a + (1 if x.stage == MilestoneStage.PAID else 0),
                proposal.milestones, 0)
            if num_paid == len(proposal.milestones):
                is_final_milestone = True
                proposal.stage = ProposalStage.COMPLETED  # WIP -> COMPLETED
                db.session.add(proposal)
                db.session.flush()
            db.session.commit()
            # email TEAM that payout request was PAID
            amount = Decimal(ms.payout_percent) * Decimal(
                proposal.target) / 100
            for member in proposal.team:
                send_email(
                    member.email_address, 'milestone_paid', {
                        'proposal':
                        proposal,
                        'milestone':
                        ms,
                        'amount':
                        amount,
                        'tx_explorer_url':
                        make_explore_url(tx_id),
                        'proposal_milestones_url':
                        make_url(f'/proposals/{proposal.id}?tab=milestones'),
                    })

            # email FOLLOWERS that milestone was accepted
            proposal.send_follower_email(
                "followed_proposal_milestone",
                email_args={"milestone": ms},
                url_suffix="?tab=milestones",
            )

            if not is_final_milestone:
                Milestone.set_v2_date_estimates(proposal)
                db.session.commit()

            return proposal_schema.dump(proposal), 200

    return {"message": "No milestone matching id"}, 404
Esempio n. 3
0
def create_proposals(count):
    user = User.query.filter_by().first()
    for i in range(count):
        if i < 5:
            stage = ProposalStage.WIP
        else:
            stage = ProposalStage.COMPLETED
        p = Proposal.create(
            stage=stage,
            status=ProposalStatus.LIVE,
            title=f'Fake Proposal #{i}',
            content=f'My fake proposal content, numero {i}',
            brief=f'This is proposal {i} generated by "flask create-proposals"',
            category=Category.ACCESSIBILITY,
            target="123.456",
            payout_address="fake123",
            deadline_duration=100)
        p.date_published = datetime.datetime.now()
        p.team.append(user)
        p.date_approved = datetime.datetime.now()
        p.accepted_with_funding = True
        p.version = '2'
        p.fully_fund_contibution_bounty()
        db.session.add(p)
        db.session.flush()
        num_ms = randint(1, 9)
        for j in range(num_ms):
            m = Milestone(
                title=f'Fake MS {j}',
                content=f'Fake milestone #{j} on fake proposal #{i}!',
                days_estimated='10',
                payout_percent=str(floor(1 / num_ms * 100)),
                immediate_payout=j == 0,
                proposal_id=p.id,
                index=j)
            db.session.add(m)
        for j in range(100):
            c = Comment(proposal_id=p.id,
                        user_id=user.id,
                        parent_comment_id=None,
                        content=f'Fake comment #{j} on fake proposal #{i}!')
            db.session.add(c)

        Milestone.set_v2_date_estimates(p)
        db.session.add(p)

    db.session.commit()
    print(f'Added {count} LIVE fake proposals')
def accept_proposal(id, is_accepted, with_funding, changes_requested_reason):
    proposal = Proposal.query.get(id)
    if not proposal:
        return {"message": "No proposal found."}, 404

    if is_accepted:
        proposal.accept_proposal(with_funding)

        if with_funding:
            Milestone.set_v2_date_estimates(proposal)
    else:
        proposal.request_changes_discussion(changes_requested_reason)

    db.session.add(proposal)
    db.session.commit()
    return proposal_schema.dump(proposal)
Esempio n. 5
0
def update_proposal(milestones, proposal_id, **kwargs):
    # Update the base proposal fields
    try:
        if g.current_proposal.status not in [
                ProposalStatus.DRAFT, ProposalStatus.REJECTED
        ]:
            raise ValidationException(
                f"Proposal with status: {g.current_proposal.status} are not authorized for updates"
            )
        g.current_proposal.update(**kwargs)
    except ValidationException as e:
        return {"message": "{}".format(str(e))}, 400
    db.session.add(g.current_proposal)

    Milestone.make(milestones, g.current_proposal)

    # Commit
    db.session.commit()
    return proposal_schema.dump(g.current_proposal), 200
Esempio n. 6
0
def create_proposals(count, category, stage, with_comments=False):
    user = User.query.filter_by().first()
    for i in range(count):
        target = "123.456"
        p = Proposal.create(
            stage=stage,
            status=ProposalStatus.LIVE,
            title=f'Fake Proposal #{i} {category} {stage}',
            content=f'My fake proposal content, numero {i}',
            brief=f'This is proposal {i} generated by e2e testing',
            category=category,
            target=target,
            payout_address="fake123",
            deadline_duration=100)
        p.date_published = datetime.now()
        p.team.append(user)
        db.session.add(p)
        db.session.flush()
        num_ms = randint(1, 9)
        for j in range(num_ms):
            m = Milestone(
                title=f'Fake MS {j}',
                content=
                f'Fake milestone #{j} on fake proposal #{i} {category} {stage}!',
                date_estimated=datetime.now(),
                payout_amount=str(float(target) / num_ms),
                immediate_payout=j == 0,
                proposal_id=p.id,
                index=j)
            db.session.add(m)
        # limit comment creation as it is slow
        if i == 0 and with_comments:
            for j in range(31):
                c = Comment(
                    proposal_id=p.id,
                    user_id=user.id,
                    parent_comment_id=None,
                    content=
                    f'Fake comment #{j} on fake proposal #{i} {category} {stage}!'
                )
                db.session.add(c)
        if stage == ProposalStage.FUNDING_REQUIRED:
            stake = p.create_contribution('1', None, True)
            stake.confirm('fakestaketxid', '1')
            db.session.add(stake)
            db.session.flush()
            fund = p.create_contribution('100', None, False)
            fund.confirm('fakefundtxid0', '100')
            db.session.add(fund)
            db.session.flush()
            p.status = ProposalStatus.LIVE
            db.session.add(p)
            db.session.flush()
Esempio n. 7
0
    def setUp(self):
        super().setUp()
        self._proposal = Proposal.create(
            status=ProposalStatus.DRAFT,
            title=test_proposal["title"],
            content=test_proposal["content"],
            brief=test_proposal["brief"],
            category=test_proposal["category"],
            target=test_proposal["target"],
        )
        self._proposal.team.append(self.user)
        db.session.add(self._proposal)
        db.session.flush()

        milestones = [
            {
                "title": "Milestone 1",
                "content": "Content 1",
                "date_estimated": (datetime.now() + timedelta(days=364)).timestamp(),  # random unix time in the future
                "payout_amount": 2,
                "immediate_payout": True
            },
            {
                "title": "Milestone 2",
                "content": "Content 2",
                "date_estimated": (datetime.now() + timedelta(days=365)).timestamp(),  # random unix time in the future
                "payout_amount": 3,
                "immediate_payout": False
            }
        ]

        Milestone.make(milestones, self._proposal)

        self._other_proposal = Proposal.create(status=ProposalStatus.DRAFT)
        self._other_proposal.team.append(self.other_user)
        db.session.add(self._other_proposal)
        db.session.commit()
        self._proposal_id = self._proposal.id
        self._other_proposal_id = self._other_proposal.id
def change_proposal_to_accepted_with_funding(id):
    proposal = Proposal.query.filter_by(id=id).first()
    if not proposal:
        return {"message": "No proposal found."}, 404
    if proposal.accepted_with_funding:
        return {"message": "Proposal already accepted with funding."}, 404
    if proposal.version != '2':
        return {
            "message":
            "Only version two proposals can be accepted with funding"
        }, 404
    if proposal.status != ProposalStatus.LIVE and proposal.status != ProposalStatus.APPROVED:
        return {
            "message":
            "Only live or approved proposals can be modified by this endpoint"
        }, 404

    proposal.update_proposal_with_funding()
    Milestone.set_v2_date_estimates(proposal)
    db.session.add(proposal)
    db.session.commit()

    return proposal_schema.dump(proposal)
    def init_proposal(self, proposal_data):
        self.login_default_user()
        resp = self.app.post("/api/v1/proposals/drafts")
        self.assertStatus(resp, 201)
        proposal_id = resp.json["proposalId"]

        resp = self.app.put(f"/api/v1/proposals/{proposal_id}",
                            data=json.dumps(proposal_data),
                            content_type='application/json')
        self.assert200(resp)

        proposal = Proposal.query.get(proposal_id)
        proposal.status = ProposalStatus.DISCUSSION

        # accept with funding
        proposal.accept_proposal(True)
        Milestone.set_v2_date_estimates(proposal)

        db.session.add(proposal)
        db.session.commit()

        print(proposal_schema.dump(proposal))
        return proposal
Esempio n. 10
0
    def setUp(self):
        super().setUp()
        self._proposal = Proposal.create(
            status=ProposalStatus.DRAFT,
            title=test_proposal["title"],
            content=test_proposal["content"],
            brief=test_proposal["brief"],
            category=test_proposal["category"],
            target=test_proposal["target"],
            payout_address=test_proposal["payoutAddress"],
            deadline_duration=test_proposal["deadlineDuration"])
        self._proposal.team.append(self.user)
        db.session.add(self._proposal)
        db.session.flush()

        milestones = [{
            "title": "Milestone 1",
            "content": "Content 1",
            "days_estimated": "30",
            "payout_percent": 50,
            "immediate_payout": True
        }, {
            "title": "Milestone 2",
            "content": "Content 2",
            "days_estimated": "20",
            "payout_percent": 50,
            "immediate_payout": False
        }]

        Milestone.make(milestones, self._proposal)

        self._other_proposal = Proposal.create(status=ProposalStatus.DRAFT)
        self._other_proposal.team.append(self.other_user)
        db.session.add(self._other_proposal)
        db.session.commit()
        self._proposal_id = self._proposal.id
        self._other_proposal_id = self._other_proposal.id
Esempio n. 11
0
def update_proposal(milestones, proposal_id, rfp_opt_in, **kwargs):
    # Update the base proposal fields
    try:
        if g.current_proposal.status not in [
                ProposalStatus.DRAFT, ProposalStatus.LIVE_DRAFT,
                ProposalStatus.REJECTED
        ]:
            raise ValidationException(
                f"Proposal with status: {g.current_proposal.status} are not authorized for updates"
            )
        g.current_proposal.update(**kwargs)
    except ValidationException as e:
        return {"message": "{}".format(str(e))}, 400
    db.session.add(g.current_proposal)

    # twiddle rfp opt-in (modifies proposal matching and/or bounty)
    if rfp_opt_in is not None:
        g.current_proposal.update_rfp_opt_in(rfp_opt_in)

    Milestone.make(milestones, g.current_proposal)

    # Commit
    db.session.commit()
    return proposal_schema.dump(g.current_proposal), 200
Esempio n. 12
0
def create_proposals(count):
    user = User.query.filter_by().first()
    for i in range(count):
        if i < 5:
            stage = ProposalStageEnum.WIP
        else:
            stage = ProposalStageEnum.COMPLETED
        target = str(randint(1, 10))
        p = Proposal.create(
            stage=stage,
            status=ProposalStatus.LIVE,
            title=f'Fake Proposal #{i}',
            content=f'My fake proposal content, numero {i}',
            brief=
            f'This is proposal {i} generated by "flask create-proposals", a useful tool for generating test proposal data.',
            category=Category.random(),
            target=target,
        )
        p.date_published = datetime.datetime.now()
        p.team.append(user)
        db.session.add(p)
        db.session.flush()

        num_ms = randint(1, 9)
        for j in range(num_ms):
            m = Milestone(
                title=f'Fake MS {j}',
                content=f'Fake milestone #{j} on fake proposal #{i}!',
                date_estimated=datetime.datetime.now(),
                payout_amount=str(float(target) / num_ms),
                immediate_payout=j == 0,
                proposal_id=p.id,
                index=j)
            db.session.add(m)
        for j in range(100):
            c = Comment(proposal_id=p.id,
                        user_id=user.id,
                        parent_comment_id=None,
                        content=f'Fake comment #{j} on fake proposal #{i}!')
            db.session.add(c)

    db.session.commit()
    print(f'Added {count} LIVE fake proposals')
    def test_milestone_deadline_noops(self, mock_send_email):
        # make sure all milestone deadline noop states work as expected

        def proposal_delete(p, m):
            db.session.delete(p)

        def proposal_status(p, m):
            p.status = ProposalStatus.DELETED
            db.session.add(p)

        def proposal_stage(p, m):
            p.stage = ProposalStage.CANCELED
            db.session.add(p)

        def milestone_delete(p, m):
            db.session.delete(m)

        def milestone_date_requested(p, m):
            m.date_requested = datetime.now()
            db.session.add(m)

        def update_posted(p, m):
            # login and post proposal update
            self.login_default_user()
            resp = self.app.post("/api/v1/proposals/{}/updates".format(
                proposal.id),
                                 data=json.dumps(test_update),
                                 content_type='application/json')
            self.assertStatus(resp, 201)

        modifiers = [
            proposal_delete, proposal_status, proposal_stage, milestone_delete,
            milestone_date_requested, update_posted
        ]

        for modifier in modifiers:
            # make proposal and milestone
            proposal = Proposal.create(status=ProposalStatus.LIVE)
            proposal.arbiter.user = self.other_user
            proposal.team.append(self.user)
            proposal_id = proposal.id
            Milestone.make(milestones_data, proposal)

            db.session.add(proposal)
            db.session.commit()

            # grab update count for blob
            update_count = len(
                ProposalUpdate.query.filter_by(proposal_id=proposal_id).all())

            # run modifications to trigger noop
            proposal = Proposal.query.get(proposal_id)
            milestone = proposal.milestones[0]
            milestone_id = milestone.id
            modifier(proposal, milestone)
            db.session.commit()

            # make task
            blob = {
                "proposal_id": proposal_id,
                "milestone_id": milestone_id,
                "update_count": update_count
            }
            task = Task(job_type=MilestoneDeadline.JOB_TYPE,
                        blob=blob,
                        execute_after=datetime.now())

            # run task
            MilestoneDeadline.process_task(task)

            # check to make sure noop occurred
            mock_send_email.assert_not_called()
 def milestones(p):
     Milestone.make(milestones_data, p)
Esempio n. 15
0
def make_proposal(crowd_fund_contract_address, content, title, milestones, category, team):
    existing_proposal = Proposal.query.filter_by(proposal_address=crowd_fund_contract_address).first()
    if existing_proposal:
        return {"message": "Oops! Something went wrong."}, 409

    proposal = Proposal.create(
        stage="FUNDING_REQUIRED",
        proposal_address=crowd_fund_contract_address,
        content=content,
        title=title,
        category=category
    )

    db.session.add(proposal)

    if not len(team) > 0:
        return {"message": "Team must be at least 1"}, 400

    for team_member in team:
        account_address = team_member.get("accountAddress")
        display_name = team_member.get("displayName")
        email_address = team_member.get("emailAddress")
        title = team_member.get("title")
        user = User.query.filter(
            (User.account_address == account_address) | (User.email_address == email_address)).first()
        if not user:
            user = User(
                account_address=account_address,
                email_address=email_address,
                display_name=display_name,
                title=title
            )
            db.session.add(user)
            db.session.flush()

            avatar_data = team_member.get("avatar")
            if avatar_data:
                avatar = Avatar(image_url=avatar_data.get('link'), user_id=user.id)
                db.session.add(avatar)

            social_medias = team_member.get("socialMedias")
            if social_medias:
                for social_media in social_medias:
                    sm = SocialMedia(social_media_link=social_media.get("link"), user_id=user.id)
                    db.session.add(sm)

        proposal.team.append(user)

    for each_milestone in milestones:
        m = Milestone(
            title=each_milestone["title"],
            content=each_milestone["description"],
            date_estimated=datetime.strptime(each_milestone["date"], '%B %Y'),
            payout_percent=str(each_milestone["payoutPercent"]),
            immediate_payout=each_milestone["immediatePayout"],
            proposal_id=proposal.id
        )

        db.session.add(m)

    try:
        db.session.commit()
    except IntegrityError as e:
        print(e)
        return {"message": "Oops! Something went wrong."}, 409

    results = proposal_schema.dump(proposal)
    return results, 201