Skip to content

Commit a6f626f

Browse files
Chapter 13: Blog post comments (13a)
1 parent 156770a commit a6f626f

File tree

12 files changed

+169
-12
lines changed

12 files changed

+169
-12
lines changed

app/main/forms.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,3 +54,8 @@ def validate_username(self, field):
5454
class PostForm(FlaskForm):
5555
body = PageDownField("What's on your mind?", validators=[DataRequired()])
5656
submit = SubmitField('Submit')
57+
58+
59+
class CommentForm(FlaskForm):
60+
body = StringField('Enter your comment', validators=[DataRequired()])
61+
submit = SubmitField('Submit')

app/main/views.py

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,10 @@
22
current_app, make_response
33
from flask_login import login_required, current_user
44
from . import main
5-
from .forms import EditProfileForm, EditProfileAdminForm, PostForm
5+
from .forms import EditProfileForm, EditProfileAdminForm, PostForm,\
6+
CommentForm
67
from .. import db
7-
from ..models import Permission, Role, User, Post
8+
from ..models import Permission, Role, User, Post, Comment
89
from ..decorators import admin_required, permission_required
910

1011

@@ -91,10 +92,28 @@ def edit_profile_admin(id):
9192
return render_template('edit_profile.html', form=form, user=user)
9293

9394

94-
@main.route('/post/<int:id>')
95+
@main.route('/post/<int:id>', methods=['GET', 'POST'])
9596
def post(id):
9697
post = Post.query.get_or_404(id)
97-
return render_template('post.html', posts=[post])
98+
form = CommentForm()
99+
if form.validate_on_submit():
100+
comment = Comment(body=form.body.data,
101+
post=post,
102+
author=current_user._get_current_object())
103+
db.session.add(comment)
104+
db.session.commit()
105+
flash('Your comment has been published.')
106+
return redirect(url_for('.post', id=post.id, page=-1))
107+
page = request.args.get('page', 1, type=int)
108+
if page == -1:
109+
page = (post.comments.count() - 1) // \
110+
current_app.config['FLASKY_COMMENTS_PER_PAGE'] + 1
111+
pagination = post.comments.order_by(Comment.timestamp.asc()).paginate(
112+
page, per_page=current_app.config['FLASKY_COMMENTS_PER_PAGE'],
113+
error_out=False)
114+
comments = pagination.items
115+
return render_template('post.html', posts=[post], form=form,
116+
comments=comments, pagination=pagination)
98117

99118

100119
@main.route('/edit/<int:id>', methods=['GET', 'POST'])

app/models.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ class User(UserMixin, db.Model):
104104
backref=db.backref('followed', lazy='joined'),
105105
lazy='dynamic',
106106
cascade='all, delete-orphan')
107+
comments = db.relationship('Comment', backref='author', lazy='dynamic')
107108

108109
@staticmethod
109110
def add_self_follows():
@@ -264,6 +265,7 @@ class Post(db.Model):
264265
body_html = db.Column(db.Text)
265266
timestamp = db.Column(db.DateTime, index=True, default=datetime.utcnow)
266267
author_id = db.Column(db.Integer, db.ForeignKey('users.id'))
268+
comments = db.relationship('Comment', backref='post', lazy='dynamic')
267269

268270
@staticmethod
269271
def on_changed_body(target, value, oldvalue, initiator):
@@ -275,3 +277,24 @@ def on_changed_body(target, value, oldvalue, initiator):
275277
tags=allowed_tags, strip=True))
276278

277279
db.event.listen(Post.body, 'set', Post.on_changed_body)
280+
281+
282+
class Comment(db.Model):
283+
__tablename__ = 'comments'
284+
id = db.Column(db.Integer, primary_key=True)
285+
body = db.Column(db.Text)
286+
body_html = db.Column(db.Text)
287+
timestamp = db.Column(db.DateTime, index=True, default=datetime.utcnow)
288+
disabled = db.Column(db.Boolean)
289+
author_id = db.Column(db.Integer, db.ForeignKey('users.id'))
290+
post_id = db.Column(db.Integer, db.ForeignKey('posts.id'))
291+
292+
@staticmethod
293+
def on_changed_body(target, value, oldvalue, initiator):
294+
allowed_tags = ['a', 'abbr', 'acronym', 'b', 'code', 'em', 'i',
295+
'strong']
296+
target.body_html = bleach.linkify(bleach.clean(
297+
markdown(value, output_format='html'),
298+
tags=allowed_tags, strip=True))
299+
300+
db.event.listen(Comment.body, 'set', Comment.on_changed_body)

app/static/styles.css

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,38 @@ div.post-content {
4141
div.post-footer {
4242
text-align: right;
4343
}
44+
ul.comments {
45+
list-style-type: none;
46+
padding: 0px;
47+
margin: 16px 0px 0px 0px;
48+
}
49+
ul.comments li.comment {
50+
margin-left: 32px;
51+
padding: 8px;
52+
border-bottom: 1px solid #e0e0e0;
53+
}
54+
ul.comments li.comment:nth-child(1) {
55+
border-top: 1px solid #e0e0e0;
56+
}
57+
ul.comments li.comment:hover {
58+
background-color: #f0f0f0;
59+
}
60+
div.comment-date {
61+
float: right;
62+
}
63+
div.comment-author {
64+
font-weight: bold;
65+
}
66+
div.comment-thumbnail {
67+
position: absolute;
68+
}
69+
div.comment-content {
70+
margin-left: 48px;
71+
min-height: 48px;
72+
}
73+
div.comment-form {
74+
margin: 16px 0px 16px 32px;
75+
}
4476
div.pagination {
4577
width: 100%;
4678
text-align: right;

app/templates/_comments.html

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<ul class="comments">
2+
{% for comment in comments %}
3+
<li class="comment">
4+
<div class="comment-thumbnail">
5+
<a href="{{ url_for('.user', username=comment.author.username) }}">
6+
<img class="img-rounded profile-thumbnail" src="{{ comment.author.gravatar(size=40) }}">
7+
</a>
8+
</div>
9+
<div class="comment-content">
10+
<div class="comment-date">{{ moment(comment.timestamp).fromNow() }}</div>
11+
<div class="comment-author"><a href="{{ url_for('.user', username=comment.author.username) }}">{{ comment.author.username }}</a></div>
12+
<div class="comment-body">
13+
{% if comment.body_html %}
14+
{{ comment.body_html | safe }}
15+
{% else %}
16+
{{ comment.body }}
17+
{% endif %}
18+
</div>
19+
</div>
20+
</li>
21+
{% endfor %}
22+
</ul>

app/templates/_macros.html

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,27 @@
1-
{% macro pagination_widget(pagination, endpoint) %}
1+
{% macro pagination_widget(pagination, endpoint, fragment='') %}
22
<ul class="pagination">
33
<li{% if not pagination.has_prev %} class="disabled"{% endif %}>
4-
<a href="{% if pagination.has_prev %}{{ url_for(endpoint, page=pagination.prev_num, **kwargs) }}{% else %}#{% endif %}">
4+
<a href="{% if pagination.has_prev %}{{ url_for(endpoint, page=pagination.prev_num, **kwargs) }}{{ fragment }}{% else %}#{% endif %}">
55
&laquo;
66
</a>
77
</li>
88
{% for p in pagination.iter_pages() %}
99
{% if p %}
1010
{% if p == pagination.page %}
1111
<li class="active">
12-
<a href="{{ url_for(endpoint, page = p, **kwargs) }}">{{ p }}</a>
12+
<a href="{{ url_for(endpoint, page = p, **kwargs) }}{{ fragment }}">{{ p }}</a>
1313
</li>
1414
{% else %}
1515
<li>
16-
<a href="{{ url_for(endpoint, page = p, **kwargs) }}">{{ p }}</a>
16+
<a href="{{ url_for(endpoint, page = p, **kwargs) }}{{ fragment }}">{{ p }}</a>
1717
</li>
1818
{% endif %}
1919
{% else %}
2020
<li class="disabled"><a href="#">&hellip;</a></li>
2121
{% endif %}
2222
{% endfor %}
2323
<li{% if not pagination.has_next %} class="disabled"{% endif %}>
24-
<a href="{% if pagination.has_next %}{{ url_for(endpoint, page=pagination.next_num, **kwargs) }}{% else %}#{% endif %}">
24+
<a href="{% if pagination.has_next %}{{ url_for(endpoint, page=pagination.next_num, **kwargs) }}{{ fragment }}{% else %}#{% endif %}">
2525
&raquo;
2626
</a>
2727
</li>

app/templates/_posts.html

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@
2929
<a href="{{ url_for('.post', id=post.id) }}">
3030
<span class="label label-default">Permalink</span>
3131
</a>
32+
<a href="{{ url_for('.post', id=post.id) }}#comments">
33+
<span class="label label-primary">{{ post.comments.count() }} Comments</span>
34+
</a>
3235
</div>
3336
</div>
3437
</li>

app/templates/post.html

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,21 @@
11
{% extends "base.html" %}
2+
{% import "bootstrap/wtf.html" as wtf %}
23
{% import "_macros.html" as macros %}
34

45
{% block title %}Flasky - Post{% endblock %}
56

67
{% block page_content %}
78
{% include '_posts.html' %}
9+
<h4 id="comments">Comments</h4>
10+
{% if current_user.can(Permission.COMMENT) %}
11+
<div class="comment-form">
12+
{{ wtf.quick_form(form) }}
13+
</div>
14+
{% endif %}
15+
{% include '_comments.html' %}
16+
{% if pagination %}
17+
<div class="pagination">
18+
{{ macros.pagination_widget(pagination, '.post', fragment='#comments', id=posts[0].id) }}
19+
</div>
20+
{% endif %}
821
{% endblock %}

app/templates/user.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ <h1>{{ user.username }}</h1>
2121
{% endif %}
2222
{% if user.about_me %}<p>{{ user.about_me }}</p>{% endif %}
2323
<p>Member since {{ moment(user.member_since).format('L') }}. Last seen {{ moment(user.last_seen).fromNow() }}.</p>
24-
<p>{{ user.posts.count() }} blog posts.</p>
24+
<p>{{ user.posts.count() }} blog posts. {{ user.comments.count() }} comments.</p>
2525
<p>
2626
{% if current_user.can(Permission.FOLLOW) and user != current_user %}
2727
{% if not current_user.is_following(user) %}

config.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ class Config:
1616
SQLALCHEMY_TRACK_MODIFICATIONS = False
1717
FLASKY_POSTS_PER_PAGE = 20
1818
FLASKY_FOLLOWERS_PER_PAGE = 50
19+
FLASKY_COMMENTS_PER_PAGE = 30
1920

2021
@staticmethod
2122
def init_app(app):

0 commit comments

Comments
 (0)