Skip to content

Commit

Permalink
Chapter 7: Large file structure (7a)
Browse files Browse the repository at this point in the history
  • Loading branch information
miguelgrinberg committed Jun 9, 2019
1 parent 4459abe commit fb13ded
Show file tree
Hide file tree
Showing 20 changed files with 239 additions and 111 deletions.
28 changes: 28 additions & 0 deletions app/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
from flask import Flask
from flask_bootstrap import Bootstrap
from flask_mail import Mail
from flask_moment import Moment
from flask_sqlalchemy import SQLAlchemy
from config import config

bootstrap = Bootstrap()
mail = Mail()
moment = Moment()
db = SQLAlchemy()

This comment has been minimized.

Copy link
@mohammadekhosravi

mohammadekhosravi Jul 17, 2020

in the 2016 pycon presentation(Flask at Scale) you said that we need to do "from . import model" so that they are registered with SQLAlchemy. that's not the case anymore?

This comment has been minimized.

Copy link
@miguelgrinberg

miguelgrinberg Jul 17, 2020

Author Owner

Yes, that is always necessary. This application, however, imports the models in other places, so having them here as well makes no difference.

This comment has been minimized.

Copy link
@mohammadekhosravi

mohammadekhosravi Jul 19, 2020

Ok. thank you for answering.


def create_app(config_name):
app = Flask(__name__)
app.config.from_object(config[config_name])
config[config_name].init_app(app)

bootstrap.init_app(app)
mail.init_app(app)
moment.init_app(app)
db.init_app(app)

from .main import main as main_blueprint
app.register_blueprint(main_blueprint)

return app

20 changes: 20 additions & 0 deletions app/email.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
from threading import Thread
from flask import current_app, render_template
from flask_mail import Message
from . import mail


def send_async_email(app, msg):
with app.app_context():
mail.send(msg)


def send_email(to, subject, template, **kwargs):
app = current_app._get_current_object()
msg = Message(app.config['FLASKY_MAIL_SUBJECT_PREFIX'] + ' ' + subject,
sender=app.config['FLASKY_MAIL_SENDER'], recipients=[to])
msg.body = render_template(template + '.txt', **kwargs)
msg.html = render_template(template + '.html', **kwargs)
thr = Thread(target=send_async_email, args=[app, msg])
thr.start()
return thr
5 changes: 5 additions & 0 deletions app/main/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from flask import Blueprint

main = Blueprint('main', __name__)

from . import views, errors
12 changes: 12 additions & 0 deletions app/main/errors.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
from flask import render_template
from . import main


@main.app_errorhandler(404)
def page_not_found(e):
return render_template('404.html'), 404


@main.app_errorhandler(500)
def internal_server_error(e):
return render_template('500.html'), 500
8 changes: 8 additions & 0 deletions app/main/forms.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
from flask_wtf import FlaskForm
from wtforms import StringField, SubmitField
from wtforms.validators import DataRequired


class NameForm(FlaskForm):
name = StringField('What is your name?', validators=[DataRequired()])
submit = SubmitField('Submit')
28 changes: 28 additions & 0 deletions app/main/views.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
from flask import render_template, session, redirect, url_for, current_app
from .. import db
from ..models import User
from ..email import send_email
from . import main
from .forms import NameForm


@main.route('/', methods=['GET', 'POST'])
def index():
form = NameForm()
if form.validate_on_submit():
user = User.query.filter_by(username=form.name.data).first()
if user is None:
user = User(username=form.name.data)
db.session.add(user)
db.session.commit()
session['known'] = False
if current_app.config['FLASKY_ADMIN']:
send_email(current_app.config['FLASKY_ADMIN'], 'New User',
'mail/new_user', user=user)
else:
session['known'] = True
session['name'] = form.name.data
return redirect(url_for('.index'))
return render_template('index.html',
form=form, name=session.get('name'),
known=session.get('known', False))
21 changes: 21 additions & 0 deletions app/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
from . import db


class Role(db.Model):
__tablename__ = 'roles'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(64), unique=True)
users = db.relationship('User', backref='role', lazy='dynamic')

def __repr__(self):
return '<Role %r>' % self.name


class User(db.Model):
__tablename__ = 'users'
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(64), unique=True, index=True)
role_id = db.Column(db.Integer, db.ForeignKey('roles.id'))

def __repr__(self):
return '<User %r>' % self.username
File renamed without changes.
File renamed without changes.
File renamed without changes.
4 changes: 2 additions & 2 deletions templates/base.html → app/templates/base.html
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,11 @@
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="/">Flasky</a>
<a class="navbar-brand" href="{{ url_for('main.index') }}">Flasky</a>
</div>
<div class="navbar-collapse collapse">
<ul class="nav navbar-nav">
<li><a href="/">Home</a></li>
<li><a href="{{ url_for('main.index') }}">Home</a></li>
</ul>
</div>
</div>
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
46 changes: 46 additions & 0 deletions config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import os
basedir = os.path.abspath(os.path.dirname(__file__))


class Config:
SECRET_KEY = os.environ.get('SECRET_KEY') or 'hard to guess string'
MAIL_SERVER = os.environ.get('MAIL_SERVER', 'smtp.googlemail.com')
MAIL_PORT = int(os.environ.get('MAIL_PORT', '587'))
MAIL_USE_TLS = os.environ.get('MAIL_USE_TLS', 'true').lower() in \
['true', 'on', '1']
MAIL_USERNAME = os.environ.get('MAIL_USERNAME')
MAIL_PASSWORD = os.environ.get('MAIL_PASSWORD')
FLASKY_MAIL_SUBJECT_PREFIX = '[Flasky]'
FLASKY_MAIL_SENDER = 'Flasky Admin <[email protected]>'
FLASKY_ADMIN = os.environ.get('FLASKY_ADMIN')
SQLALCHEMY_TRACK_MODIFICATIONS = False

@staticmethod
def init_app(app):
pass


class DevelopmentConfig(Config):
DEBUG = True
SQLALCHEMY_DATABASE_URI = os.environ.get('DEV_DATABASE_URL') or \
'sqlite:///' + os.path.join(basedir, 'data-dev.sqlite')


class TestingConfig(Config):
TESTING = True
SQLALCHEMY_DATABASE_URI = os.environ.get('TEST_DATABASE_URL') or \
'sqlite://'


class ProductionConfig(Config):
SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL') or \
'sqlite:///' + os.path.join(basedir, 'data.sqlite')


config = {
'development': DevelopmentConfig,
'testing': TestingConfig,
'production': ProductionConfig,

'default': DevelopmentConfig
}
25 changes: 25 additions & 0 deletions flasky.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import os
import click
from flask_migrate import Migrate
from app import create_app, db
from app.models import User, Role

app = create_app(os.getenv('FLASK_CONFIG') or 'default')
migrate = Migrate(app, db)


@app.shell_context_processor
def make_shell_context():
return dict(db=db, User=User, Role=Role)


@app.cli.command()
@click.argument('test_names', nargs=-1)
def test(test_names):
"""Run the unit tests."""
import unittest
if test_names:
tests = unittest.TestLoader().loadTestsFromNames(test_names)
else:
tests = unittest.TestLoader().discover('tests')
unittest.TextTestRunner(verbosity=2).run(tests)
109 changes: 0 additions & 109 deletions hello.py

This file was deleted.

22 changes: 22 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
alembic==0.9.3
blinker==1.4
click==6.7
dominate==2.3.1
Flask==0.12.2
Flask-Bootstrap==3.3.7.1
Flask-Mail==0.9.1
Flask-Migrate==2.0.4
Flask-Moment==0.5.1
Flask-SQLAlchemy==2.2
Flask-WTF==0.14.2
itsdangerous==0.24
Jinja2==2.9.6
Mako==1.0.7
MarkupSafe==1.0
python-dateutil==2.6.1
python-editor==1.0.3
six==1.10.0
SQLAlchemy==1.1.11
visitor==0.1.3
Werkzeug==0.12.2
WTForms==2.1
Empty file added tests/__init__.py
Empty file.
22 changes: 22 additions & 0 deletions tests/test_basics.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import unittest
from flask import current_app
from app import create_app, db


class BasicsTestCase(unittest.TestCase):
def setUp(self):
self.app = create_app('testing')
self.app_context = self.app.app_context()
self.app_context.push()
db.create_all()

def tearDown(self):
db.session.remove()
db.drop_all()
self.app_context.pop()

def test_app_exists(self):
self.assertFalse(current_app is None)

def test_app_is_testing(self):
self.assertTrue(current_app.config['TESTING'])

7 comments on commit fb13ded

@pdamoune
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi Miguel,

It appears that i can't run the test individually :
`test_app_is_testing (unittest.loader._FailedTest) ... ERROR

======================================================================
ERROR: test_app_is_testing (unittest.loader._FailedTest)

ImportError: Failed to import test module: test_app_is_testing
Traceback (most recent call last):
File "/usr/local/Cellar/python/3.7.4/Frameworks/Python.framework/Versions/3.7/lib/python3.7/unittest/loader.py", line 154, in loadTestsFromName
module = import(module_name)
ModuleNotFoundError: No module named 'test_app_is_testing'


Ran 1 test in 0.001s

FAILED (errors=1)`

Any idea ?

@miguelgrinberg
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@pdamoune what command are you using for this?

@LinzzMichael
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't see any code to run the app, and when I use 'python flasky.py test', there is no reaction

@miguelgrinberg
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@LinzzMichael do you have the book? This repo is not a standalone application, it is a teaching application that you have to follow using the instructions in the book. In any case, flask apps these days are started with the flask command.

@LinzzMichael
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@miguelgrinberg yes,I had follow the book to write my code, but I got the error "discover() missing 1 required positional argument: 'start_dir' ", so I decide to try the example code, but I find it's different from the book, I am not quite understand the example, dose it mean it can't not run?

@miguelgrinberg
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe you are using the first edition of the book? This code is for the 2nd edition, first edition is here: https://github.com/miguelgrinberg/flasky-first-edition

@LinzzMichael
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@miguelgrinberg Thank you very much for answer my question! your book is pretty good, May be I need to buy the new book.

Please sign in to comment.