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

Team name restrictions #3943

Merged
merged 11 commits into from
Mar 15, 2016
1 change: 1 addition & 0 deletions gratipay/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ class NoSelfTipping(Exception): pass
class NoTippee(Exception): pass
class NoTeam(Exception): pass
class BadAmount(Exception): pass
class InvalidTeamName(Exception): pass

class FailedToReserveUsername(Exception): pass

Expand Down
21 changes: 21 additions & 0 deletions gratipay/models/team.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,32 @@
"""Teams on Gratipay receive payments and distribute payroll.
"""
import re
import requests
from aspen import json, log
from gratipay.exceptions import InvalidTeamName
from gratipay.models import add_event
from postgres.orm import Model


# Should have at least one letter.
TEAM_NAME_PATTERN = re.compile(r'^(?=.*[A-Za-z])([A-Za-z0-9.,-_ ]+)$')


def slugize(name):
""" Create a slug from a team name.
"""
if TEAM_NAME_PATTERN.match(name) is None:
raise InvalidTeamName

slug = name.strip()
for c in (',', ' '):
slug = slug.replace(c, '-') # Avoid % encoded characters in slug url.
while '--' in slug:
slug = slug.replace('--', '-')
slug = slug.strip('-')
return slug


class Team(Model):
"""Represent a Gratipay team.
"""
Expand Down
5 changes: 2 additions & 3 deletions gratipay/utils/fake_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
from gratipay import wireup, MAX_TIP, MIN_TIP
from gratipay.elsewhere import PLATFORMS
from gratipay.models.participant import Participant
from gratipay.models.team import Team
from gratipay.models.team import slugize, Team
from gratipay.models import community
from gratipay.models import check_db

Expand Down Expand Up @@ -82,8 +82,7 @@ def fake_team(db, teamowner):
teamslugname = faker.city()

try:
#using community.slugize
teamslug = community.slugize(teamslugname)
teamslug = slugize(teamslugname)
homepage = 'http://www.example.org/' + fake_text_id(3)
_fake_thing( db
, "teams"
Expand Down
679 changes: 648 additions & 31 deletions tests/py/fixtures/TestTeams.yml

Large diffs are not rendered by default.

57 changes: 55 additions & 2 deletions tests/py/test_teams.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

from aspen.testing.client import FileUpload
from gratipay.testing import Harness
from gratipay.models.team import Team, AlreadyMigrated
from gratipay.models.team import Team, AlreadyMigrated, slugize, InvalidTeamName


REVIEW_URL = "https://github.com/gratipay/test-gremlin/issues/9"
Expand Down Expand Up @@ -235,6 +235,15 @@ def test_casing_of_urls_survives(self):
assert team.onboarding_url == 'http://INSIDE.GRATipay.com/'
assert team.todo_url == 'hTTPS://github.com/GRATIPAY'

def test_casing_of_slug_survives(self):
self.make_participant('alice', claimed_time='now', email_address='', last_paypal_result='')
data = dict(self.valid_data)
data['name'] = 'GratiTeam'
self.post_new(dict(data))
team = Team.from_slug('GratiTeam')
assert team is not None
assert team.slug_lower == 'gratiteam'

def test_401_for_anon_creating_new_team(self):
self.post_new(self.valid_data, auth_as=None, expected=401)
assert self.db.one("SELECT COUNT(*) FROM teams") == 0
Expand Down Expand Up @@ -296,12 +305,20 @@ def test_error_message_for_bad_url(self):
r = self.post_new(dict(self.valid_data, todo_url='foo'), expected=400)
assert "Please enter an http[s]:// URL for the 'To-do URL' field." in r.body

def test_error_message_for_invalid_team_name(self):
self.make_participant('alice', claimed_time='now', email_address='[email protected]', last_paypal_result='')
data = dict(self.valid_data)
data['name'] = '~Invalid:Name;'
r = self.post_new(data, expected=400)
assert self.db.one("SELECT COUNT(*) FROM teams") == 0
assert "Sorry, your team name is invalid." in r.body

def test_error_message_for_slug_collision(self):
self.make_participant('alice', claimed_time='now', email_address='[email protected]', last_paypal_result='')
self.post_new(dict(self.valid_data))
r = self.post_new(dict(self.valid_data), expected=400)
assert self.db.one("SELECT COUNT(*) FROM teams") == 1
assert "Sorry, there is already a team using 'gratiteam'." in r.body
assert "Sorry, there is already a team using 'Gratiteam'." in r.body

def test_approved_team_shows_up_on_homepage(self):
self.make_team(is_approved=True)
Expand Down Expand Up @@ -478,3 +495,39 @@ def test_update_updates_object_attributes(self):
team.update(name='Enterprise', product_or_service='We save galaxies.')
assert team.name == 'Enterprise'
assert team.product_or_service == 'We save galaxies.'


# slugize

def test_slugize_slugizes(self):
assert slugize('Foo') == 'Foo'

def test_slugize_requires_a_letter(self):
assert pytest.raises(InvalidTeamName, slugize, '123')

def test_slugize_accepts_letter_in_middle(self):
assert slugize('1a23') == '1a23'

def test_slugize_converts_comma_to_dash(self):
assert slugize('foo,bar') == 'foo-bar'

def test_slugize_converts_space_to_dash(self):
assert slugize('foo bar') == 'foo-bar'

def test_slugize_allows_underscore(self):
assert slugize('foo_bar') == 'foo_bar'

def test_slugize_allows_period(self):
assert slugize('foo.bar') == 'foo.bar'

def test_slugize_trims_whitespace(self):
assert slugize(' Foo Bar ') == 'Foo-Bar'

def test_slugize_trims_dashes(self):
assert slugize('--Foo Bar--') == 'Foo-Bar'

def test_slugize_trims_replacement_dashes(self):
assert slugize(',,Foo Bar,,') == 'Foo-Bar'

def test_slugize_folds_dashes_together(self):
assert slugize('1a----------------23') == '1a-23'
2 changes: 1 addition & 1 deletion www/new.spt
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
from datetime import datetime

from aspen import Response
from gratipay.models.community import slugize
from gratipay.models.team import Team
[---]
request.allow('GET')
Expand Down Expand Up @@ -58,6 +57,7 @@ still_migrating = delta > 0
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">

<label><h2>{{ _("Team Name") }}</h2></label>
<p><i>{{ _("At least one letter A through Z, plus numbers, dashes (-), underscores (_), periods (.), comma (,) and whitespace.") }}</i></p>
<input type="text" name="name" required autofocus>

<label><h2>{{ _("Image") }}</h2></label>
Expand Down
11 changes: 7 additions & 4 deletions www/teams/create.json.spt
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ from cStringIO import StringIO
import requests
from aspen import Response

from gratipay.models.community import slugize
from gratipay.models.team import Team
from gratipay.exceptions import InvalidTeamName
from gratipay.models.team import slugize, Team
from gratipay.utils.images import (
imgize,
ImageTooLarge,
Expand Down Expand Up @@ -70,6 +70,11 @@ if request.method == 'POST':
, field_names[field]
))

try:
fields['slug'] = slugize(fields['name'])
except InvalidTeamName:
raise Response(400, _("Sorry, your team name is invalid."))

try:
large, small = imgize(image, image_type)
except ImageTooLarge:
Expand All @@ -79,8 +84,6 @@ if request.method == 'POST':
except UnknownImageError:
raise Response(500, _("Sorry, there was a problem saving your image. Please try again."))

fields['slug'] = slugize(fields['name'])

try:
team = Team.insert(user.participant, **fields)
except IntegrityError:
Expand Down