pascale
pascale

Reputation: 145

pass a parameter to a view function (form) in a for loop to populate form flask wtforms sqlalchemy

I want to be able to display the rows of a database model on my web page as an updatable prepopulated form! I can display the rows on the page easily and I can add a link to each to open a seperate page to edit each row, I can also display each row with the update form, but I cant display the form populated. here is my code:

@bp.route('/stock', methods=['GET', 'POST'])
@bp.route('/stock/<int:id>', methods=['GET', 'POST'])
@login_required
def stock(id=None):
    if id is not None:
        obj = Stock.query.get_or_404(id)
        form = AddStockForm(request.form, obj=obj)
        if form.validate_on_submit():
            form.populate_obj(obj)
            db.session.commit()
        return redirect(url_for('stock.stock'))
    else:
        form = AddStockForm()
        page = request.args.get('page', 1, type=int)
        stock = Stock.query.order_by(Stock.id.desc()).paginate(
        page, current_app.config['ITEMS_PER_PAGE'], False)
        next_url = url_for('stock.stock', page=stock.next_num) \
            if stock.has_next else None
        prev_url = url_for('stock.stock', page=stock.prev_num) \
            if stock.has_prev else None
        return render_template('stock/stock.html',form=form, title=Stock, stock=stock.items,
             next_url=next_url, prev_url=prev_url)

and the stock.html code:

{% extends "base.html" %}

{% block content %}
<div class="main">
    <h2>Stock</h2>
    <div class="books"><a href="{{ url_for('stock.upload_stock') }}">Upload Stock csv</a> 
</div>
    <div class="books"><a href="{{ url_for('stock.add_stock') }}">Add a new Item</a></div>
    {% for s in stock %}
        {% include 'stock/_stock.html' %}
    {% endfor %}
</div>

_stock.html code:

<div class="stock">
    <div class="c0"><img src="{{ s.image_url }}" alt="{{ s.image_filename }}"></div>

        <form action="{{ url_for('stock._stock', id=s.id) }}" method="post", 
           enctype="multipart/form-data">
            {{ form.hidden_tag() }}
            <div>SKU<BR> {{ s.id }} </div>
            <div>Date of Purchase<br>{{ form.date(class="input") }}</div>
            <div>Description<br>{{ form.description(class="input") }}</div>
            <div>Market<br>{{ form.event(class="input") }}</div>
            <div>Purchase Price<br>{{ form.achat(class="input") }}</div>
            <div>Sale Price<br>{{ form.vente(class="input") }}</div>
            <div>Sold<br>{{ form.sold(class="input") }}</div>
            <div>{{ form.submit(class="submit") }}</div>
        </form>

</div>

on google developer tools each form shows stock/4 or 3 but this does not seem to then populate the form. there are no error messages. thanks in advance for any help. regards Paul

EDIT hi @Greg Cowell, Thank you for your help, I have addapted your code but I Get this error

  File "/var/www/accounts/bdf/templates/base.html", line 30, in root
</body>
  File "/var/www/accounts/env/lib/python3.7/site-packages/jinja2/runtime.py", line 262, in call
return __obj(*args, **kwargs)
  File "/var/www/accounts/env/lib/python3.7/site-packages/flask/helpers.py", line 370, in url_for
return appctx.app.handle_url_build_error(error, endpoint, values)
  File "/var/www/accounts/env/lib/python3.7/site-packages/flask/app.py", line 2215, in handle_url_build_error
reraise(exc_type, exc_value, tb)
  File "/var/www/accounts/env/lib/python3.7/site-packages/flask/_compat.py", line 39, in reraise
raise value
  File "/var/www/accounts/env/lib/python3.7/site-packages/flask/helpers.py", line 358, in url_for
endpoint, values, method=method, force_external=external
  File "/var/www/accounts/env/lib/python3.7/site-packages/werkzeug/routing.py", line 2020, in build
raise BuildError(endpoint, values, method, self)
werkzeug.routing.BuildError: Could not build url for endpoint 'stock.stock'. Did you forget to specify values ['id']?
[pid: 29588|app: 0|req: 1/1] 192.168.0.13 () {48 vars in 1095 bytes} [Sun Nov 17 09:26:58 2019] GET /index =>

here is the adapted code:

@bp.route('/stock/stock/<int:id>/', methods=['GET', 'POST'])
@login_required
def stock(id=None):
    if id is not None:
        group = current_user.group()
        try:
            stock = Stock.query.filter_by(group=group, id=id).one()
        except NoResultFound:
            flash('Invalid account.')
            return redirect(url_for('stock.add_stock'))
        accounts = current_user.group().stock
        form = AddStockForm()
        form.stock_id.default = stock.id
        if form.validate_on_submit():
            if form.modify.data:
                for s in stock:
                    if (
                        item.id == form.stock_id.data and
                        item.id != form.stock_id.default
                    ):
                        flash('Another account already has this name.')
                        return redirect(url_for('stock.add_stock', id=id))
                stock.id = form.stock_id.data
                db.session.add(stock)
            db.session.commit()
        form.process()  # Do this after validate_on_submit or breaks CSRF token
    else:
        form = AddStockForm()
        page = request.args.get('page', 1, type=int)
        stock = Stock.query.order_by(Stock.id.desc()).paginate(
             page, current_app.config['ITEMS_PER_PAGE'], False)
        next_url = url_for('stock.stock', page=stock.next_num) \
            if stock.has_next else None
        prev_url = url_for('stock.stock', page=stock.prev_num) \
            if stock.has_prev else None
    return render_template('stock/stock.html',form=form, title=Stock, stock=stock.items,
         next_url=next_url, prev_url=prev_url)

SECOND EDIT

I added this to the top of the view function:

@bp.route('/stock', methods=['GET', 'POST'])

which got rid of the above error, I then got a similar error for the "stock.html code"

    {% for s in stock %}
        {% include 'stock/_stock.html' %}
    {% endfor %}

which I changed to:

   {% include {{ url_for('stock._stock', id=['s.id']) }} %}

Which gives me the following error:

jinja2.exceptions.TemplateSyntaxError: expected token ':', got '}'

Upvotes: 0

Views: 457

Answers (2)

Greg Cowell
Greg Cowell

Reputation: 693

To make an HTML page which has a form for each record and an individual submit button for each record, you need to create multiple forms in your view/route function and then loop through them all in your HTML template.

View/route:

@web.route('/stocks', methods=['GET', 'POST'])
def stocks():
    """Return Stocks HTML page."""
    stocks = Stock.query.all()
    forms = []
    for stock in stocks:
        form = ModifyStockForm()
        form.stock_id.default = stock.stock_id
        form.stock_name.default = stock.stock_name
        forms.append(form)

    for form in forms:
        if form.validate_on_submit():
            if form.modify.data:
                stock = Stock.query.filter_by(stock_id=form.stock_id.data).one()
            stock.stock_name = form.stock_name.data
            db.session.add(stock)
            db.session.commit()
        elif form.delete.data:
            stock = Stock.query.filter_by(stock_id=form.stock_id.data).one()
            db.session.delete(stock)
            db.session.commit()
        return redirect(url_for('.stocks'))

        form.process()  # Do this after validate_on_submit or breaks CSRF token

return render_template('stocks.html', forms=forms, stocks=stocks)

Template:

<h2>Stocks:</h2>

   {% for form in forms %}

    <form method="post" role="form" enctype="multipart/form-data">
        {{ form.hidden_tag() }}

        {{ form.stock_id }}
        {{ form.stock_name }}
        {{ form.modify }}
        {{ form.delete }}

    </form>

   {% endfor %}

I've put a complete example in this repo: Example of multiple forms on a single page

Upvotes: 1

Greg Cowell
Greg Cowell

Reputation: 693

I use a different method for prepopulating forms.

In summary, I set the default for each form field and process the form before rendering. In the example below I set the value for the account_name field default using form.account_name.default = account.accname and then call form.process() before rendering the form.

@web.route('/accounts/modify/<int:accno>/', methods=['GET', 'POST'])
@login_required
def modify_account(accno):
    """
    Modify or delete accounts.
    Return a form for modifying accounts or process submitted
    form and redirect to Accounts HTML page.
    """
    group = current_user.group()
    try:
        account = Account.query.filter_by(group=group, accno=accno).one()
    except NoResultFound:
        flash('Invalid account.')
        return redirect(url_for('.accounts_page'))
    accounts = current_user.group().accounts
    form = ModifyAccountForm()
    form.account_name.default = account.accname

    if form.validate_on_submit():
        if form.modify.data:
            for item in accounts:
                if (
                    item.accname == form.account_name.data and
                    item.accname != form.account_name.default
                ):
                    flash('Another account already has this name.')
                    return redirect(url_for('.modify_account', accno=accno))
            account.accname = form.account_name.data
            db.session.add(account)
            db.session.commit()
        elif form.delete.data:
            for transaction in current_user.group().transactions:
                if transaction.account == account:
                    unknown_account = Account.query.filter_by(
                        group=current_user.group(), accname='Unknown').one()
                    transaction.account = unknown_account
                    db.session.add(transaction)
                    db.session.commit()
            db.session.delete(account)
            db.session.commit()
        elif form.cancel.data:
            pass
        return redirect(url_for('.accounts_page'))

    form.process()  # Do this after validate_on_submit or breaks CSRF token

    return render_template(
        'modify_account.html', form=form, accno=accno, menu="accounts")

Upvotes: 1

Related Questions