This video is available to students only

Editing Data with Forms

You can use WTForms to prepopulate data for forms as well, which is handy when you are editing records. To prepoulate data in a form from a object, WTForms provides a method called process where we can pass in raw form data and/or an object

@products.route('/<product_id>/edit', methods=['GET', 'POST'])
def edit(product_id):
    product = Product.query.get_or_404(product_id)
    form = ProductForm()
    if form.validate_on_submit():
        product.name = form.name.data
        product.description = form.description.data
        db.session.add(product)
        db.session.commit()
    elif not form.errors:
        # We do this to make sure we're not overwriting a user's input
        # if there was an error.
        form.process(formdata=form.data, obj=product)
    return render_template('products/edit.html', form=form, product=product)

A shortcut for the above logic, is to simply pass in the object when instantiating the form.

@products.route('/<product_id>/edit', methods=['GET', 'POST'])
def edit(product_id):
    product = Product.query.get_or_404(product_id)
    form = ProductForm(product)
    if form.validate_on_submit():
        product.name = form.name.data
        product.description = form.description.data
        db.session.add(product)
        db.session.commit()
    return render_template('products/edit.html', form=form, product=product)

The template for the edit form (yumroad/templates/products/edit.html), looks very similar to the creation form, but uses the edit route instead. It's useful to have a separate template since we may want to customize what fields we allow on the edit field.

{% block title %} Edit {{ product.name }} {% endblock %}

{% block content %}
  <div class="container">

    <form method="POST" action="{{ url_for('products.edit', product_id=product.id) }}">
      {{ render_field(form.name) }}
      {{ render_field(form.description) }}
      <button type="submit" class="btn btn-primary">Edit</button>
    </form>

  </div>
{% endblock %}

CSRF

Our application right now will accept any POST requests and process the change if the user is logged in (by looking at the cookies). This creates a security vulnerability called Cross Site Request Forgery where other websites can create requests on behalf of users who happened to be logged into our site and land on a malicious webpage. It would be bad if we allowed malicious websites the opportunity create products, or worse transfer funds, on behalf of our users.

You can read more about CSRF here.

Setting up CSRF Protection

Flask WTForms allows us to enable CSRF Protection. To support it, we'll need to

In extensions.py, we can import CSRFProtect from flask_wtf.csrf and instantiate it.

extensions.py should look like this.

yumroad-app/yumroad/extensions.py
from flask_sqlalchemy import SQLAlchemy
from flask_wtf.csrf import CSRFProtect

db = SQLAlchemy()
csrf = CSRFProtect()

In yumroad/__init__.py, we will have to call csrf.init_app with our app to set it up.

yumroad/__init__.py should look like this:

yumroad-app/yumroad/extensions.py
from flask_sqlalchemy import SQLAlchemy
from flask_wtf.csrf import CSRFProtect

db = SQLAlchemy()
csrf = CSRFProtect()

You will likely also need to set SECRET_KEY in order to generate CSRF tokens. We will discuss how the SECRET_KEY is used in the next chapter.

To quickly generate a random value using Python, you can use the os.urandom method. Run this command in your terminal to get a random string of bytes: python -c 'import os; print(os.urandom(16))'

For the development environment, we can add a simple default value. In yumroad/config.py, ensure that the DevConfig specifies a SECRET_KEY.

class BaseConfig:
    TESTING = False
    SQLALCHEMY_TRACK_MODIFICATIONS = False
    SECRET_KEY = os.getenv('YUMROAD_SECRET_KEY', '00000abcdef')
...
class ProdConfig(BaseConfig):
    SECRET_KEY = os.getenv('YUMROAD_SECRET_KEY')

Implementing CSRF Protection in Forms

Implementing CSRF protection is straight form thanks to flask_wtf. All forms have a csrf_token field that we need to render in the template. The field is configured to be hidden but the value of the csrf_token field will be checked through the form validations to ensure that it the provided token is valid.

Start a new discussion. All notification go to the author.