Skip to content
This repository has been archived by the owner on Feb 8, 2018. It is now read-only.

Commit

Permalink
Checkpoint 2
Browse files Browse the repository at this point in the history
  • Loading branch information
chadwhitacre committed Apr 28, 2017
1 parent cf55953 commit 771c40f
Show file tree
Hide file tree
Showing 46 changed files with 1,278 additions and 371 deletions.
72 changes: 72 additions & 0 deletions emails/verification-notice.spt
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
{{ _("New activity on your account") }}

[---] text/html
{% if new_email_verified %}
{{ ngettext( "We are connecting the {package_name} npm package to the {username} account on "
"Gratipay. This is a notification sent to {email_address} because that is the "
"primary email address we have on file."
, "We are connecting {n} npm packages to the {username} account on Gratipay. This is "
"a notification sent to {email_address} because that is the primary email address "
"we have on file."
, n=npackages
, package_name=('<b>{}</b>'|safe).format(package_name)
, username=('<b><a href="https://gratipay.com/{0}/">{0}</a></b>'|safe).format(username)
, email_address=('<b>{}</b>'|safe).format(email)
) }}
{% elif npackages > 0 %}
{{ ngettext( "We are connecting {email_address} and the {package_name} npm package to the "
"{username} account on Gratipay. This is a notification sent to {email_address_2} "
"because that is the primary email address we have on file."
, "We are connecting {email_address} and {n} npm packages to the {username} account on "
"Gratipay. This is a notification sent to {email_address_2} because that is the "
"primary email address we have on file."
, n=npackages
, package_name=('<b>{}</b>'|safe).format(package_name)
, username=('<b><a href="https://gratipay.com/{0}/">{0}</a></b>'|safe).format(username)
, email_address=('<b>{}</b>'|safe).format(new_email)
, email_address_2=('<b>{}</b>'|safe).format(email)
) }}
{% else %}
{{ _( "We are connecting {email_address} to the {username} account on Gratipay. This is a "
"notification sent to {email_address_2} because that is the primary email address we have "
"on file."
, username=('<b><a href="https://gratipay.com/{0}/">{0}</a></b>'|safe).format(username)
, email_address=('<b>{}</b>'|safe).format(new_email)
, email_address_2=('<b>{}</b>'|safe).format(email)
) }}
{% endif %}
[---] text/plain
{% if new_email_verified %}
{{ ngettext( "We are connecting the {package_name} npm package to the {username} account on "
"Gratipay. This is a notification sent to {email_address} because that is the "
"primary email address we have on file."
, "We are connecting {n} npm packages to the {username} account on Gratipay. This is "
"a notification sent to {email_address} because that is the primary email address "
"we have on file."
, n=npackages
, package_name=package_name
, username=username
, email_address=email
) }}
{% elif npackages > 0 %}
{{ ngettext( "We are connecting {email_address} and the {package_name} npm package to the "
"{username} account on Gratipay. This is a notification sent to {email_address_2} "
"because that is the primary email address we have on file."
, "We are connecting {email_address} and {n} npm packages to the {username} account on "
"Gratipay. This is a notification sent to {email_address_2} because that is the "
"primary email address we have on file."
, n=npackages
, package_name=package_name
, username=username
, email_address=new_email
, email_address_2=email
) }}
{% else %}
{{ _( "We are connecting {email_address} to the {username} account on Gratipay. This is a "
"notification sent to {email_address_2} because that is the primary email address we have "
"on file."
, username=username
, email_address=new_email
, email_address_2=email
) }}
{% endif %}
57 changes: 52 additions & 5 deletions emails/verification.spt
Original file line number Diff line number Diff line change
@@ -1,16 +1,63 @@
{{ _("Connect to {0} on Gratipay?", username) }}

[---] text/html
{{ _("We've received a request to connect {0} to the {1} account on Gratipay. Sound familiar?",
('<b>%s</b>'|safe) % email,
('<b><a href="https://gratipay.com/~{0}/">{0}</a></b>'|safe).format(username)) }}
{% if new_email_verified %}
{{ ngettext( "We've received a request to connect the {package_name} npm package to the "
"{username} account on Gratipay. Sound familiar?"
, "We've received a request to connect {n} npm packages to the {username} account "
"on Gratipay. Sound familiar?"
, n=npackages
, package_name=('<b>{}</b>'|safe).format(package_name)
, username=('<b><a href="https://gratipay.com/~{0}/">{0}</a></b>'|safe).format(username)
) }}
{% elif npackages > 0 %}
{{ ngettext( "We've received a request to connect {email_address} and the {package_name} npm "
"package to the {username} account on Gratipay. Sound familiar?"
, "We've received a request to connect {email_address} and {n} npm packages to the "
"{username} account on Gratipay. Sound familiar?"
, n=npackages
, package_name=('<b>{}</b>'|safe).format(package_name)
, email_address=('<b>{}</b>'|safe).format(new_email)
, username=('<b><a href="https://gratipay.com/~{0}/">{0}</a></b>'|safe).format(username)
) }}
{% else %}
{{ _( "We've received a request to connect {email_address} to the {username} account on Gratipay. "
"Sound familiar?"
, email_address=('<b>{}</b>'|safe).format(new_email)
, username=('<b><a href="https://gratipay.com/~{0}/">{0}</a></b>'|safe).format(username)
) }}
{% endif %}
<br>
<br>
<a href="{{ link }}" style="{{ button_style }}">{{ _("Yes, proceed!") }}</a>

[---] text/plain
{{ _("We've received a request to connect {0} to the {1} account on Gratipay. Sound familiar?",
email, username) }}
{% if new_email_verified %}
{{ ngettext( "We've received a request to connect the {package_name} npm package to the "
"{username} account on Gratipay. Sound familiar?"
, "We've received a request to connect {n} npm packages to the {username} account "
"on Gratipay. Sound familiar?"
, n=npackages
, package_name=package_name
, username=username
) }}
{% elif npackages > 0 %}
{{ ngettext( "We've received a request to connect {email_address} and the {package_name} npm "
"package to the {username} account on Gratipay. Sound familiar?"
, "We've received a request to connect {email_address} and {n} npm packages to the "
"{username} account on Gratipay. Sound familiar?"
, n=npackages
, package_name=package_name
, email_address=new_email
, username=username
) }}
{% else %}
{{ _( "We've received a request to connect {email_address} to the {username} account on Gratipay. "
"Sound familiar?"
, email_address=new_email
, username=username
) }}
{% endif %}

{{ _("Follow this link to finish connecting your email:") }}

Expand Down
13 changes: 0 additions & 13 deletions emails/verification_notice.spt

This file was deleted.

5 changes: 2 additions & 3 deletions error.spt
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@ from __future__ import absolute_import, division, print_function, unicode_litera

from aspen.http import status_strings

from gratipay.utils import LazyResponse
from gratipay.utils.i18n import HTTP_ERRORS
from gratipay.utils.i18n import HTTP_ERRORS, LocalizedErrorResponse

[----------------------------------------]

Expand All @@ -19,7 +18,7 @@ try:
except Exception as e:
website.tell_sentry(e, state)

if isinstance(response, LazyResponse):
if isinstance(response, LocalizedErrorResponse):
response.render_body(state)
err = response.body
if code == 500 and not err:
Expand Down
6 changes: 3 additions & 3 deletions gratipay/elsewhere/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
from requests_oauthlib import OAuth1Session, OAuth2Session

from gratipay.elsewhere._extractors import not_available
from gratipay.utils import LazyResponse
from gratipay.utils.i18n import LocalizedErrorResponse


ACTIONS = {'opt-in', 'connect'}
Expand Down Expand Up @@ -137,13 +137,13 @@ def msg(_, to_age):
return _("You've consumed your quota of requests, you can try again in {0}.", to_age(reset))
else:
return _("You're making requests too fast, please try again later.")
raise LazyResponse(status, msg)
raise LocalizedErrorResponse(status, msg)
if status != 200:
log('{} api responded with {}:\n{}'.format(self.name, status, response.text)
, level=logging.ERROR)
msg = lambda _: _("{0} returned an error, please try again later.",
self.display_name)
raise LazyResponse(502, msg)
raise LocalizedErrorResponse(502, msg)

return response

Expand Down
32 changes: 21 additions & 11 deletions gratipay/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

from __future__ import print_function, unicode_literals

from aspen import Response
from gratipay.utils.i18n import LocalizedErrorResponse


class ProblemChangingUsername(Exception):
Expand All @@ -27,28 +27,37 @@ class UsernameAlreadyTaken(ProblemChangingUsername):
msg = "The username '{}' is already taken."


class ProblemChangingEmail(Response):
def __init__(self, *args):
Response.__init__(self, 400, self.msg.format(*args))
class ProblemChangingEmail(LocalizedErrorResponse):
pass

class EmailAlreadyVerified(ProblemChangingEmail):
msg = "{} is already verified for this Gratipay account."
def lazy_body(self, _):
return _("You have already added and verified that address.")

class EmailTaken(ProblemChangingEmail):
msg = "{} is already connected to a different Gratipay account."
def lazy_body(self, _):
return _("That address is already linked to a different Gratipay account.")

class CannotRemovePrimaryEmail(ProblemChangingEmail):
msg = "You cannot remove your primary email address."
def lazy_body(self, _):
return _("You cannot remove your primary email address.")

class EmailNotOnFile(ProblemChangingEmail):
def lazy_body(self, _):
return _("That email address is not on file for this package.")

class EmailNotVerified(ProblemChangingEmail):
msg = "The email address '{}' is not verified."
def lazy_body(self, _):
return _("That email address is not verified.")

class TooManyEmailAddresses(ProblemChangingEmail):
msg = "You've reached the maximum number of email addresses we allow."
def lazy_body(self, _):
return _("You've reached the maximum number of email addresses we allow.")


class Throttled(Exception):
msg = "You've initiated too many emails too quickly. Please try again in a minute or two."
class Throttled(LocalizedErrorResponse):
def lazy_body(self, _):
return _("You've initiated too many emails too quickly. Please try again in a minute or two.")


class ProblemChangingNumber(Exception):
Expand Down Expand Up @@ -78,3 +87,4 @@ def __str__(self):
return "Negative balance not allowed in this context."

class NotWhitelisted(Exception): pass
class NoPackages(Exception): pass
63 changes: 59 additions & 4 deletions gratipay/models/package/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
# -*- coding: utf-8 -*-
from __future__ import absolute_import, division, print_function, unicode_literals

import uuid

from gratipay.models.team import Team
from postgres.orm import Model


Expand All @@ -10,6 +13,14 @@

class Package(Model):
"""Represent a gratipackage. :-)
Packages are entities on open source package managers; `npm
<https://www.npmjs.com/>`_ is the only one we support so far. Each package
on npm has a page on Gratipay with an URL of the form ``/on/npm/foo/``.
Packages can be claimed by Gratipay participants, at which point we create
a :py:class:`~gratipay.models.team.Team` for them under the hood so they
can start accepting payments.
"""

typname = 'packages'
Expand All @@ -25,6 +36,13 @@ def __ne__(self, other):
return self.id != other.id


@property
def url_path(self):
"""The path part of the URL for this package on Gratipay.
"""
return '/on/{}/{}/'.format(self.package_manager, self.name)


# Constructors
# ============

Expand All @@ -42,8 +60,45 @@ def from_names(cls, package_manager, name):
"WHERE package_manager=%s and name=%s", (package_manager, name))


# Emails
# ======
@property
def team(self):
"""A computed attribute, the :py:class:`~gratipay.models.team.Team`
linked to this package if there is one, otherwise ``None``. Makes a
database call.
"""
return self._load_team(self.db)


def ensure_team(self, cursor, owner):
"""Given a db cursor and :py:class:`Participant`, insert into ``teams`` if need be.
"""
if self._load_team(cursor):
return

def slug_options():
yield self.name
for i in range(1, 10):
yield '{}-{}'.format(self.name, i)
yield uuid.uuid4().hex

for slug in slug_options():
if cursor.one('SELECT count(*) FROM teams WHERE slug=%s', (slug,)) > 0:
continue
team = Team.insert( slug=slug
, slug_lower=slug.lower()
, name=slug
, homepage=''
, product_or_service=''
, owner=owner
, _cursor=cursor
)
cursor.run('INSERT INTO teams_to_packages (team_id, package_id) '
'VALUES (%s, %s)', (team.id, self.id))
break


def send_confirmation_email(self, address):
pass
def _load_team(self, cursor):
return cursor.one( 'SELECT t.*::teams FROM teams t WHERE t.id='
'(SELECT team_id FROM teams_to_packages tp WHERE tp.package_id=%s)'
, (self.id,)
)
Loading

0 comments on commit 771c40f

Please sign in to comment.