2

I am trying to update a MySQL record from flask-sqlalchemy, but instead of updating the current record, it is adding a new one. But here's the really weird thing, when I run the same code from the Python prompt it updates properly without adding a new record.

Here is the relevant portion of the code:

First, the template that feeds the blog:

{% for post in posts %}
  <article class="blog">
    <header>
      <h2>{{ post.title|safe }}</h2>
    </header>
    <main>
      {{ post.content|safe }}
    </main>
    <footer>
      <p>{{ post.posted.strftime('%d %B %Y %I:%M')|safe }}<p>
      {% if session.logged_in %}
        <form action="{{ url_for('blog') }}" method="post">
          <div style="display:none;">
            <input type="hidden" name="blog_id" value="{{ post.id|safe }}">
          </div>

          <div class="form-group submit">
            <input type="submit" name="submit" class="btn" value="Edit Post">
            <input type="submit" name="submit" class="btn" value="Delete Post">
          </div>
        </form>
      {% endif %}
    </footer>
  </article>
{% endfor %}

Now, the code for the views:

@app.route('/blog', methods = ['GET', 'POST'])
def blog():
    # Get all records from database blog table:
    posts = models.Blog.query.order_by(models.Blog.posted.desc()).all()
    context = {
        'copyright': COPYRIGHT,
        'posts': posts
    }
    if request.method == 'POST':
        blog_id = request.form.get('blog_id')
        if request.form.get('submit') == 'Edit Post':
            return redirect(url_for('edit_entry', blog_id = blog_id))
        elif request.form.get('submit') == 'Delete Post':
            pass
    elif request.method == 'GET':
        return render_template('blog.html', **context)

@app.route('/edit_entry', methods = ['GET', 'POST'])
def edit_entry():
    form = BlogEntry() # A WTForms object.
    # blog_id is passed from a redirect. Tested and ensured it is passing proper value.
    blog_id = request.args['blog_id']
    post = models.Blog.query.filter_by(id = blog_id).one()
    if request.method == 'POST':
        post.title = form.title.data
        post.content = form.content.data
        db.session.commit()
        return redirect(url_for('blog'))
    elif request.method == 'GET':
        context = {
            'copyright': COPYRIGHT,
            'form': form,
        }
        form.title.data = post.title
        form.content.data = post.content
        return render_template('edit_entry.html', **context)

This is the code as it is right now in my app file. I've run the exact same code from the Python prompt in the terminal, and it works perfectly, but when this code runs from the app file, it creates a new record instead of updating the existing one.

As a note, I have also tried:

models.Blog.query.filter_by(id=blog_id).update({
    'title': form.title.data,
    'content': form.content.data
})
db.session.commit()

and by replacing models.Blog with db.session.query(models.Blog). Basically, i've tried every method and combination of code for updating records using flask-sqlalchemy that I could find on this forums and others. All the same issue. When I run them from the python prompt, they update the correct record in the database. When I run them from the app, they create a new record.

If you want to see more of the code, you can check the entire project here: https://github.com/tbellerose/lauraopfelphotography.com

halfer
  • 19,824
  • 17
  • 99
  • 186
Tyler
  • 90
  • 1
  • 7
  • You'll need to provide more debugging information. *What* value is being received for `blog_id`? Does it correctly retrieve the record and assign it to the `post` variable? Does the code correctly go into the POST block? Does it correctly assign title and content to that post object? Does it redirect? – Daniel Roseman Jun 15 '16 at 12:07
  • I see `blog_id` being moved around as a string, but your model's `id` would likely be an integer, perhaps that's a potential pain point. Also `.first_or_404()` might be a nicer argument then just `.one()` too. – Doobeh Jun 15 '16 at 12:47
  • Okay, I updated the post to add the code from the blog template, and blog route which feed edit_entry the blog_id. For the other questions: it is receiving the record properly, I've tested by simply returning fields from the record. I am trying to find if the code is going into POST properly, but now running into an issue where it is redirecting back to blog, even though I've changed it to a return render_template in order to get a look at the POST variables being sent by edit_entry. – Tyler Jun 15 '16 at 12:56

1 Answers1

1

Okay, so thanks to Daniel and Doobeh for pointing me in the right directions. Basically it came down to me not requesting the blog_id properly in the POST method of edit_entry. Here is the new (and working) code.

def edit_entry():
    form = BlogEntry()
    if request.method == 'POST':
        blog_id = request.form.get('blog_id')
        update = db.session.query(models.Blog).filter_by(id = blog_id).update({
             'title': request.form.get('title'),
             'content': request.form.get('content')
        })
        db.session.commit()
        return redirect(url_for('blog'))
    elif request.method == 'GET':
        blog_id = int(request.args['blog_id'])
        post = models.Blog.query.filter_by(id = blog_id).first_or_404()
        context = {
            'copyright': COPYRIGHT,
            'form': form,
            'blog_id': blog_id
        }
        form.title.data = post.title
        form.content.data = post.content
        return render_template('edit_entry.html', **context)

There ended up being two major issues. Fist was an oversight: when I copied the edit_entry template from the new_entry template, I forgot to change the action of the form, so the form was actually posting to the new_entry route, thus the duplication. After I found that problem, I also realized that while blog_id was passed in request.args to the 'GET' method, it wasn't being passed in the 'POST' method (since post wasn't coming from the redirect), so I actually created a new field in the edit_entry template to pass the blog_id back to POST.

Tyler
  • 90
  • 1
  • 7