Run reformatter on all files (#98)

This commit is contained in:
Daniel Ternyak 2019-01-23 09:00:30 -06:00 committed by William O'Beirne
parent 8f7fa45bb6
commit c51e850cb0
66 changed files with 665 additions and 634 deletions

View File

@ -1,2 +1,2 @@
[settings]
line_length=120
line_length = 120

View File

@ -1,6 +1,6 @@
repos:
- repo: https://github.com/ambv/black
- repo: https://github.com/ambv/black
rev: stable
hooks:
- id: black
language_version: python3.6
- id: black
language_version: python3.6

View File

@ -5,6 +5,7 @@ from grant.blockchain.bootstrap import send_bootstrap_data
app = create_app()
@app.before_first_request
def bootstrap_watcher():
send_bootstrap_data()
send_bootstrap_data()

View File

@ -1,2 +1,2 @@
from . import views
from . import commands
from . import views

View File

@ -1,9 +1,8 @@
import click
import getpass
from flask.cli import with_appcontext
from grant.utils.admin import generate_admin_password_hash
import click
from grant.settings import SECRET_KEY
from grant.utils.admin import generate_admin_password_hash
@click.command()

View File

@ -5,6 +5,7 @@ class FakeUser(object):
display_name = 'Example User'
title = 'Email Example Dude'
class FakeProposal(object):
id = 123
title = 'Example proposal'
@ -12,18 +13,21 @@ class FakeProposal(object):
content = 'Example example example example'
target = "100"
class FakeContribution(object):
id = 123
amount = '12.5'
proposal_id = 123
user_id = 123
class FakeUpdate(object):
id = 123
title = 'Example update'
content = 'Example example example example\n\nExample example example example'
proposal_id = 123
user = FakeUser()
proposal = FakeProposal()
contribution = FakeContribution()

View File

@ -1,13 +1,9 @@
from functools import wraps
from flask import Blueprint, request
from flask import Blueprint, request
from flask_yoloapi import endpoint, parameter
from hashlib import sha256
from uuid import uuid4
from sqlalchemy import func, or_
from grant.comment.models import Comment, user_comments_schema
from grant.email.send import generate_email
from grant.extensions import db
from grant.utils.admin import admin_auth_required, admin_is_authed, admin_login, admin_logout
from grant.user.models import User, users_schema, user_schema
from grant.proposal.models import (
Proposal,
ProposalContribution,
@ -16,10 +12,11 @@ from grant.proposal.models import (
user_proposal_contributions_schema,
PENDING
)
from grant.comment.models import Comment, comments_schema, user_comments_schema
from grant.email.send import generate_email
from .example_emails import example_email_args
from grant.user.models import User, users_schema, user_schema
from grant.utils.admin import admin_auth_required, admin_is_authed, admin_login, admin_logout
from sqlalchemy import func, or_
from .example_emails import example_email_args
blueprint = Blueprint('admin', __name__, url_prefix='/api/v1/admin')

View File

@ -1,16 +1,14 @@
# -*- coding: utf-8 -*-
"""The app module, containing the app factory function."""
import sentry_sdk
from flask import Flask
from flask_cors import CORS
from flask_sslify import SSLify
from flask_security import SQLAlchemyUserDatastore
from sentry_sdk.integrations.flask import FlaskIntegration
import sentry_sdk
from flask_sslify import SSLify
from grant import commands, proposal, user, comment, milestone, admin, email, blockchain
from grant.extensions import bcrypt, migrate, db, ma, security
from grant.settings import SENTRY_RELEASE, ENV
from grant.blockchain.bootstrap import send_bootstrap_data
from sentry_sdk.integrations.flask import FlaskIntegration
def create_app(config_objects=["grant.settings"]):

View File

@ -1,31 +1,33 @@
from grant.utils.requests import blockchain_post
from grant.proposal.models import (
ProposalContribution,
proposal_contributions_schema,
PENDING,
CONFIRMED,
)
)
from grant.utils.requests import blockchain_post
def make_bootstrap_data():
pending_contributions = ProposalContribution.query \
.filter_by(status=PENDING) \
.all()
latest_contribution = ProposalContribution.query\
.filter_by(status=CONFIRMED) \
.order_by(ProposalContribution.date_created.desc()) \
.first()
return {
"pendingContributions": proposal_contributions_schema.dump(pending_contributions),
"latestTxId": latest_contribution.tx_id if latest_contribution else None,
}
pending_contributions = ProposalContribution.query \
.filter_by(status=PENDING) \
.all()
latest_contribution = ProposalContribution.query \
.filter_by(status=CONFIRMED) \
.order_by(ProposalContribution.date_created.desc()) \
.first()
return {
"pendingContributions": proposal_contributions_schema.dump(pending_contributions),
"latestTxId": latest_contribution.tx_id if latest_contribution else None,
}
def send_bootstrap_data():
data = make_bootstrap_data()
print('Sending bootstrap data to blockchain watcher microservice')
print(' * Latest transaction ID: {}'.format(data['latestTxId']))
print(' * Number of pending contributions: {}'.format(len(data['pendingContributions'])))
data = make_bootstrap_data()
print('Sending bootstrap data to blockchain watcher microservice')
print(' * Latest transaction ID: {}'.format(data['latestTxId']))
print(' * Number of pending contributions: {}'.format(len(data['pendingContributions'])))
res = blockchain_post('/bootstrap', data)
print('Blockchain watcher has started')
print('Starting chain height: {}'.format(res['startHeight']))
print('Current chain height: {}'.format(res['currentHeight']))
res = blockchain_post('/bootstrap', data)
print('Blockchain watcher has started')
print('Starting chain height: {}'.format(res['startHeight']))
print('Current chain height: {}'.format(res['currentHeight']))

View File

@ -1,15 +1,15 @@
from flask import Blueprint, g
from flask import Blueprint
from flask_yoloapi import endpoint
from grant.utils.auth import internal_webhook
from grant.blockchain.bootstrap import send_bootstrap_data
from grant.utils.auth import internal_webhook
blueprint = Blueprint("blockchain", __name__, url_prefix="/api/v1/blockchain")
@blueprint.route("/bootstrap", methods=["GET"])
@internal_webhook
@endpoint.api()
def get_bootstrap_info():
print('Bootstrap data requested from blockchain watcher microservice...')
send_bootstrap_data()
return True
print('Bootstrap data requested from blockchain watcher microservice...')
send_bootstrap_data()
return True

View File

@ -1,2 +1,2 @@
from . import views
from . import models
from . import views

View File

@ -1,8 +1,8 @@
import datetime
from sqlalchemy.orm import raiseload
from grant.extensions import ma, db
from grant.utils.misc import dt_to_unix
from sqlalchemy.orm import raiseload
class Comment(db.Model):
@ -70,6 +70,7 @@ class UserCommentSchema(ma.Schema):
"content",
"date_created",
)
proposal = ma.Nested(
"ProposalSchema",
exclude=[

View File

@ -1,2 +1,2 @@
from . import views
from . import models
from . import views

View File

@ -1,5 +1,6 @@
from datetime import timedelta
from datetime import datetime
from datetime import timedelta
from grant.extensions import ma, db
from grant.utils.misc import gen_random_code

View File

@ -1,9 +1,9 @@
from flask import render_template, Markup, current_app
import sendgrid
from sendgrid.helpers.mail import Email, Mail, Content
from flask import render_template, Markup, current_app
from grant.settings import SENDGRID_API_KEY, SENDGRID_DEFAULT_FROM
from python_http_client import HTTPError
from sendgrid.helpers.mail import Email, Mail, Content
from .subscription_settings import EmailSubscription, is_subscribed
default_template_args = {
@ -55,6 +55,7 @@ def proposal_rejected(email_args):
'subscription': EmailSubscription.MY_PROPOSAL_APPROVAL
}
def proposal_contribution(email_args):
return {
'subject': 'You just got a contribution!',
@ -67,6 +68,7 @@ def proposal_contribution(email_args):
'subscription': EmailSubscription.MY_PROPOSAL_FUNDED,
}
def proposal_comment(email_args):
return {
'subject': 'New comment from {}'.format(email_args['author'].display_name),
@ -78,6 +80,7 @@ def proposal_comment(email_args):
'subscription': EmailSubscription.MY_PROPOSAL_COMMENT,
}
def contribution_confirmed(email_args):
return {
'subject': 'Your contribution has been confirmed!',
@ -89,6 +92,7 @@ def contribution_confirmed(email_args):
'subscription': EmailSubscription.FUNDED_PROPOSAL_CONTRIBUTION,
}
def contribution_update(email_args):
return {
'subject': 'The {} team posted an update'.format(email_args['proposal'].title),
@ -100,6 +104,7 @@ def contribution_update(email_args):
'subscription': EmailSubscription.FUNDED_PROPOSAL_UPDATE,
}
def comment_reply(email_args):
return {
'subject': 'New reply from {}'.format(email_args['author'].display_name),

View File

@ -1,4 +1,5 @@
from enum import Enum
from grant.utils.exceptions import ValidationException

View File

@ -1,8 +1,7 @@
from flask import Blueprint
from flask_yoloapi import endpoint, parameter
from .models import EmailVerification, EmailRecovery, db
from flask_yoloapi import endpoint
from .models import EmailVerification, db
blueprint = Blueprint("email", __name__, url_prefix="/api/v1/email")

View File

@ -3,8 +3,8 @@
from flask_bcrypt import Bcrypt
from flask_marshmallow import Marshmallow
from flask_migrate import Migrate
from flask_sqlalchemy import SQLAlchemy
from flask_security import Security
from flask_sqlalchemy import SQLAlchemy
bcrypt = Bcrypt()
db = SQLAlchemy()

View File

@ -1,2 +1,2 @@
from . import views
from . import models
from . import views

View File

@ -43,7 +43,7 @@ class Milestone(db.Model):
self.immediate_payout = immediate_payout
self.proposal_id = proposal_id
self.date_created = datetime.datetime.now()
@staticmethod
def validate(milestone):
if len(milestone.title) > 60:

View File

@ -1,15 +1,13 @@
import datetime
from typing import List
from sqlalchemy import func, or_
from functools import reduce
from grant.comment.models import Comment
from grant.extensions import ma, db
from grant.utils.misc import dt_to_unix, make_url
from grant.utils.exceptions import ValidationException
from grant.utils.requests import blockchain_get
from grant.email.send import send_email
from grant.extensions import ma, db
from grant.utils.exceptions import ValidationException
from grant.utils.misc import dt_to_unix, make_url
from grant.utils.requests import blockchain_get
from sqlalchemy import func, or_
# Proposal states
DRAFT = 'DRAFT'
@ -101,10 +99,10 @@ class ProposalContribution(db.Model):
user = db.relationship("User")
def __init__(
self,
proposal_id: int,
user_id: int,
amount: str
self,
proposal_id: int,
user_id: int,
amount: str
):
self.proposal_id = proposal_id
self.user_id = user_id
@ -243,14 +241,14 @@ class Proposal(db.Model):
.all()
def update(
self,
title: str = '',
brief: str = '',
category: str = '',
content: str = '',
target: str = '0',
payout_address: str = '',
deadline_duration: int = 5184000 # 60 days
self,
title: str = '',
brief: str = '',
category: str = '',
content: str = '',
target: str = '0',
payout_address: str = '',
deadline_duration: int = 5184000 # 60 days
):
self.title = title
self.brief = brief
@ -439,6 +437,7 @@ class ProposalTeamInviteSchema(ma.Schema):
proposal_team_invite_schema = ProposalTeamInviteSchema()
proposal_team_invites_schema = ProposalTeamInviteSchema(many=True)
# TODO: Find a way to extend ProposalTeamInviteSchema instead of redefining

View File

@ -1,19 +1,15 @@
from dateutil.parser import parse
from functools import wraps
import ast
from flask import Blueprint, g
from flask_yoloapi import endpoint, parameter
from sqlalchemy.exc import IntegrityError
from sqlalchemy import or_
from grant.comment.models import Comment, comment_schema, comments_schema
from grant.milestone.models import Milestone
from grant.user.models import User, SocialMedia, Avatar
from grant.email.send import send_email
from grant.milestone.models import Milestone
from grant.user.models import User
from grant.utils.auth import requires_auth, requires_team_member_auth, get_authed_user, internal_webhook
from grant.utils.exceptions import ValidationException
from grant.utils.misc import is_email, make_url, from_zat, make_preview
from sqlalchemy import or_
from .models import (
Proposal,
proposals_schema,
@ -35,7 +31,6 @@ from .models import (
DELETED,
CONFIRMED,
)
import traceback
blueprint = Blueprint("proposal", __name__, url_prefix="/api/v1/proposals")
@ -136,14 +131,14 @@ def get_proposals(stage):
if stage:
proposals = (
Proposal.query.filter_by(status=LIVE, stage=stage)
.order_by(Proposal.date_created.desc())
.all()
.order_by(Proposal.date_created.desc())
.all()
)
else:
proposals = (
Proposal.query.filter_by(status=LIVE)
.order_by(Proposal.date_created.desc())
.all()
.order_by(Proposal.date_created.desc())
.all()
)
dumped_proposals = proposals_schema.dump(proposals)
return dumped_proposals
@ -170,11 +165,11 @@ def make_proposal_draft():
def get_proposal_drafts():
proposals = (
Proposal.query
.filter(or_(Proposal.status == DRAFT, Proposal.status == REJECTED))
.join(proposal_team)
.filter(proposal_team.c.user_id == g.current_user.id)
.order_by(Proposal.date_created.desc())
.all()
.filter(or_(Proposal.status == DRAFT, Proposal.status == REJECTED))
.join(proposal_team)
.filter(proposal_team.c.user_id == g.current_user.id)
.order_by(Proposal.date_created.desc())
.all()
)
return proposals_schema.dump(proposals), 200
@ -367,7 +362,7 @@ def get_proposal_contributions(proposal_id):
proposal = Proposal.query.filter_by(id=proposal_id).first()
if not proposal:
return {"message": "No proposal matching id"}, 404
top_contributions = ProposalContribution.query \
.filter_by(proposal_id=proposal_id, status=CONFIRMED) \
.order_by(ProposalContribution.amount.desc()) \
@ -378,12 +373,11 @@ def get_proposal_contributions(proposal_id):
.order_by(ProposalContribution.date_created.desc()) \
.limit(5) \
.all()
return {
'top': proposal_proposal_contributions_schema.dump(top_contributions),
'latest': proposal_proposal_contributions_schema.dump(latest_contributions),
}
@blueprint.route("/<proposal_id>/contributions/<contribution_id>", methods=["GET"])
@ -479,6 +473,7 @@ def post_contribution_confirmation(contribution_id, to, amount, txid):
return None, 200
@blueprint.route("/contribution/<contribution_id>", methods=["DELETE"])
@requires_auth
@endpoint.api()

View File

@ -6,10 +6,8 @@ Most configuration is set via environment variables.
For local development, use a .env file to set
environment variables.
"""
import subprocess
from environs import Env
env = Env()
env.read_env()

View File

@ -1,25 +1,26 @@
<p style="margin: 0 0 20px;">
You just received a reply from
<a href="{{ args.author_url }}" target="_blank">{{ args.author.display_name }}</a>
on <strong>{{ args.proposal.title }}</strong>:
You just received a reply from
<a href="{{ args.author_url }}" target="_blank">{{ args.author.display_name }}</a>
on <strong>{{ args.proposal.title }}</strong>:
</p>
<p style="margin: 10px 0; padding: 20px; background: #F8F8F8;">
“{{ args.preview }}”
“{{ args.preview }}”
</p>
<table width="100%" border="0" cellspacing="0" cellpadding="0">
<tr>
<td bgcolor="#ffffff" align="center" style="padding: 40px 30px 40px 30px;">
<table border="0" cellspacing="0" cellpadding="0">
<tr>
<td align="center" style="border-radius: 3px;" bgcolor="#530EEC">
<a href="{{ args.comment_url }}" target="_blank" style="font-size: 20px; font-family: Helvetica, Arial, sans-serif; color: #ffffff; text-decoration: none; color: #ffffff; text-decoration: none; padding: 20px 50px; border-radius: 4px; border: 1px solid #530EEC; display: inline-block;">
View their comment
</a>
</td>
</tr>
</table>
</td>
<td bgcolor="#ffffff" align="center" style="padding: 40px 30px 40px 30px;">
<table border="0" cellspacing="0" cellpadding="0">
<tr>
<td align="center" style="border-radius: 3px;" bgcolor="#530EEC">
<a href="{{ args.comment_url }}" target="_blank"
style="font-size: 20px; font-family: Helvetica, Arial, sans-serif; color: #ffffff; text-decoration: none; color: #ffffff; text-decoration: none; padding: 20px 50px; border-radius: 4px; border: 1px solid #530EEC; display: inline-block;">
View their comment
</a>
</td>
</tr>
</table>
</td>
</tr>
</table>
</table>

View File

@ -1,20 +1,20 @@
<p style="margin: 0 0 20px;">
Your <strong>{{ args.contribution.amount }} ZEC</strong> contribution has
been confirmed! <strong>{{ args.proposal.title}}</strong> has been updated
to reflect your funding, and your account will now show your contribution.
You can view your transaction below:
Your <strong>{{ args.contribution.amount }} ZEC</strong> contribution has
been confirmed! <strong>{{ args.proposal.title}}</strong> has been updated
to reflect your funding, and your account will now show your contribution.
You can view your transaction below:
</p>
<p style="margin: 0 0 20px;">
<a
href="{{ args.tx_explorer_url }}"
target="_blank"
rel="nofollow noopener"
>
{{ args.tx_explorer_url }}
</a>
<a
href="{{ args.tx_explorer_url }}"
target="_blank"
rel="nofollow noopener"
>
{{ args.tx_explorer_url }}
</a>
</p>
<p style="margin: 0;">
Thank you for your help in improving the ZCash ecosystem.
Thank you for your help in improving the ZCash ecosystem.
</p>

View File

@ -1,32 +1,32 @@
<p style="margin: 0;">
A proposal you follow, <strong>{{ args.proposal.title }}</strong>, has
posted an update:
A proposal you follow, <strong>{{ args.proposal.title }}</strong>, has
posted an update:
</p>
<div style="margin: 10px 0; padding: 20px; background: #F8F8F8;">
<h2 style="margin: 0 0 20px 0; font-size: 20px; font-weight: bold;">
{{ args.proposal_update.title }}
</h2>
<p style="margin: 0;">{{ args.preview }}</p>
<h2 style="margin: 0 0 20px 0; font-size: 20px; font-weight: bold;">
{{ args.proposal_update.title }}
</h2>
<p style="margin: 0;">{{ args.preview }}</p>
</div>
<table width="100%" border="0" cellspacing="0" cellpadding="0">
<table width="100%" border="0" cellspacing="0" cellpadding="0">
<tr>
<td bgcolor="#ffffff" align="center" style="padding: 40px 30px 40px 30px;">
<table border="0" cellspacing="0" cellpadding="0">
<tr>
<td align="center" style="border-radius: 3px;" bgcolor="#530EEC">
<a
href="{{ args.update_url }}"
target="_blank"
style="font-size: 20px; font-family: Helvetica, Arial, sans-serif; color: #ffffff; text-decoration: none; color: #ffffff; text-decoration: none; padding: 20px 50px; border-radius: 4px; border: 1px solid #530EEC; display: inline-block;"
>
View the full update
</a>
</td>
</tr>
</table>
</td>
<td bgcolor="#ffffff" align="center" style="padding: 40px 30px 40px 30px;">
<table border="0" cellspacing="0" cellpadding="0">
<tr>
<td align="center" style="border-radius: 3px;" bgcolor="#530EEC">
<a
href="{{ args.update_url }}"
target="_blank"
style="font-size: 20px; font-family: Helvetica, Arial, sans-serif; color: #ffffff; text-decoration: none; color: #ffffff; text-decoration: none; padding: 20px 50px; border-radius: 4px; border: 1px solid #530EEC; display: inline-block;"
>
View the full update
</a>
</td>
</tr>
</table>
</td>
</tr>
</table>
</table>

View File

@ -1,34 +1,34 @@
<p style="margin: 0;">
Congratulations on your approval! We look forward to seeing the support your
proposal receives. To get your campaign started, click below and follow the
instructions to publish your proposal.
Congratulations on your approval! We look forward to seeing the support your
proposal receives. To get your campaign started, click below and follow the
instructions to publish your proposal.
</p>
{% if args.admin_note %}
<p style="margin: 20px 0 0;">
A note from the admin team was attached to your approval:
A note from the admin team was attached to your approval:
</p>
<p style="margin: 10px 0; padding: 20px; background: #F8F8F8;">
“{{ args.admin_note }}”
“{{ args.admin_note }}”
</p>
{% endif %}
<table width="100%" border="0" cellspacing="0" cellpadding="0">
<tr>
<td bgcolor="#ffffff" align="center" style="padding: 40px 30px 40px 30px;">
<table border="0" cellspacing="0" cellpadding="0">
<tr>
<td align="center" style="border-radius: 3px;" bgcolor="#530EEC">
<a
href="{{ args.proposal_url }}"
target="_blank"
style="font-size: 20px; font-family: Helvetica, Arial, sans-serif; color: #ffffff; text-decoration: none; color: #ffffff; text-decoration: none; padding: 20px 50px; border-radius: 4px; border: 1px solid #530EEC; display: inline-block;"
>
Publish your proposal
</a>
</td>
</tr>
</table>
</td>
</tr>
<tr>
<td bgcolor="#ffffff" align="center" style="padding: 40px 30px 40px 30px;">
<table border="0" cellspacing="0" cellpadding="0">
<tr>
<td align="center" style="border-radius: 3px;" bgcolor="#530EEC">
<a
href="{{ args.proposal_url }}"
target="_blank"
style="font-size: 20px; font-family: Helvetica, Arial, sans-serif; color: #ffffff; text-decoration: none; color: #ffffff; text-decoration: none; padding: 20px 50px; border-radius: 4px; border: 1px solid #530EEC; display: inline-block;"
>
Publish your proposal
</a>
</td>
</tr>
</table>
</td>
</tr>
</table>

View File

@ -1,24 +1,25 @@
<p style="margin: 0 0 20px;">
Your proposal <strong>{{ args.proposal.title }}</strong> just received a comment from
<a href="{{ args.author_url }}" target="_blank">{{ args.author.display_name }}</a>:
</p>
</p>
<p style="margin: 10px 0; padding: 20px; background: #F8F8F8;">
<p style="margin: 10px 0; padding: 20px; background: #F8F8F8;">
“{{ args.preview }}”
</p>
</p>
<table width="100%" border="0" cellspacing="0" cellpadding="0">
<tr>
<table width="100%" border="0" cellspacing="0" cellpadding="0">
<tr>
<td bgcolor="#ffffff" align="center" style="padding: 40px 30px 40px 30px;">
<table border="0" cellspacing="0" cellpadding="0">
<tr>
<td align="center" style="border-radius: 3px;" bgcolor="#530EEC">
<a href="{{ args.comment_url }}" target="_blank" style="font-size: 20px; font-family: Helvetica, Arial, sans-serif; color: #ffffff; text-decoration: none; color: #ffffff; text-decoration: none; padding: 20px 50px; border-radius: 4px; border: 1px solid #530EEC; display: inline-block;">
View their comment
</a>
</td>
</tr>
</table>
<table border="0" cellspacing="0" cellpadding="0">
<tr>
<td align="center" style="border-radius: 3px;" bgcolor="#530EEC">
<a href="{{ args.comment_url }}" target="_blank"
style="font-size: 20px; font-family: Helvetica, Arial, sans-serif; color: #ffffff; text-decoration: none; color: #ffffff; text-decoration: none; padding: 20px 50px; border-radius: 4px; border: 1px solid #530EEC; display: inline-block;">
View their comment
</a>
</td>
</tr>
</table>
</td>
</tr>
</table>
</tr>
</table>

View File

@ -1,23 +1,24 @@
<p style="margin: 0 0 20px;">
Your proposal <strong>{{ args.proposal.title }}</strong> just got a
<strong>{{ args.contribution.amount }} ZEC</strong> contribution from
<a href="{{ args.contributor_url }}" target="_blank">{{ args.contributor.display_name }}</a>.
Your proposal is now at
<strong>{{ args.funded }} / {{ args.proposal.target }} ZEC</strong>.
Your proposal <strong>{{ args.proposal.title }}</strong> just got a
<strong>{{ args.contribution.amount }} ZEC</strong> contribution from
<a href="{{ args.contributor_url }}" target="_blank">{{ args.contributor.display_name }}</a>.
Your proposal is now at
<strong>{{ args.funded }} / {{ args.proposal.target }} ZEC</strong>.
</p>
<table width="100%" border="0" cellspacing="0" cellpadding="0">
<tr>
<td bgcolor="#ffffff" align="center" style="padding: 40px 30px 40px 30px;">
<table border="0" cellspacing="0" cellpadding="0">
<tr>
<td align="center" style="border-radius: 3px;" bgcolor="#530EEC">
<a href="{{ args.proposal_url }}" target="_blank" style="font-size: 20px; font-family: Helvetica, Arial, sans-serif; color: #ffffff; text-decoration: none; color: #ffffff; text-decoration: none; padding: 20px 50px; border-radius: 4px; border: 1px solid #530EEC; display: inline-block;">
View your Proposal
</a>
</td>
</tr>
</table>
</td>
</tr>
<tr>
<td bgcolor="#ffffff" align="center" style="padding: 40px 30px 40px 30px;">
<table border="0" cellspacing="0" cellpadding="0">
<tr>
<td align="center" style="border-radius: 3px;" bgcolor="#530EEC">
<a href="{{ args.proposal_url }}" target="_blank"
style="font-size: 20px; font-family: Helvetica, Arial, sans-serif; color: #ffffff; text-decoration: none; color: #ffffff; text-decoration: none; padding: 20px 50px; border-radius: 4px; border: 1px solid #530EEC; display: inline-block;">
View your Proposal
</a>
</td>
</tr>
</table>
</td>
</tr>
</table>

View File

@ -1,19 +1,19 @@
<p style="margin: 0;">
Your proposal has unfortunately been rejected. You're free to modify it
and try submitting again.
Your proposal has unfortunately been rejected. You're free to modify it
and try submitting again.
</p>
{% if args.admin_note %}
<p style="margin: 20px 0 0;">
A note from the admin team was attached to your rejection:
A note from the admin team was attached to your rejection:
</p>
<p style="margin: 10px 0; padding: 20px; background: #F8F8F8;">
“{{ args.admin_note }}”
“{{ args.admin_note }}”
</p>
{% endif %}
<p style="margin: 20px 0 0; font-size: 12px; line-height: 18px; color: #999; text-align: center;">
Please note that repeated submissions without significant changes or with
content that doesn't match the platform guidelines may result in a removal
of your submission privileges.
Please note that repeated submissions without significant changes or with
content that doesn't match the platform guidelines may result in a removal
of your submission privileges.
</p>

View File

@ -1,40 +1,40 @@
<p style="margin: 0;">
Please use the below link to reset your password. The link will expire in one
hour.
Please use the below link to reset your password. The link will expire in one
hour.
</p>
<table width="100%" border="0" cellspacing="0" cellpadding="0">
<tr>
<td bgcolor="#ffffff" align="center" style="padding: 40px 30px 40px 30px;">
<table border="0" cellspacing="0" cellpadding="0">
<tr>
<td align="center" style="border-radius: 3px;" bgcolor="#530EEC">
<a
href="{{ args.recover_url }}"
target="_blank"
style="font-size: 20px; font-family: Helvetica, Arial, sans-serif; color: #ffffff; text-decoration: none; color: #ffffff; text-decoration: none; padding: 20px 50px; border-radius: 4px; border: 1px solid #530EEC; display: inline-block;"
>
Reset Password
</a>
</td>
</tr>
</table>
</td>
</tr>
<tr>
<td bgcolor="#ffffff" align="center" style="padding: 40px 30px 40px 30px;">
<table border="0" cellspacing="0" cellpadding="0">
<tr>
<td align="center" style="border-radius: 3px;" bgcolor="#530EEC">
<a
href="{{ args.recover_url }}"
target="_blank"
style="font-size: 20px; font-family: Helvetica, Arial, sans-serif; color: #ffffff; text-decoration: none; color: #ffffff; text-decoration: none; padding: 20px 50px; border-radius: 4px; border: 1px solid #530EEC; display: inline-block;"
>
Reset Password
</a>
</td>
</tr>
</table>
</td>
</tr>
</table>
<p style="margin: 0 0 10px; font-size: 14px; text-align: center;">
If that doesn't work, copy and paste the following link in your browser
If that doesn't work, copy and paste the following link in your browser
</p>
<p style="margin: 0 0 30px; font-size: 12px; text-align: center;">
<a href="{{ args.recover_url }}" target="_blank" style="color: #530EEC;">{{
args.recover_url
}}</a>
<a href="{{ args.recover_url }}" target="_blank" style="color: #530EEC;">{{
args.recover_url
}}</a>
</p>
<p style="margin: 0; font-size: 10px; color: #AAA; text-align: center;">
If you did not make this request, you can safely ignore this email. A password
reset request can be made by anyone, and it does not indicate that your
account is in any danger of being accessed by someone else.
If you did not make this request, you can safely ignore this email. A password
reset request can be made by anyone, and it does not indicate that your
account is in any danger of being accessed by someone else.
</p>

View File

@ -1,31 +1,32 @@
<p style="margin: 0;">
We're excited to have you get started. First, you need to confirm your email address. Just click the button below.
We're excited to have you get started. First, you need to confirm your email address. Just click the button below.
</p>
<table width="100%" border="0" cellspacing="0" cellpadding="0">
<tr>
<td bgcolor="#ffffff" align="center" style="padding: 40px 30px 40px 30px;">
<table border="0" cellspacing="0" cellpadding="0">
<tr>
<td align="center" style="border-radius: 3px;" bgcolor="#530EEC">
<a href="{{ args.confirm_url }}" target="_blank" style="font-size: 20px; font-family: Helvetica, Arial, sans-serif; color: #ffffff; text-decoration: none; color: #ffffff; text-decoration: none; padding: 20px 50px; border-radius: 4px; border: 1px solid #530EEC; display: inline-block;">
Confirm Email
</a>
</td>
</tr>
</table>
</td>
</tr>
<tr>
<td bgcolor="#ffffff" align="center" style="padding: 40px 30px 40px 30px;">
<table border="0" cellspacing="0" cellpadding="0">
<tr>
<td align="center" style="border-radius: 3px;" bgcolor="#530EEC">
<a href="{{ args.confirm_url }}" target="_blank"
style="font-size: 20px; font-family: Helvetica, Arial, sans-serif; color: #ffffff; text-decoration: none; color: #ffffff; text-decoration: none; padding: 20px 50px; border-radius: 4px; border: 1px solid #530EEC; display: inline-block;">
Confirm Email
</a>
</td>
</tr>
</table>
</td>
</tr>
</table>
<p style="margin: 0 0 10px; font-size: 14px; text-align: center;">
If that doesn't work, copy and paste the following link in your browser
If that doesn't work, copy and paste the following link in your browser
</p>
<p style="margin: 0 0 30px; font-size: 12px; text-align: center;">
<a href="{{ args.confirm_url }}" target="_blank" style="color: #530EEC;">{{ args.confirm_url }}</a>
<a href="{{ args.confirm_url }}" target="_blank" style="color: #530EEC;">{{ args.confirm_url }}</a>
</p>
<p style="margin: 0; font-size: 10px; color: #AAA; text-align: center;">
Dont know why you got this email? Dont worry, you can safely ignore it. We wont send you anymore.
Dont know why you got this email? Dont worry, you can safely ignore it. We wont send you anymore.
</p>

View File

@ -1,34 +1,34 @@
<p style="margin: 0;">
Youve been invited by <strong>{{ args.inviter.display_name }}</strong> to
join the team for
<strong>{{ args.proposal.title or '<em>Untitled Project</em>'|safe }}</strong
>, a project on Grant.io! If you want to accept the invitation, continue to
the site below.
Youve been invited by <strong>{{ args.inviter.display_name }}</strong> to
join the team for
<strong>{{ args.proposal.title or '<em>Untitled Project</em>'|safe }}</strong
>, a project on Grant.io! If you want to accept the invitation, continue to
the site below.
</p>
{% if not args.user %}
<p style="margin: 20px 0 0;">
It looks like you don't yet have a Grant.io account, so you'll need to sign up
first before you can join the team.
It looks like you don't yet have a Grant.io account, so you'll need to sign up
first before you can join the team.
</p>
{% endif %}
<table width="100%" border="0" cellspacing="0" cellpadding="0">
<tr>
<td bgcolor="#ffffff" align="center" style="padding: 40px 30px 0 30px;">
<table border="0" cellspacing="0" cellpadding="0">
<tr>
<td align="center" style="border-radius: 3px;" bgcolor="#530EEC">
<a
href="{{ args.invite_url }}"
target="_blank"
style="font-size: 20px; font-family: Helvetica, Arial, sans-serif; color: #ffffff; text-decoration: none; color: #ffffff; text-decoration: none; padding: 20px 50px; border-radius: 4px; border: 1px solid #530EEC; display: inline-block;"
>
{% if args.user %} See invitation {% else %} Get started {% endif %}
</a>
</td>
</tr>
</table>
</td>
</tr>
<tr>
<td bgcolor="#ffffff" align="center" style="padding: 40px 30px 0 30px;">
<table border="0" cellspacing="0" cellpadding="0">
<tr>
<td align="center" style="border-radius: 3px;" bgcolor="#530EEC">
<a
href="{{ args.invite_url }}"
target="_blank"
style="font-size: 20px; font-family: Helvetica, Arial, sans-serif; color: #ffffff; text-decoration: none; color: #ffffff; text-decoration: none; padding: 20px 50px; border-radius: 4px; border: 1px solid #530EEC; display: inline-block;"
>
{% if args.user %} See invitation {% else %} Get started {% endif %}
</a>
</td>
</tr>
</table>
</td>
</tr>
</table>

View File

@ -4,45 +4,72 @@
<!DOCTYPE html>
<html>
<head>
<title></title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<style type="text/css">
/* FONTS */
@import url('https://fonts.googleapis.com/css?family=Nunito+Sans');
/* CLIENT-SPECIFIC STYLES */
body, table, td, a { -webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; }
table, td { mso-table-lspace: 0pt; mso-table-rspace: 0pt; }
img { -ms-interpolation-mode: bicubic; }
<title></title>
<meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
<meta content="width=device-width, initial-scale=1" name="viewport">
<meta content="IE=edge" http-equiv="X-UA-Compatible"/>
<style type="text/css">
/* FONTS */
@import url('https://fonts.googleapis.com/css?family=Nunito+Sans');
/* RESET STYLES */
img { border: 0; height: auto; line-height: 100%; outline: none; text-decoration: none; }
table { border-collapse: collapse !important; }
body { height: 100% !important; margin: 0 !important; padding: 0 !important; width: 100% !important; }
/* iOS BLUE LINKS */
a[x-apple-data-detectors] {
color: inherit !important;
text-decoration: none !important;
font-size: inherit !important;
font-family: inherit !important;
font-weight: inherit !important;
line-height: inherit !important;
}
/* MOBILE STYLES */
@media screen and (max-width:600px){
h1 {
font-size: 32px !important;
line-height: 32px !important;
/* CLIENT-SPECIFIC STYLES */
body, table, td, a {
-webkit-text-size-adjust: 100%;
-ms-text-size-adjust: 100%;
}
}
/* ANDROID CENTER FIX */
div[style*="margin: 16px 0;"] { margin: 0 !important; }
</style>
table, td {
mso-table-lspace: 0pt;
mso-table-rspace: 0pt;
}
img {
-ms-interpolation-mode: bicubic;
}
/* RESET STYLES */
img {
border: 0;
height: auto;
line-height: 100%;
outline: none;
text-decoration: none;
}
table {
border-collapse: collapse !important;
}
body {
height: 100% !important;
margin: 0 !important;
padding: 0 !important;
width: 100% !important;
}
/* iOS BLUE LINKS */
a[x-apple-data-detectors] {
color: inherit !important;
text-decoration: none !important;
font-size: inherit !important;
font-family: inherit !important;
font-weight: inherit !important;
line-height: inherit !important;
}
/* MOBILE STYLES */
@media screen and (max-width: 600px) {
h1 {
font-size: 32px !important;
line-height: 32px !important;
}
}
/* ANDROID CENTER FIX */
div[style*="margin: 16px 0;"] {
margin: 0 !important;
}
</style>
</head>
<body style="background-color: #f4f4f4; margin: 0 !important; padding: 0 !important;">
@ -54,17 +81,19 @@
<table border="0" cellpadding="0" cellspacing="0" width="100%">
<!-- LOGO -->
<tr>
<td bgcolor="#530EEC" align="center">
<td align="center" bgcolor="#530EEC">
<!--[if (gte mso 9)|(IE)]>
<table align="center" border="0" cellspacing="0" cellpadding="0" width="600">
<tr>
<td align="center" valign="top" width="600">
<![endif]-->
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="max-width: 600px;" >
<tr>
<td align="center" valign="top" style="padding: 40px 10px 40px 10px;">
<td align="center" valign="top" width="600">
<![endif]-->
<table border="0" cellpadding="0" cellspacing="0" style="max-width: 600px;" width="100%">
<tr>
<td align="center" style="padding: 40px 10px 40px 10px;" valign="top">
<a href="{{ args.home_url }}" target="_blank">
<img alt="Logo" src="https://i.imgur.com/t0DPkyl.png" width="120" height="44" style="display: block; width: 150px; max-width: 150px; min-width: 150px; font-family: 'Nunito Sans', Helvetica, Arial, sans-serif; color: #ffffff; font-size: 18px;" border="0">
<img alt="Logo" border="0" height="44" src="https://i.imgur.com/t0DPkyl.png"
style="display: block; width: 150px; max-width: 150px; min-width: 150px; font-family: 'Nunito Sans', Helvetica, Arial, sans-serif; color: #ffffff; font-size: 18px;"
width="120">
</a>
</td>
</tr>
@ -78,17 +107,18 @@
</tr>
<!-- TITLE -->
<tr>
<td bgcolor="#530EEC" align="center" style="padding: 0px 10px 0px 10px;">
<td align="center" bgcolor="#530EEC" style="padding: 0px 10px 0px 10px;">
<!--[if (gte mso 9)|(IE)]>
<table align="center" border="0" cellspacing="0" cellpadding="0" width="600">
<tr>
<td align="center" valign="top" width="600">
<![endif]-->
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="max-width: 600px;" >
<tr>
<td bgcolor="#ffffff" align="center" valign="top" style="padding: 40px 20px 20px 20px; border-radius: 4px 4px 0px 0px; color: #221F1F; font-family: 'Nunito Sans', Helvetica, Arial, sans-serif; font-size: 48px; font-weight: 400; letter-spacing: 2px; line-height: 48px;">
<h1 style="font-size: 42px; font-weight: 400; margin: 0;">
{{ args.title }}
<td align="center" valign="top" width="600">
<![endif]-->
<table border="0" cellpadding="0" cellspacing="0" style="max-width: 600px;" width="100%">
<tr>
<td align="center" bgcolor="#ffffff" style="padding: 40px 20px 20px 20px; border-radius: 4px 4px 0px 0px; color: #221F1F; font-family: 'Nunito Sans', Helvetica, Arial, sans-serif; font-size: 48px; font-weight: 400; letter-spacing: 2px; line-height: 48px;"
valign="top">
<h1 style="font-size: 42px; font-weight: 400; margin: 0;">
{{ args.title }}
</h1>
</td>
</tr>
@ -102,18 +132,19 @@
</tr>
<!-- BODY -->
<tr>
<td bgcolor="#f4f4f4" align="center" style="padding: 0px 10px 0px 10px;">
<td align="center" bgcolor="#f4f4f4" style="padding: 0px 10px 0px 10px;">
<!--[if (gte mso 9)|(IE)]>
<table align="center" border="0" cellspacing="0" cellpadding="0" width="600">
<tr>
<td align="center" valign="top" width="600">
<tr>
<td align="center" valign="top" width="600">
<![endif]-->
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="max-width: 600px;" >
<tr>
<td bgcolor="#ffffff" style="padding: 20px 30px 40px 30px; color: #666666; font-family: 'Nunito Sans', Helvetica, Arial, sans-serif; font-size: 18px; font-weight: 400; line-height: 25px; border-radius: 0px 0px 4px 4px;" >
{{ args.body }}
</td>
</tr>
<table border="0" cellpadding="0" cellspacing="0" style="max-width: 600px;" width="100%">
<tr>
<td bgcolor="#ffffff"
style="padding: 20px 30px 40px 30px; color: #666666; font-family: 'Nunito Sans', Helvetica, Arial, sans-serif; font-size: 18px; font-weight: 400; line-height: 25px; border-radius: 0px 0px 4px 4px;">
{{ args.body }}
</td>
</tr>
</table>
<!--[if (gte mso 9)|(IE)]>
</td>
@ -124,43 +155,50 @@
</tr>
<!-- FOOTER -->
<tr>
<td bgcolor="#f4f4f4" align="center" style="padding: 0px 10px 0px 10px;">
<td align="center" bgcolor="#f4f4f4" style="padding: 0px 10px 0px 10px;">
<!--[if (gte mso 9)|(IE)]>
<table align="center" border="0" cellspacing="0" cellpadding="0" width="600">
<tr>
<td align="center" valign="top" width="600">
<tr>
<td align="center" valign="top" width="600">
<![endif]-->
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="max-width: 600px;" >
<!-- NAVIGATION -->
<tr>
<td bgcolor="#f4f4f4" align="center" style="padding: 30px 30px 30px 30px; color: #666666; font-family: 'Nunito Sans', Helvetica, Arial, sans-serif; font-size: 14px; font-weight: 400; line-height: 18px;" >
<p style="margin: 0;">
<a href="{{ args.home_url }}" target="_blank" style="color: #221F1F; font-weight: 700;">Grant.io</a> -
<a href="{{ args.account_url }}" target="_blank" style="color: #221F1F; font-weight: 700;">Your Account</a> -
<a href="{{ args.email_settings_url }}" target="_blank" style="color: #221F1F; font-weight: 700;">Email Settings</a>
</p>
</td>
</tr>
<!-- UNSUBSCRIBE -->
<tr>
<td bgcolor="#f4f4f4" align="center" style="padding: 0px 30px 30px 30px; color: #666666; font-family: 'Nunito Sans', Helvetica, Arial, sans-serif; font-size: 14px; font-weight: 400; line-height: 18px;" >
<p style="margin: 0;">
Dont want anymore emails?
<a href="{{ args.unsubscribe_url }}" target="_blank" style="color: #221F1F; font-weight: 700;">
Click here to unsubscribe
</a>
.
</p>
</td>
</tr>
<!-- ADDRESS -->
<tr>
<td bgcolor="#f4f4f4" align="center" style="padding: 0px 30px 30px 30px; color: #AAAAAA; font-family: 'Nunito Sans', Helvetica, Arial, sans-serif; font-size: 12px; font-weight: 400; line-height: 18px;" >
<p style="margin: 0;">
Grant.io Inc, 123 Address Street, Somewhere, NY 11211
</p>
</td>
</tr>
<table border="0" cellpadding="0" cellspacing="0" style="max-width: 600px;" width="100%">
<!-- NAVIGATION -->
<tr>
<td align="center" bgcolor="#f4f4f4"
style="padding: 30px 30px 30px 30px; color: #666666; font-family: 'Nunito Sans', Helvetica, Arial, sans-serif; font-size: 14px; font-weight: 400; line-height: 18px;">
<p style="margin: 0;">
<a href="{{ args.home_url }}" style="color: #221F1F; font-weight: 700;" target="_blank">Grant.io</a>
-
<a href="{{ args.account_url }}" style="color: #221F1F; font-weight: 700;" target="_blank">Your
Account</a> -
<a href="{{ args.email_settings_url }}" style="color: #221F1F; font-weight: 700;"
target="_blank">Email Settings</a>
</p>
</td>
</tr>
<!-- UNSUBSCRIBE -->
<tr>
<td align="center" bgcolor="#f4f4f4"
style="padding: 0px 30px 30px 30px; color: #666666; font-family: 'Nunito Sans', Helvetica, Arial, sans-serif; font-size: 14px; font-weight: 400; line-height: 18px;">
<p style="margin: 0;">
Dont want anymore emails?
<a href="{{ args.unsubscribe_url }}" style="color: #221F1F; font-weight: 700;"
target="_blank">
Click here to unsubscribe
</a>
.
</p>
</td>
</tr>
<!-- ADDRESS -->
<tr>
<td align="center" bgcolor="#f4f4f4"
style="padding: 0px 30px 30px 30px; color: #AAAAAA; font-family: 'Nunito Sans', Helvetica, Arial, sans-serif; font-size: 12px; font-weight: 400; line-height: 18px;">
<p style="margin: 0;">
Grant.io Inc, 123 Address Street, Somewhere, NY 11211
</p>
</td>
</tr>
</table>
<!--[if (gte mso 9)|(IE)]>
</td>
@ -170,6 +208,6 @@
</td>
</tr>
</table>
</body>
</html>

View File

@ -1,3 +1,3 @@
from . import commands
from . import views
from . import models
from . import views

View File

@ -11,15 +11,15 @@ def delete_user(identity):
print(identity)
user = None
if str.isdigit(identity):
user = User.get_by_id(identity)
user = User.get_by_id(identity)
else:
user = User.get_by_email(identity)
user = User.get_by_email(identity)
if user:
db.session.delete(user)
db.session.commit()
click.echo(f'Succesfully deleted {user.display_name} (uid {user.id})')
db.session.delete(user)
db.session.commit()
click.echo(f'Succesfully deleted {user.display_name} (uid {user.id})')
else:
raise click.BadParameter('Invalid user identity. Must be a userid, '\
'account address, or email address of an '\
'existing user.')
raise click.BadParameter('Invalid user identity. Must be a userid, ' \
'account address, or email address of an ' \
'existing user.')

View File

@ -1,26 +1,24 @@
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
from grant.utils.exceptions import ValidationException
from grant.comment.models import Comment
from grant.email.models import EmailVerification, EmailRecovery
from grant.email.send import send_email
from grant.extensions import ma, db, security
from grant.utils.misc import make_url
from grant.utils.social import generate_social_url
from grant.utils.upload import extract_avatar_filename, construct_avatar_url
from grant.email.subscription_settings import (
get_default_email_subscriptions,
email_subscriptions_to_bits,
email_subscriptions_to_dict
)
from grant.extensions import ma, db, security
from grant.utils.misc import make_url
from grant.utils.social import generate_social_url
from grant.utils.upload import extract_avatar_filename, construct_avatar_url
from sqlalchemy.ext.hybrid import hybrid_property
def is_current_authed_user_id(user_id):
return current_user.is_authenticated and \
current_user.id == user_id
current_user.id == user_id
class RolesUsers(db.Model):
@ -233,6 +231,7 @@ class SocialMediaSchema(ma.Schema):
"service",
"username",
)
url = ma.Method("get_url")
def get_url(self, obj):

View File

@ -1,11 +1,10 @@
from flask import Blueprint, g, request
from flask_yoloapi import endpoint, parameter
from animal_case import keys_to_snake_case
from flask import Blueprint, g
from flask_yoloapi import endpoint, parameter
from grant.comment.models import Comment, user_comments_schema
from grant.email.models import EmailRecovery
from grant.proposal.models import (
Proposal,
proposals_schema,
proposal_team,
ProposalTeamInvite,
invites_with_proposal_schema,
@ -17,15 +16,13 @@ from grant.proposal.models import (
REJECTED,
CONFIRMED
)
from grant.utils.exceptions import ValidationException
from grant.utils.auth import requires_auth, requires_same_user_auth, get_authed_user
from grant.utils.upload import remove_avatar, sign_avatar_upload, AvatarException
from grant.utils.exceptions import ValidationException
from grant.utils.social import verify_social, get_social_login_url, VerifySocialException
from grant.email.models import EmailRecovery
from grant.utils.upload import remove_avatar, sign_avatar_upload, AvatarException
from .models import (
User,
UserSettings,
SocialMedia,
Avatar,
users_schema,
@ -48,10 +45,10 @@ def get_users(proposal_id):
else:
users = (
User.query
.join(proposal_team)
.join(Proposal)
.filter(proposal_team.c.proposal_id == proposal.id)
.all()
.join(proposal_team)
.join(Proposal)
.filter(proposal_team.c.proposal_id == proposal.id)
.all()
)
result = users_schema.dump(users)
return result

View File

@ -1,10 +1,9 @@
from functools import wraps
from hashlib import sha256
from flask import session
from grant.settings import SECRET_KEY, ADMIN_PASS_HASH
admin_auth = {
"username": "admin",
"password": ADMIN_PASS_HASH,

View File

@ -1,14 +1,10 @@
import ast
import json
from functools import wraps
import requests
from flask_security.core import current_user
from flask import request, g, jsonify
import sentry_sdk
from grant.settings import SECRET_KEY, BLOCKCHAIN_API_SECRET
from flask import request, g, jsonify
from flask_security.core import current_user
from grant.proposal.models import Proposal
from grant.settings import BLOCKCHAIN_API_SECRET
from grant.user.models import User
@ -27,6 +23,7 @@ def requires_auth(f):
"id": current_user.id,
}
return f(*args, **kwargs)
return decorated
@ -68,6 +65,7 @@ def requires_team_member_auth(f):
return requires_auth(decorated)
def internal_webhook(f):
@wraps(f)
def decorated(*args, **kwargs):
@ -79,4 +77,5 @@ def internal_webhook(f):
print(f'Internal webhook provided invalid "Authorization" header: {secret}')
return jsonify(message="Invalid 'Authorization' header"), 403
return f(*args, **kwargs)
return decorated

View File

@ -1,4 +1,5 @@
import math
def get_quarter_formatted(date):
return "Q" + str(math.ceil(date.date_created.month / 3.)) + " " + str(date.date_created.year)
return "Q" + str(math.ceil(date.date_created.month / 3.)) + " " + str(date.date_created.year)

View File

@ -1,2 +1,2 @@
class ValidationException(Exception):
pass
pass

View File

@ -1,8 +1,9 @@
import datetime
import time
import random
import string
import re
import string
import time
from grant.settings import SITE_URL
epoch = datetime.datetime.utcfromtimestamp(0)
@ -20,35 +21,41 @@ def dt_to_ms(dt):
def dt_to_unix(dt):
return int(time.mktime(dt.timetuple()))
def gen_random_code(length=32):
return ''.join(
[random.choice(string.ascii_letters + string.digits) for n in range(length)]
)
return ''.join(
[random.choice(string.ascii_letters + string.digits) for n in range(length)]
)
def make_url(path: str):
return f'{SITE_URL}{path}'
return f'{SITE_URL}{path}'
def is_email(email: str):
return bool(re.match(r"[^@]+@[^@]+\.[^@]+", email))
return bool(re.match(r"[^@]+@[^@]+\.[^@]+", email))
def from_zat(zat: int):
return zat / 100000000
return zat / 100000000
def to_zat(zec: float):
return zec * 100000000
return zec * 100000000
def make_preview(content: str, max_length: int):
truncated = False
truncated = False
# Show only the first line. Add ellipsis if there are more than two lines,
# even if first line isn't truncated.
preview = content.split('\n', 1)[0]
if len(preview) != len(content):
truncated = True
# Show only the first line. Add ellipsis if there are more than two lines,
# even if first line isn't truncated.
preview = content.split('\n', 1)[0]
if len(preview) != len(content):
truncated = True
# Truncate to max length
if len(preview) > max_length:
preview = preview[:max_length - 3]
truncated = True
# Truncate to max length
if len(preview) > max_length:
preview = preview[:max_length - 3]
truncated = True
return content + '...' if truncated else content
return content + '...' if truncated else content

View File

@ -1,7 +1,7 @@
import requests
import json
from grant.settings import BLOCKCHAIN_REST_API_URL, BLOCKCHAIN_API_SECRET
### REST API ###
def handle_res(res):
@ -10,18 +10,20 @@ def handle_res(res):
raise Exception('Blockchain API Error: {}'.format(j['error']))
return j['data']
def blockchain_get(path, params = None):
def blockchain_get(path, params=None):
res = requests.get(
f'{BLOCKCHAIN_REST_API_URL}{path}',
headers={ 'authorization': BLOCKCHAIN_API_SECRET },
headers={'authorization': BLOCKCHAIN_API_SECRET},
params=params,
)
return handle_res(res)
def blockchain_post(path, data = None):
def blockchain_post(path, data=None):
res = requests.post(
f'{BLOCKCHAIN_REST_API_URL}{path}',
headers={ 'authorization': BLOCKCHAIN_API_SECRET },
headers={'authorization': BLOCKCHAIN_API_SECRET},
json=data,
)
return handle_res(res)

View File

@ -1,10 +1,5 @@
import requests
import re
import json
import pprint
import abc
from requests_oauthlib import OAuth1Session, OAuth2Session
from flask import session
from grant.settings import (
SITE_URL,
GITHUB_CLIENT_ID,
@ -14,6 +9,7 @@ from grant.settings import (
LINKEDIN_CLIENT_ID,
LINKEDIN_CLIENT_SECRET
)
from requests_oauthlib import OAuth1Session, OAuth2Session
class VerifySocialException(Exception):

View File

@ -1,5 +1,6 @@
import re
import uuid
import boto3
from flask import current_app

View File

@ -1,8 +1,10 @@
from __future__ import with_statement
import logging
from logging.config import fileConfig
from alembic import context
from sqlalchemy import engine_from_config, pool
from logging.config import fileConfig
import logging
# this is the Alembic Config object, which provides
# access to the values within the .ini file in use.
@ -18,10 +20,12 @@ logger = logging.getLogger('alembic.env')
# from myapp import mymodel
# target_metadata = mymodel.Base.metadata
from flask import current_app
config.set_main_option('sqlalchemy.url',
current_app.config.get('SQLALCHEMY_DATABASE_URI'))
target_metadata = current_app.extensions['migrate'].db.metadata
# other values from the config, defined by the needs of env.py,
# can be acquired:
# my_important_option = config.get_main_option("my_important_option")
@ -81,6 +85,7 @@ def run_migrations_online():
finally:
connection.close()
if context.is_offline_mode():
run_migrations_offline()
else:

View File

@ -17,8 +17,8 @@ depends_on = ${repr(depends_on)}
def upgrade():
${upgrades if upgrades else "pass"}
${upgrades if upgrades else "pass"}
def downgrade():
${downgrades if downgrades else "pass"}
${downgrades if downgrades else "pass"}

View File

@ -5,9 +5,8 @@ Revises:
Create Date: 2019-01-09 16:35:34.349666
"""
from alembic import op
import sqlalchemy as sa
from alembic import op
# revision identifiers, used by Alembic.
revision = '4af29f8b2143'
@ -19,139 +18,139 @@ depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('proposal',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('date_created', sa.DateTime(), nullable=True),
sa.Column('status', sa.String(length=255), nullable=False),
sa.Column('title', sa.String(length=255), nullable=False),
sa.Column('brief', sa.String(length=255), nullable=False),
sa.Column('stage', sa.String(length=255), nullable=False),
sa.Column('content', sa.Text(), nullable=False),
sa.Column('category', sa.String(length=255), nullable=False),
sa.Column('date_approved', sa.DateTime(), nullable=True),
sa.Column('date_published', sa.DateTime(), nullable=True),
sa.Column('reject_reason', sa.String(length=255), nullable=True),
sa.Column('target', sa.String(length=255), nullable=False),
sa.Column('payout_address', sa.String(length=255), nullable=False),
sa.Column('deadline_duration', sa.Integer(), nullable=False),
sa.PrimaryKeyConstraint('id')
)
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('date_created', sa.DateTime(), nullable=True),
sa.Column('status', sa.String(length=255), nullable=False),
sa.Column('title', sa.String(length=255), nullable=False),
sa.Column('brief', sa.String(length=255), nullable=False),
sa.Column('stage', sa.String(length=255), nullable=False),
sa.Column('content', sa.Text(), nullable=False),
sa.Column('category', sa.String(length=255), nullable=False),
sa.Column('date_approved', sa.DateTime(), nullable=True),
sa.Column('date_published', sa.DateTime(), nullable=True),
sa.Column('reject_reason', sa.String(length=255), nullable=True),
sa.Column('target', sa.String(length=255), nullable=False),
sa.Column('payout_address', sa.String(length=255), nullable=False),
sa.Column('deadline_duration', sa.Integer(), nullable=False),
sa.PrimaryKeyConstraint('id')
)
op.create_table('role',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('name', sa.String(length=80), nullable=True),
sa.Column('description', sa.String(length=255), nullable=True),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('name')
)
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('name', sa.String(length=80), nullable=True),
sa.Column('description', sa.String(length=255), nullable=True),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('name')
)
op.create_table('user',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('email_address', sa.String(length=255), nullable=False),
sa.Column('password', sa.String(length=255), nullable=False),
sa.Column('display_name', sa.String(length=255), nullable=True),
sa.Column('title', sa.String(length=255), nullable=True),
sa.Column('active', sa.Boolean(), nullable=True),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('email_address')
)
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('email_address', sa.String(length=255), nullable=False),
sa.Column('password', sa.String(length=255), nullable=False),
sa.Column('display_name', sa.String(length=255), nullable=True),
sa.Column('title', sa.String(length=255), nullable=True),
sa.Column('active', sa.Boolean(), nullable=True),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('email_address')
)
op.create_table('avatar',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('image_url', sa.String(length=255), nullable=True),
sa.Column('user_id', sa.Integer(), nullable=True),
sa.ForeignKeyConstraint(['user_id'], ['user.id'], ),
sa.PrimaryKeyConstraint('id')
)
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('image_url', sa.String(length=255), nullable=True),
sa.Column('user_id', sa.Integer(), nullable=True),
sa.ForeignKeyConstraint(['user_id'], ['user.id'], ),
sa.PrimaryKeyConstraint('id')
)
op.create_table('comment',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('date_created', sa.DateTime(), nullable=True),
sa.Column('content', sa.Text(), nullable=False),
sa.Column('parent_comment_id', sa.Integer(), nullable=True),
sa.Column('proposal_id', sa.Integer(), nullable=False),
sa.Column('user_id', sa.Integer(), nullable=False),
sa.ForeignKeyConstraint(['parent_comment_id'], ['comment.id'], ),
sa.ForeignKeyConstraint(['proposal_id'], ['proposal.id'], ),
sa.ForeignKeyConstraint(['user_id'], ['user.id'], ),
sa.PrimaryKeyConstraint('id')
)
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('date_created', sa.DateTime(), nullable=True),
sa.Column('content', sa.Text(), nullable=False),
sa.Column('parent_comment_id', sa.Integer(), nullable=True),
sa.Column('proposal_id', sa.Integer(), nullable=False),
sa.Column('user_id', sa.Integer(), nullable=False),
sa.ForeignKeyConstraint(['parent_comment_id'], ['comment.id'], ),
sa.ForeignKeyConstraint(['proposal_id'], ['proposal.id'], ),
sa.ForeignKeyConstraint(['user_id'], ['user.id'], ),
sa.PrimaryKeyConstraint('id')
)
op.create_table('email_recovery',
sa.Column('user_id', sa.Integer(), nullable=False),
sa.Column('code', sa.String(length=255), nullable=False),
sa.Column('date_created', sa.DateTime(), nullable=True),
sa.ForeignKeyConstraint(['user_id'], ['user.id'], ),
sa.PrimaryKeyConstraint('user_id'),
sa.UniqueConstraint('code')
)
sa.Column('user_id', sa.Integer(), nullable=False),
sa.Column('code', sa.String(length=255), nullable=False),
sa.Column('date_created', sa.DateTime(), nullable=True),
sa.ForeignKeyConstraint(['user_id'], ['user.id'], ),
sa.PrimaryKeyConstraint('user_id'),
sa.UniqueConstraint('code')
)
op.create_table('email_verification',
sa.Column('user_id', sa.Integer(), nullable=False),
sa.Column('code', sa.String(length=255), nullable=False),
sa.Column('has_verified', sa.Boolean(), nullable=True),
sa.ForeignKeyConstraint(['user_id'], ['user.id'], ),
sa.PrimaryKeyConstraint('user_id'),
sa.UniqueConstraint('code')
)
sa.Column('user_id', sa.Integer(), nullable=False),
sa.Column('code', sa.String(length=255), nullable=False),
sa.Column('has_verified', sa.Boolean(), nullable=True),
sa.ForeignKeyConstraint(['user_id'], ['user.id'], ),
sa.PrimaryKeyConstraint('user_id'),
sa.UniqueConstraint('code')
)
op.create_table('milestone',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('date_created', sa.DateTime(), nullable=False),
sa.Column('title', sa.String(length=255), nullable=False),
sa.Column('content', sa.Text(), nullable=False),
sa.Column('stage', sa.String(length=255), nullable=False),
sa.Column('payout_percent', sa.String(length=255), nullable=False),
sa.Column('immediate_payout', sa.Boolean(), nullable=True),
sa.Column('date_estimated', sa.DateTime(), nullable=False),
sa.Column('proposal_id', sa.Integer(), nullable=False),
sa.ForeignKeyConstraint(['proposal_id'], ['proposal.id'], ),
sa.PrimaryKeyConstraint('id')
)
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('date_created', sa.DateTime(), nullable=False),
sa.Column('title', sa.String(length=255), nullable=False),
sa.Column('content', sa.Text(), nullable=False),
sa.Column('stage', sa.String(length=255), nullable=False),
sa.Column('payout_percent', sa.String(length=255), nullable=False),
sa.Column('immediate_payout', sa.Boolean(), nullable=True),
sa.Column('date_estimated', sa.DateTime(), nullable=False),
sa.Column('proposal_id', sa.Integer(), nullable=False),
sa.ForeignKeyConstraint(['proposal_id'], ['proposal.id'], ),
sa.PrimaryKeyConstraint('id')
)
op.create_table('proposal_contribution',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('date_created', sa.DateTime(), nullable=False),
sa.Column('proposal_id', sa.Integer(), nullable=False),
sa.Column('user_id', sa.Integer(), nullable=True),
sa.Column('status', sa.String(length=255), nullable=False),
sa.Column('amount', sa.String(length=255), nullable=False),
sa.Column('tx_id', sa.String(length=255), nullable=True),
sa.ForeignKeyConstraint(['proposal_id'], ['proposal.id'], ),
sa.ForeignKeyConstraint(['user_id'], ['user.id'], ),
sa.PrimaryKeyConstraint('id')
)
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('date_created', sa.DateTime(), nullable=False),
sa.Column('proposal_id', sa.Integer(), nullable=False),
sa.Column('user_id', sa.Integer(), nullable=True),
sa.Column('status', sa.String(length=255), nullable=False),
sa.Column('amount', sa.String(length=255), nullable=False),
sa.Column('tx_id', sa.String(length=255), nullable=True),
sa.ForeignKeyConstraint(['proposal_id'], ['proposal.id'], ),
sa.ForeignKeyConstraint(['user_id'], ['user.id'], ),
sa.PrimaryKeyConstraint('id')
)
op.create_table('proposal_team',
sa.Column('user_id', sa.Integer(), nullable=True),
sa.Column('proposal_id', sa.Integer(), nullable=True),
sa.ForeignKeyConstraint(['proposal_id'], ['proposal.id'], ),
sa.ForeignKeyConstraint(['user_id'], ['user.id'], )
)
sa.Column('user_id', sa.Integer(), nullable=True),
sa.Column('proposal_id', sa.Integer(), nullable=True),
sa.ForeignKeyConstraint(['proposal_id'], ['proposal.id'], ),
sa.ForeignKeyConstraint(['user_id'], ['user.id'], )
)
op.create_table('proposal_team_invite',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('date_created', sa.DateTime(), nullable=True),
sa.Column('proposal_id', sa.Integer(), nullable=False),
sa.Column('address', sa.String(length=255), nullable=False),
sa.Column('accepted', sa.Boolean(), nullable=True),
sa.ForeignKeyConstraint(['proposal_id'], ['proposal.id'], ),
sa.PrimaryKeyConstraint('id')
)
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('date_created', sa.DateTime(), nullable=True),
sa.Column('proposal_id', sa.Integer(), nullable=False),
sa.Column('address', sa.String(length=255), nullable=False),
sa.Column('accepted', sa.Boolean(), nullable=True),
sa.ForeignKeyConstraint(['proposal_id'], ['proposal.id'], ),
sa.PrimaryKeyConstraint('id')
)
op.create_table('proposal_update',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('date_created', sa.DateTime(), nullable=True),
sa.Column('proposal_id', sa.Integer(), nullable=False),
sa.Column('title', sa.String(length=255), nullable=False),
sa.Column('content', sa.Text(), nullable=False),
sa.ForeignKeyConstraint(['proposal_id'], ['proposal.id'], ),
sa.PrimaryKeyConstraint('id')
)
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('date_created', sa.DateTime(), nullable=True),
sa.Column('proposal_id', sa.Integer(), nullable=False),
sa.Column('title', sa.String(length=255), nullable=False),
sa.Column('content', sa.Text(), nullable=False),
sa.ForeignKeyConstraint(['proposal_id'], ['proposal.id'], ),
sa.PrimaryKeyConstraint('id')
)
op.create_table('roles_users',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('user_id', sa.Integer(), nullable=True),
sa.Column('role_id', sa.Integer(), nullable=True),
sa.ForeignKeyConstraint(['role_id'], ['role.id'], ),
sa.ForeignKeyConstraint(['user_id'], ['user.id'], ),
sa.PrimaryKeyConstraint('id')
)
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('user_id', sa.Integer(), nullable=True),
sa.Column('role_id', sa.Integer(), nullable=True),
sa.ForeignKeyConstraint(['role_id'], ['role.id'], ),
sa.ForeignKeyConstraint(['user_id'], ['user.id'], ),
sa.PrimaryKeyConstraint('id')
)
op.create_table('social_media',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('service', sa.String(length=255), nullable=False),
sa.Column('username', sa.String(length=255), nullable=False),
sa.Column('user_id', sa.Integer(), nullable=False),
sa.ForeignKeyConstraint(['user_id'], ['user.id'], ),
sa.PrimaryKeyConstraint('id')
)
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('service', sa.String(length=255), nullable=False),
sa.Column('username', sa.String(length=255), nullable=False),
sa.Column('user_id', sa.Integer(), nullable=False),
sa.ForeignKeyConstraint(['user_id'], ['user.id'], ),
sa.PrimaryKeyConstraint('id')
)
# ### end Alembic commands ###

View File

@ -5,9 +5,8 @@ Revises: 4af29f8b2143
Create Date: 2019-01-10 14:44:42.536248
"""
from alembic import op
import sqlalchemy as sa
from alembic import op
# revision identifiers, used by Alembic.
revision = 'e0d970ed6500'
@ -19,12 +18,12 @@ depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('user_settings',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('email_subscriptions', sa.Integer(), nullable=True),
sa.Column('user_id', sa.Integer(), nullable=False),
sa.ForeignKeyConstraint(['user_id'], ['user.id'], ),
sa.PrimaryKeyConstraint('id')
)
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('email_subscriptions', sa.Integer(), nullable=True),
sa.Column('user_id', sa.Integer(), nullable=False),
sa.ForeignKeyConstraint(['user_id'], ['user.id'], ),
sa.PrimaryKeyConstraint('id')
)
# ### end Alembic commands ###

View File

@ -1,3 +1,3 @@
[flake8]
ignore = D401
max-line-length=120
max-line-length = 120

View File

@ -1,10 +1,8 @@
import json
from grant.proposal.models import APPROVED, REJECTED
from grant.utils.admin import generate_admin_password_hash
from mock import patch
from grant.utils.admin import generate_admin_password_hash
from grant.proposal.models import Proposal, APPROVED, REJECTED, PENDING, DRAFT
from ..config import BaseProposalCreatorConfig
from ..test_data import test_proposal, test_user
plaintext_mock_password = "p4ssw0rd"
mock_admin_auth = {

View File

@ -1,10 +1,11 @@
import json
from flask_testing import TestCase
from flask_testing import TestCase
from grant.app import create_app
from grant.user.models import User, SocialMedia, db, Avatar
from grant.proposal.models import Proposal
from .test_data import test_user, test_other_user, test_proposal, message
from grant.user.models import User, SocialMedia, db, Avatar
from .test_data import test_user, test_other_user, test_proposal
class BaseTestConfig(TestCase):
@ -29,7 +30,7 @@ class BaseTestConfig(TestCase):
"""
message = message or 'HTTP Status %s expected but got %s. Response json: %s' \
% (status_code, response.status_code, response.json or response.data)
% (status_code, response.status_code, response.json or response.data)
self.assertEqual(response.status_code, status_code, message)
assert_status = assertStatus

View File

@ -1,16 +1,11 @@
import copy
import json
from datetime import datetime, timedelta
from animal_case import animalify
from grant.proposal.models import Proposal
from grant.user.models import User, user_schema, db
from grant.email.subscription_settings import get_default_email_subscriptions
from grant.email.models import EmailVerification
from mock import patch, Mock
from grant.user.models import db
from mock import patch
from ..config import BaseUserConfig
from ..test_data import test_team, test_proposal, test_user
from ..test_data import test_user
class TestEmailAPI(BaseUserConfig):

View File

@ -1,9 +1,4 @@
import json
from mock import patch
import pytest
from ..config import BaseTestConfig
from grant.utils.exceptions import ValidationException
from grant.email.subscription_settings import (
email_subscriptions_to_bits,
email_subscriptions_to_dict,
@ -12,6 +7,9 @@ from grant.email.subscription_settings import (
is_subscribed,
EmailSubscription
)
from grant.utils.exceptions import ValidationException
from ..config import BaseTestConfig
test_dict = get_default_email_subscriptions()

View File

@ -26,4 +26,3 @@ class UserFactory(BaseFactory):
class Meta:
"""Factory configuration."""

View File

@ -2,6 +2,8 @@ def mock_request(response):
def mock_request_func(*args, **kwargs):
class MockResponse:
def json(self):
return { 'data': response }
return {'data': response}
return MockResponse()
return mock_request_func

View File

@ -1,9 +1,9 @@
import json
from mock import patch
from grant.proposal.models import Proposal, APPROVED, PENDING, DRAFT
from grant.proposal.models import Proposal, PENDING
from ..config import BaseProposalCreatorConfig
from ..test_data import test_proposal, test_user
from ..test_data import test_proposal
class TestProposalAPI(BaseProposalCreatorConfig):

View File

@ -1,7 +1,7 @@
import json
from mock import patch
from grant.proposal.models import Proposal, db
from ..config import BaseUserConfig
from ..test_data import test_comment, test_reply

View File

@ -2,10 +2,8 @@ import json
from mock import patch
from grant.proposal.models import Proposal
from grant.user.models import SocialMedia, Avatar
from grant.utils.requests import blockchain_get
from ..config import BaseUserConfig
from ..test_data import test_proposal, test_user
from ..test_data import test_proposal
from ..mocks import mock_request
mock_contribution_addresses = mock_request({
@ -14,6 +12,7 @@ mock_contribution_addresses = mock_request({
'memo': '123',
})
class TestProposalContributionAPI(BaseUserConfig):
@patch('requests.get', side_effect=mock_contribution_addresses)
def test_create_proposal_contribution(self, mock_blockchain_get):

View File

@ -1,9 +1,8 @@
import json
from mock import patch
from grant.proposal.models import Proposal, ProposalTeamInvite, db
from grant.proposal.models import ProposalTeamInvite, db
from ..config import BaseProposalCreatorConfig
from ..test_data import test_proposal, test_user
class TestProposalInviteAPI(BaseProposalCreatorConfig):

View File

@ -1,7 +1,5 @@
import json
from mock import patch
from grant.proposal.models import Proposal, db
from ..config import BaseProposalCreatorConfig
test_update = {

View File

@ -1,4 +1,3 @@
import json
import random
from grant.proposal.models import CATEGORIES

View File

@ -1,4 +1,5 @@
"""Sample test for CI"""
def test_runs():
assert True
assert True

View File

@ -1,10 +1,7 @@
import json
from mock import patch
from grant.proposal.models import Proposal, ProposalTeamInvite, db
from grant.user.models import SocialMedia, Avatar
from grant.proposal.models import ProposalTeamInvite, db
from ..config import BaseProposalCreatorConfig
from ..test_data import test_proposal, test_user
class TestUserInviteAPI(BaseProposalCreatorConfig):

View File

@ -3,13 +3,12 @@ import json
from datetime import datetime, timedelta
from animal_case import animalify
from grant.proposal.models import Proposal
from grant.user.models import User, user_schema, db
from grant.email.subscription_settings import get_default_email_subscriptions
from mock import patch, Mock
from grant.user.models import User, user_schema, db
from mock import patch
from ..config import BaseUserConfig
from ..test_data import test_team, test_proposal, test_user
from ..test_data import test_user
class TestUserAPI(BaseUserConfig):