Initial commit
This commit is contained in:
commit
5f78d35ab1
32 changed files with 10116 additions and 0 deletions
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
.DS_Store
|
||||||
|
.idea/
|
||||||
|
*.py[co]
|
||||||
|
tmp/
|
||||||
BIN
app.db
Normal file
BIN
app.db
Normal file
Binary file not shown.
15
config.py
Normal file
15
config.py
Normal 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
16
db_create.py
Executable 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
8
db_downgrade.py
Executable 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
19
db_migrate.py
Executable 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
4
db_repository/README
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
This is a database migration repository.
|
||||||
|
|
||||||
|
More information at
|
||||||
|
http://code.google.com/p/sqlalchemy-migrate/
|
||||||
0
db_repository/__init__.py
Normal file
0
db_repository/__init__.py
Normal file
5
db_repository/manage.py
Normal file
5
db_repository/manage.py
Normal 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
25
db_repository/migrate.cfg
Normal 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
|
||||||
19
db_repository/versions/001_migration.py
Normal file
19
db_repository/versions/001_migration.py
Normal 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
|
||||||
0
db_repository/versions/__init__.py
Normal file
0
db_repository/versions/__init__.py
Normal file
7
db_upgrade.py
Executable file
7
db_upgrade.py
Executable 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
21
megaproject/__init__.py
Normal 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
15
megaproject/forms.py
Normal 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
54
megaproject/models.py
Normal 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
|
||||||
1109
megaproject/static/css/bootstrap-responsive.css
vendored
Normal file
1109
megaproject/static/css/bootstrap-responsive.css
vendored
Normal file
File diff suppressed because it is too large
Load diff
9
megaproject/static/css/bootstrap-responsive.min.css
vendored
Normal file
9
megaproject/static/css/bootstrap-responsive.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
6167
megaproject/static/css/bootstrap.css
vendored
Normal file
6167
megaproject/static/css/bootstrap.css
vendored
Normal file
File diff suppressed because it is too large
Load diff
9
megaproject/static/css/bootstrap.min.css
vendored
Normal file
9
megaproject/static/css/bootstrap.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
BIN
megaproject/static/img/glyphicons-halflings-white.png
Normal file
BIN
megaproject/static/img/glyphicons-halflings-white.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 8.6 KiB |
BIN
megaproject/static/img/glyphicons-halflings.png
Normal file
BIN
megaproject/static/img/glyphicons-halflings.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 12 KiB |
2280
megaproject/static/js/bootstrap.js
vendored
Normal file
2280
megaproject/static/js/bootstrap.js
vendored
Normal file
File diff suppressed because it is too large
Load diff
6
megaproject/static/js/bootstrap.min.js
vendored
Normal file
6
megaproject/static/js/bootstrap.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
40
megaproject/templates/base/base.html
Normal file
40
megaproject/templates/base/base.html
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>{{ title }} · 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>
|
||||||
64
megaproject/templates/base/base_with_nav.html
Normal file
64
megaproject/templates/base/base_with_nav.html
Normal 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 %}
|
||||||
|
|
||||||
6
megaproject/templates/base/base_without_nav.html
Normal file
6
megaproject/templates/base/base_without_nav.html
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
{% extends "base/base.html" %}
|
||||||
|
{% block body %}
|
||||||
|
|
||||||
|
{% block content %}{% endblock %}
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
39
megaproject/templates/create_project.html
Normal file
39
megaproject/templates/create_project.html
Normal 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 %}
|
||||||
1
megaproject/templates/index.html
Normal file
1
megaproject/templates/index.html
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
{% extends "base/base_with_nav.html" %}
|
||||||
90
megaproject/templates/login.html
Normal file
90
megaproject/templates/login.html
Normal 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
80
megaproject/views.py
Normal 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
4
run.py
Executable file
|
|
@ -0,0 +1,4 @@
|
||||||
|
#!/usr/bin/python
|
||||||
|
from megaproject import app
|
||||||
|
|
||||||
|
app.run(debug=True)
|
||||||
Loading…
Reference in a new issue