From 853f927a591f1c6a645c84e98cbe79f84d586f06 Mon Sep 17 00:00:00 2001 From: Mitesh Ashar Date: Wed, 14 Aug 2013 19:53:24 +0530 Subject: [PATCH 01/16] Added setup.cfg for tests --- setup.cfg | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 setup.cfg diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..ab28448 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,7 @@ +[nosetests] +match=^test +nocapture=1 +cover-package=lastuser_core +with-coverage=1 +cover-erase=1 +with-doctest=1 From 71f53727fd9dd45b49f289df219ad7fb6b498a59 Mon Sep 17 00:00:00 2001 From: Mitesh Ashar Date: Wed, 14 Aug 2013 19:55:37 +0530 Subject: [PATCH 02/16] Without reloading sys, sys.setdefaultencoding() isnt available in test environment. --- sitecustomize.py | 1 + 1 file changed, 1 insertion(+) diff --git a/sitecustomize.py b/sitecustomize.py index 0cbc9eb..6738424 100644 --- a/sitecustomize.py +++ b/sitecustomize.py @@ -1,3 +1,4 @@ # Required to make OpenID work with Wordpress (first instance where it came up) import sys +reload(sys) sys.setdefaultencoding("utf-8") From 1ce6fa13e9ed1f04a46cbae54458397d24e29286 Mon Sep 17 00:00:00 2001 From: Mitesh Ashar Date: Sat, 17 Aug 2013 00:34:24 +0530 Subject: [PATCH 03/16] nosetests will only check the tests directory for tests. --- setup.cfg | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.cfg b/setup.cfg index ab28448..45b4735 100644 --- a/setup.cfg +++ b/setup.cfg @@ -5,3 +5,4 @@ cover-package=lastuser_core with-coverage=1 cover-erase=1 with-doctest=1 +where=tests \ No newline at end of file From 73ed609e562b4bc7a93d6579fdb371d5c3fbde85 Mon Sep 17 00:00:00 2001 From: Mitesh Ashar Date: Mon, 19 Aug 2013 18:38:45 +0530 Subject: [PATCH 04/16] Bootstrapping the testing environment. Basic fixtures in place. --- instance/testing.py | 101 ++++++++++++++++++++++++++++++++++++++++++++ tests/__init__.py | 0 tests/fixtures.py | 28 ++++++++++++ tests/test_db.py | 20 +++++++++ 4 files changed, 149 insertions(+) create mode 100644 instance/testing.py create mode 100644 tests/__init__.py create mode 100644 tests/fixtures.py create mode 100644 tests/test_db.py diff --git a/instance/testing.py b/instance/testing.py new file mode 100644 index 0000000..081437c --- /dev/null +++ b/instance/testing.py @@ -0,0 +1,101 @@ +# -*- coding: utf-8 -*- +from flask import Markup + +#: The title of this site +SITE_TITLE = 'Lastuser' + +#: Support contact email +SITE_SUPPORT_EMAIL = 'test@example.com' + +#: TypeKit code for fonts +TYPEKIT_CODE = '' + +#: Google Analytics code UA-XXXXXX-X +GA_CODE = '' + +#: Database backend +SQLALCHEMY_BINDS = { + 'lastuser': 'postgresql://postgres@localhost/myapp_test', + } + +#: Cache type +CACHE_TYPE = 'redis' + +#: Secret key +SECRET_KEY = 'random_string_here' + +#: Timezone +TIMEZONE = 'Asia/Calcutta' + +#: Reserved usernames +#: Add to this list but do not remove any unless you want to break +#: the website +RESERVED_USERNAMES = set([ + 'app', + 'apps', + 'auth', + 'client', + 'confirm', + 'login', + 'logout', + 'new', + 'profile', + 'reset', + 'register', + 'token', + 'organizations', + ]) + +#: Mail settings +#: MAIL_FAIL_SILENTLY : default True +#: MAIL_SERVER : default 'localhost' +#: MAIL_PORT : default 25 +#: MAIL_USE_TLS : default False +#: MAIL_USE_SSL : default False +#: MAIL_USERNAME : default None +#: MAIL_PASSWORD : default None +#: DEFAULT_MAIL_SENDER : default None +MAIL_FAIL_SILENTLY = False +MAIL_SERVER = 'localhost' +DEFAULT_MAIL_SENDER = ('Lastuser', 'test@example.com') +MAIL_DEFAULT_SENDER = DEFAULT_MAIL_SENDER # For new versions of Flask-Mail + +#: Logging: recipients of error emails +ADMINS = [] + +#: Log file +LOGFILE = 'error.log' + +#: Use SSL for some URLs +USE_SSL = False + +#: Twitter integration +OAUTH_TWITTER_KEY = '' +OAUTH_TWITTER_SECRET = '' + +#: GitHub integration +OAUTH_GITHUB_KEY = '' +OAUTH_GITHUB_SECRET = '' + +#: Recaptcha for the registration form +RECAPTCHA_USE_SSL = USE_SSL +RECAPTCHA_PUBLIC_KEY = '' +RECAPTCHA_PRIVATE_KEY = '' +RECAPTCHA_OPTIONS = '' + +#: SMS gateways +SMS_SMSGUPSHUP_MASK = '' +SMS_SMSGUPSHUP_USER = '' +SMS_SMSGUPSHUP_PASS = '' + +#: Messages (text or HTML) +MESSAGE_FOOTER = Markup('Copyright © HasGeek. Powered by Lastuser, open source software from HasGeek.') +USERNAME_REASON = '' +EMAIL_REASON = 'Please provide an email address to complete your profile' +BIO_REASON = '' +TIMEZONE_REASON = 'Dates and times will be shown in your preferred timezone' +ORG_NAME_REASON = u"Your company’s name as it will appear in the URL. Letters, numbers and dashes only" +ORG_TITLE_REASON = u"Your organization’s given name, preferably without legal suffixes" +ORG_DESCRIPTION_REASON = u"A few words about your organization (optional). Plain text only" +LOGIN_MESSAGE_1 = "" +LOGIN_MESSAGE_2 = "" diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/fixtures.py b/tests/fixtures.py new file mode 100644 index 0000000..e833ab3 --- /dev/null +++ b/tests/fixtures.py @@ -0,0 +1,28 @@ +# -*- coding: utf-8 -*- + +from coaster import buid +from lastuserapp import db, app +from lastuser_core.models import * + +def make_fixtures(): + user1 = User(username=u"user1", fullname=u"User 1") + user2 = User(username=u"user2", fullname=u"User 2") + + email1 = UserEmail(email=u"user1@example.com", user=user1) + phone1 = UserPhone(phone=u"1234567890", user=user1) + email2 = UserEmail(email=u"user2@example.com", user=user2) + phone2 = UserPhone(phone=u"1234567891", user=user2) + db.session.add_all([email1, phone1, email2, phone2]) + + org = Organization(name=u"org", title=u"Organization") + org.owners.users.append(user1) + db.session.add(org) + + client = Client(title=u"Test Application", org=org, user=user1) + db.session.add(client) + + resource = Resource(name=u"test_resource", title=u"Test Resource", client=client) + db.session.add(resource) + + action = ResourceAction(name=u"read", title=u"Read", resource=resource) + db.session.add(action) \ No newline at end of file diff --git a/tests/test_db.py b/tests/test_db.py new file mode 100644 index 0000000..8be37e2 --- /dev/null +++ b/tests/test_db.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- + +import unittest +from lastuserapp import app, db, init_for +from .fixtures import make_fixtures + +class LastuserTest(unittest.TestCase): + def setUp(self): + with app.test_request_context(): + init_for('testing') + app.config['TESTING'] = True + db.create_all() + self.db = db + make_fixtures() + + def test_noop(self): + pass + + def tearDown(self): + self.db.drop_all() \ No newline at end of file From 32e76fad08bc28444d9e0007109842fc770f991b Mon Sep 17 00:00:00 2001 From: Mitesh Ashar Date: Mon, 19 Aug 2013 21:00:40 +0530 Subject: [PATCH 05/16] Committed the db session in make_fixtures() --- tests/fixtures.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/fixtures.py b/tests/fixtures.py index e833ab3..c2b8bf9 100644 --- a/tests/fixtures.py +++ b/tests/fixtures.py @@ -25,4 +25,6 @@ def make_fixtures(): db.session.add(resource) action = ResourceAction(name=u"read", title=u"Read", resource=resource) - db.session.add(action) \ No newline at end of file + db.session.add(action) + + db.session.commit() \ No newline at end of file From 68d283e5991a657b4c0f6603fb8c3beb2f4c9920 Mon Sep 17 00:00:00 2001 From: Mitesh Ashar Date: Mon, 19 Aug 2013 21:21:45 +0530 Subject: [PATCH 06/16] Using db.app = app removes dependency on test_request_context(). Specified website for client, else throws IntegrityError. --- tests/fixtures.py | 2 +- tests/test_db.py | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/fixtures.py b/tests/fixtures.py index c2b8bf9..8798dcb 100644 --- a/tests/fixtures.py +++ b/tests/fixtures.py @@ -18,7 +18,7 @@ def make_fixtures(): org.owners.users.append(user1) db.session.add(org) - client = Client(title=u"Test Application", org=org, user=user1) + client = Client(title=u"Test Application", org=org, user=user1, website=u"http://example.com") db.session.add(client) resource = Resource(name=u"test_resource", title=u"Test Resource", client=client) diff --git a/tests/test_db.py b/tests/test_db.py index 8be37e2..8c06533 100644 --- a/tests/test_db.py +++ b/tests/test_db.py @@ -6,12 +6,12 @@ class LastuserTest(unittest.TestCase): def setUp(self): - with app.test_request_context(): - init_for('testing') - app.config['TESTING'] = True - db.create_all() - self.db = db - make_fixtures() + init_for('testing') + app.config['TESTING'] = True + db.app = app + db.create_all() + self.db = db + make_fixtures() def test_noop(self): pass From 291b743a33ef82afa2e9e661459b36c0a511a195 Mon Sep 17 00:00:00 2001 From: Mitesh Ashar Date: Mon, 19 Aug 2013 21:28:42 +0530 Subject: [PATCH 07/16] Added .travis.yml & test_requirements.txt --- .travis.yml | 11 +++++++++++ test_requirements.txt | 2 ++ 2 files changed, 13 insertions(+) create mode 100644 .travis.yml create mode 100644 test_requirements.txt diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..340533f --- /dev/null +++ b/.travis.yml @@ -0,0 +1,11 @@ +language: python +python: + - 2.7 +install: + - pip install -r requirements.txt --use-mirrors + - pip install -r test_requirements.txt --use-mirrors +script: + - nosetests tests --with-coverage +notifications: + email: false + irc: "irc.freenode.net#hasgeek-dev" diff --git a/test_requirements.txt b/test_requirements.txt new file mode 100644 index 0000000..a6c88f5 --- /dev/null +++ b/test_requirements.txt @@ -0,0 +1,2 @@ +nose +coverage From 2a447b12533888d1c76660f7e210583728e1d8b6 Mon Sep 17 00:00:00 2001 From: Mitesh Ashar Date: Tue, 20 Aug 2013 12:32:48 +0530 Subject: [PATCH 08/16] s/LastuserTest/TestDatabaseFixture --- tests/test_db.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_db.py b/tests/test_db.py index 8c06533..61bb72c 100644 --- a/tests/test_db.py +++ b/tests/test_db.py @@ -4,7 +4,7 @@ from lastuserapp import app, db, init_for from .fixtures import make_fixtures -class LastuserTest(unittest.TestCase): +class TestDatabaseFixture(unittest.TestCase): def setUp(self): init_for('testing') app.config['TESTING'] = True From daff24319208962a47a022eccab793b8d0239b4c Mon Sep 17 00:00:00 2001 From: Mitesh Ashar Date: Tue, 20 Aug 2013 13:05:00 +0530 Subject: [PATCH 09/16] Adding the users was not supposed to be removed. Removed it while removing the debugging messages. drop_all before create_all could be a good idea in fixtures. --- tests/fixtures.py | 1 + tests/test_db.py | 1 + 2 files changed, 2 insertions(+) diff --git a/tests/fixtures.py b/tests/fixtures.py index 8798dcb..98107b9 100644 --- a/tests/fixtures.py +++ b/tests/fixtures.py @@ -7,6 +7,7 @@ def make_fixtures(): user1 = User(username=u"user1", fullname=u"User 1") user2 = User(username=u"user2", fullname=u"User 2") + db.session.add_all([user1, user2]) email1 = UserEmail(email=u"user1@example.com", user=user1) phone1 = UserPhone(phone=u"1234567890", user=user1) diff --git a/tests/test_db.py b/tests/test_db.py index 61bb72c..6bd75ea 100644 --- a/tests/test_db.py +++ b/tests/test_db.py @@ -9,6 +9,7 @@ def setUp(self): init_for('testing') app.config['TESTING'] = True db.app = app + db.drop_all() db.create_all() self.db = db make_fixtures() From 49040e32fbb3c9b40cfb3b8e10f7ebbf36e6aaa1 Mon Sep 17 00:00:00 2001 From: Mitesh Ashar Date: Tue, 20 Aug 2013 14:39:02 +0530 Subject: [PATCH 10/16] Removed dummy test. --- tests/test_db.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/tests/test_db.py b/tests/test_db.py index 6bd75ea..24da80d 100644 --- a/tests/test_db.py +++ b/tests/test_db.py @@ -14,8 +14,5 @@ def setUp(self): self.db = db make_fixtures() - def test_noop(self): - pass - def tearDown(self): self.db.drop_all() \ No newline at end of file From a7be390245996868b4b0ba8a73f3875d7194edf9 Mon Sep 17 00:00:00 2001 From: Mitesh Ashar Date: Tue, 20 Aug 2013 14:43:27 +0530 Subject: [PATCH 11/16] Newline required at end of all files. --- setup.cfg | 2 +- tests/fixtures.py | 2 +- tests/test_db.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/setup.cfg b/setup.cfg index 45b4735..e551a27 100644 --- a/setup.cfg +++ b/setup.cfg @@ -5,4 +5,4 @@ cover-package=lastuser_core with-coverage=1 cover-erase=1 with-doctest=1 -where=tests \ No newline at end of file +where=tests diff --git a/tests/fixtures.py b/tests/fixtures.py index 98107b9..6fafd84 100644 --- a/tests/fixtures.py +++ b/tests/fixtures.py @@ -28,4 +28,4 @@ def make_fixtures(): action = ResourceAction(name=u"read", title=u"Read", resource=resource) db.session.add(action) - db.session.commit() \ No newline at end of file + db.session.commit() diff --git a/tests/test_db.py b/tests/test_db.py index 24da80d..e5c4105 100644 --- a/tests/test_db.py +++ b/tests/test_db.py @@ -15,4 +15,4 @@ def setUp(self): make_fixtures() def tearDown(self): - self.db.drop_all() \ No newline at end of file + self.db.drop_all() From fbd7654505300bb8f0962b717b005219386cd36a Mon Sep 17 00:00:00 2001 From: Mitesh Ashar Date: Tue, 20 Aug 2013 14:44:49 +0530 Subject: [PATCH 12/16] tests should cover all three lastuser blueprints --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index e551a27..a27c7f0 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,7 +1,7 @@ [nosetests] match=^test nocapture=1 -cover-package=lastuser_core +cover-package=lastuser_core, lastuser_oauth, lastuser_ui with-coverage=1 cover-erase=1 with-doctest=1 From 4f8977a1d1f7cdf4fb420193b099b9616dbfa0b5 Mon Sep 17 00:00:00 2001 From: Mitesh Ashar Date: Tue, 20 Aug 2013 14:49:21 +0530 Subject: [PATCH 13/16] Using spaces for indentation. --- tests/fixtures.py | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/tests/fixtures.py b/tests/fixtures.py index 6fafd84..969668b 100644 --- a/tests/fixtures.py +++ b/tests/fixtures.py @@ -5,27 +5,27 @@ from lastuser_core.models import * def make_fixtures(): - user1 = User(username=u"user1", fullname=u"User 1") - user2 = User(username=u"user2", fullname=u"User 2") - db.session.add_all([user1, user2]) - - email1 = UserEmail(email=u"user1@example.com", user=user1) - phone1 = UserPhone(phone=u"1234567890", user=user1) - email2 = UserEmail(email=u"user2@example.com", user=user2) - phone2 = UserPhone(phone=u"1234567891", user=user2) - db.session.add_all([email1, phone1, email2, phone2]) + user1 = User(username=u"user1", fullname=u"User 1") + user2 = User(username=u"user2", fullname=u"User 2") + db.session.add_all([user1, user2]) + + email1 = UserEmail(email=u"user1@example.com", user=user1) + phone1 = UserPhone(phone=u"1234567890", user=user1) + email2 = UserEmail(email=u"user2@example.com", user=user2) + phone2 = UserPhone(phone=u"1234567891", user=user2) + db.session.add_all([email1, phone1, email2, phone2]) - org = Organization(name=u"org", title=u"Organization") - org.owners.users.append(user1) - db.session.add(org) + org = Organization(name=u"org", title=u"Organization") + org.owners.users.append(user1) + db.session.add(org) - client = Client(title=u"Test Application", org=org, user=user1, website=u"http://example.com") - db.session.add(client) + client = Client(title=u"Test Application", org=org, user=user1, website=u"http://example.com") + db.session.add(client) - resource = Resource(name=u"test_resource", title=u"Test Resource", client=client) - db.session.add(resource) + resource = Resource(name=u"test_resource", title=u"Test Resource", client=client) + db.session.add(resource) - action = ResourceAction(name=u"read", title=u"Read", resource=resource) - db.session.add(action) + action = ResourceAction(name=u"read", title=u"Read", resource=resource) + db.session.add(action) - db.session.commit() + db.session.commit() From 252ac96fda709fca590b8de62131c99014cb7433 Mon Sep 17 00:00:00 2001 From: Mitesh Ashar Date: Tue, 20 Aug 2013 14:57:45 +0530 Subject: [PATCH 14/16] Call reload(sys) in sitecustomize, only when setdefaultencoding is not available. --- sitecustomize.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sitecustomize.py b/sitecustomize.py index 6738424..6752327 100644 --- a/sitecustomize.py +++ b/sitecustomize.py @@ -1,4 +1,5 @@ # Required to make OpenID work with Wordpress (first instance where it came up) import sys -reload(sys) +if not hasttr(sys, 'setdefaultencoding'): + reload(sys) sys.setdefaultencoding("utf-8") From d1e9d79856ff9ad3d4c0b15bcdf505c26da0eb33 Mon Sep 17 00:00:00 2001 From: Kiran Jonnalagadda Date: Tue, 20 Aug 2013 15:34:32 +0530 Subject: [PATCH 15/16] Upgrade for Flask-WTF 0.9.0 imports. --- lastuser_oauth/forms/login.py | 30 +++++------ lastuser_oauth/forms/profile.py | 49 +++++++++--------- lastuser_oauth/providers/openid.py | 5 +- lastuser_ui/forms/client.py | 81 +++++++++++++++--------------- lastuser_ui/forms/org.py | 12 ++--- lastuser_ui/forms/profile.py | 27 +++++----- 6 files changed, 105 insertions(+), 99 deletions(-) diff --git a/lastuser_oauth/forms/login.py b/lastuser_oauth/forms/login.py index 5aac3e1..24d6a3b 100644 --- a/lastuser_oauth/forms/login.py +++ b/lastuser_oauth/forms/login.py @@ -1,6 +1,8 @@ # -*- coding: utf-8 -*- from flask import Markup, url_for, current_app +import wtforms +import wtforms.fields.html5 import flask.ext.wtf as wtf from coaster import valid_username from baseframe.forms import Form @@ -9,46 +11,46 @@ class LoginForm(Form): - username = wtf.TextField('Username or Email', validators=[wtf.Required()]) - password = wtf.PasswordField('Password', validators=[wtf.Required()]) + username = wtforms.TextField('Username or Email', validators=[wtforms.validators.Required()]) + password = wtforms.PasswordField('Password', validators=[wtforms.validators.Required()]) def validate_username(self, field): existing = getuser(field.data) if existing is None: - raise wtf.ValidationError("User does not exist") + raise wtforms.ValidationError("User does not exist") def validate_password(self, field): user = getuser(self.username.data) if user is None or not user.password_is(field.data): - raise wtf.ValidationError("Incorrect password") + raise wtforms.ValidationError("Incorrect password") self.user = user class RegisterForm(Form): - fullname = wtf.TextField('Full name', validators=[wtf.Required()]) - email = wtf.html5.EmailField('Email address', validators=[wtf.Required(), wtf.Email()]) - username = wtf.TextField('Username', validators=[wtf.Required()], + fullname = wtforms.TextField('Full name', validators=[wtforms.validators.Required()]) + email = wtforms.fields.html5.EmailField('Email address', validators=[wtforms.validators.Required(), wtforms.validators.Email()]) + username = wtforms.TextField('Username', validators=[wtforms.validators.Required()], description="Single word that can contain letters, numbers and dashes") - password = wtf.PasswordField('Password', validators=[wtf.Required()]) - confirm_password = wtf.PasswordField('Confirm password', - validators=[wtf.Required(), wtf.EqualTo('password')]) + password = wtforms.PasswordField('Password', validators=[wtforms.validators.Required()]) + confirm_password = wtforms.PasswordField('Confirm password', + validators=[wtforms.validators.Required(), wtforms.validators.EqualTo('password')]) recaptcha = wtf.RecaptchaField('Are you human?', description="Type both words into the text box to prove that you are a human and not a computer program") def validate_username(self, field): if field.data in current_app.config['RESERVED_USERNAMES']: - raise wtf.ValidationError, "That name is reserved" + raise wtforms.ValidationError, "That name is reserved" if not valid_username(field.data): - raise wtf.ValidationError(u"Invalid characters in name. Names must be made of ‘a-z’, ‘0-9’ and ‘-’, without trailing dashes") + raise wtforms.ValidationError(u"Invalid characters in name. Names must be made of ‘a-z’, ‘0-9’ and ‘-’, without trailing dashes") existing = User.query.filter_by(username=field.data).first() if existing is not None: - raise wtf.ValidationError("That username is taken") + raise wtforms.ValidationError("That username is taken") def validate_email(self, field): field.data = field.data.lower() # Convert to lowercase existing = UserEmail.query.filter_by(email=field.data).first() if existing is not None: - raise wtf.ValidationError(Markup( + raise wtforms.ValidationError(Markup( 'This email address is already registered. Do you want to login instead?' % url_for('.login') )) diff --git a/lastuser_oauth/forms/profile.py b/lastuser_oauth/forms/profile.py index 619b665..069de38 100644 --- a/lastuser_oauth/forms/profile.py +++ b/lastuser_oauth/forms/profile.py @@ -1,7 +1,8 @@ # -*- coding: utf-8 -*- from flask import g, current_app -import flask.ext.wtf as wtf +import wtforms +import wtforms.fields.html5 from coaster import valid_username, sorted_timezones from baseframe.forms import Form, ValidEmailDomain @@ -11,48 +12,48 @@ class PasswordResetRequestForm(Form): - username = wtf.TextField('Username or Email', validators=[wtf.Required()]) + username = wtforms.TextField('Username or Email', validators=[wtforms.validators.Required()]) def validate_username(self, field): user = getuser(field.data) if user is None: - raise wtf.ValidationError("Could not find a user with that id") + raise wtforms.ValidationError("Could not find a user with that id") self.user = user class PasswordResetForm(Form): - username = wtf.TextField('Username or Email', validators=[wtf.Required()], + username = wtforms.TextField('Username or Email', validators=[wtforms.validators.Required()], description="Please reconfirm your username or email address") - password = wtf.PasswordField('New password', validators=[wtf.Required()]) - confirm_password = wtf.PasswordField('Confirm password', - validators=[wtf.Required(), wtf.EqualTo('password')]) + password = wtforms.PasswordField('New password', validators=[wtforms.validators.Required()]) + confirm_password = wtforms.PasswordField('Confirm password', + validators=[wtforms.validators.Required(), wtforms.validators.EqualTo('password')]) def validate_username(self, field): user = getuser(field.data) if user is None or user != self.user: - raise wtf.ValidationError( + raise wtforms.ValidationError( "This username or email does not match the user the reset code is for") class PasswordChangeForm(Form): - old_password = wtf.PasswordField('Current password', validators=[wtf.Required()]) - password = wtf.PasswordField('New password', validators=[wtf.Required()]) - confirm_password = wtf.PasswordField('Confirm password', - validators=[wtf.Required(), wtf.EqualTo('password')]) + old_password = wtforms.PasswordField('Current password', validators=[wtforms.validators.Required()]) + password = wtforms.PasswordField('New password', validators=[wtforms.validators.Required()]) + confirm_password = wtforms.PasswordField('Confirm password', + validators=[wtforms.validators.Required(), wtforms.validators.EqualTo('password')]) def validate_old_password(self, field): if g.user is None: - raise wtf.ValidationError, "Not logged in" + raise wtforms.ValidationError, "Not logged in" if not g.user.password_is(field.data): - raise wtf.ValidationError, "Incorrect password" + raise wtforms.ValidationError, "Incorrect password" class ProfileForm(Form): - fullname = wtf.TextField('Full name', validators=[wtf.Required()]) - email = wtf.html5.EmailField('Email address', validators=[wtf.Required(), wtf.Email(), ValidEmailDomain()]) - username = wtf.TextField('Username', validators=[wtf.Required()]) - description = wtf.TextAreaField('Bio') - timezone = wtf.SelectField('Timezone', validators=[wtf.Required()], choices=timezones) + fullname = wtforms.TextField('Full name', validators=[wtforms.validators.Required()]) + email = wtforms.fields.html5.EmailField('Email address', validators=[wtforms.validators.Required(), wtforms.validators.Email(), ValidEmailDomain()]) + username = wtforms.TextField('Username', validators=[wtforms.validators.Required()]) + description = wtforms.TextAreaField('Bio') + timezone = wtforms.SelectField('Timezone', validators=[wtforms.validators.Required()], choices=timezones) def __init__(self, *args, **kwargs): super(ProfileForm, self).__init__(*args, **kwargs) @@ -65,22 +66,22 @@ def validate_username(self, field): # return field.data = field.data.lower() # Usernames can only be lowercase if not valid_username(field.data): - raise wtf.ValidationError("Usernames can only have alphabets, numbers and dashes (except at the ends)") + raise wtforms.ValidationError("Usernames can only have alphabets, numbers and dashes (except at the ends)") if field.data in current_app.config['RESERVED_USERNAMES']: - raise wtf.ValidationError("This name is reserved") + raise wtforms.ValidationError("This name is reserved") existing = User.query.filter_by(username=field.data).first() if existing is not None and existing.id != self.edit_id: - raise wtf.ValidationError("This username is taken") + raise wtforms.ValidationError("This username is taken") existing = Organization.query.filter_by(name=field.data).first() if existing is not None: - raise wtf.ValidationError("This username is taken") + raise wtforms.ValidationError("This username is taken") # TODO: Move to function and place before ValidEmailDomain() def validate_email(self, field): field.data = field.data.lower() # Convert to lowercase existing = UserEmail.query.filter_by(email=field.data).first() if existing is not None and existing.user != self.edit_obj: - raise wtf.ValidationError("This email address has been claimed by another user.") + raise wtforms.ValidationError("This email address has been claimed by another user.") class ProfileMergeForm(Form): diff --git a/lastuser_oauth/providers/openid.py b/lastuser_oauth/providers/openid.py index b0df251..6924f24 100644 --- a/lastuser_oauth/providers/openid.py +++ b/lastuser_oauth/providers/openid.py @@ -3,7 +3,8 @@ from __future__ import absolute_import from flask import Markup, session -import flask.ext.wtf as wtf +import wtforms +import wtforms.fields.html5 from baseframe.forms import Form from lastuser_core.registry import LoginProvider from ..views.login import oid @@ -13,7 +14,7 @@ class OpenIdForm(Form): - openid = wtf.html5.URLField('Login with OpenID', validators=[wtf.Required()], default='http://', + openid = wtforms.fields.html5.URLField('Login with OpenID', validators=[wtforms.validators.Required()], default='http://', description=Markup("Don't forget the http:// or https:// prefix")) diff --git a/lastuser_ui/forms/client.py b/lastuser_ui/forms/client.py index dc1a5c4..8f610a9 100644 --- a/lastuser_ui/forms/client.py +++ b/lastuser_ui/forms/client.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- from flask import g -import flask.ext.wtf as wtf +import wtforms +import wtforms.fields.html5 from baseframe.forms import Form from coaster import valid_username @@ -12,38 +13,38 @@ class ConfirmDeleteForm(Form): """ Confirm a delete operation """ - delete = wtf.SubmitField('Delete') - cancel = wtf.SubmitField('Cancel') + delete = wtforms.SubmitField('Delete') + cancel = wtforms.SubmitField('Cancel') class RegisterClientForm(Form): """ Register a new OAuth client application """ - title = wtf.TextField('Application title', validators=[wtf.Required()], + title = wtforms.TextField('Application title', validators=[wtforms.validators.Required()], description="The name of your application") - description = wtf.TextAreaField('Description', validators=[wtf.Required()], + description = wtforms.TextAreaField('Description', validators=[wtforms.validators.Required()], description="A description to help users recognize your application") - client_owner = wtf.RadioField('Owner', validators=[wtf.Required()], + client_owner = wtforms.RadioField('Owner', validators=[wtforms.validators.Required()], description="User or organization that owns this application. Changing the owner " "will revoke all currently assigned permissions for this app") - website = wtf.html5.URLField('Application website', validators=[wtf.Required(), wtf.URL()], + website = wtforms.fields.html5.URLField('Application website', validators=[wtforms.validators.Required(), wtforms.validators.URL()], description="Website where users may access this application") - redirect_uri = wtf.html5.URLField('Redirect URL', validators=[wtf.Optional(), wtf.URL()], + redirect_uri = wtforms.fields.html5.URLField('Redirect URL', validators=[wtforms.validators.Optional(), wtforms.validators.URL()], description="OAuth2 Redirect URL") - notification_uri = wtf.html5.URLField('Notification URL', validators=[wtf.Optional(), wtf.URL()], + notification_uri = wtforms.fields.html5.URLField('Notification URL', validators=[wtforms.validators.Optional(), wtforms.validators.URL()], description="When the user's data changes, Lastuser will POST a notice to this URL. " "Other notices may be posted too") - iframe_uri = wtf.html5.URLField('IFrame URL', validators=[wtf.Optional(), wtf.URL()], + iframe_uri = wtforms.fields.html5.URLField('IFrame URL', validators=[wtforms.validators.Optional(), wtforms.validators.URL()], description="Front-end notifications URL. This is loaded in a hidden iframe to notify the app that the " "user updated their profile in some way (not yet implemented)") - resource_uri = wtf.html5.URLField('Resource URL', validators=[wtf.Optional(), wtf.URL()], + resource_uri = wtforms.fields.html5.URLField('Resource URL', validators=[wtforms.validators.Optional(), wtforms.validators.URL()], description="URL at which this application provides resources as per the Lastuser Resource API " "(not yet implemented)") - allow_any_login = wtf.BooleanField('Allow anyone to login', default=True, + allow_any_login = wtforms.BooleanField('Allow anyone to login', default=True, description="If your application requires access to be restricted to specific users, uncheck this, " "and only users who have been assigned a permission to the app will be able to login") - team_access = wtf.BooleanField('Requires access to teams', default=False, + team_access = wtforms.BooleanField('Requires access to teams', default=False, description="If your application is capable of assigning access permissions to teams, check this. " "Organization owners will then able to grant access to teams in their organizations") @@ -54,7 +55,7 @@ def validate_client_owner(self, field): else: orgs = [org for org in g.user.organizations_owned() if org.userid == field.data] if len(orgs) != 1: - raise wtf.ValidationError("Invalid owner") + raise wtforms.ValidationError("Invalid owner") self.user = None self.org = orgs[0] @@ -63,16 +64,16 @@ class PermissionForm(Form): """ Create or edit a permission """ - name = wtf.TextField('Permission name', validators=[wtf.Required()], + name = wtforms.TextField('Permission name', validators=[wtforms.validators.Required()], description='Name of the permission as a single word in lower case. ' 'This is passed to the application when a user logs in. ' 'Changing the name will not automatically update it everywhere. ' 'You must reassign the permission to users who had it with the old name') - title = wtf.TextField('Title', validators=[wtf.Required()], + title = wtforms.TextField('Title', validators=[wtforms.validators.Required()], description='Permission title that is displayed to users') - description = wtf.TextAreaField('Description', + description = wtforms.TextAreaField('Description', description='An optional description of what the permission is for') - context = wtf.RadioField('Context', validators=[wtf.Required()], + context = wtforms.RadioField('Context', validators=[wtforms.validators.Required()], description='Context where this permission is available') def validate(self): @@ -110,7 +111,7 @@ def validate_context(self, field): else: orgs = [org for org in g.user.organizations_owned() if org.userid == field.data] if len(orgs) != 1: - raise wtf.ValidationError("Invalid context") + raise wtforms.ValidationError("Invalid context") self.user = None self.org = orgs[0] @@ -119,14 +120,14 @@ class UserPermissionAssignForm(Form): """ Assign permissions to a user """ - username = wtf.TextField("User", validators=[wtf.Required()], + username = wtforms.TextField("User", validators=[wtforms.validators.Required()], description='Lookup a user by their username or email address') - perms = wtf.SelectMultipleField("Permissions", validators=[wtf.Required()]) + perms = wtforms.SelectMultipleField("Permissions", validators=[wtforms.validators.Required()]) def validate_username(self, field): existing = getuser(field.data) if existing is None: - raise wtf.ValidationError("User does not exist") + raise wtforms.ValidationError("User does not exist") self.user = existing @@ -134,14 +135,14 @@ class TeamPermissionAssignForm(Form): """ Assign permissions to a team """ - team_id = wtf.RadioField("Team", validators=[wtf.Required()], + team_id = wtforms.RadioField("Team", validators=[wtforms.validators.Required()], description='Select a team to assign permissions to') - perms = wtf.SelectMultipleField("Permissions", validators=[wtf.Required()]) + perms = wtforms.SelectMultipleField("Permissions", validators=[wtforms.validators.Required()]) def validate_team_id(self, field): teams = [team for team in self.org.teams if team.userid == field.data] if len(teams) != 1: - raise wtf.ValidationError("Unknown team") + raise wtforms.ValidationError("Unknown team") self.team = teams[0] @@ -149,65 +150,65 @@ class PermissionEditForm(Form): """ Edit a user or team's permissions """ - perms = wtf.SelectMultipleField("Permissions", validators=[wtf.Required()]) + perms = wtforms.SelectMultipleField("Permissions", validators=[wtforms.validators.Required()]) class ResourceForm(Form): """ Edit a resource provided by an application """ - name = wtf.TextField('Resource name', validators=[wtf.Required()], + name = wtforms.TextField('Resource name', validators=[wtforms.validators.Required()], description="Name of the resource as a single word in lower case. " "This is provided by applications as part of the scope " "when requesting access to a user's resources.") - title = wtf.TextField('Title', validators=[wtf.Required()], + title = wtforms.TextField('Title', validators=[wtforms.validators.Required()], description='Resource title that is displayed to users') - description = wtf.TextAreaField('Description', + description = wtforms.TextAreaField('Description', description='An optional description of what the resource is') - siteresource = wtf.BooleanField('Site resource', + siteresource = wtforms.BooleanField('Site resource', description='Enable if this resource is generic to the site and not owned by specific users') - trusted = wtf.BooleanField('Trusted applications only', + trusted = wtforms.BooleanField('Trusted applications only', description='Enable if access to the resource should be restricted to trusted ' 'applications. You may want to do this for sensitive information like billing data') def validate_name(self, field): if not valid_username(field.data): - raise wtf.ValidationError("Name contains invalid characters.") + raise wtforms.ValidationError("Name contains invalid characters.") if field.data in resource_registry: - raise wtf.ValidationError("This name is reserved for internal use") + raise wtforms.ValidationError("This name is reserved for internal use") existing = Resource.query.filter_by(name=field.data).first() if existing and existing.id != self.edit_id: - raise wtf.ValidationError("A resource with that name already exists") + raise wtforms.ValidationError("A resource with that name already exists") class ResourceActionForm(Form): """ Edit an action associated with a resource """ - name = wtf.TextField('Action name', validators=[wtf.Required()], + name = wtforms.TextField('Action name', validators=[wtforms.validators.Required()], description="Name of the action as a single word in lower case. " "This is provided by applications as part of the scope in the form " "'resource/action' when requesting access to a user's resources. " "Read actions are implicit when applications request just 'resource' " "in the scope and do not need to be specified as an explicit action.") - title = wtf.TextField('Title', validators=[wtf.Required()], + title = wtforms.TextField('Title', validators=[wtforms.validators.Required()], description='Action title that is displayed to users') - description = wtf.TextAreaField('Description', + description = wtforms.TextAreaField('Description', description='An optional description of what the action is') def validate_name(self, field): if not valid_username(field.data): - raise wtf.ValidationError("Name contains invalid characters.") + raise wtforms.ValidationError("Name contains invalid characters.") existing = ResourceAction.query.filter_by(name=field.data, resource=self.edit_resource).first() if existing and existing.id != self.edit_id: - raise wtf.ValidationError("An action with that name already exists for this resource") + raise wtforms.ValidationError("An action with that name already exists for this resource") class ClientTeamAccessForm(Form): """ Select organizations that the client has access to the teams of """ - organizations = wtf.SelectMultipleField('Organizations') + organizations = wtforms.SelectMultipleField('Organizations') diff --git a/lastuser_ui/forms/org.py b/lastuser_ui/forms/org.py index 1a9cf11..090d670 100644 --- a/lastuser_ui/forms/org.py +++ b/lastuser_ui/forms/org.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- from flask import current_app -import flask.ext.wtf as wtf +import wtforms from coaster import valid_username from baseframe.forms import Form, HiddenMultiField @@ -9,9 +9,9 @@ class OrganizationForm(Form): - title = wtf.TextField('Organization name', validators=[wtf.Required()]) - name = wtf.TextField('URL name', validators=[wtf.Required()]) - description = wtf.TextAreaField('Description') + title = wtforms.TextField('Organization name', validators=[wtforms.validators.Required()]) + name = wtforms.TextField('URL name', validators=[wtforms.validators.Required()]) + description = wtforms.TextAreaField('Description') def validate_name(self, field): if not valid_username(field.data): @@ -27,5 +27,5 @@ def validate_name(self, field): class TeamForm(Form): - title = wtf.TextField('Team name', validators=[wtf.Required()]) - users = HiddenMultiField('Users', validators=[wtf.Required()]) + title = wtforms.TextField('Team name', validators=[wtforms.validators.Required()]) + users = HiddenMultiField('Users', validators=[wtforms.validators.Required()]) diff --git a/lastuser_ui/forms/profile.py b/lastuser_ui/forms/profile.py index f66a6c6..a277ca6 100644 --- a/lastuser_ui/forms/profile.py +++ b/lastuser_ui/forms/profile.py @@ -1,7 +1,8 @@ # -*- coding: utf-8 -*- from flask import g -import flask.ext.wtf as wtf +import wtforms +import wtforms.fields.html5 from baseframe.forms import Form, ValidEmailDomain from lastuser_core.utils import strip_phone, valid_phone @@ -9,7 +10,7 @@ class NewEmailAddressForm(Form): - email = wtf.html5.EmailField('Email address', validators=[wtf.Required(), wtf.Email(), ValidEmailDomain()]) + email = wtforms.fields.html5.EmailField('Email address', validators=[wtforms.validators.Required(), wtforms.validators.Email(), ValidEmailDomain()]) # TODO: Move to function and place before ValidEmailDomain() def validate_email(self, field): @@ -17,41 +18,41 @@ def validate_email(self, field): existing = UserEmail.query.filter_by(email=field.data).first() if existing is not None: if existing.user == g.user: - raise wtf.ValidationError("You have already registered this email address.") + raise wtforms.ValidationError("You have already registered this email address.") else: - raise wtf.ValidationError("This email address has already been claimed.") + raise wtforms.ValidationError("This email address has already been claimed.") existing = UserEmailClaim.query.filter_by(email=field.data, user=g.user).first() if existing is not None: - raise wtf.ValidationError("This email address is pending verification.") + raise wtforms.ValidationError("This email address is pending verification.") class NewPhoneForm(Form): - phone = wtf.TextField('Phone number', default='+91', validators=[wtf.Required()], + phone = wtforms.TextField('Phone number', default='+91', validators=[wtforms.validators.Required()], description="Indian mobile numbers only") def validate_phone(self, field): existing = UserPhone.query.filter_by(phone=field.data).first() if existing is not None: if existing.user == g.user: - raise wtf.ValidationError("You have already registered this phone number.") + raise wtforms.ValidationError("You have already registered this phone number.") else: - raise wtf.ValidationError("That phone number has already been claimed.") + raise wtforms.ValidationError("That phone number has already been claimed.") existing = UserPhoneClaim.query.filter_by(phone=field.data, user=g.user).first() if existing is not None: - raise wtf.ValidationError("That phone number is pending verification.") + raise wtforms.ValidationError("That phone number is pending verification.") # Step 1: Remove punctuation in number field.data = strip_phone(field.data) # Step 2: Validate number format if not valid_phone(field.data): - raise wtf.ValidationError("Invalid phone number (must be in international format with a leading + symbol)") + raise wtforms.ValidationError("Invalid phone number (must be in international format with a leading + symbol)") # Step 3: Check if Indian number (startswith('+91')) if not field.data.startswith('+91') or len(field.data) != 13: - raise wtf.ValidationError("Only Indian mobile numbers are allowed at this time") + raise wtforms.ValidationError("Only Indian mobile numbers are allowed at this time") class VerifyPhoneForm(Form): - verification_code = wtf.TextField('Verification code', validators=[wtf.Required()]) + verification_code = wtforms.TextField('Verification code', validators=[wtforms.validators.Required()]) def validate_verification_code(self, field): if self.phoneclaim.verification_code != field.data: - raise wtf.ValidationError("Verification code does not match.") + raise wtforms.ValidationError("Verification code does not match.") From 82fc2e7c00b41fe6f33d8c4d19ca684063cb6581 Mon Sep 17 00:00:00 2001 From: Mitesh Ashar Date: Tue, 20 Aug 2013 16:10:46 +0530 Subject: [PATCH 16/16] Added pypy to .travis.yml --- .travis.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 340533f..a4db4c4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,7 @@ language: python python: - - 2.7 + - "2.7" + - "pypy" install: - pip install -r requirements.txt --use-mirrors - pip install -r test_requirements.txt --use-mirrors