Flask – form field data becomes Null after clicking submit

This is my first big Flask project, I am building a CRUD app to help manage cases for a relocation company (personal project). I want to have a db table for Companies (ie, our clients), Packages (the packages we offer), and one for the price we charge every company for every package, as this varies.

Unfortunately, I didn’t get very far before encountering a big issue. I am currently working on an “Add Company” page, where the user can add a new customer (company), providing the company name, contact addresses, as well as the prices we will charge that company for each package we offer. The package name and ID form fields are correctly populating the form based on information in the db, leaving the user to add the correct price. However, once I hit submit, it the package name and ID values are “lost”. I have tried so many print statement debugs, and the issue seems to be that the info I send to the form disappears after hitting submit (for some reason the price, provided by the user, persists). If you could take the time to review and assist me in finding a solution, I would be extremely grateful! I have attached the relevant snippets below, full project can be found on github.

Here is the error I get when I hit submit (function cannot access package_id):

sqlalchemy.exc.IntegrityError: (sqlite3.IntegrityError) NOT NULL constraint failed: company_package.package_id
[SQL: INSERT INTO company_package (company_id, package_id, price) VALUES (?, ?, ?) RETURNING id]
[parameters: (14, None, 1.0)]

Python view function:

@app.route("/companies/add_company", methods=["GET", "POST"])
@login_required
def add_company():
    form = NewCompanyForm()

    #fetch all packages and send them to company form package field
    if request.method == "GET":
        # Fetch all packages from db
        packages = Package.query.all()
        for package in packages:
            package_form = PackagePriceForm()
            package_form.package_id.data = package.id
            package_form.package_name.data = package.name
            package_form.price = None
            form.packages.append_entry(package_form)

    #submit the company section of the form
    if form.validate_on_submit() and request.method == "POST":

        company = Company(
            name = form.name.data,
            contact = form.contact.data,
            notes = form.notes.data
        )

        db.session.add(company)
        db.session.flush() # Flush to get the company ID for the relationships

        #pull the price for each package and send to db

        for package_form in form.packages:

            company_package = CompanyPackage(
                company_id = company.id,
                package_id = package_form.package_id.data,
                price = package_form.price.data               
            )
            db.session.add(company_package)


        db.session.commit()
        flash("Company and package prices successfully added")
        return redirect(url_for("companies"))
    
    else:
        print(form.errors)
    
    return render_template("add_company.html", title="Add New Company", form=form)

HTML

{% extends "base.html" %}

{% block content %}
<h1>Add New Company</h1>
<form action="", method="POST">
    {{ form.hidden_tag() }}
    <p>
        {{form.name.label }}
        {{ form.name(size=32) }}
        {% for error in form.name.errors %}
        <span style="color:red">[{{ error }}] </span>
        {% endfor %}
    </p>
    <p>
        {{form.contact.label }}
        {{ form.contact(size=32) }}
        {% for error in form.contact.errors %}
        <span style="color:red">[{{ error }}] </span>
        {% endfor %}
    </p>
    <p>
        {{form.notes.label }}
        {{ form.notes(size=32) }}
        {% for error in form.notes.errors %}
        <span style="color:red">[{{ error }}] </span>
        {% endfor %}
    </p>
    <h2>Package Prices</h2>
    <table>
        <tr>
            <th>Package</th>
            <th>Price</th>
            
        </tr>
        {% for package_form in form.packages %}
        <tr>
            <td>{{ package_form.package_name.data }}</td>
            <td>{{ package_form.price() }} </td>
            <td>{{ package_form.package_id.data }} </td>  
        </tr>
        {% endfor %}
    </table>
    <p>{{ form.submit() }} </p>
</form>
{% endblock %}

Forms

class NewCompanyForm(FlaskForm):
    name = StringField("Company Name", validators=[DataRequired()])
    contact = StringField("Contact email(s)", validators=[DataRequired()])
    notes = TextAreaField("Additional Notes")
    packages = FieldList(FormField(PackagePriceForm))
    submit = SubmitField("Submit")



class PackagePriceForm(FlaskForm):
    package_id = HiddenField("Package ID", render_kw={"readonly":True})  # Hidden field for package ID
    package_name = StringField("Package Name", render_kw={"readonly":True})  # Read-only field to display the package name
    price = FloatField('Price', validators=[NumberRange(min=0)], default="")

    class Meta:
        csrf = False

db Models

class Package(db.Model):
    id: so.Mapped[int] = so.mapped_column(primary_key=True)
    name: so.Mapped[str] = so.mapped_column(sa.String(100), index=True, unique=True)
    description: so.Mapped[Optional[str]] = so.mapped_column(sa.String(500))

    # Relationships
    company_packages: so.Mapped[List["CompanyPackage"]] = so.relationship(back_populates="package")
    assignees: so.Mapped[List["Assignee"]] = so.relationship(
        secondary="assignee_package",
        back_populates="packages"
    )


class Company(db.Model):
    id: so.Mapped[int] = so.mapped_column(primary_key=True)
    name: so.Mapped[str] = so.mapped_column(sa.String(100), index=True, unique=True)
    contact: so.Mapped[str] = so.mapped_column(sa.String(256))
    notes: so.Mapped[Optional[str]] = so.mapped_column(sa.String(1064))

    # Relationships
    assignees: so.Mapped[List["Assignee"]] = so.relationship(back_populates="company")
    company_packages: so.Mapped[List["CompanyPackage"]] = so.relationship(back_populates="company")


class CompanyPackage(db.Model):
    id: so.Mapped[int] = so.mapped_column(primary_key=True)
    company_id: so.Mapped[int] = so.mapped_column(sa.ForeignKey("company.id"))
    package_id: so.Mapped[int] = so.mapped_column(sa.ForeignKey("package.id"))
    price: so.Mapped[float] = so.mapped_column(sa.Float)

    # Relationships
    company: so.Mapped["Company"] = so.relationship(back_populates="company_packages")
    package: so.Mapped["Package"] = so.relationship(back_populates="company_packages")

When the form is submitted, it should create a new entry in the Company table. It should also create new entries in the CompanyPackage table, one entry for every package, where the company_id is that of the newly created Company, and the package_id is the id for the package in question. My code is almost working, but it is failing to pull the package_id and as a result, my SQL query is failing.

A print statement before validation (ie, at the GET request stage, where FieldList(FormField(PackagePriceForm)))is populated shows that the correct package names and ids are being pulled from the DB. The package name is printed correctly in that table, and although a hidden field, the package_id is visible through browser developer tools / inspect element (Ctrl + Shift + I).

Executing the same print statement after submit (form.validate_on_submit), the package_name and package_id values both become Null. Interestingly, the user-populated field of “price” is correctly retained, and submitted to the SQL query. I have tried this debug print both before and after the initial db.commit (creating a new instance of Company), and on either side of this commit, I have the same problem.

I can only conclude that upon clicking submit, the data which I sent to the web page as part of the GET request, is being lost upon POST/form validation.

Any help is greatly appreciated.

The problem results from the fact that you are not rendering the input fields for package_id within the template, but only their value. For this reason, the values ​​are not part of the dict request.form, which contains the form data sent by the client and is used to fill your form object.

The following example shows you a simplified way of implementing this.

{% block content %}
<h1>Add New Company</h1>
<form action="", method="POST">
    {{ form.hidden_tag() }}
    <div>
        {{form.name.label }}
        {{ form.name(size=32) }}
        {% for error in form.name.errors %}
        <span style="color:red">[{{ error }}] </span>
        {% endfor %}
    </div>
    <div>
        {{form.contact.label }}
        {{ form.contact(size=32) }}
        {% for error in form.contact.errors %}
        <span style="color:red">[{{ error }}] </span>
        {% endfor %}
    </div>
    <div>
        {{form.notes.label }}
        {{ form.notes(size=32) }}
        {% for error in form.notes.errors %}
        <span style="color:red">[{{ error }}] </span>
        {% endfor %}
    </div>
    <h2>Package Prices</h2>
    <table>
        <tr>
            <th>Package</th>
            <th>Price</th>
        </tr>
        {% for package_form in form.company_packages -%}
        <tr>
            <td>{{ package_form.package_name.data }}</td>
            <td>{{ package_form.price() }} </td>
            <td>{{ package_form.package_id() }} </td>  
        </tr>
        {% endfor -%}
    </table>
    <div>{{ form.submit() }} </div>
</form>
{% endblock %}
from flask import (
    Flask, 
    flash, 
    redirect, 
    render_template, 
    request, 
    url_for
)
from flask_sqlalchemy import SQLAlchemy
from flask_wtf import FlaskForm
from sqlalchemy.orm import DeclarativeBase
from typing import List, Optional 
from wtforms import (
    FieldList, 
    FloatField, 
    FormField, 
    HiddenField, 
    StringField, 
    SubmitField, 
    TextAreaField
)
from wtforms.validators import DataRequired, NumberRange

class Base(DeclarativeBase):
    pass

app = Flask(__name__)
app.config.from_mapping(
    SECRET_KEY='your secret here',
    SQLALCHEMY_DATABASE_URI='sqlite:///example.db'
)
db = SQLAlchemy(app, model_class=Base)

class Package(db.Model):
    id: db.Mapped[int] = db.mapped_column(primary_key=True)
    name: db.Mapped[str] = db.mapped_column(db.String(100), index=True, unique=True)
    # ...

    # Relationships
    company_packages: db.Mapped[List["CompanyPackage"]] = db.relationship(back_populates="package")
    # ...

class Company(db.Model):
    id: db.Mapped[int] = db.mapped_column(primary_key=True)
    name: db.Mapped[str] = db.mapped_column(db.String(100), index=True, unique=True)
    contact: db.Mapped[str] = db.mapped_column(db.String(256))
    notes: db.Mapped[Optional[str]] = db.mapped_column(db.String(1064)) 

    # Relationships
    company_packages: db.Mapped[List["CompanyPackage"]] = db.relationship(back_populates="company")
    # ...

class CompanyPackage(db.Model):
    id: db.Mapped[int] = db.mapped_column(primary_key=True)
    company_id: db.Mapped[int] = db.mapped_column(db.ForeignKey("company.id"))
    package_id: db.Mapped[int] = db.mapped_column(db.ForeignKey("package.id"))
    price: db.Mapped[float] = db.mapped_column(db.Float)

    # Relationships
    company: db.Mapped["Company"] = db.relationship(back_populates="company_packages")
    package: db.Mapped["Package"] = db.relationship(back_populates="company_packages")

with app.app_context():
    db.drop_all()
    db.create_all()

    packages = [
        Package(
            name=f'Package-{i}', 
        ) for i in range(1,11)
    ]
    db.session.add_all(packages)
    db.session.commit()

class PackagePriceForm(FlaskForm):
    package_id = HiddenField("Package ID", render_kw={"readonly":True})  # Hidden field for package ID
    package_name = StringField("Package Name", render_kw={"readonly":True})  # Read-only field to display the package name
    price = FloatField('Price', validators=[NumberRange(min=0)], default=0.0)

    class Meta:
        csrf = False

class NewCompanyForm(FlaskForm):
    name = StringField("Company Name", validators=[DataRequired()])
    contact = StringField("Contact email(s)", validators=[DataRequired()])
    notes = TextAreaField("Additional Notes")
    company_packages = FieldList(FormField(PackagePriceForm, default=CompanyPackage))
    submit = SubmitField("Submit")

@app.route("/companies/add_company", methods=["GET", "POST"])
def add_company():
    form = NewCompanyForm(request.form)
    if request.method == 'GET':
        form.process(
            data={ 
                'company_packages': [
                    { 'package_id': pkg.id, 'package_name': pkg.name } 
                    for pkg in db.session.execute(db.select(Package)).scalars()
                ]
            }
        )

    #submit the company section of the form
    if form.validate_on_submit():
        company = Company()
        form.populate_obj(company)
        db.session.add(company)
        db.session.commit()
        flash("Company and package prices successfully added")
        return redirect(url_for("companies"))

    return render_template("add_company.html", **locals())

@app.route('/companies')
def companies():
    companies = db.session.execute(db.select(Company)).scalars()
    return render_template("companies.html", **locals())

Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa Dịch vụ tổ chức sự kiện 5 sao Thông tin về chúng tôi Dịch vụ sinh nhật bé trai Dịch vụ sinh nhật bé gái Sự kiện trọn gói Các tiết mục giải trí Dịch vụ bổ trợ Tiệc cưới sang trọng Dịch vụ khai trương Tư vấn tổ chức sự kiện Hình ảnh sự kiện Cập nhật tin tức Liên hệ ngay Thuê chú hề chuyên nghiệp Tiệc tất niên cho công ty Trang trí tiệc cuối năm Tiệc tất niên độc đáo Sinh nhật bé Hải Đăng Sinh nhật đáng yêu bé Khánh Vân Sinh nhật sang trọng Bích Ngân Tiệc sinh nhật bé Thanh Trang Dịch vụ ông già Noel Xiếc thú vui nhộn Biểu diễn xiếc quay đĩa Dịch vụ tổ chức tiệc uy tín Khám phá dịch vụ của chúng tôi Tiệc sinh nhật cho bé trai Trang trí tiệc cho bé gái Gói sự kiện chuyên nghiệp Chương trình giải trí hấp dẫn Dịch vụ hỗ trợ sự kiện Trang trí tiệc cưới đẹp Khởi đầu thành công với khai trương Chuyên gia tư vấn sự kiện Xem ảnh các sự kiện đẹp Tin mới về sự kiện Kết nối với đội ngũ chuyên gia Chú hề vui nhộn cho tiệc sinh nhật Ý tưởng tiệc cuối năm Tất niên độc đáo Trang trí tiệc hiện đại Tổ chức sinh nhật cho Hải Đăng Sinh nhật độc quyền Khánh Vân Phong cách tiệc Bích Ngân Trang trí tiệc bé Thanh Trang Thuê dịch vụ ông già Noel chuyên nghiệp Xem xiếc khỉ đặc sắc Xiếc quay đĩa thú vị
Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa
Thiết kế website Thiết kế website Thiết kế website Cách kháng tài khoản quảng cáo Mua bán Fanpage Facebook Dịch vụ SEO Tổ chức sinh nhật