Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

NameForm is Useless class type in complete flasky #463

Open
wants to merge 45 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
fb13ded
Chapter 7: Large file structure (7a)
miguelgrinberg Jul 18, 2017
4de41b0
Chapter 8: Password hashing with Werkzeug (8a)
miguelgrinberg Jul 18, 2017
db681d3
Chapter 8: Authentication blueprint (8b)
miguelgrinberg Jul 18, 2017
cfc8be2
Chapter 8: Login and logout with Flask-Login (8c)
miguelgrinberg Jul 18, 2017
225ae8d
Chapter 8: User registration (8d)
miguelgrinberg Jul 18, 2017
b345154
Chapter 8: Account confirmation (8e)
miguelgrinberg Jul 18, 2017
74eb936
Chapter 8: Password updates (8f)
miguelgrinberg Jul 18, 2017
4711144
Chapter 8: Password resets (8g)
miguelgrinberg Jul 18, 2017
809d39f
Chapter 8: Email address changes (8h)
miguelgrinberg Jul 18, 2017
97e2ef9
Chapter 9: User roles and permissions (9a)
miguelgrinberg Jul 18, 2017
d578133
Chapter 10: User profiles (10a)
miguelgrinberg Jul 18, 2017
1d203ce
Chapter 10: Profiles editor (10b)
miguelgrinberg Jul 18, 2017
168a9e8
Chapter 10: User avatars (10c)
miguelgrinberg Jul 18, 2017
20ce2f9
Chapter 10: Caching of user avatar hashes (10d)
miguelgrinberg Jul 18, 2017
734aa98
Chapter 11: Blog posts (11a)
miguelgrinberg Jul 18, 2017
e00a745
Chapter 11: Blog posts in profile pages (11b)
miguelgrinberg Jul 18, 2017
8d69cb1
Chapter 11: Generate fake users and posts (11c)
miguelgrinberg Jul 18, 2017
b05e1a6
Chapter 11: Blog post pagination (11d)
miguelgrinberg Jul 18, 2017
2caf5b7
Chapter 11: Rich text blog posts with Flask-PageDown (11e)
miguelgrinberg Jul 18, 2017
99984b8
Chapter 11: Rich text server side handling with Markdown and Bleach (…
miguelgrinberg Jul 18, 2017
f338fb0
Chapter 11: Permanent links to posts (11g)
miguelgrinberg Jul 18, 2017
ec2d7e3
Chapter 11: Blog post editor (11h)
miguelgrinberg Jul 18, 2017
c22b78b
Chapter 12: Database representaton of followers (12a)
miguelgrinberg Jul 18, 2017
44dd094
Chapter 12: Followers in the application (12b)
miguelgrinberg Jul 18, 2017
3ce5aea
Chapter 12: Followed posts with a join (12c)
miguelgrinberg Jul 18, 2017
4d077c5
Chapter 12: Show followed blog posts in home page (12d)
miguelgrinberg Jul 18, 2017
156770a
Chapter 12: Self-followers (12e)
miguelgrinberg Jul 18, 2017
a6f626f
Chapter 13: Blog post comments (13a)
miguelgrinberg Jul 18, 2017
4229217
Chapter 13: Comment moderation (13b)
miguelgrinberg Jul 18, 2017
f5cecbd
Chapter 14: API (14a)
miguelgrinberg Jul 18, 2017
ca278c2
Chapter 15: Coverage metrics (15a)
miguelgrinberg Jul 18, 2017
359a494
Chapter 15: Unit tests with the Flask test client (15b)
miguelgrinberg Jul 18, 2017
29d2b9c
Chapter 15: API testing with the Flask test client (15c)
miguelgrinberg Jul 18, 2017
2289b0e
Chapter 15: Unit tests with Selenium (15d)
miguelgrinberg Jul 18, 2017
ebbff88
Chapter 16: Logging of slow database queries (16a)
miguelgrinberg Jul 18, 2017
e114c34
Chapter 16: Source code profiling (16b)
miguelgrinberg Jul 18, 2017
c9d605a
Chapter 17: Deploy command (17a)
miguelgrinberg Jul 18, 2017
b285ab8
Chapter 17: Email notification of application errors (17b)
miguelgrinberg Jul 18, 2017
ac67993
Chapter 17: Heroku support with Waitress (17c-waitress)
miguelgrinberg Jul 18, 2017
7eba28f
Chapter 17: Heroku support with Gunicorn (17c)
miguelgrinberg Aug 31, 2017
c097596
Chapter 17: Docker support (17d)
miguelgrinberg Aug 5, 2017
3fb6dc1
Chapter 17: MySQL support for Docker (17e)
miguelgrinberg Aug 12, 2017
5c285fb
Chapter 17: Docker Compose support (17f)
miguelgrinberg Aug 25, 2017
6ff384a
Chapter 17: Traditional hosting (17g)
miguelgrinberg Jul 18, 2017
b0b2de4
Update forms.py
Kaiji33 Mar 24, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,7 @@ nosetests.xml

# Virtual environment
venv

# Environment files
.env
.env-mysql
21 changes: 21 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
FROM python:3.6-alpine

ENV FLASK_APP flasky.py
ENV FLASK_CONFIG production

RUN adduser -D flasky
USER flasky

WORKDIR /home/flasky

COPY requirements requirements
RUN python -m venv venv
RUN venv/bin/pip install -r requirements/docker.txt

COPY app app
COPY migrations migrations
COPY flasky.py config.py boot.sh ./

# run-time configuration
EXPOSE 5000
ENTRYPOINT ["./boot.sh"]
1 change: 1 addition & 0 deletions Procfile
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
web: gunicorn flasky:app
45 changes: 45 additions & 0 deletions app/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
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 flask_login import LoginManager
from flask_pagedown import PageDown
from config import config

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

login_manager = LoginManager()
login_manager.login_view = 'auth.login'


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)
login_manager.init_app(app)
pagedown.init_app(app)

if app.config['SSL_REDIRECT']:
from flask_sslify import SSLify
sslify = SSLify(app)

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

from .auth import auth as auth_blueprint
app.register_blueprint(auth_blueprint, url_prefix='/auth')

from .api import api as api_blueprint
app.register_blueprint(api_blueprint, url_prefix='/api/v1')

return app
5 changes: 5 additions & 0 deletions app/api/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from flask import Blueprint

api = Blueprint('api', __name__)

from . import authentication, posts, users, comments, errors
44 changes: 44 additions & 0 deletions app/api/authentication.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
from flask import g, jsonify
from flask_httpauth import HTTPBasicAuth
from ..models import User
from . import api
from .errors import unauthorized, forbidden

auth = HTTPBasicAuth()


@auth.verify_password
def verify_password(email_or_token, password):
if email_or_token == '':
return False
if password == '':
g.current_user = User.verify_auth_token(email_or_token)
g.token_used = True
return g.current_user is not None
user = User.query.filter_by(email=email_or_token.lower()).first()
if not user:
return False
g.current_user = user
g.token_used = False
return user.verify_password(password)


@auth.error_handler
def auth_error():
return unauthorized('Invalid credentials')


@api.before_request
@auth.login_required
def before_request():
if not g.current_user.is_anonymous and \
not g.current_user.confirmed:
return forbidden('Unconfirmed account')


@api.route('/tokens/', methods=['POST'])
def get_token():
if g.current_user.is_anonymous or g.token_used:
return unauthorized('Invalid credentials')
return jsonify({'token': g.current_user.generate_auth_token(
expiration=3600), 'expiration': 3600})
67 changes: 67 additions & 0 deletions app/api/comments.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
from flask import jsonify, request, g, url_for, current_app
from .. import db
from ..models import Post, Permission, Comment
from . import api
from .decorators import permission_required


@api.route('/comments/')
def get_comments():
page = request.args.get('page', 1, type=int)
pagination = Comment.query.order_by(Comment.timestamp.desc()).paginate(
page, per_page=current_app.config['FLASKY_COMMENTS_PER_PAGE'],
error_out=False)
comments = pagination.items
prev = None
if pagination.has_prev:
prev = url_for('api.get_comments', page=page-1)
next = None
if pagination.has_next:
next = url_for('api.get_comments', page=page+1)
return jsonify({
'comments': [comment.to_json() for comment in comments],
'prev': prev,
'next': next,
'count': pagination.total
})


@api.route('/comments/<int:id>')
def get_comment(id):
comment = Comment.query.get_or_404(id)
return jsonify(comment.to_json())


@api.route('/posts/<int:id>/comments/')
def get_post_comments(id):
post = Post.query.get_or_404(id)
page = request.args.get('page', 1, type=int)
pagination = post.comments.order_by(Comment.timestamp.asc()).paginate(
page, per_page=current_app.config['FLASKY_COMMENTS_PER_PAGE'],
error_out=False)
comments = pagination.items
prev = None
if pagination.has_prev:
prev = url_for('api.get_post_comments', id=id, page=page-1)
next = None
if pagination.has_next:
next = url_for('api.get_post_comments', id=id, page=page+1)
return jsonify({
'comments': [comment.to_json() for comment in comments],
'prev': prev,
'next': next,
'count': pagination.total
})


@api.route('/posts/<int:id>/comments/', methods=['POST'])
@permission_required(Permission.COMMENT)
def new_post_comment(id):
post = Post.query.get_or_404(id)
comment = Comment.from_json(request.json)
comment.author = g.current_user
comment.post = post
db.session.add(comment)
db.session.commit()
return jsonify(comment.to_json()), 201, \
{'Location': url_for('api.get_comment', id=comment.id)}
14 changes: 14 additions & 0 deletions app/api/decorators.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
from functools import wraps
from flask import g
from .errors import forbidden


def permission_required(permission):
def decorator(f):
@wraps(f)
def decorated_function(*args, **kwargs):
if not g.current_user.can(permission):
return forbidden('Insufficient permissions')
return f(*args, **kwargs)
return decorated_function
return decorator
26 changes: 26 additions & 0 deletions app/api/errors.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
from flask import jsonify
from app.exceptions import ValidationError
from . import api


def bad_request(message):
response = jsonify({'error': 'bad request', 'message': message})
response.status_code = 400
return response


def unauthorized(message):
response = jsonify({'error': 'unauthorized', 'message': message})
response.status_code = 401
return response


def forbidden(message):
response = jsonify({'error': 'forbidden', 'message': message})
response.status_code = 403
return response


@api.errorhandler(ValidationError)
def validation_error(e):
return bad_request(e.args[0])
57 changes: 57 additions & 0 deletions app/api/posts.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
from flask import jsonify, request, g, url_for, current_app
from .. import db
from ..models import Post, Permission
from . import api
from .decorators import permission_required
from .errors import forbidden


@api.route('/posts/')
def get_posts():
page = request.args.get('page', 1, type=int)
pagination = Post.query.paginate(
page, per_page=current_app.config['FLASKY_POSTS_PER_PAGE'],
error_out=False)
posts = pagination.items
prev = None
if pagination.has_prev:
prev = url_for('api.get_posts', page=page-1)
next = None
if pagination.has_next:
next = url_for('api.get_posts', page=page+1)
return jsonify({
'posts': [post.to_json() for post in posts],
'prev': prev,
'next': next,
'count': pagination.total
})


@api.route('/posts/<int:id>')
def get_post(id):
post = Post.query.get_or_404(id)
return jsonify(post.to_json())


@api.route('/posts/', methods=['POST'])
@permission_required(Permission.WRITE)
def new_post():
post = Post.from_json(request.json)
post.author = g.current_user
db.session.add(post)
db.session.commit()
return jsonify(post.to_json()), 201, \
{'Location': url_for('api.get_post', id=post.id)}


@api.route('/posts/<int:id>', methods=['PUT'])
@permission_required(Permission.WRITE)
def edit_post(id):
post = Post.query.get_or_404(id)
if g.current_user != post.author and \
not g.current_user.can(Permission.ADMIN):
return forbidden('Insufficient permissions')
post.body = request.json.get('body', post.body)
db.session.add(post)
db.session.commit()
return jsonify(post.to_json())
53 changes: 53 additions & 0 deletions app/api/users.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
from flask import jsonify, request, current_app, url_for
from . import api
from ..models import User, Post


@api.route('/users/<int:id>')
def get_user(id):
user = User.query.get_or_404(id)
return jsonify(user.to_json())


@api.route('/users/<int:id>/posts/')
def get_user_posts(id):
user = User.query.get_or_404(id)
page = request.args.get('page', 1, type=int)
pagination = user.posts.order_by(Post.timestamp.desc()).paginate(
page, per_page=current_app.config['FLASKY_POSTS_PER_PAGE'],
error_out=False)
posts = pagination.items
prev = None
if pagination.has_prev:
prev = url_for('api.get_user_posts', id=id, page=page-1)
next = None
if pagination.has_next:
next = url_for('api.get_user_posts', id=id, page=page+1)
return jsonify({
'posts': [post.to_json() for post in posts],
'prev': prev,
'next': next,
'count': pagination.total
})


@api.route('/users/<int:id>/timeline/')
def get_user_followed_posts(id):
user = User.query.get_or_404(id)
page = request.args.get('page', 1, type=int)
pagination = user.followed_posts.order_by(Post.timestamp.desc()).paginate(
page, per_page=current_app.config['FLASKY_POSTS_PER_PAGE'],
error_out=False)
posts = pagination.items
prev = None
if pagination.has_prev:
prev = url_for('api.get_user_followed_posts', id=id, page=page-1)
next = None
if pagination.has_next:
next = url_for('api.get_user_followed_posts', id=id, page=page+1)
return jsonify({
'posts': [post.to_json() for post in posts],
'prev': prev,
'next': next,
'count': pagination.total
})
5 changes: 5 additions & 0 deletions app/auth/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from flask import Blueprint

auth = Blueprint('auth', __name__)

from . import views
Loading