My blog app written in Flask allows users to post comments under each post. I wanted to implement an easy way for users to delete their own comments that didn't look too much out of place and wasn't too complicated to implement. HTMX came to the rescue.
Setup
First I included HTMX in the head template:
<script src="https://unpkg.com/htmx.org@1.5.0" integrity="sha384-oGA+prIp5Vchu6we2YkI51UtVzN9Jpx2Z7PnR1I78PnZlN8LkrCT4lqqqmDkyrvI" crossorigin="anonymous"></script>
Models
The relevant parts of user model:
class User(db.Model, UserMixin):
__tablename__ = 'users'
id = db.Column(db.Integer, primary_key=True)
# Some other fields...
posts = db.relationship('Post', backref='author', lazy=True)
And the post model is defined like this:
class Post(db.Model):
__tablename__ = 'posts'
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(100), nullable=False)
content = db.Column(db.Text, nullable=False)
user_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False)
# Some other fields...
# Opening post id, if this post is part of a comment thread
op_id = db.Column(db.Integer, db.ForeignKey('posts.id'), nullable=True)
# Comments with this post as their OP
comments = db.relationship('Post', backref=db.backref('op', remote_side=[id]), lazy=True)
Adding the delete button
Somewhere in the template for the comment, I've added this code:
{% if comment.author == current_user %}
<div class="level-right">
<div class="level-item">
<button class="delete"
hx-delete="{{ url_for('posts.del_comment', post_id=comment.id) }}"
hx-confirm="Are you sure you wish to delete this comment?"
hx-target="#comment-{{ comment.id }}"
hx-swap="outerHTML"></button>
</div>
</div>
{% endif %}
In terms of HTMX, here we have four tags:
- hx-delete: This tells HTMX to send a DELETE request to the url provided. In this case, it's the URL for post.del_comment function which accepts the post_id (the id of the comment) as a parameter.
- hx-confirm: This is a neat way to make the browser ask for confirmation before completing the action. We're deleting stuff and it's nice to ask if the click on the button was intentional or not.
- hx-target: This tag tells HTMX which element on the page should be changed with the HTML we get back from the server after the function call. In this case, it's the box for this specific comment.
- hx-swap: And finally, we're telling HTMX to swap the whole comment box with whatever we get back.
The route to call from HTMX
Now that we have the button set up, we need to implement the function it will call. In my posts routes, I have this code for that purpose:
@posts.route("/post/<int:post_id>", methods=['DELETE'])
def del_comment(post_id):
post = Post.query.get_or_404(post_id)
if post.author != current_user:
abort(403)
db.session.delete(post)
db.session.commit()
return ''
Since it's only doing deletes, the only accepted method for it is the DELETE method. We make sure that the post indeed exists and that the user trying to delete it is the author of that post. Once we're happy with that, we proceed to delete the post. The return is an empty string because the whole box containing the comment on the page will now be removed.
And that's it! The result is a page with little delete buttons for each comment written by the current user. When clicked, the user is prompted if they're sure they want to delete the comment and when they click yes, the comment is gone without reloading the page. Simple and elegant.