Merv Merzoug
Merv Merzoug

Reputation: 1237

Flask Form Handling in Jinja2 For Loop

I have a blog-type application where the homepage displays posts with some information and with an Add Comment form. The form is intended to write to my Comments() model in models.py for that specific post.

The Problem: is that because I am looping through the posts in home.html, the post.id is not available to the home function in routes.py. So, when the form is validated, the comment is applied to all the posts, not just the one in which the comment is added.

The question: how can I get the relevant post.id in the home function from the Jinja forloop and have the comment be applied to the specific post, not just all the posts on the homepage?? I'm not seeing my logic/syntax error - what am I missing here? Thanks

The resulting error: AttributeError: 'function' object has no attribute 'id' which of course makes sense because the application has no clue what post we are referencing in the Jinja2 forloop in home.html.

here is the Comments db model in models.py:

class Comments(db.Model):
    comment_id = db.Column(db.Integer, primary_key=True)
    user_id = db.Column(db.Integer, nullable=True, primary_key=False)
    post_id = db.Column(db.Integer, nullable=True, primary_key=False)
    comment = db.Column(db.String(2000), unique=False, nullable=True)
    comment_date = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)

    class Meta:
        database = db

    def fetch_post_comments(self, post_id):
        comments = Comments.query.filter(Comments.post_id==post_id)
        return comments

    def fetch_user(self, user_id):
        user = User.query.filter(User.id==user_id).first_or_404()
        return user.username

    def __repr__(self):
        return f"Comments ('{self.user_id}', '{self.post_id}', '{self.comment}', '{self.comment_date}')"

And here is my home function in routes.py:

@app.route("/")
@app.route("/home", methods=['GET', 'POST'])
@login_required
def home():
    page = request.args.get('page', 1, type=int)
    posts = Post.query.order_by(Post.date_posted.desc()).paginate(page=page, per_page=5)
    comment_form = CommentForm()

    def add_comment(post_id):
        if comment_form.validate_on_submit():
            new_comment = Comments(user_id=current_user.id,
                                   post_id=post_id,
                                   comment=comment_form.comment_string.data)
            db.session.add(new_comment)
            db.session.commit()

            flash('HOT TAKE! Posted your comment.', 'success')
            # return redirect(url_for('post', post_id=post.id))

    def get_upvote_count(post_id):
        count = Vote.query.filter(Vote.post_id==post_id).count()
        return count

    def get_flag_count(post_id):
        count = Flag.query.filter(Flag.post_id == post_id).count()
        return count

    def get_comment_count(post_id):
        count = Comments.query.filter(Comments.post_id == post_id).count()
        return count

    def get_favorite_count(post_id):
        count = Favorites.query.filter(Favorites.post_id == post_id).count()
        return count

    def get_youtube_id_from_url(url):
        video_id = url.split('v=')[1]
        if '&' in video_id:
            video_id = video_id.split('&')[0]

        base_url = "https://www.youtube.com/embed/"

        return base_url + video_id

    def get_spotify_embed_url(url):
        track_or_playlist = url.split('https://open.spotify.com/')[1].split('/')[0]
        base_url = f"https://open.spotify.com/embed/{track_or_playlist}/"
        spotify_id = url.split('https://open.spotify.com/')[1].split('/')[1]

        embed_url = base_url + spotify_id

        return embed_url

        return base_url + video_id

    return render_template('home.html',
                           posts=posts,
                           get_upvote_count=get_upvote_count,
                           get_comment_count=get_comment_count,
                           get_flag_count=get_flag_count,
                           get_favorite_count=get_favorite_count,
                           comment_form=comment_form,
                           add_comment=add_comment,
                           get_youtube_id_from_url=get_youtube_id_from_url,
                           get_spotify_embed_url=get_spotify_embed_url)

And here is my home.html

{% extends "layout.html" %}
{% block content %}
    {% for post in posts.items %}
        <article class="media content-section">
            <img class="rounded-circle article-img" src="{{ url_for('static', filename='profile_pics/' + post.author.image_file) }}">

          <div class="media-body">
            <div class="article-metadata">

                <div align="left">
                    <a class="mr-2 text-secondary" href="{{ url_for('user_posts', username=post.author.username) }}">{{ post.author.username }}</a>
                </div>

                <div align="left">
                    <small class="text-muted">Posted on: {{ post.date_posted.strftime('%Y-%m-%d') }}</small>
                </div>

                <div align="right">
                    <a class="mr-2 text-secondary" href="{{ url_for('flag', post_id=post.id, user_id=current_user.id) }}">Flag Post</a>
                </div>

                <div align="right">
                    <a class="mr-2 text-secondary" href="{{ url_for('add_favorite', post_id=post.id, user_id=current_user.id) }}">Favorite Post ({{ get_favorite_count(post.id) }})</a>
                </div>

                <div align="right">
                    <a class="mr-2 text-secondary" href="{{ url_for('post', post_id=post.id) }}">Comments ({{ get_comment_count(post.id) }})</a>
                </div>


            </div>
                <h3><a class="article-title" href="{{ url_for('post', post_id=post.id) }}">{{ post.title }}</a></h3>
                <p class="article-content justify-content-center">{{ post.content }}</p>
                <br>
                {% for url in post.urls.split('||') %}
                    {% if 'youtube.com' in url %}
                        <div class="embed-responsive embed-responsive-16by9">
                            <iframe class="embed-responsive-item"
                                    src="{{ get_youtube_id_from_url(url) }}" allowfullscreen="" frameborder="0">
                            </iframe>
                        </div>
                        <a href="{{ url }}">Link</a>
                    {% elif 'soundcloud.com' in url %}
                        <div class="embed-responsive embed-responsive-16by9">
                            <iframe class="embed-responsive-item" scrolling="no" frameborder="no"
                                 src="{{ 'https://w.soundcloud.com/player/?url=' + url }}">
                            </iframe>
                        </div>
                        <a href="{{ url }}">Link</a>
                    {% elif 'spotify.com' in url %}
                        <div class="embed-responsive embed-responsive-16by9">
                            <iframe class="embed-responsive-item"
                                    src="{{ get_spotify_embed_url(url) }}" allowfullscreen allow="encrypted-media">
                            </iframe>
                        </div>
                        <a href="{{ url }}">Link</a>
                    {% elif 'vimeo.com' in url %}
                        <div class="embed-responsive embed-responsive-16by9">
                            <iframe class="embed-responsive-item" scrolling="no" frameborder="no"
                                 src="{{ 'https://player.vimeo.com/video/' + url.split('https://vimeo.com/')[1] }}">
                            </iframe>
                        </div>
                        <a href="{{ url }}">Link</a>
                    {% elif 'tumblr.com' in url %}
                        <div class="embed-responsive embed-responsive-16by9">
                            <iframe class="embed-responsive-item"
                                    src="{{ url }}" frameborder="0">
                            </iframe>
                        </div>
                        <a href="{{ url }}">Link</a>
                    {% else %}
                        <a href="{{ url }}">Link</a>
                        <br>
                    {% endif %}
                {% endfor %}
                <br>
                <br>
                <p class="text-muted"><strong>Tags:</strong></p>
                  {% for tag in post.tags.replace('  ', ' ').strip(',').split(' ') %}
                    <a class="btn btn-light" href="{{url_for('tag_posts', tag=tag)}}">{{tag.strip('#').strip(' ').lower() }}</a>
                  {% endfor %}
                  <br>
                    <form method="POST" action="" enctype="multipart/form-data">
                        {{ comment_form.hidden_tag() }}
                        <fieldset class="form-group">

                            <br>
                            <br>
                            <p class="text-muted"><strong>Add a comment:</strong></p>
                            <div class="form-group">

                                {% if comment_form.comment_string.errors %}
                                    {{ comment_form.comment_string(class="form-control form-control-lg is-invalid") }}
                                    <div class="invalid-feedback">
                                        {% for error in comment_form.comment_string.errors %}
                                            <span>{{ error }}</span>
                                        {% endfor %}
                                    </div>
                                {% else %}
                                    {{ comment_form.comment_string(class="form-control form-control-lg") }}
<!--                                    {{ add_comment(post_id=post.id) }}-->
                                {% endif %}

                            </div>

                        </fieldset>
                        <div class="form-group">
                            {{ comment_form.submit(class="btn btn-secondary") }}
                        </div>
                    </form>
                  <br>
                  <p class="text-muted mt-4"><strong>Street Cred:  </strong>{{ get_upvote_count(post.id) }}</p>
                <a class="btn btn-secondary mb-4" href="{{url_for('upvote', user_id=post.author.id, post_id=post.id)}}">Upvote</a>
          </div>
        </article>

    {% endfor %}
    {% for page_num in posts.iter_pages(left_edge=1, right_edge=1, left_current=1, right_current=2) %}
        {% if page_num %}
            {% if posts.page == page_num %}
                <a class="btn btn-secondary mb-4" href="{{ url_for('home', page=page_num) }}">{{ page_num }}</a>
            {% else %}
                <a class="btn btn-outline-info mb-4" href="{{ url_for('home', page=page_num) }}">{{ page_num }}</a>
            {% endif %}
        {% else %}
            ...
        {% endif %}
    {% endfor %}
{% endblock content %}

Upvotes: 2

Views: 639

Answers (1)

djnz
djnz

Reputation: 2050

A couple of options:

  1. Add the post.id to the url as a parameter or arg, here's the arg method:

Add the url to the form and set the post.id as the arg:

<form method="POST" action="{{url_for('home', post_id=post.id)}}" enctype="multipart/form-data">

And in the route:

new_comment = Comments(user_id=current_user.id,
                               post_id=request.args.get('post_id', type=int),
                               comment=comment_form.comment_string.data)

OR

  1. Add a hidden field to your form, and set the value of that to be the post.id:

Add a hidden field onto your form:

post_id = HiddenField()

You'll need to replace the CSRF render (hidden_tag()) to prevent auto rendering of the post_id field:

{{ comment_form.csrf_token }} 

Next, set the value of your hidden field data (credit to this answer for this function):

{% set p = comment_form.post_id.process_data(post.id) %}
{{ comment_form.post_id() }}

and finally, in the route, (remove the add_comment declaration):

def home():
    # Omitted ...

    if comment_form.validate_on_submit():
        new_comment = Comments(user_id=current_user.id,
                               post_id=comment_form.post_id.data,
                               comment=comment_form.comment_string.data)
        db.session.add(new_comment)
        # etc...

Hope this helps, note it's not tested

Upvotes: 2

Related Questions