├── 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 |
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 |
--------------------------------------------------------------------------------