Skip to content

Commit

Permalink
Merge pull request #201 from liberapay/team-avatars
Browse files Browse the repository at this point in the history
Team avatars
  • Loading branch information
Changaco committed Mar 4, 2016
2 parents 4f7472a + 806ba5c commit e7ebd6e
Show file tree
Hide file tree
Showing 15 changed files with 179 additions and 51 deletions.
1 change: 1 addition & 0 deletions liberapay/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ def __str__(self):
"-_")

AVATAR_QUERY = '?s=160&default=retro'
AVATAR_SOURCES = 'libravatar bitbucket facebook github google twitter'.split()

BALANCE_MAX = Decimal("1000")

Expand Down
2 changes: 1 addition & 1 deletion liberapay/elsewhere/_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ def extract_user_info(self, info):
if r.email and not gravatar_id:
gravatar_id = hashlib.md5(r.email.strip().lower()).hexdigest()
if gravatar_id:
r.avatar_url = 'https://secure.gravatar.com/avatar/'+gravatar_id
r.avatar_url = 'https://seccdn.libravatar.org/avatar/'+gravatar_id
r.is_team = self.x_is_team(r, info, False)
r.extra_info = info
return r
Expand Down
3 changes: 2 additions & 1 deletion liberapay/models/account_elsewhere.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,8 @@ def upsert(cls, i):
scheme, netloc, path, query, fragment = urlsplit(i.avatar_url)
fragment = ''
if netloc.endswith('githubusercontent.com') or \
netloc.endswith('gravatar.com'):
netloc.endswith('gravatar.com') or \
netloc.endswith('libravatar.org'):
query = AVATAR_QUERY
i.avatar_url = urlunsplit((scheme, netloc, path, query, fragment))

Expand Down
88 changes: 62 additions & 26 deletions liberapay/models/participant.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ def make_active(cls, username, kind, password, cursor=None):
p.change_username(username, c)
return p

def make_team(self, name):
def make_team(self, name, email=None):
with self.db.get_cursor() as c:
t = c.one("""
INSERT INTO participants
Expand All @@ -129,6 +129,8 @@ def make_team(self, name):
""")
t.change_username(name, c)
t.add_member(self, c)
if email:
t.add_email(email)
return t

@classmethod
Expand Down Expand Up @@ -580,15 +582,7 @@ def add_email(self, email, cursor=None):
self.send_email('verification_notice', new_email=email)
return 2
else:
gravatar_id = md5(email.strip().lower()).hexdigest()
gravatar_url = 'https://secure.gravatar.com/avatar/'+gravatar_id
gravatar_url += AVATAR_QUERY
(cursor or self.db).run("""
UPDATE participants
SET avatar_url = %s
WHERE id = %s
""", (gravatar_url, self.id))
self.set_attributes(avatar_url=gravatar_url)
self.update_avatar(cursor=cursor)

return 1

Expand All @@ -604,6 +598,7 @@ def update_email(self, email):
WHERE id=%(id)s
""", locals())
self.set_attributes(email=email)
self.update_avatar()

def verify_email(self, email, nonce):
if '' in (email, nonce):
Expand Down Expand Up @@ -649,6 +644,14 @@ def get_emails(self):
ORDER BY id
""", (self.id,))

def get_any_email(self, cursor=None):
return (cursor or self.db).one("""
SELECT address
FROM emails
WHERE participant=%s
LIMIT 1
""", (self.id,))

def remove_email(self, address):
if address == self.email:
raise CannotRemovePrimaryEmail()
Expand Down Expand Up @@ -960,23 +963,52 @@ def change_username(self, suggested, cursor=None):

return suggested

def update_avatar(self):
if self.status != 'stub':
def update_avatar(self, src=None, cursor=None):
if self.status == 'stub':
assert src is None

platform, key = src.split(':', 1) if src else (None, None)
email = self.avatar_email or self.email or self.get_any_email(cursor)

if platform == 'libravatar' or platform is None and email:
if not email:
return
avatar_id = md5(email.strip().lower()).hexdigest()
avatar_url = 'https://seccdn.libravatar.org/avatar/'+avatar_id
avatar_url += AVATAR_QUERY

elif platform is None:
avatar_url = (cursor or self.db).one("""
SELECT avatar_url
FROM elsewhere
WHERE participant = %s
ORDER BY platform = 'github' DESC,
avatar_url LIKE '%%libravatar.org%%' DESC,
avatar_url LIKE '%%gravatar.com%%' DESC
LIMIT 1
""", (self.id,))

else:
avatar_url = (cursor or self.db).one("""
SELECT avatar_url
FROM elsewhere
WHERE participant = %s
AND platform = %s
-- AND user_id = %%s -- not implemented yet
""", (self.id, platform))

if not avatar_url:
return
avatar_url = self.db.run("""
UPDATE participants p
SET avatar_url = (
SELECT avatar_url
FROM elsewhere
WHERE participant = p.id
ORDER BY platform = 'github' DESC,
avatar_url LIKE '%%gravatar.com%%' DESC
LIMIT 1
)
WHERE p.id = %s
RETURNING avatar_url
""", (self.id,))
self.set_attributes(avatar_url=avatar_url)

(cursor or self.db).run("""
UPDATE participants
SET avatar_url = %s
, avatar_src = %s
WHERE id = %s
""", (avatar_url, src, self.id))
self.set_attributes(avatar_src=src, avatar_url=avatar_url)

return avatar_url

def update_goal(self, goal, cursor=None):
with self.db.get_cursor(cursor) as c:
Expand Down Expand Up @@ -1609,6 +1641,10 @@ def to_dict(self, details=False, inquirer=None):
def path(self, path):
return '/%s/%s' % (self.username, path)

@property
def is_person(self):
return self.kind in ('individual', 'organization')


class NeedConfirmation(Exception):
"""Represent the case where we need user confirmation during a merge.
Expand Down
21 changes: 21 additions & 0 deletions sql/branch.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
BEGIN;

UPDATE elsewhere
SET avatar_url = regexp_replace(avatar_url,
'^https://secure\.gravatar\.com/',
'https://seccdn.libravatar.org/'
)
WHERE avatar_url LIKE '%//secure.gravatar.com/%';

UPDATE participants
SET avatar_url = regexp_replace(avatar_url,
'^https://secure\.gravatar\.com/',
'https://seccdn.libravatar.org/'
)
WHERE avatar_url LIKE '%//secure.gravatar.com/%';

ALTER TABLE participants ADD COLUMN avatar_src text;

ALTER TABLE participants ADD COLUMN avatar_email text;

END;
2 changes: 1 addition & 1 deletion templates/connected-accounts.html
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
% from 'templates/auth.html' import auth_button with context
% from 'templates/elsewhere.html' import account_elsewhere with context

% set accounts = participant.get_accounts_elsewhere()
% set accounts = accounts or participant.get_accounts_elsewhere()
% if accounts or edit
<h3 class="banderole default">{{ _("Accounts Elsewhere") }}</h3>
<div class="accounts">
Expand Down
15 changes: 7 additions & 8 deletions templates/create-team.html
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
<form action="/about/teams" method="POST">
<form class="form-inline" action="/about/teams" method="POST">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}" />
<div class="input-group">
<input class="form-control" name="name" size=30
placeholder="{{ _('Name of the team') }}" />
<div class="input-group-btn">
<button class="btn btn-success">{{ _("Create") }}</button>
</div>
</div>

<input class="form-control" name="name" size=30
placeholder="{{ _('Name of the team') }}" />
<input class="form-control" name="email" type="email" size=30
placeholder="{{ _('Email of the team (optional)') }}" />
<button class="btn btn-success">{{ _("Create") }}</button>
</form>
8 changes: 2 additions & 6 deletions templates/profile-box.html
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,8 @@
% endif

% if edit
% if participant.kind == 'group'
{{ _("Sorry, teams can't change their avatar yet.") }}
% else
<a class="btn btn-default" href="https://gravatar.com/"
target="_blank">{{ _("Modify your Gravatar") }}</a>
% endif
<a class="btn btn-default" href="#avatar"
>{{ _("Modify your avatar") }}</a>
% elif participant.join_time and not embedded
<p>{{ _('Joined {0} ago.', to_age(participant.join_time)) }}</p>
% endif
Expand Down
3 changes: 2 additions & 1 deletion templates/settings.html
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
% extends "templates/base.html"

% block subnav
{% set pages = [
{% set pages = ([
('/giving/', _('Giving')),
('/receiving/', _('Receiving')),
('/wallet/', _('Wallet')),
('/settings/', _('Settings')),
('/identity', _('Identity')),
] if participant.is_person else []) + [
('/emails/', _('Emails')),
('/widgets/', _('Widgets')),
] + ([
Expand Down
31 changes: 31 additions & 0 deletions www/%username/avatar.spt
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
from aspen import Response

from liberapay.utils import b64encode_s, get_participant

[---]

participant = get_participant(state, restrict=True, allow_member=True)

msg = None

if request.method == 'POST':
src, email = request.body['src'], request.body.get('email') or None

if src not in constants.AVATAR_SOURCES:
raise Response(400, 'bad src')

website.db.run("""
UPDATE participants SET avatar_email = %s WHERE id = %s
""", (email, participant.id))
participant.set_attributes(avatar_email=email)

r = participant.update_avatar(src+':')
if not r:
raise Response(400, _("We were unable to get an avatar for you from {0}.", src))

msg = _("Your new avatar URL is: {0}", participant.avatar_url)
if request.headers.get('X-Requested-With') != 'XMLHttpRequest':
response.redirect(participant.path('edit')+'?success='+b64encode_s(msg)+'#avatar')

[---] application/json
{"url": participant.avatar_url, "msg": msg}
43 changes: 41 additions & 2 deletions www/%username/edit.spt
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,12 @@ GOAL_PARTS = GOAL_RAW.split("{0}")

edit = True

accounts = participant.get_accounts_elsewhere()

title = participant.username

[---] text/html
% from 'templates/icons.html' import glyphicon
% from 'templates/profile-box.html' import profile_box with context
% from "templates/select-lang.html" import lang_options with context

Expand Down Expand Up @@ -110,8 +113,6 @@ title = participant.username
% endcall
% endblock

{% block subnav %}{% endblock %}

% block content

<h3 class="banderole info" id="statement">{{ _("Statement") }}</h3>
Expand Down Expand Up @@ -169,4 +170,42 @@ title = participant.username
% include "templates/community-listing.html"
% endif

<h3 class="banderole default" id="avatar">{{ _("Avatar") }}</h3>

<a class="btn btn-default" target="_blank" href="https://www.libravatar.org/account/login/">
{{ glyphicon('edit') }}
<span>{{ _("Modify your Libravatar") }}</span>
</a>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
<a class="btn btn-default btn-sm" target="_blank" href="https://www.libravatar.org/">
{{ glyphicon('info-sign') }}
<span>{{ _("What is Libravatar?") }}</span>
</a><br>
<br>

<form action="{{ participant.path('avatar') }}" method="POST"
class="form-inline js-submit">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}" />

<div class="form-group">
<label for="avatar-src">{{ _("Avatar source") }}</label><br>
<select class="form-control" name="src" id="avatar-src">
% for src in constants.AVATAR_SOURCES
<option value="{{ src }}" {% if src == participant.avatar_src %} selected {% endif %}
{% if src != 'libravatar' and src not in accounts %} disabled {% endif %}
>{{ src }}</option>
% endfor
</select>
</div>

<div class="form-group">
<label for="avatar_email">{{ _('Avatar email (for Libravatar only)') }}</label><br>
<input class="form-control" id="avatar_email" name="email"
type="email" size=30
placeholder="{{ participant.email or participant.get_any_email() or '' }}"
value="{{ participant.avatar_email or '' }}" />
<button class="save btn btn-success">{{ _("Save") }}</button>
</div>
</form>

% endblock
2 changes: 1 addition & 1 deletion www/%username/emails/index.spt
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ from liberapay.utils import get_participant

[---]

participant = get_participant(state, restrict=True)
participant = get_participant(state, restrict=True, allow_member=True)
title = participant.username
subhead = _("Email settings")
emails = participant.get_emails()
Expand Down
2 changes: 1 addition & 1 deletion www/%username/emails/modify.json.spt
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ from liberapay.utils import get_participant
[-----------------------------------------]

request.allow("POST")
participant = get_participant(state, restrict=True)
participant = get_participant(state, restrict=True, allow_member=True)

if not participant.email_lang:
participant.set_email_lang(request.headers.get("Accept-Language"))
Expand Down
7 changes: 5 additions & 2 deletions www/%username/emails/verify.html.spt
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@ from liberapay.utils import emails, get_participant
[-----------------------------------------------------------------------------]

participant = get_participant(state, restrict=False)
if participant == user:
ok = participant == user
if not ok and not user.ANON and participant.kind == 'group':
ok = user.member_of(participant)
if ok:
email = request.qs.get('email', '')
nonce = request.qs.get('nonce', '')
result = participant.verify_email(email, nonce)
Expand All @@ -27,7 +30,7 @@ if participant == user:
<p>{{ _("Sign in to finish connecting your email.") }}</p>

<p>{% include "templates/sign-in-link.html" %}</p>
% elif user != participant
% elif not ok
<h1>{{ _("Wrong Account") }}</h1>

<p>{{ _("You're signed into the wrong Liberapay account to complete this email "
Expand Down
2 changes: 1 addition & 1 deletion www/about/teams.spt
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ if request.method == 'POST':
else:
raise UsernameAlreadyTaken(name)
else:
t = user.make_team(name)
t = user.make_team(name, request.body.get('email'))
response.redirect('/'+t.username+'/edit')

title = _("Teams")
Expand Down

0 comments on commit e7ebd6e

Please sign in to comment.