diff --git a/app/__init__.py b/app/__init__.py
index fb0ef3a9..6692e23c 100644
--- a/app/__init__.py
+++ b/app/__init__.py
@@ -1,7 +1,14 @@
from flask import *
from flask_user import *
+import flask_menu as menu
+from flask.ext import markdown
+from flask_github import GitHub
app = Flask(__name__)
app.config.from_pyfile('../config.cfg')
+menu.Menu(app=app)
+markdown.Markdown(app, extensions=['fenced_code'])
+github = GitHub(app)
+
import models, views
diff --git a/app/models.py b/app/models.py
index 61293306..97dc3c93 100644
--- a/app/models.py
+++ b/app/models.py
@@ -1,5 +1,5 @@
from flask import Flask, url_for
-from flask.ext.sqlalchemy import SQLAlchemy
+from flask_sqlalchemy import SQLAlchemy
from app import app
from datetime import datetime
from sqlalchemy.orm import validates
@@ -22,6 +22,10 @@ class User(db.Model, UserMixin):
password = db.Column(db.String(255), nullable=False, server_default='')
reset_password_token = db.Column(db.String(100), nullable=False, server_default='')
+ # Account linking
+ github_username = db.Column(db.String(50), nullable=True, unique=True)
+ forums_username = db.Column(db.String(50), nullable=True, unique=True)
+
# User email information
email = db.Column(db.String(255), nullable=True, unique=True)
confirmed_at = db.Column(db.DateTime())
@@ -38,6 +42,7 @@ class User(db.Model, UserMixin):
self.username = username
self.confirmed_at = datetime.datetime.now() - datetime.timedelta(days=6000)
+ self.display_name = username
def isClaimed(self):
return self.password is not None and self.password != ""
diff --git a/app/templates/flask_user/login.html b/app/templates/flask_user/login.html
index 44274bbc..3d36c206 100644
--- a/app/templates/flask_user/login.html
+++ b/app/templates/flask_user/login.html
@@ -68,9 +68,16 @@ Sign in
New here?
{% if user_manager.enable_register and not user_manager.require_invitation %}
- {%trans%}Create an account{%endtrans%}
+ {%trans%}Create an account{%endtrans%}
{% endif %}
+
+
+
{% endblock %}
diff --git a/app/views/__init__.py b/app/views/__init__.py
index deb5ac5b..bfe8c1b0 100644
--- a/app/views/__init__.py
+++ b/app/views/__init__.py
@@ -3,72 +3,20 @@ from flask import *
from flask_user import *
from flask_login import login_user, logout_user
from app.models import *
-from flask.ext import menu, markdown
+import flask_menu as menu
+from flask.ext import markdown
from sqlalchemy import func
from werkzeug.contrib.cache import SimpleCache
cache = SimpleCache()
-menu.Menu(app=app)
-markdown.Markdown(app, extensions=['fenced_code'])
-
# TODO: remove on production!
@app.route('/static/')
def send_static(path):
return send_from_directory('static', path)
+import users, githublogin
+
@app.route('/')
@menu.register_menu(app, '.', 'Home')
def home_page():
return render_template('index.html')
-
-# Define the User registration form
-# It augments the Flask-User RegisterForm with additional fields
-from flask_user.forms import RegisterForm
-from flask_wtf import FlaskForm
-from wtforms import StringField, SubmitField, validators
-class MyRegisterForm(RegisterForm):
- first_name = StringField('First name', validators=[
- validators.DataRequired('First name is required')])
- last_name = StringField('Last name', validators=[
- validators.DataRequired('Last name is required')])
-
-# Define the User profile form
-class UserProfileForm(FlaskForm):
- first_name = StringField('First name', validators=[
- validators.DataRequired('First name is required')])
- last_name = StringField('Last name', validators=[
- validators.DataRequired('Last name is required')])
- submit = SubmitField('Save')
-
-@app.route('/user/', methods=['GET', 'POST'])
-@app.route('/user//', methods=['GET'])
-def user_profile_page(username=None):
- user = None
- form = None
- if username is None:
- if not current_user.is_authenticated:
- return current_app.login_manager.unauthorized()
- user = current_user
- else:
- user = User.query.filter_by(username=username).first()
- if not user:
- abort(404)
-
- if user == current_user:
- # Initialize form
- form = UserProfileForm(request.form, current_user)
-
- # Process valid POST
- if request.method=='POST' and form.validate():
- # Copy form fields to user_profile fields
- form.populate_obj(current_user)
-
- # Save user_profile
- db.session.commit()
-
- # Redirect to home page
- return redirect(url_for('home_page'))
-
- # Process GET or invalid POST
- return render_template('users/user_profile_page.html',
- user=user, form=form)
diff --git a/app/views/githublogin.py b/app/views/githublogin.py
new file mode 100644
index 00000000..327fa8ef
--- /dev/null
+++ b/app/views/githublogin.py
@@ -0,0 +1,100 @@
+from flask import *
+from flask_user import *
+from flask_login import login_user, logout_user
+import flask_menu as menu
+from flask_github import GitHub
+from app import app, github
+from app.models import *
+
+
+@app.route('/user/github/start/')
+def github_signin_page():
+ return github.authorize("public_repo,repo")
+
+
+def _do_login_user(user, remember_me=False):
+ def _call_or_get(v):
+ if callable(v):
+ return v()
+ else:
+ return v
+
+ # User must have been authenticated
+ if not user:
+ return False
+
+ user.active = True
+ db.session.commit()
+
+ # Check if user account has been disabled
+ if not _call_or_get(user.is_active):
+ flash('Your account has not been enabled.', 'error')
+ return False
+
+ # Check if user has a confirmed email address
+ user_manager = current_app.user_manager
+ if user_manager.enable_email and user_manager.enable_confirm_email \
+ and not current_app.user_manager.enable_login_without_confirm_email \
+ and not user.has_confirmed_email():
+ url = url_for('user.resend_confirm_email')
+ flash("Your email address has not yet been confirmed", 'error')
+ return False
+
+ # Use Flask-Login to sign in user
+ login_user(user, remember=remember_me)
+ signals.user_logged_in.send(current_app._get_current_object(), user=user)
+
+ flash('You have signed in successfully.', 'success')
+
+ return True
+
+
+
+def _login_user(user):
+ user_mixin = None
+ if user_manager.enable_username:
+ user_mixin = user_manager.find_user_by_username(user.username)
+
+ return _do_login_user(user_mixin, False)
+
+
+
+@app.route('/user/github/callback/')
+@github.authorized_handler
+def github_authorized(oauth_token):
+ next_url = request.args.get('next')
+ if oauth_token is None:
+ flash("Authorization failed [err=gh-oauth-login-failed]", "danger")
+ return redirect(url_for("user.login"))
+
+ import requests
+
+ # Get Github username
+ url = "https://api.github.com/user"
+ r = requests.get(url, headers={"Authorization": "token " + oauth_token})
+ username = r.json()["login"]
+
+ # Get user by github username
+ userByGithub = User.query.filter_by(github_username=username).first()
+
+ # If logged in, connect
+ if current_user and current_user.is_authenticated:
+ if userByGithub is None:
+ current_user.github_username = username
+ db.session.add(auth)
+ db.session.commit()
+ return redirect(url_for("gitAccount", id=auth.id))
+ else:
+ flash("Github account is already associated with another user", "danger")
+ return redirect(url_for("home_page"))
+
+ # If not logged in, log in
+ else:
+ if userByGithub is None:
+ flash("Authorization failed [err=gh-no-such-account]", "danger")
+ return redirect(url_for("user.login"))
+ elif _login_user(userByGithub):
+ return redirect(next_url or url_for("home_page"))
+ else:
+ flash("Authorization failed [err=gh-login-failed]", "danger")
+ return redirect(url_for("user.login"))
diff --git a/app/views/users.py b/app/views/users.py
new file mode 100644
index 00000000..99cf19c4
--- /dev/null
+++ b/app/views/users.py
@@ -0,0 +1,60 @@
+from flask import *
+from flask_user import *
+from flask_login import login_user, logout_user
+from flask.ext import menu
+from app import app
+from app.models import *
+
+
+
+# Define the User registration form
+# It augments the Flask-User RegisterForm with additional fields
+from flask_user.forms import RegisterForm
+from flask_wtf import FlaskForm
+from wtforms import StringField, SubmitField, validators
+class MyRegisterForm(RegisterForm):
+ first_name = StringField('First name', validators=[
+ validators.DataRequired('First name is required')])
+ last_name = StringField('Last name', validators=[
+ validators.DataRequired('Last name is required')])
+
+# Define the User profile form
+class UserProfileForm(FlaskForm):
+ first_name = StringField('First name', validators=[
+ validators.DataRequired('First name is required')])
+ last_name = StringField('Last name', validators=[
+ validators.DataRequired('Last name is required')])
+ submit = SubmitField('Save')
+
+@app.route('/user/', methods=['GET', 'POST'])
+@app.route('/user//', methods=['GET'])
+def user_profile_page(username=None):
+ user = None
+ form = None
+ if username is None:
+ if not current_user.is_authenticated:
+ return current_app.login_manager.unauthorized()
+ user = current_user
+ else:
+ user = User.query.filter_by(username=username).first()
+ if not user:
+ abort(404)
+
+ if user == current_user:
+ # Initialize form
+ form = UserProfileForm(request.form, current_user)
+
+ # Process valid POST
+ if request.method=='POST' and form.validate():
+ # Copy form fields to user_profile fields
+ form.populate_obj(current_user)
+
+ # Save user_profile
+ db.session.commit()
+
+ # Redirect to home page
+ return redirect(url_for('home_page'))
+
+ # Process GET or invalid POST
+ return render_template('users/user_profile_page.html',
+ user=user, form=form)
diff --git a/config.example.cfg b/config.example.cfg
index 7fffe2c0..05fa9bec 100644
--- a/config.example.cfg
+++ b/config.example.cfg
@@ -4,3 +4,6 @@ SECRET_KEY=""
WTF_CSRF_SECRET_KEY=""
SQLALCHEMY_DATABASE_URI = "sqlite:///../db.sqlite"
+
+GITHUB_CLIENT_ID = ""
+GITHUB_CLIENT_SECRET = ""
diff --git a/requirements.txt b/requirements.txt
index dd7f72c5..0fdd36f5 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -3,3 +3,4 @@ Flask-SQLAlchemy>=2.3
Flask-User>=0.6.19
Flask-Menu>=0.7.0
Flask-Markdown>=0.3
+GitHub-Flask>=3.2.0
diff --git a/setup.py b/setup.py
index 0ba2b5a7..9949776b 100644
--- a/setup.py
+++ b/setup.py
@@ -6,12 +6,15 @@ if delete_db and os.path.isfile("app/data.sqlite"):
os.remove("app/data.sqlite")
if not os.path.isfile("app/data.sqlite"):
- from app import models
+ from app.models import *
print("Creating database tables...")
- models.db.create_all()
-
+ db.create_all()
print("Filling database...")
- models.db.session.commit()
+
+ ruben = User("rubenwardy")
+ ruben.github_username = "rubenwardy"
+ db.session.add(ruben)
+ db.session.commit()
else:
print("Database already exists")