Browse Source

Chapter 7: Large file structure (7a)

T. Meissner 2 years ago
parent
commit
e344f46b81

+ 28
- 0
app/__init__.py View File

@@ -0,0 +1,28 @@
1
+from flask import Flask
2
+from flask_bootstrap import Bootstrap
3
+from flask_mail import Mail
4
+from flask_moment import Moment
5
+from flask_sqlalchemy import SQLAlchemy
6
+from config import config
7
+
8
+
9
+bootstrap = Bootstrap()
10
+mail = Mail()
11
+moment = Moment()
12
+db = SQLAlchemy()
13
+
14
+
15
+def create_app(config_name):
16
+    app = Flask(__name__)
17
+    app.config.from_object(config[config_name])
18
+    config[config_name].init_app(app)
19
+
20
+    bootstrap.init_app(app)
21
+    mail.init_app(app)
22
+    moment.init_app(app)
23
+    db.init_app(app)
24
+
25
+    from .main import main as main_blueprint
26
+    app.register_blueprint(main_blueprint)
27
+
28
+    return app

+ 20
- 0
app/email.py View File

@@ -0,0 +1,20 @@
1
+from threading import Thread
2
+from flask import current_app, render_template
3
+from flask_mail import Message
4
+from . import mail
5
+
6
+
7
+def send_async_email(app, msg):
8
+    with app.app_context():
9
+        mail.send(msg)
10
+
11
+
12
+def send_email(to, subject, template, **kwargs):
13
+    app = current_app._get_current_object()
14
+    msg = Message(app.config['FLASKY_MAIL_SUBJECT_PREFIX'] + ' ' + subject,
15
+                  sender=app.config['FLASKY_MAIL_SENDER'], recipients=[to])
16
+    msg.body = render_template(template + '.txt', **kwargs)
17
+    msg.html = render_template(template + '.html', **kwargs)
18
+    thr = Thread(target=send_async_email, args=[app, msg])
19
+    thr.start()
20
+    return thr

+ 7
- 0
app/main/__init__.py View File

@@ -0,0 +1,7 @@
1
+from flask import Blueprint
2
+
3
+
4
+main = Blueprint('main', __name__)
5
+
6
+
7
+from . import views, errors

+ 12
- 0
app/main/errors.py View File

@@ -0,0 +1,12 @@
1
+from flask import render_template
2
+from . import main
3
+
4
+
5
+@main.app_errorhandler(404)
6
+def page_not_found(e):
7
+    return render_template('404.html'), 404
8
+
9
+
10
+@main.app_errorhandler(500)
11
+def internal_server_error(e):
12
+    return render_template('500.html'), 500

+ 8
- 0
app/main/forms.py View File

@@ -0,0 +1,8 @@
1
+from flask_wtf import FlaskForm
2
+from wtforms import StringField, SubmitField
3
+from wtforms.validators import DataRequired
4
+
5
+
6
+class NameForm(FlaskForm):
7
+    name = StringField('What is your name?', validators=[DataRequired()])
8
+    submit = SubmitField('Submit')

+ 29
- 0
app/main/views.py View File

@@ -0,0 +1,29 @@
1
+from flask import render_template, session, redirect, url_for, current_app
2
+from .. import db
3
+from ..models import User
4
+from ..email import send_email
5
+from . import main
6
+from .forms import NameForm
7
+
8
+
9
+@main.route('/', methods=['GET', 'POST'])
10
+def index():
11
+    form = NameForm()
12
+    if form.validate_on_submit():
13
+        user = User.query.filter_by(username=form.name.data).first()
14
+        if user is None:
15
+            user = User(username=form.name.data)
16
+            db.session.add(user)
17
+            db.session.commit()
18
+            session['known'] = False
19
+            if current_app.config['FLASKY_ADMIN']:
20
+                send_email(current_app.config['FLASKY_ADMIN'], 'New user',
21
+                           'mail/new_user', user=user)
22
+        else:
23
+            session['known'] = True
24
+        session['name'] = form.name.data
25
+        form.name.data = ''
26
+        return redirect(url_for('.index'))
27
+    return render_template('index.html',
28
+                           form=form, name=session.get('name'),
29
+                           known=session.get('known', False))

+ 21
- 0
app/models.py View File

@@ -0,0 +1,21 @@
1
+from . import db
2
+
3
+
4
+class Role(db.Model):
5
+    __tablename__ = 'roles'
6
+    id = db.Column(db.Integer, primary_key=True)
7
+    name = db.Column(db.String(64), unique=True)
8
+    users = db.relationship('User', backref='role', lazy='dynamic')
9
+
10
+    def __repr__(self):
11
+        return '<Role %r>' % self.name
12
+
13
+
14
+class User(db.Model):
15
+    __tablename__ = 'users'
16
+    id = db.Column(db.Integer, primary_key=True)
17
+    username = db.Column(db.String(64), unique=True, index=True)
18
+    role_id = db.Column(db.Integer, db.ForeignKey('roles.id'))
19
+
20
+    def __repr__(self):
21
+        return '<User %r>' % self.username

static/favicon.ico → app/static/favicon.ico View File


templates/404.html → app/templates/404.html View File


templates/500.html → app/templates/500.html View File


templates/base.html → app/templates/base.html View File

@@ -19,11 +19,11 @@
19 19
                 <span class="icon-bar"></span>
20 20
                 <span class="icon-bar"></span>
21 21
                 </button> 
22
-            <a class="navbar-brand" href="/">Flasky</a>
22
+            <a class="navbar-brand" href="{{ url_for('main.index') }}">Flasky</a>
23 23
         </div>
24 24
         <div class="navbar-collapse collapse">
25 25
             <ul class="nav navbar-nav">
26
-                <li><a href="/">Home</a></li>
26
+                <li><a href="{{ url_for('main.index') }}">Home</a></li>
27 27
             </ul>
28 28
         </div>
29 29
     </div>

templates/index.html → app/templates/index.html View File


templates/mail/new_user.html → app/templates/mail/new_user.html View File


templates/mail/new_user.txt → app/templates/mail/new_user.txt View File


+ 46
- 0
config.py View File

@@ -0,0 +1,46 @@
1
+import os
2
+basedir = os.path.abspath(os.path.dirname(__file__))
3
+
4
+
5
+class Config:
6
+    SECRET_KEY = os.environ.get('SECRET_KEY') or 'hard to guess string'
7
+    MAIL_SERVER = os.environ.get('MAIL_SERVER', 'smtp.strato.de')
8
+    MAIL_PORT = int(os.environ.get('MAIL_PORT', '587'))
9
+    MAIL_USE_TLS = os.environ.get('MAIL_USE_TLS', 'true').lower() in \
10
+        ['true', 'on', '1']
11
+    MAIL_USERNAME = os.environ.get('MAIL_USERNAME')
12
+    MAIL_PASSWORD = os.environ.get('MAIL_PASSWORD')
13
+    FLASKY_MAIL_SUBJECT_PREFIX = '[Flasky]'
14
+    FLASKY_MAIL_SENDER = 'Flasky Admin <flasky@example.com>'
15
+    FLASKY_ADMIN = os.environ.get('FLASKY_ADMIN')
16
+    SQLALCHEMY_TRACK_MODIFICATIONS = False
17
+
18
+    @staticmethod
19
+    def init_app(app):
20
+        pass
21
+
22
+
23
+class DevelopmentConfig(Config):
24
+    DEBUG = True
25
+    SQLALCHEMY_DATABASE_URI = os.environ.get('DEV_DATABASE_URL') or \
26
+        'sqlite:///' + os.path.join(basedir, 'data-dev.sqlite')
27
+
28
+
29
+class TestingConfig(Config):
30
+    TESTING = True
31
+    SQLALCHEMY_DATABASE_URI = os.environ.get('TEST_DATABASE_URL') or \
32
+        'sqlite://'
33
+
34
+
35
+class ProductionConfig(Config):
36
+    SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL') or \
37
+        'sqlite:///' + os.path.join(basedir, 'data.sqlite')
38
+
39
+
40
+config = {
41
+    'development': DevelopmentConfig,
42
+    'testing': TestingConfig,
43
+    'production': ProductionConfig,
44
+
45
+    'default': DevelopmentConfig
46
+}

+ 21
- 0
flasky.py View File

@@ -0,0 +1,21 @@
1
+import os
2
+from flask_migrate import Migrate
3
+from app import create_app, db
4
+from app.models import User, Role
5
+
6
+
7
+app = create_app(os.getenv('FLASK_CONFIG') or 'default')
8
+migrate = Migrate(app, db)
9
+
10
+
11
+@app.shell_context_processor
12
+def make_shell_context():
13
+    return dict(db=db, User=User, Role=Role)
14
+
15
+
16
+@app.cli.command()
17
+def test():
18
+    """Run the unit tests."""
19
+    import unittest
20
+    tests = unittest.TestLoader().discover('tests')
21
+    unittest.TextTestRunner(verbosity=2).run(tests)

+ 0
- 111
hello.py View File

@@ -1,111 +0,0 @@
1
-import os
2
-from threading import Thread
3
-from flask import Flask, render_template, session, redirect, url_for
4
-from flask_bootstrap import Bootstrap
5
-from flask_moment import Moment
6
-from flask_wtf import FlaskForm
7
-from wtforms import StringField, SubmitField
8
-from wtforms.validators import DataRequired
9
-from flask_sqlalchemy import SQLAlchemy
10
-from flask_migrate import Migrate
11
-from flask_mail import Mail, Message
12
-
13
-
14
-basedir = os.path.abspath(os.path.dirname(__file__))
15
-
16
-app = Flask(__name__)
17
-app.config['SECRET_KEY'] = 'hard to guess string'
18
-app.config['SQLALCHEMY_DATABASE_URI'] = \
19
-    'sqlite:///' + os.path.join(basedir, 'data.sqlite')
20
-app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
21
-app.config['MAIL_SERVER'] = 'smtp.strato.com'
22
-app.config['MAIL_PORT'] = 587
23
-app.config['MAIL_USE_TLS'] = True
24
-app.config['MAIL_USERNAME'] = os.environ.get('MAIL_USERNAME')
25
-app.config['MAIL_PASSWORD'] = os.environ.get('MAIL_PASSWORD')
26
-app.config['FLASKY_MAIL_SUBJECT_PREFIX'] = '[Flasky]'
27
-app.config['FLASKY_MAIL_SENDER'] = 'Flasky Admin <flasky@example.com>'
28
-app.config['FLASKY_ADMIN'] = os.environ.get('FLASKY_ADMIN')
29
-
30
-bootstrap = Bootstrap(app)
31
-moment = Moment(app)
32
-db = SQLAlchemy(app)
33
-migrate = Migrate(app, db)
34
-mail = Mail(app)
35
-
36
-
37
-class Role(db.Model):
38
-    __tablename__ = 'roles'
39
-    id = db.Column(db.Integer, primary_key=True)
40
-    name = db.Column(db.String(64), unique=True)
41
-    users = db.relationship('User', backref='role', lazy='dynamic')
42
-
43
-    def __repr__(self):
44
-        return '<Role %r>' % self.name
45
-
46
-
47
-class User(db.Model):
48
-    __tablename__ = 'users'
49
-    id = db.Column(db.Integer, primary_key=True)
50
-    username = db.Column(db.String(64), unique=True, index=True)
51
-    role_id = db.Column(db.Integer, db.ForeignKey('roles.id'))
52
-
53
-    def __repr__(self):
54
-        return '<User %r>' % self.username
55
-
56
-
57
-def send_async_email(app, msg):
58
-    with app.app_context():
59
-        mail.send(msg)
60
-
61
-
62
-def send_email(to, subject, template, **kwargs):
63
-    msg = Message(app.config['FLASKY_MAIL_SUBJECT_PREFIX'] + subject,
64
-                  sender=app.config['FLASKY_MAIL_SENDER'], recipients=[to])
65
-    msg.body = render_template(template + '.txt', **kwargs)
66
-    msg.html = render_template(template + '.html', **kwargs)
67
-    thr = Thread(target=send_async_email, args=[app, msg])
68
-    thr.start()
69
-    return thr
70
-
71
-
72
-class NameForm(FlaskForm):
73
-    name = StringField('What is your name?', validators=[DataRequired()])
74
-    submit = SubmitField('Submit')
75
-
76
-
77
-@app.shell_context_processor
78
-def make_shell_context():
79
-    return dict(db=db, User=User, Role=Role)
80
-
81
-
82
-@app.errorhandler(404)
83
-def page_not_found(e):
84
-    return render_template('404.html'), 404
85
-
86
-
87
-@app.errorhandler(500)
88
-def internal_server_error(e):
89
-    return render_template('500.html'), 500
90
-
91
-
92
-@app.route('/', methods=['GET', 'POST'])
93
-def index():
94
-    form = NameForm()
95
-    if form.validate_on_submit():
96
-        user = User.query.filter_by(username=form.name.data).first()
97
-        if user is None:
98
-            user = User(username=form.name.data)
99
-            db.session.add(user)
100
-            db.session.commit()
101
-            session['known'] = False
102
-            if app.config['FLASKY_ADMIN']:
103
-                send_email(app.config['FLASKY_ADMIN'], ' New user',
104
-                           'mail/new_user', user=user)
105
-        else:
106
-            session['known'] = True
107
-        session['name'] = form.name.data
108
-        form.name.data = ''
109
-        return redirect(url_for('index'))
110
-    return render_template('index.html', form=form, name=session.get('name'),
111
-                           known=session.get('known', False))

+ 22
- 0
requirements.txt View File

@@ -0,0 +1,22 @@
1
+alembic==1.0.1
2
+blinker==1.4
3
+Click==7.0
4
+dominate==2.3.4
5
+Flask==1.0.2
6
+Flask-Bootstrap==3.3.7.1
7
+Flask-Mail==0.9.1
8
+Flask-Migrate==2.2.1
9
+Flask-Moment==0.6.0
10
+Flask-SQLAlchemy==2.3.2
11
+Flask-WTF==0.14.2
12
+itsdangerous==0.24
13
+Jinja2==2.10
14
+Mako==1.0.7
15
+MarkupSafe==1.0
16
+python-dateutil==2.7.3
17
+python-editor==1.0.3
18
+six==1.11.0
19
+SQLAlchemy==1.2.12
20
+visitor==0.1.3
21
+Werkzeug==0.14.1
22
+WTForms==2.2.1

+ 0
- 0
tests/__init__.py View File


+ 22
- 0
tests/test_basics.py View File

@@ -0,0 +1,22 @@
1
+import unittest
2
+from flask import current_app
3
+from app import create_app, db
4
+
5
+
6
+class BasicTestCase(unittest.TestCase):
7
+    def setUp(self):
8
+        self.app = create_app('testing')
9
+        self.app_context = self.app.app_context()
10
+        self.app_context.push()
11
+        db.create_all()
12
+
13
+    def tearDown(self):
14
+        db.session.remove()
15
+        db.drop_all()
16
+        self.app_context.pop()
17
+
18
+    def test_app_exists(self):
19
+        self.assertFalse(current_app is None)
20
+
21
+    def test_app_is_testing(self):
22
+        self.assertTrue(current_app.config['TESTING'])