Reputation: 145
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
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
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