From 11eec8a365b2ce17a6b7a562c8888b140f30458c Mon Sep 17 00:00:00 2001 From: Alex Couper Date: Fri, 31 May 2013 12:11:10 +0100 Subject: [PATCH 01/20] Add initial users app. Taken largely from: https://github.com/mitsuhiko/flask/wiki/Large-app-how-to --- config/settings.py | 20 +++++- deploystream/__init__.py | 5 ++ deploystream/apps/users/__init__.py | 0 deploystream/apps/users/constants.py | 19 ++++++ deploystream/apps/users/decorators.py | 13 ++++ deploystream/apps/users/forms.py | 21 +++++++ deploystream/apps/users/models.py | 27 ++++++++ deploystream/apps/users/views.py | 73 ++++++++++++++++++++++ deploystream/templates/base-angular.html | 10 +++ deploystream/templates/base.html | 7 --- deploystream/templates/forms/macros.html | 12 ++++ deploystream/templates/index-async.html | 2 +- deploystream/templates/index.html | 11 +++- deploystream/templates/users/login.html | 11 ++++ deploystream/templates/users/profile.html | 4 ++ deploystream/templates/users/register.html | 16 +++++ scripts/shell.py | 9 +++ 17 files changed, 249 insertions(+), 11 deletions(-) create mode 100644 deploystream/apps/users/__init__.py create mode 100644 deploystream/apps/users/constants.py create mode 100644 deploystream/apps/users/decorators.py create mode 100644 deploystream/apps/users/forms.py create mode 100644 deploystream/apps/users/models.py create mode 100644 deploystream/apps/users/views.py create mode 100644 deploystream/templates/base-angular.html create mode 100644 deploystream/templates/forms/macros.html create mode 100644 deploystream/templates/users/login.html create mode 100644 deploystream/templates/users/profile.html create mode 100644 deploystream/templates/users/register.html create mode 100644 scripts/shell.py diff --git a/config/settings.py b/config/settings.py index 33843ed..78e88fe 100644 --- a/config/settings.py +++ b/config/settings.py @@ -1,12 +1,28 @@ from os import path, environ import traceback +_basedir = path.abspath(path.dirname(__file__)) -APP_PACKAGE = path.basename(path.dirname(__file__)) +APP_PACKAGE = path.basename(_basedir) + +# Default settings, overridden by the python file pointed to by CONFIG variable +SQLALCHEMY_DATABASE_URI = 'sqlite:///' + path.join(_basedir, 'app.db') +DATABASE_CONNECT_OPTIONS = {} + +# THREADS_PER_PAGE = 8 + +# CSRF_ENABLED = True +# CSRF_SESSION_KEY = "somethingimpossibletoguess" + +RECAPTCHA_USE_SSL = False +RECAPTCHA_PUBLIC_KEY = 'blahblahblahblahblahblahblahblahblah' +RECAPTCHA_PRIVATE_KEY = 'blahblahblahblahblahblahprivate' +RECAPTCHA_OPTIONS = {'theme': 'white'} + +GITHUB_CONFIG = GIT_CONFIG = SPRINTLY_CONFIG = JIRA_CONFIG = None # The following is the programmatic equivalent of # from deploystream.local_settings_ import * -GITHUB_CONFIG = GIT_CONFIG = SPRINTLY_CONFIG = JIRA_CONFIG = None try: CONFIG = environ.get('CONFIG', 'sample') diff --git a/deploystream/__init__.py b/deploystream/__init__.py index 263a040..cafa438 100644 --- a/deploystream/__init__.py +++ b/deploystream/__init__.py @@ -3,6 +3,7 @@ from os import environ from os.path import join, dirname from flask import Flask +from flask.ext.sqlalchemy import SQLAlchemy APP_DIR = dirname(__file__) CONFIG_DIR = join(dirname(APP_DIR), 'config') @@ -51,6 +52,7 @@ # something that is actually a secret app.secret_key = 'mysecret' +db = SQLAlchemy(app) # Initialise the providers. from providers import init_providers classes = init_providers(app.config['PROVIDERS']) @@ -62,3 +64,6 @@ # Import any views we want to register here at the bottom of the file: import deploystream.views # NOQA import deploystream.apps.feature.views # NOQA + +from deploystream.apps.users.views import mod as usersModule +app.register_blueprint(usersModule) diff --git a/deploystream/apps/users/__init__.py b/deploystream/apps/users/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/deploystream/apps/users/constants.py b/deploystream/apps/users/constants.py new file mode 100644 index 0000000..ab54a21 --- /dev/null +++ b/deploystream/apps/users/constants.py @@ -0,0 +1,19 @@ +# User role +ADMIN = 0 +STAFF = 1 +USER = 2 +ROLE = { + ADMIN: 'admin', + STAFF: 'staff', + USER: 'user', +} + +# user status +INACTIVE = 0 +NEW = 1 +ACTIVE = 2 +STATUS = { + INACTIVE: 'inactive', + NEW: 'new', + ACTIVE: 'active', +} diff --git a/deploystream/apps/users/decorators.py b/deploystream/apps/users/decorators.py new file mode 100644 index 0000000..aa0e4e1 --- /dev/null +++ b/deploystream/apps/users/decorators.py @@ -0,0 +1,13 @@ +from functools import wraps + +from flask import g, flash, redirect, url_for, request + + +def requires_login(f): + @wraps(f) + def decorated_function(*args, **kwargs): + if g.user is None: + flash(u'You need to be signed in for this page.') + return redirect(url_for('users.login', next=request.path)) + return f(*args, **kwargs) + return decorated_function diff --git a/deploystream/apps/users/forms.py b/deploystream/apps/users/forms.py new file mode 100644 index 0000000..71743d1 --- /dev/null +++ b/deploystream/apps/users/forms.py @@ -0,0 +1,21 @@ +from flask.ext.wtf import ( + Form, TextField, PasswordField, BooleanField, RecaptchaField +) +from flask.ext.wtf import Required, Email, EqualTo + + +class LoginForm(Form): + email = TextField('Email address', [Required(), Email()]) + password = PasswordField('Password', [Required()]) + + +class RegisterForm(Form): + name = TextField('NickName', [Required()]) + email = TextField('Email address', [Required(), Email()]) + password = PasswordField('Password', [Required()]) + confirm = PasswordField('Repeat Password', [ + Required(), + EqualTo('password', message='Passwords must match') + ]) + accept_tos = BooleanField('I accept the TOS', [Required()]) + #recaptcha = RecaptchaField() diff --git a/deploystream/apps/users/models.py b/deploystream/apps/users/models.py new file mode 100644 index 0000000..8ea66bd --- /dev/null +++ b/deploystream/apps/users/models.py @@ -0,0 +1,27 @@ +from deploystream import db +from deploystream.apps.users import constants as USER + + +class User(db.Model): + + __tablename__ = 'users_user' + id = db.Column(db.Integer, primary_key=True) + name = db.Column(db.String(50), unique=True) + email = db.Column(db.String(120), unique=True) + password = db.Column(db.String(20)) + role = db.Column(db.SmallInteger, default=USER.USER) + status = db.Column(db.SmallInteger, default=USER.NEW) + + def __init__(self, name=None, email=None, password=None): + self.name = name + self.email = email + self.password = password + + def getStatus(self): + return USER.STATUS[self.status] + + def getRole(self): + return USER.ROLE[self.role] + + def __repr__(self): + return '' % (self.name) diff --git a/deploystream/apps/users/views.py b/deploystream/apps/users/views.py new file mode 100644 index 0000000..c7ba9cf --- /dev/null +++ b/deploystream/apps/users/views.py @@ -0,0 +1,73 @@ +from flask import (Blueprint, request, render_template, flash, g, session, + redirect, url_for) +from werkzeug import check_password_hash, generate_password_hash + +from deploystream import db +from deploystream.apps.users.forms import RegisterForm, LoginForm +from deploystream.apps.users.models import User +from deploystream.apps.users.decorators import requires_login + +mod = Blueprint('users', __name__, url_prefix='/users') + + +@mod.route('/me/') +@requires_login +def home(): + return render_template("users/profile.html", user=g.user) + + +@mod.before_request +def before_request(): + """ + pull user's profile from the database before every request are treated + """ + g.user = None + if 'user_id' in session: + g.user = User.query.get(session['user_id']) + + +@mod.route('/login/', methods=['GET', 'POST']) +def login(): + """ + Login form + """ + form = LoginForm(request.form) + # make sure data are valid, but doesn't validate password is right + if form.validate_on_submit(): + user = User.query.filter_by(email=form.email.data).first() + # we use werzeug to validate user's password + if user and check_password_hash(user.password, form.password.data): + # the session can't be modified as it's signed, + # it's a safe place to store the user id + session['user_id'] = user.id + flash('Welcome %s' % user.name) + return redirect(url_for('users.home')) + flash('Wrong email or password', 'error-message') + return render_template("users/login.html", form=form) + + +@mod.route('/register/', methods=['GET', 'POST']) +def register(): + """ + Registration Form + """ + form = RegisterForm(request.form) + if form.validate_on_submit(): + print "VALID" + # create an user instance not yet stored in the database + user = User(form.name.data, form.email.data, \ + generate_password_hash(form.password.data)) + # Insert the record in our database and commit it + db.session.add(user) + db.session.commit() + + # Log the user in, as he now has an id + session['user_id'] = user.id + + # flash will display a message to the user + flash('Thanks for registering') + # redirect user to the 'home' method of the user module. + return redirect(url_for('users.home')) + else: + print form.__dict__ + return render_template("users/register.html", form=form) diff --git a/deploystream/templates/base-angular.html b/deploystream/templates/base-angular.html new file mode 100644 index 0000000..14535f0 --- /dev/null +++ b/deploystream/templates/base-angular.html @@ -0,0 +1,10 @@ +{% extends "base.html" %} + +{% block extrahead %} + +{% endblock %} diff --git a/deploystream/templates/base.html b/deploystream/templates/base.html index e36bb90..24fe0f6 100644 --- a/deploystream/templates/base.html +++ b/deploystream/templates/base.html @@ -6,13 +6,6 @@ DeployStream {% block extratitle %}{% endblock %} - - diff --git a/deploystream/templates/forms/macros.html b/deploystream/templates/forms/macros.html new file mode 100644 index 0000000..9496292 --- /dev/null +++ b/deploystream/templates/forms/macros.html @@ -0,0 +1,12 @@ +{% macro render_field(field) %} +
+ {{ field.label(class="label") }} + {% if field.errors %} + {% set css_class = 'has_error ' + kwargs.pop('class', '') %} + {{ field(class=css_class, **kwargs) }} +
    {% for error in field.errors %}
  • {{ error|e }}
  • {% endfor %}
+ {% else %} + {{ field(**kwargs) }} + {% endif %} +
+{% endmacro %} diff --git a/deploystream/templates/index-async.html b/deploystream/templates/index-async.html index f8ac747..5e8fa8f 100644 --- a/deploystream/templates/index-async.html +++ b/deploystream/templates/index-async.html @@ -1,4 +1,4 @@ -{% extends "base.html" %} +{% extends "base-angular.html" %} {% block scripts %} diff --git a/deploystream/templates/users/login.html b/deploystream/templates/users/login.html new file mode 100644 index 0000000..f68bbc2 --- /dev/null +++ b/deploystream/templates/users/login.html @@ -0,0 +1,11 @@ +{% extends "base.html" %} +{% block content %} + {% from "forms/macros.html" import render_field %} +
+ {{ form.csrf_token }} + {{ render_field(form.email, class="input text") }} + {{ render_field(form.password, class="input text") }} + +
+ Register +{% endblock %} diff --git a/deploystream/templates/users/profile.html b/deploystream/templates/users/profile.html new file mode 100644 index 0000000..11e2df1 --- /dev/null +++ b/deploystream/templates/users/profile.html @@ -0,0 +1,4 @@ +{% extends "base.html" %} +{% block content %} + {{ user.name }} +{% endblock %} diff --git a/deploystream/templates/users/register.html b/deploystream/templates/users/register.html new file mode 100644 index 0000000..4587062 --- /dev/null +++ b/deploystream/templates/users/register.html @@ -0,0 +1,16 @@ +{% extends "base.html" %} +{% block content %} + {% from "forms/macros.html" import render_field %} +
+ {{ form.csrf_token }} + {{ render_field(form.name, class="input text") }} + {{ render_field(form.email, class="input text") }} + {{ render_field(form.password, class="input text") }} + {{ render_field(form.confirm, class="input text") }} + {{ render_field(form.accept_tos, class="input checkbox") }} + + {{ form.recaptcha }} + +
+ Login +{% endblock %} diff --git a/scripts/shell.py b/scripts/shell.py new file mode 100644 index 0000000..156a0d0 --- /dev/null +++ b/scripts/shell.py @@ -0,0 +1,9 @@ +#!/usr/bin/env python +import os +import readline +from pprint import pprint + +from flask import * +from deploystream import * + +os.environ['PYTHONINSPECT'] = 'True' From f93dc78287f502de3dc27c0a76249a8d321c42dd Mon Sep 17 00:00:00 2001 From: Alex Couper Date: Fri, 31 May 2013 12:14:44 +0100 Subject: [PATCH 02/20] Add script for creating the DB --- scripts/create_db.py | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 scripts/create_db.py diff --git a/scripts/create_db.py b/scripts/create_db.py new file mode 100644 index 0000000..f6d7cca --- /dev/null +++ b/scripts/create_db.py @@ -0,0 +1,3 @@ +from deploystream import db + +db.create_all() From e60dce42b7d98e265bb06ccf29fb5a813797ef3e Mon Sep 17 00:00:00 2001 From: Alex Couper Date: Fri, 31 May 2013 16:08:57 +0100 Subject: [PATCH 03/20] Allow sign in with github or directly with the site. --- deploystream/apps/oauth/views.py | 27 ++++++++++++++++++----- deploystream/apps/users/lib.py | 3 +++ deploystream/apps/users/models.py | 14 +++++++++++- deploystream/apps/users/views.py | 22 +++++++++--------- deploystream/static/partials/home.html | 2 +- deploystream/templates/forms/macros.html | 6 ++--- deploystream/templates/navbar.html | 9 ++++++-- deploystream/templates/users/login.html | 6 ++++- requirements/runtime.txt | 2 ++ tests/test_providers/test_git_provider.py | 7 +++++- 10 files changed, 72 insertions(+), 26 deletions(-) create mode 100644 deploystream/apps/users/lib.py diff --git a/deploystream/apps/oauth/views.py b/deploystream/apps/oauth/views.py index 5a31280..1ec292d 100644 --- a/deploystream/apps/oauth/views.py +++ b/deploystream/apps/oauth/views.py @@ -1,8 +1,10 @@ from flask import session, redirect, flash, request, url_for from flask_oauth import OAuth -from deploystream import app +from deploystream import app, db from deploystream.apps.oauth import get_token, set_token +from deploystream.apps.users.models import User, OAuth as UserOAuth +from deploystream.apps.users.lib import load_user_to_session from deploystream.providers.interfaces import class_implements, IOAuthProvider @@ -63,14 +65,29 @@ def oauth_authorized(resp): set_token(session, oauth_name, resp['access_token']) if request.args.get('islogin'): - user = OAUTH_OBJECTS[oauth_name].get('/user') - username = user.data['login'] - session['username'] = username + remote_user = OAUTH_OBJECTS[oauth_name].get('/user') + remote_user_id = remote_user.data['id'] + # Save user if doesn't already exist. + oauth_obj = UserOAuth.query.filter_by(service_user_id=remote_user_id, + service=oauth_name).first() + if not oauth_obj: + # Create a user and an OAuth linked to it. + user = User(name=remote_user.data['login']) + oauth = UserOAuth(service_user_id=remote_user_id, + service=oauth_name) + oauth.user = user + db.session.add(user) + db.session.add(oauth) + db.session.commit() + else: + user = oauth_obj.user + + load_user_to_session(session, user) return redirect(next_url) -@app.route('/login') +@app.route('/github-login') def login(): "Handler for calls to login via github." return start_token_processing('github', islogin=True) diff --git a/deploystream/apps/users/lib.py b/deploystream/apps/users/lib.py new file mode 100644 index 0000000..7ba0af4 --- /dev/null +++ b/deploystream/apps/users/lib.py @@ -0,0 +1,3 @@ +def load_user_to_session(session, user): + session['user_id'] = user.id + session['user_name'] = user.name diff --git a/deploystream/apps/users/models.py b/deploystream/apps/users/models.py index 8ea66bd..60391b2 100644 --- a/deploystream/apps/users/models.py +++ b/deploystream/apps/users/models.py @@ -6,11 +6,12 @@ class User(db.Model): __tablename__ = 'users_user' id = db.Column(db.Integer, primary_key=True) - name = db.Column(db.String(50), unique=True) + name = db.Column(db.String(50)) email = db.Column(db.String(120), unique=True) password = db.Column(db.String(20)) role = db.Column(db.SmallInteger, default=USER.USER) status = db.Column(db.SmallInteger, default=USER.NEW) + oauth_keys = db.relationship('OAuth', backref='user') def __init__(self, name=None, email=None, password=None): self.name = name @@ -25,3 +26,14 @@ def getRole(self): def __repr__(self): return '' % (self.name) + + +class OAuth(db.Model): + + __tablename__ = 'users_oauth' + id = db.Column(db.Integer, primary_key=True) + user_id = db.Column(db.Integer, db.ForeignKey('users_user.id')) + service = db.Column(db.String(20)) + service_user_id = db.Column(db.String(120)) + + db.UniqueConstraint('service', 'service_user_id', name='service_user') diff --git a/deploystream/apps/users/views.py b/deploystream/apps/users/views.py index c7ba9cf..5853850 100644 --- a/deploystream/apps/users/views.py +++ b/deploystream/apps/users/views.py @@ -3,9 +3,10 @@ from werkzeug import check_password_hash, generate_password_hash from deploystream import db -from deploystream.apps.users.forms import RegisterForm, LoginForm -from deploystream.apps.users.models import User -from deploystream.apps.users.decorators import requires_login +from .forms import RegisterForm, LoginForm +from .models import User +from .lib import load_user_to_session +from .decorators import requires_login mod = Blueprint('users', __name__, url_prefix='/users') @@ -37,9 +38,8 @@ def login(): user = User.query.filter_by(email=form.email.data).first() # we use werzeug to validate user's password if user and check_password_hash(user.password, form.password.data): - # the session can't be modified as it's signed, - # it's a safe place to store the user id - session['user_id'] = user.id + load_user_to_session(session, user) + flash('Welcome %s' % user.name) return redirect(url_for('users.home')) flash('Wrong email or password', 'error-message') @@ -53,10 +53,9 @@ def register(): """ form = RegisterForm(request.form) if form.validate_on_submit(): - print "VALID" - # create an user instance not yet stored in the database - user = User(form.name.data, form.email.data, \ - generate_password_hash(form.password.data)) + # create a user instance not yet stored in the database + user = User(form.name.data, form.email.data, + generate_password_hash(form.password.data)) # Insert the record in our database and commit it db.session.add(user) db.session.commit() @@ -68,6 +67,5 @@ def register(): flash('Thanks for registering') # redirect user to the 'home' method of the user module. return redirect(url_for('users.home')) - else: - print form.__dict__ + return render_template("users/register.html", form=form) diff --git a/deploystream/static/partials/home.html b/deploystream/static/partials/home.html index 4248b82..dce9602 100644 --- a/deploystream/static/partials/home.html +++ b/deploystream/static/partials/home.html @@ -10,7 +10,7 @@

Welcome to Ployst!

from various tools.

- + Sign in via Github

diff --git a/deploystream/templates/forms/macros.html b/deploystream/templates/forms/macros.html index 9496292..5d3f1e0 100644 --- a/deploystream/templates/forms/macros.html +++ b/deploystream/templates/forms/macros.html @@ -1,8 +1,8 @@ {% macro render_field(field) %} -
- {{ field.label(class="label") }} +
+ {{ field.label(class="control-label") }} {% if field.errors %} - {% set css_class = 'has_error ' + kwargs.pop('class', '') %} + {% set css_class = 'has_error ' + kwargs.pop('class', 'input-xlarge') %} {{ field(class=css_class, **kwargs) }}
    {% for error in field.errors %}
  • {{ error|e }}
  • {% endfor %}
{% else %} diff --git a/deploystream/templates/navbar.html b/deploystream/templates/navbar.html index b2a31db..5b625b6 100644 --- a/deploystream/templates/navbar.html +++ b/deploystream/templates/navbar.html @@ -10,14 +10,14 @@ [Ployst]
diff --git a/deploystream/templates/users/login.html b/deploystream/templates/users/login.html index f68bbc2..21ee7b5 100644 --- a/deploystream/templates/users/login.html +++ b/deploystream/templates/users/login.html @@ -1,7 +1,11 @@ {% extends "base.html" %} + {% block content %} {% from "forms/macros.html" import render_field %} -
+ +
+ Login +
{{ form.csrf_token }} {{ render_field(form.email, class="input text") }} {{ render_field(form.password, class="input text") }} diff --git a/requirements/runtime.txt b/requirements/runtime.txt index 59abf03..954f000 100644 --- a/requirements/runtime.txt +++ b/requirements/runtime.txt @@ -1,6 +1,8 @@ docopt # create command-line interface from docstring Flask # web framework Flask-OAuth # Oauth library for flask +flask-sqlalchemy # SQLAlchemy support for flask +Flask-WTF # Handle User's data submission github3.py # client for the GitHub v3 API GitPython # access git repositories zope.interface # define and enforce interfaces diff --git a/tests/test_providers/test_git_provider.py b/tests/test_providers/test_git_provider.py index 116b02f..0f0d4d2 100644 --- a/tests/test_providers/test_git_provider.py +++ b/tests/test_providers/test_git_provider.py @@ -15,11 +15,16 @@ def ensure_dummy_clone_available(): if not os.path.exists(DUMMY_CODE_DIR): os.mkdir(DUMMY_CODE_DIR) folder_name = join(DUMMY_CODE_DIR, 'dummyrepo') + if not exists(folder_name): + os.system('git clone git://github.com/pretenders/dummyrepo.git {0}' .format(folder_name)) else: - os.system('git --git-dir={0} fetch'.format(folder_name)) + print "RUNNING GIT FETCH" + cmd = 'git --git-dir={0}/.git fetch'.format(folder_name) + print cmd + os.system(cmd) @with_setup(ensure_dummy_clone_available) From 7499a872ce7ba7392b73b295c3f7393a856d1bee Mon Sep 17 00:00:00 2001 From: Alex Couper Date: Fri, 31 May 2013 16:11:39 +0100 Subject: [PATCH 04/20] Fix git fetch of test repo. --- tests/test_providers/test_git_provider.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/tests/test_providers/test_git_provider.py b/tests/test_providers/test_git_provider.py index 0f0d4d2..112c7cd 100644 --- a/tests/test_providers/test_git_provider.py +++ b/tests/test_providers/test_git_provider.py @@ -15,16 +15,14 @@ def ensure_dummy_clone_available(): if not os.path.exists(DUMMY_CODE_DIR): os.mkdir(DUMMY_CODE_DIR) folder_name = join(DUMMY_CODE_DIR, 'dummyrepo') - if not exists(folder_name): - os.system('git clone git://github.com/pretenders/dummyrepo.git {0}' .format(folder_name)) else: - print "RUNNING GIT FETCH" cmd = 'git --git-dir={0}/.git fetch'.format(folder_name) - print cmd - os.system(cmd) + ans = os.system(cmd) + if ans != 0: + raise Exception("Git fetch failed") @with_setup(ensure_dummy_clone_available) @@ -53,7 +51,7 @@ def test_git_provider_feature_breakup_regex(): Test that GitProvider breaks up feature ids into appropriate parts. """ provider = GitProvider( - feature_breakup_regex="(?P[a-zA-Z]+)-?(?P[0-9]+)") + feature_breakup_regex="(?P[a-zA-Z]+)-?(?P[0-9]+)") for feature, expected in [ ('DD-334', {'id': '334', 'project':'DD'}), ('DD334', {'id': '334', 'project':'DD'}), From d5317d6e0c18adc53155f4f19272928c8c25b5d6 Mon Sep 17 00:00:00 2001 From: Alex Couper Date: Fri, 31 May 2013 16:34:34 +0100 Subject: [PATCH 05/20] Add tests. --- deploystream/apps/users/forms.py | 2 +- deploystream/templates/users/register.html | 2 -- tests/test_users/__init__.py | 0 tests/test_users/test_views.py | 40 ++++++++++++++++++++++ 4 files changed, 41 insertions(+), 3 deletions(-) create mode 100644 tests/test_users/__init__.py create mode 100644 tests/test_users/test_views.py diff --git a/deploystream/apps/users/forms.py b/deploystream/apps/users/forms.py index 71743d1..c6409cb 100644 --- a/deploystream/apps/users/forms.py +++ b/deploystream/apps/users/forms.py @@ -10,7 +10,7 @@ class LoginForm(Form): class RegisterForm(Form): - name = TextField('NickName', [Required()]) + name = TextField('Name', [Required()]) email = TextField('Email address', [Required(), Email()]) password = PasswordField('Password', [Required()]) confirm = PasswordField('Repeat Password', [ diff --git a/deploystream/templates/users/register.html b/deploystream/templates/users/register.html index 4587062..2be4926 100644 --- a/deploystream/templates/users/register.html +++ b/deploystream/templates/users/register.html @@ -8,8 +8,6 @@ {{ render_field(form.password, class="input text") }} {{ render_field(form.confirm, class="input text") }} {{ render_field(form.accept_tos, class="input checkbox") }} - - {{ form.recaptcha }} Login diff --git a/tests/test_users/__init__.py b/tests/test_users/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_users/test_views.py b/tests/test_users/test_views.py new file mode 100644 index 0000000..156de3d --- /dev/null +++ b/tests/test_users/test_views.py @@ -0,0 +1,40 @@ +from nose.tools import assert_true, assert_equal, assert_false + +import deploystream + + +class TestRegister(object): + + def setup(self): + self.client = deploystream.app.test_client() + + def send_register_post(self, email, password='123', + confirm_password='123'): + return self.client.post('/users/register', + {'name': 'testuser', 'email': email, 'password': password, + 'confirm': confirm_password, 'accept_tos': True}) + + def test_adds_user_to_the_database(self): + response = self.send_register_post('test@test.com') + + assert_equal(response.status_code, 302) + assert_true("/users/me" in response.location) + assert_false("NEED TO CHECK IN THE DB FOR THE OBJECT HERE") + + def test_incomplete_when_email_already_exists(self): + response = self.send_register_post('test@test.com') + assert_true("/users/me" in response.location) + + response = self.send_register_post('test@test.com') + assert_true("This user already exists" in response.data) + + def test_incomplete_when_passwords_do_not_match(self): + response = self.send_register_post('test@test.com', '123', '111') + + assert_true("Passwords must match" in response.data) + + +class TestLogin(object): + + def test_login_to_existing_user_account(): + raise NotImplementedError() From f05976b8e8da1ebd68a406003fd5f8ffed4c6dc2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carles=20Barrob=C3=A9s?= Date: Sat, 1 Jun 2013 14:38:02 +0100 Subject: [PATCH 06/20] Add bootstrap styles for forms --- deploystream/static/less/bootstrap-modules.less | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deploystream/static/less/bootstrap-modules.less b/deploystream/static/less/bootstrap-modules.less index 798c5e0..8afb5bf 100644 --- a/deploystream/static/less/bootstrap-modules.less +++ b/deploystream/static/less/bootstrap-modules.less @@ -13,7 +13,7 @@ // Base CSS @import "bootstrap/type.less"; @import "bootstrap/code.less"; -// @import "bootstrap/forms.less"; +@import "bootstrap/forms.less"; @import "bootstrap/tables.less"; // Components: common From 5744d5d1ec826f29b58bcfeb9c617d567b2fadc3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carles=20Barrob=C3=A9s?= Date: Sat, 1 Jun 2013 14:38:35 +0100 Subject: [PATCH 07/20] Make create_db script executable --- scripts/create_db.py | 1 + 1 file changed, 1 insertion(+) mode change 100644 => 100755 scripts/create_db.py diff --git a/scripts/create_db.py b/scripts/create_db.py old mode 100644 new mode 100755 index f6d7cca..93e8470 --- a/scripts/create_db.py +++ b/scripts/create_db.py @@ -1,3 +1,4 @@ +#!/usr/bin/env python from deploystream import db db.create_all() From 22fa34bc4e14408f6ca424cdf897889f9bcbd31a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carles=20Barrob=C3=A9s?= Date: Sat, 1 Jun 2013 15:10:41 +0100 Subject: [PATCH 08/20] Improved bootstrap form layout --- deploystream/templates/forms/macros.html | 18 ++++++++++-------- deploystream/templates/users/login.html | 16 +++++++++++----- deploystream/templates/users/register.html | 15 ++++++++++++--- 3 files changed, 33 insertions(+), 16 deletions(-) diff --git a/deploystream/templates/forms/macros.html b/deploystream/templates/forms/macros.html index 5d3f1e0..af8b188 100644 --- a/deploystream/templates/forms/macros.html +++ b/deploystream/templates/forms/macros.html @@ -1,12 +1,14 @@ {% macro render_field(field) %}
- {{ field.label(class="control-label") }} - {% if field.errors %} - {% set css_class = 'has_error ' + kwargs.pop('class', 'input-xlarge') %} - {{ field(class=css_class, **kwargs) }} -
    {% for error in field.errors %}
  • {{ error|e }}
  • {% endfor %}
- {% else %} - {{ field(**kwargs) }} - {% endif %} + {{ field.label(class="control-label") }} +
+ {% if field.errors %} + {% set css_class = 'has_error ' + kwargs.pop('class', 'input-xlarge') %} + {{ field(class=css_class, **kwargs) }} +
    {% for error in field.errors %}
  • {{ error|e }}
  • {% endfor %}
+ {% else %} + {{ field(**kwargs) }} + {% endif %} +
{% endmacro %} diff --git a/deploystream/templates/users/login.html b/deploystream/templates/users/login.html index 21ee7b5..0a3dd6b 100644 --- a/deploystream/templates/users/login.html +++ b/deploystream/templates/users/login.html @@ -1,15 +1,21 @@ {% extends "base.html" %} {% block content %} + {% from "forms/macros.html" import render_field %}
-
- Login -
{{ form.csrf_token }} {{ render_field(form.email, class="input text") }} {{ render_field(form.password, class="input text") }} - +
+ +
- Register {% endblock %} diff --git a/deploystream/templates/users/register.html b/deploystream/templates/users/register.html index 2be4926..c6c6ef8 100644 --- a/deploystream/templates/users/register.html +++ b/deploystream/templates/users/register.html @@ -1,14 +1,23 @@ {% extends "base.html" %} {% block content %} + {% from "forms/macros.html" import render_field %} -
+ {{ form.csrf_token }} {{ render_field(form.name, class="input text") }} {{ render_field(form.email, class="input text") }} {{ render_field(form.password, class="input text") }} {{ render_field(form.confirm, class="input text") }} {{ render_field(form.accept_tos, class="input checkbox") }} - +
+ +
- Login {% endblock %} From f2eb401d0997dab7adb3ddd6196a2b4b0a5fa74a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carles=20Barrob=C3=A9s?= Date: Sun, 2 Jun 2013 00:43:45 +0100 Subject: [PATCH 09/20] Change the approach to login by using a popover --- deploystream/apps/users/views.py | 6 ++++- deploystream/templates/base.html | 25 ++++++++++++++++---- deploystream/templates/navbar.html | 4 +++- deploystream/templates/users/login_ajax.html | 9 +++++++ 4 files changed, 38 insertions(+), 6 deletions(-) create mode 100644 deploystream/templates/users/login_ajax.html diff --git a/deploystream/apps/users/views.py b/deploystream/apps/users/views.py index 5853850..8e8d731 100644 --- a/deploystream/apps/users/views.py +++ b/deploystream/apps/users/views.py @@ -43,7 +43,11 @@ def login(): flash('Welcome %s' % user.name) return redirect(url_for('users.home')) flash('Wrong email or password', 'error-message') - return render_template("users/login.html", form=form) + if request.method == 'POST': + suffix = ".html" + else: + suffix = "_ajax.html" + return render_template("users/login" + suffix, form=form) @mod.route('/register/', methods=['GET', 'POST']) diff --git a/deploystream/templates/base.html b/deploystream/templates/base.html index 24fe0f6..bb39707 100644 --- a/deploystream/templates/base.html +++ b/deploystream/templates/base.html @@ -11,6 +11,11 @@ + + + + + {% block extrahead %} {% endblock %} {% endblock %} @@ -36,10 +41,22 @@ - - - - + {% block scripts %}{% endblock %} diff --git a/deploystream/templates/navbar.html b/deploystream/templates/navbar.html index 5b625b6..f565fde 100644 --- a/deploystream/templates/navbar.html +++ b/deploystream/templates/navbar.html @@ -28,7 +28,9 @@ {% else %} {% endif %} diff --git a/deploystream/templates/users/login_ajax.html b/deploystream/templates/users/login_ajax.html new file mode 100644 index 0000000..b347cf9 --- /dev/null +++ b/deploystream/templates/users/login_ajax.html @@ -0,0 +1,9 @@ +{% from "forms/macros.html" import render_field %} +
+ {{ form.csrf_token }} + {{ render_field(form.email, class="input text", autofocus="autofocus") }} + {{ render_field(form.password, class="input text") }} +
+ +
+
From ac402f6a170d2563137935a04892348aa50116f3 Mon Sep 17 00:00:00 2001 From: Alex Couper Date: Sun, 2 Jun 2013 01:39:50 +0100 Subject: [PATCH 10/20] Template tidy up. --- config/settings.py | 8 ++++---- deploystream/static/partials/home.html | 2 +- deploystream/templates/index.html | 9 --------- deploystream/templates/navbar.html | 4 ++-- 4 files changed, 7 insertions(+), 16 deletions(-) diff --git a/config/settings.py b/config/settings.py index 78e88fe..47dad00 100644 --- a/config/settings.py +++ b/config/settings.py @@ -14,10 +14,10 @@ # CSRF_ENABLED = True # CSRF_SESSION_KEY = "somethingimpossibletoguess" -RECAPTCHA_USE_SSL = False -RECAPTCHA_PUBLIC_KEY = 'blahblahblahblahblahblahblahblahblah' -RECAPTCHA_PRIVATE_KEY = 'blahblahblahblahblahblahprivate' -RECAPTCHA_OPTIONS = {'theme': 'white'} +# RECAPTCHA_USE_SSL = False +# RECAPTCHA_PUBLIC_KEY = 'blahblahblahblahblahblahblahblahblah' +# RECAPTCHA_PRIVATE_KEY = 'blahblahblahblahblahblahprivate' +# RECAPTCHA_OPTIONS = {'theme': 'white'} GITHUB_CONFIG = GIT_CONFIG = SPRINTLY_CONFIG = JIRA_CONFIG = None diff --git a/deploystream/static/partials/home.html b/deploystream/static/partials/home.html index dce9602..3334a2d 100644 --- a/deploystream/static/partials/home.html +++ b/deploystream/static/partials/home.html @@ -10,7 +10,7 @@

Welcome to Ployst!

from various tools.

- + Sign in via Github

diff --git a/deploystream/templates/index.html b/deploystream/templates/index.html index 5118926..f9b8f15 100644 --- a/deploystream/templates/index.html +++ b/deploystream/templates/index.html @@ -1,14 +1,5 @@ {% extends "base-angular.html" %} -{% block extrahead %} - -{% endblock %} - {% block scripts %} diff --git a/deploystream/templates/navbar.html b/deploystream/templates/navbar.html index 5b625b6..20b9685 100644 --- a/deploystream/templates/navbar.html +++ b/deploystream/templates/navbar.html @@ -12,7 +12,7 @@