├── Procfile ├── .gitignore ├── requirements.txt ├── templates └── flask_dashed │ └── footer.html ├── config.py ├── README.rst └── app.py /Procfile: -------------------------------------------------------------------------------- 1 | web: python app.py -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | test.db 3 | github.txt 4 | .env 5 | *.sw* 6 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | hg+https://bitbucket.org/jaraco/httplib2#egg=httplib2 2 | flask-wtf==0.5.4 3 | git+git://github.com/jeanphix/Flask-Dashed.git#egg=flask-dashed==0.1.0 4 | flask-sqlalchemy 5 | psycopg2 6 | flask-oauth 7 | -------------------------------------------------------------------------------- /templates/flask_dashed/footer.html: -------------------------------------------------------------------------------- 1 | Fork me on GitHub 2 | -------------------------------------------------------------------------------- /config.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | 4 | SECRET_KEY = os.environ['APP_SECRET'] 5 | DEBUG = False 6 | 7 | try: 8 | if 'DATABASE_URL' in os.environ: 9 | SQLALCHEMY_DATABASE_URI = os.environ['DATABASE_URL'] 10 | except: 11 | print "Unexpected error:", sys.exc_info() 12 | 13 | GITHUB = None 14 | if 'GITHUB_KEY' and 'GITHUB_SECRET' in os.environ: 15 | GITHUB = { 16 | 'base_url': 'https://api.github.com/', 17 | 'request_token_url': None, 18 | 'access_token_url': 'https://github.com/login/oauth/access_token', 19 | 'authorize_url': 'https://github.com/login/oauth/authorize', 20 | 'consumer_key': os.environ['GITHUB_KEY'], 21 | 'consumer_secret': os.environ['GITHUB_SECRET'], 22 | } 23 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | This repository contains is a sample `Flask-Dashed `_ application. 2 | 3 | You may browse this `app online `_ 4 | 5 | Installation 6 | ============ 7 | 8 | Locally 9 | ------- 10 | 11 | You need to set a secret key and your database DSN:: 12 | 13 | export APP_SECRET=myawesomesecret 14 | export DATABASE_URL=sqlite:///:memory: 15 | 16 | 17 | Heroku 18 | ------ 19 | 20 | Application:: 21 | 22 | heroku create --stack cedar 23 | heroku addons:add shared-database 24 | git push heroku master 25 | heroku config:add APP_SECRET='YOURAPPLICATIONSECRET' 26 | 27 | Github oauth API (Optional):: 28 | 29 | heroku config:add GITHUB_KEY='YOURGITHUBAPPKEY' 30 | heroku config:add GITHUB_SECRET='YOURGITHUBSECRET' 31 | -------------------------------------------------------------------------------- /app.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import os 3 | import wtforms 4 | 5 | from werkzeug import OrderedMultiDict 6 | 7 | from flask import Flask, redirect, url_for, request, flash, session 8 | from flask_dashed.admin import Admin 9 | from flask_dashed.ext.sqlalchemy import ModelAdminModule, model_form 10 | from flask.ext.sqlalchemy import SQLAlchemy 11 | from flask.ext import oauth 12 | from sqlalchemy.orm import aliased, contains_eager 13 | 14 | 15 | app = Flask(__name__) 16 | app.config.from_pyfile(os.path.join(app.root_path, 'config.py')) 17 | # SQLAlchemy 18 | db = SQLAlchemy(app) 19 | db_session = db.session 20 | 21 | 22 | class Company(db.Model): 23 | id = db.Column(db.Integer, primary_key=True) 24 | name = db.Column(db.String(255), unique=True, nullable=False) 25 | 26 | def __unicode__(self): 27 | return unicode(self.name) 28 | 29 | def __repr__(self): 30 | return '' % self.name 31 | 32 | 33 | class User(db.Model): 34 | id = db.Column(db.Integer, primary_key=True) 35 | username = db.Column(db.String(255), unique=True, nullable=False) 36 | password = db.Column(db.String(255)) 37 | is_active = db.Column(db.Boolean()) 38 | 39 | 40 | class Profile(db.Model): 41 | id = db.Column(db.Integer, db.ForeignKey(User.id), primary_key=True) 42 | name = db.Column(db.String(255), nullable=False) 43 | location = db.Column(db.String(255)) 44 | company_id = db.Column(db.Integer, db.ForeignKey(Company.id), 45 | nullable=True) 46 | 47 | user = db.relationship(User, backref=db.backref("profile", 48 | remote_side=id, uselist=False, cascade="all, delete-orphan")) 49 | 50 | company = db.relationship(Company, backref=db.backref("staff")) 51 | 52 | 53 | user_group = db.Table( 54 | 'user_group', db.Model.metadata, 55 | db.Column('user_id', db.Integer, db.ForeignKey('user.id')), 56 | db.Column('group_id', db.Integer, db.ForeignKey('group.id')) 57 | ) 58 | 59 | 60 | class Group(db.Model): 61 | id = db.Column(db.Integer, primary_key=True) 62 | name = db.Column(db.String(255), unique=True, nullable=False) 63 | 64 | users = db.relationship("User", secondary=user_group, 65 | backref=db.backref("groups", lazy='dynamic')) 66 | 67 | def __unicode__(self): 68 | return unicode(self.name) 69 | 70 | def __repr__(self): 71 | return '' % self.name 72 | 73 | 74 | db.create_all() 75 | 76 | 77 | UserForm = model_form(User, db_session, exclude=['password']) 78 | 79 | 80 | class UserForm(UserForm): 81 | """Embeds OneToOne has FormField.""" 82 | profile = wtforms.FormField(model_form(Profile, db_session, 83 | exclude=['user'], base_class=wtforms.Form)) 84 | 85 | 86 | class UserModule(ModelAdminModule): 87 | model = User 88 | db_session = db_session 89 | profile_alias = aliased(Profile) 90 | 91 | list_fields = OrderedMultiDict(( 92 | ('id', {'label': 'id', 'column': User.id}), 93 | ('username', {'label': 'username', 'column': User.username}), 94 | ('profile.name', {'label': 'name', 'column': profile_alias.name}), 95 | ('profile.location', {'label': 'location', 96 | 'column': profile_alias.location}), 97 | )) 98 | 99 | list_title = 'user list' 100 | 101 | searchable_fields = ['username', 'profile.name', 'profile.location'] 102 | 103 | order_by = ('id', 'desc') 104 | 105 | list_query_factory = model.query\ 106 | .outerjoin(profile_alias, 'profile')\ 107 | .options(contains_eager('profile', alias=profile_alias))\ 108 | 109 | form_class = UserForm 110 | 111 | def create_object(self): 112 | user = self.model() 113 | user.profile = Profile() 114 | return user 115 | 116 | 117 | class GroupModule(ModelAdminModule): 118 | model = Group 119 | db_session = db_session 120 | form_class = model_form(Group, db_session, only=['name']) 121 | 122 | 123 | class CompanyModule(ModelAdminModule): 124 | model = Company 125 | db_session = db_session 126 | form_class = model_form(Company, db_session, only=['name']) 127 | 128 | 129 | admin = Admin(app, title="my business") 130 | security = admin.register_node('/security', 'security', 'security management') 131 | user_module = admin.register_module(UserModule, '/users', 'users', 132 | 'users', parent=security) 133 | group_module = admin.register_module(GroupModule, '/groups', 'groups', 134 | 'groups', parent=security) 135 | company_module = admin.register_module(CompanyModule, '/companies', 136 | 'companies', 'companies') 137 | 138 | 139 | @app.route('/') 140 | def redirect_to_admin(): 141 | return redirect('/admin') 142 | 143 | 144 | @app.errorhandler(401) 145 | def login_require(e): 146 | """HTTP<401>.""" 147 | return redirect("%s?next=%s" % (url_for('login'), request.path)) 148 | 149 | # Oauth 150 | if app.config['GITHUB']: 151 | oauth = oauth.OAuth() 152 | github = oauth.remote_app('github', **app.config['GITHUB']) 153 | 154 | admin.add_path_security('/', lambda: 'github_token' in session, 155 | http_code=401) 156 | 157 | @app.route('/login') 158 | def login(): 159 | """Signs in via github. 160 | """ 161 | return github.authorize(callback=url_for('github_authorized', 162 | next=request.args.get('next') or request.referrer or None, 163 | _external=True)) 164 | 165 | @app.route('/logout') 166 | def logout(): 167 | del session['user'] 168 | return redirect(url_for('home')) 169 | 170 | @github.tokengetter 171 | def get_github_token(): 172 | return session.get('github_token'), '' 173 | 174 | @app.route('/github/callback') 175 | @github.authorized_handler 176 | def github_authorized(response): 177 | """Gets back user from github oauth server. 178 | """ 179 | next_url = request.args.get('next') or url_for('index') 180 | if response is None: 181 | flash(u'You denied the request to sign in.') 182 | return redirect(next_url) 183 | session['github_token'] = response['access_token'] 184 | me = github.get('/user') 185 | flash("you are now connected as %s" % me.data['login'], 'success') 186 | return redirect(next_url) 187 | 188 | 189 | if __name__ == '__main__': 190 | port = int(os.environ.get("PORT", 5000)) 191 | app.run(host='0.0.0.0', port=port) 192 | --------------------------------------------------------------------------------