Initial commit

This commit is contained in:
Justin Lewis Salmon 2013-05-20 12:01:01 +01:00
commit 5f78d35ab1
32 changed files with 10116 additions and 0 deletions

4
.gitignore vendored Normal file
View file

@ -0,0 +1,4 @@
.DS_Store
.idea/
*.py[co]
tmp/

BIN
app.db Normal file

Binary file not shown.

15
config.py Normal file
View file

@ -0,0 +1,15 @@
import os
basedir = os.path.abspath(os.path.dirname(__file__))
CSRF_ENABLED = True
SECRET_KEY = 'you-will-never-guess'
OPENID_PROVIDERS = [
{'name': 'Google', 'url': 'https://www.google.com/accounts/o8/id'},
{'name': 'Yahoo', 'url': 'https://me.yahoo.com'},
{'name': 'AOL', 'url': 'http://openid.aol.com/<username>'},
{'name': 'Flickr', 'url': 'http://www.flickr.com/<username>'},
{'name': 'MyOpenID', 'url': 'https://www.myopenid.com'}]
SQLALCHEMY_DATABASE_URI = 'sqlite:///' + os.path.join(basedir, 'app.db')
SQLALCHEMY_MIGRATE_REPO = os.path.join(basedir, 'db_repository')

16
db_create.py Executable file
View file

@ -0,0 +1,16 @@
#!/usr/bin/python
import os.path
from migrate.versioning import api
from config import SQLALCHEMY_DATABASE_URI
from config import SQLALCHEMY_MIGRATE_REPO
from megaproject import db
db.create_all()
if not os.path.exists(SQLALCHEMY_MIGRATE_REPO):
api.create(SQLALCHEMY_MIGRATE_REPO, 'database repository')
api.version_control(SQLALCHEMY_DATABASE_URI, SQLALCHEMY_MIGRATE_REPO)
else:
api.version_control(SQLALCHEMY_DATABASE_URI, SQLALCHEMY_MIGRATE_REPO, api.version(SQLALCHEMY_MIGRATE_REPO))

8
db_downgrade.py Executable file
View file

@ -0,0 +1,8 @@
#!/usr/bin/python
from migrate.versioning import api
from config import SQLALCHEMY_DATABASE_URI
from config import SQLALCHEMY_MIGRATE_REPO
v = api.db_version(SQLALCHEMY_DATABASE_URI, SQLALCHEMY_MIGRATE_REPO)
api.downgrade(SQLALCHEMY_DATABASE_URI, SQLALCHEMY_MIGRATE_REPO, v - 1)
print 'Current database version: ' + str(api.db_version(SQLALCHEMY_DATABASE_URI, SQLALCHEMY_MIGRATE_REPO))

19
db_migrate.py Executable file
View file

@ -0,0 +1,19 @@
#!/usr/bin/python
import imp
from migrate.versioning import api
from megaproject import db
from config import SQLALCHEMY_DATABASE_URI
from config import SQLALCHEMY_MIGRATE_REPO
migration = SQLALCHEMY_MIGRATE_REPO + '/versions/%03d_migration.py' \
% (api.db_version(SQLALCHEMY_DATABASE_URI, SQLALCHEMY_MIGRATE_REPO) + 1)
tmp_module = imp.new_module('old_model')
old_model = api.create_model(SQLALCHEMY_DATABASE_URI, SQLALCHEMY_MIGRATE_REPO)
exec old_model in tmp_module.__dict__
script = api.make_update_script_for_model(SQLALCHEMY_DATABASE_URI, SQLALCHEMY_MIGRATE_REPO, tmp_module.meta,
db.metadata)
open(migration, "wt").write(script)
a = api.upgrade(SQLALCHEMY_DATABASE_URI, SQLALCHEMY_MIGRATE_REPO)
print 'New migration saved as ' + migration
print 'Current database version: ' + str(api.db_version(SQLALCHEMY_DATABASE_URI, SQLALCHEMY_MIGRATE_REPO))

4
db_repository/README Normal file
View file

@ -0,0 +1,4 @@
This is a database migration repository.
More information at
http://code.google.com/p/sqlalchemy-migrate/

View file

5
db_repository/manage.py Normal file
View file

@ -0,0 +1,5 @@
#!/usr/bin/env python
from migrate.versioning.shell import main
if __name__ == '__main__':
main()

25
db_repository/migrate.cfg Normal file
View file

@ -0,0 +1,25 @@
[db_settings]
# Used to identify which repository this database is versioned under.
# You can use the name of your project.
repository_id=database repository
# The name of the database table used to track the schema version.
# This name shouldn't already be used by your project.
# If this is changed once a database is under version control, you'll need to
# change the table name in each database too.
version_table=migrate_version
# When committing a change script, Migrate will attempt to generate the
# sql for all supported databases; normally, if one of them fails - probably
# because you don't have that database installed - it is ignored and the
# commit continues, perhaps ending successfully.
# Databases in this list MUST compile successfully during a commit, or the
# entire commit will fail. List the databases your application will actually
# be using to ensure your updates to that database work properly.
# This must be a list; example: ['postgres','sqlite']
required_dbs=[]
# When creating new change scripts, Migrate will stamp the new script with
# a version number. By default this is latest_version + 1. You can set this
# to 'true' to tell Migrate to use the UTC timestamp instead.
use_timestamp_numbering=False

View file

@ -0,0 +1,19 @@
from sqlalchemy import *
from migrate import *
from migrate.changeset import schema
pre_meta = MetaData()
post_meta = MetaData()
def upgrade(migrate_engine):
# Upgrade operations go here. Don't create your own engine; bind
# migrate_engine to your metadata
pre_meta.bind = migrate_engine
post_meta.bind = migrate_engine
def downgrade(migrate_engine):
# Operations to reverse the above upgrade go here.
pre_meta.bind = migrate_engine
post_meta.bind = migrate_engine

View file

7
db_upgrade.py Executable file
View file

@ -0,0 +1,7 @@
#!/usr/bin/python
from migrate.versioning import api
from config import SQLALCHEMY_DATABASE_URI
from config import SQLALCHEMY_MIGRATE_REPO
api.upgrade(SQLALCHEMY_DATABASE_URI, SQLALCHEMY_MIGRATE_REPO)
print 'Current database version: ' + str(api.db_version(SQLALCHEMY_DATABASE_URI, SQLALCHEMY_MIGRATE_REPO))

21
megaproject/__init__.py Normal file
View file

@ -0,0 +1,21 @@
__author__ = 'jsalmon'
import os
from flask.ext.login import LoginManager
from flask.ext.openid import OpenID
from config import basedir
from flask import Flask
from flask.ext.sqlalchemy import SQLAlchemy
app = Flask(__name__)
app.config.from_object('config')
db = SQLAlchemy(app)
lm = LoginManager()
lm.init_app(app)
lm.login_view = 'login'
oid = OpenID(app, os.path.join(basedir, 'tmp'))
from megaproject import views, models

15
megaproject/forms.py Normal file
View file

@ -0,0 +1,15 @@
from flask.ext.wtf import Form, TextField, BooleanField
from flask.ext.wtf import Required
class LoginForm(Form):
openid = TextField('openid', validators=[Required()])
remember_me = BooleanField('remember_me', default=False)
class CreateProjectForm(Form):
project_name = TextField('project_name', validators=[Required()])
start_date = TextField('start_date', validators=[Required()])
end_date = TextField('end_date', validators=[Required()])
info = TextField('info')
team = TextField('team', validators=[Required()])

54
megaproject/models.py Normal file
View file

@ -0,0 +1,54 @@
from megaproject import db
ROLE_USER = 0
ROLE_ADMIN = 1
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
nickname = db.Column(db.String(64), index=True, unique=True)
email = db.Column(db.String(120), index=True, unique=True)
role = db.Column(db.SmallInteger, default=ROLE_USER)
projects = db.relationship('Project', backref='owner', lazy='dynamic')
#tasks = db.relationship('Task', backref='workers', lazy='dynamic')
def is_authenticated(self):
return True
def is_active(self):
return True
def is_anonymous(self):
return False
def get_id(self):
return unicode(self.id)
@staticmethod
def make_unique_nickname(nickname):
if User.query.filter_by(nickname=nickname).first() is None:
return nickname
version = 2
while True:
new_nickname = nickname + str(version)
if User.query.filter_by(nickname=new_nickname).first() is None:
break
version += 1
return new_nickname
def __repr__(self):
return '<User %r>' % self.nickname
class Project(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(256), index=True)
start_date = db.Column(db.Date)
end_date = db.Column(db.Date)
progress = db.Column(db.Integer)
owner_id = db.Column(db.Integer, db.ForeignKey('user.id'))
# members = db.relationship('Members', backref='members', lazy='dynamic')
# subtasks = db.relationship('Task', backref='subtasks', lazy='dynamic')
# class Task(db.Model):
# pass

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

6167
megaproject/static/css/bootstrap.css vendored Normal file

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

2280
megaproject/static/js/bootstrap.js vendored Normal file

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,40 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>{{ title }} &middot; megaproject</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="">
<meta name="author" content="">
<!-- Le styles -->
<link href="../../static/css/bootstrap.min.css" rel="stylesheet">
<link href="../../static/css/bootstrap-responsive.min.css" rel="stylesheet">
{% block header %}{% endblock %}
<!-- HTML5 shim, for IE6-8 support of HTML5 elements -->
<!--[if lt IE 9]>
<script src="../static/js/html5shiv.js"></script>
<![endif]-->
<!-- Fav and touch icons -->
<link rel="apple-touch-icon-precomposed" sizes="144x144" href="../static/ico/apple-touch-icon-144-precomposed.png">
<link rel="apple-touch-icon-precomposed" sizes="114x114" href="../static/ico/apple-touch-icon-114-precomposed.png">
<link rel="apple-touch-icon-precomposed" sizes="72x72" href="../static/ico/apple-touch-icon-72-precomposed.png">
<link rel="apple-touch-icon-precomposed" href="../static/ico/apple-touch-icon-57-precomposed.png">
<link rel="shortcut icon" href="../static/ico/favicon.png">
</head>
<body>
{% block body %}{% endblock %}
<!-- Le javascript
================================================== -->
<!-- Placed at the end of the document so the pages load faster -->
<script src="http://code.jquery.com/jquery.js"></script>
<script src="../../static/js/bootstrap.min.js"></script>
</body>
</html>

View file

@ -0,0 +1,64 @@
{% extends "base/base.html" %}
{% block header %}
<style>
body {
padding-top: 60px; /* 60px to make the container go all the way to the bottom of the topbar */
}
</style>
{% endblock %}
{% block body %}
<div class="navbar navbar-inverse navbar-fixed-top">
<div class="navbar-inner">
<div class="container">
<button type="button" class="btn btn-navbar" data-toggle="collapse" data-target=".nav-collapse">
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="brand" href="#">megaproject</a>
<div class="nav-collapse collapse">
<ul class="nav">
<li class="active"><a href="/">Home</a></li>
<li><a href="/create-project">Create Project</a></li>
{% if g.user.is_authenticated() %}
<li class="dropdown pull-right">
<a href="#" class="dropdown-toggle" data-toggle="dropdown">
{{ user.nickname }}
<b class="caret"></b>
</a>
<ul class="dropdown-menu">
<li><a href="#">Profile</a></li>
<li class="divider"></li>
<li class="nav-header">Actions</li>
<li><a href="{{ url_for('logout') }}">Logout</a></li>
</ul>
</li>
{% endif %}
</ul>
</div>
<!--/.nav-collapse -->
</div>
</div>
</div>
<div class="container">
{% with messages = get_flashed_messages() %}
{% if messages %}
{% for message in messages %}
<div class="alert alert-info">{{ message }} </div>
{% endfor %}
{% endif %}
{% endwith %}
{% block content %}{% endblock %}
</div>
{% endblock %}

View file

@ -0,0 +1,6 @@
{% extends "base/base.html" %}
{% block body %}
{% block content %}{% endblock %}
{% endblock %}

View file

@ -0,0 +1,39 @@
{% extends "base/base_with_nav.html" %}
{% block content %}
<h1>Create Project</h1>
<form action="" method="post" name="create-project">
{{ form.hidden_tag() }}
<input type="text" class="input-block-level"
placeholder="Project Name"
id="{{ form.project_name.id }}"
name="{{ form.project_name.id }}">
<input type="text" class="input-block-level"
placeholder="Start Date"
id="{{ form.start_date.id }}"
name="{{ form.start_date.id }}">
<input type="text" class="input-block-level"
placeholder="End Date"
id="{{ form.end_date.id }}"
name="{{ form.end_date.id }}">
<input type="text" class="input-block-level"
placeholder="Info"
id="{{ form.info.id }}"
name="{{ form.info.id }}">
<input type="text" class="input-block-level"
placeholder="Team"
id="{{ form.team.id }}"
name="{{ form.team.id }}">
{% for error in form.errors %}
<span class="help-inline">{{ error }}</span>
{% endfor %}
<button class="btn btn-large btn-primary" type="submit">Create</button>
</form>
{% endblock %}

View file

@ -0,0 +1 @@
{% extends "base/base_with_nav.html" %}

View file

@ -0,0 +1,90 @@
{% extends "base/base_without_nav.html" %}
{% block header %}
<style type="text/css">
body {
padding-top: 40px;
padding-bottom: 40px;
background-color: #f5f5f5;
}
.form-signin {
max-width: 400px;
padding: 19px 29px 29px;
margin: 0 auto 20px;
background-color: #fff;
border: 1px solid #e5e5e5;
-webkit-border-radius: 5px;
-moz-border-radius: 5px;
border-radius: 5px;
-webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, .05);
-moz-box-shadow: 0 1px 2px rgba(0, 0, 0, .05);
box-shadow: 0 1px 2px rgba(0, 0, 0, .05);
}
.form-signin .form-signin-heading,
.form-signin .checkbox {
margin-bottom: 10px;
}
.form-signin input[type="text"],
.form-signin input[type="password"] {
font-size: 16px;
height: auto;
margin-bottom: 15px;
padding: 7px 9px;
}
</style>
{% endblock %}
{% block content %}
<form class="form-signin" action="" method="post" name="login">
<h2 class="form-signin-heading">Please sign in</h2>
{{ form.hidden_tag() }}
{% if form.errors.openid %}
<div class="control-group error">
<div class="controls">
<input type="text" class="input-block-level"
placeholder="OpenID"
id="{{ form.openid.id }}"
name="{{ form.openid.name }}">
{% for error in form.errors.openid %}
<span class="help-inline">{{ error }}</span>
{% endfor %}
</div>
</div>
{% else %}
<input type="text" class="input-block-level"
placeholder="OpenID"
id="{{ form.openid.id }}"
name="{{ form.openid.name }}">
{% endif %}
{% for pr in providers %}
<a href="javascript:set_openid('{{ pr.url }}', '{{ pr.name }}');">{{ pr.name }}</a> |
{% endfor %}
<label class="checkbox">
<input type="checkbox" value="remember-me"
id="{{ form.remember_me.id }}"
name="{{ form.remember_me.name }}">
Remember me
</label>
<button class="btn btn-large btn-primary" type="submit">Sign in</button>
</form>
<script type="text/javascript">
function set_openid(openid, pr) {
u = openid.search('<username>')
if (u != -1) {
// openid requires username
user = prompt('Enter your ' + pr + ' username:')
openid = openid.substr(0, u) + user
}
form = document.forms['login'];
form.elements['openid'].value = openid
}
</script>
{% endblock %}

80
megaproject/views.py Normal file
View file

@ -0,0 +1,80 @@
from flask import render_template, flash, redirect, session, url_for, g, request
from flask.ext.login import login_user, current_user, login_required, logout_user
from megaproject import app, lm, oid, db
from forms import LoginForm, CreateProjectForm
from models import User, ROLE_USER
@app.route('/')
@app.route('/index')
@login_required
def index():
user = g.user
return render_template('index.html', title='Index', user=user)
@lm.user_loader
def load_user(id):
return User.query.get(int(id))
@app.before_request
def before_request():
g.user = current_user
@app.route('/login', methods=['GET', 'POST'])
@oid.loginhandler
def login():
if g.user is not None and g.user.is_authenticated():
return redirect(url_for('index'))
form = LoginForm()
if form.validate_on_submit():
session['remember_me'] = form.remember_me.data
return oid.try_login(form.openid.data, ask_for=['nickname', 'email'])
return render_template('login.html',
title='Sign In',
form=form,
providers=app.config['OPENID_PROVIDERS'])
@oid.after_login
def after_login(resp):
if resp.email is None or resp.email == "":
flash('Invalid login. Please try again.')
redirect(url_for('login'))
user = User.query.filter_by(email=resp.email).first()
if user is None:
nickname = resp.nickname
if nickname is None or nickname == "":
nickname = resp.email.split('@')[0]
user = User(nickname=nickname, email=resp.email, role=ROLE_USER)
db.session.add(user)
db.session.commit()
remember_me = False
if 'remember_me' in session:
remember_me = session['remember_me']
session.pop('remember_me', None)
login_user(user, remember=remember_me)
return redirect(request.args.get('next') or url_for('index'))
@app.route('/logout')
def logout():
logout_user()
return redirect(url_for('index'))
@app.route('/create-project', methods=['GET', 'POST'])
@login_required
def create_project():
user = g.user
form = CreateProjectForm()
if form.validate_on_submit():
return 'ok'
return render_template('create_project.html', title='Create Project', user=user, form=form)

4
run.py Executable file
View file

@ -0,0 +1,4 @@
#!/usr/bin/python
from megaproject import app
app.run(debug=True)