I am new to SQLAlchemy and HTML forms and need to understand how to delete multiple rows in my dB table that correspond to are checked checkboxes in my HTML form. As it stands only the first checked item is being deleted from the table. I was hoping this could be done without a for loop on the SQLAlchemy side?
Here is my HTML code:-
<ul>
{% for alert in alerts %}
<li> <input name="alert_id" type="checkbox" value="{{ alert.alert_id }}"/> Ticker:{{ alert.ticker }} Price:{{ alert.price }}</li>
{% endfor %}
</ul>
And the flask python SQLAlchemy script:-
if request.method == "POST":
if request.form["alert_id"]:
Alert.query.filter(Alert.alert_id == request.form["alert_id"]).delete()
db.session.commit()
This line
Alert.query.filter(Alert.alert_id == request.form["alert_id"]).delete()
should be
Alert.query.filter(Alert.alert_id.in_(request.form["alert_id"])).delete()
which does a SELECT * FROM <table_name> WHERE alert_id IN (<val>, <val2>)
Also, it's good to stick to request.form.getlist("alert_id") instead of request.form["alert_id"] so you can always make sure you get a list and not a KeyError in case alert_id doesn't exist.
Related
I'm using Flask and am sending reports and displaying them in html. I want each report entry to have a corresponding delete button that when clicked, will trigger a python function to delete that report from a database I have. I am not struggling with the logic of how to delete the report from within that python function, I am trying to figure out how to send the information to python. I know this can be done with a form where the user enters the name of what they want deleted but I would prefer that the button just correspond to the report and not have to have the user type out which report they want to delete.
{% if report_data %} {% for report in report_data %}
<div class="report">
<dl>
<h1><b>{{ report['name'] }}</b></h1>
<dt><b>Date of Report:</b> {{ report['time'] }}</dt>
<dt><b>Overall Score:</b> {{ report['overall'] }}</dt>
<dt><b>Cleanliness Score:</b> {{ report['cleanliness'] }}</dt>
<dt><b>Availability Score:</b> {{ report['avail'] }}</dt>
<dt><b>Location Score:</b> {{ report['location'] }}</dt>
</dl>
<button method="post"> <a href='/delete_report'>Delete Report</a></button>
</div>
{% endfor %} {% elif error_message %}
My main.py has the following function:
#app.route('/delete_report', methods = ["GET", "POST"])
def delete_report():
#remove report from database
which is being reached but how do I send say report['name'] to it? Is that something that can be done without having to create a form?
I've created a HTML table that lists many rows from a SQLAlchemy table. The table is created with a loop using jinja2 templates:
{% for single_merchant in merchants %}
<tr>
<td>{{single_merchant.id}}</td>
<td><button type="button" class="btn btn-sm btn-outline-danger">Delete</button>
</tr>
Each of the rows has a "delete" button. Im am trying to figure out how can I assign the delete button to delete that particular SQLalchemy row. I tried creating the button as a separate flask form called delete_form and adding an id="{{single_merchant.id}} attribute to the button like so:
{% for single_merchant in merchants %}
<tr>
<td>{{single_merchant.id}}</td>
<form method="post">
{{ delete_form.hidden_tag() }}
<td>{{ delete_form.delete(id=single_merchant.id) }}</td>
</form>
</tr>
In the app.py I then created an if statement:
if delete_form.validate_on_submit():
print(f"merchant to delete ID - {delete_form.delete.id}")
I was hoping to get the single_merchant.id in the output and use it in the if statement to delete the particular merchant from my SQLAlchemy table, but instead I got in the output merchant to delete ID - delete even though from the HTML file the id attribute had a value of 1 since {{single_merchant.id}} is 1
Is there a way how to execute SQLAlchemy row deletion from HTML tables? Here's a rough example how the HTML table is expected to look like:
Implement a route to handle the deletion:
def merch_delete(mid):
merch = Merchant.query.filter_by(id=mid).first()
if merch:
msg_text = 'Merchant %s successfully removed' % str(merch)
cs.delete(merch)
cs.commit()
flash(msg_text)
return redirect(url_for('merchants_view'))
Then, add the following to a column in your jinja table:
<a href="{{ url_for('merch_delete', mid=single_merchant.id) }}"
onclick="return confirm('Do you want to permanently delete merchant {{ merchant }}?');" title="Delete Merchant">
<i class="material-icons" style="font-size:16px">delete</i></a>
The icon and Js verification step are optional.
You can use your original approach as well just add a hidden field to the form that has your delete button in which the value could be your id. You just have to then explicitly define the csrf_token rather than using the hidden_tag() method.
In your WtfForm class object define a hidden field like:
class DeleteForm(FlaskForm):
delete_id = HiddenField("Hidden table row ID")
delete = SubmitField("Delete")
Render it in your HTML Jinja2 template like this. Notice I flipped the <td> and the <form> so that your whole table data cell is the form
{% for single_merchant in merchants %}
<tr>
<td>{{single_merchant.id}}</td>
<td>
<form method="post">
{{ delete_form.csrf_token }}
{{ delete_form.delete_id(value=single_merchant.id) }}
{{ delete_form.delete(class="btn btn-danger") }}
</form>
</td>
</tr>
Then in your code you can easily check it using the wtfform validate_on_submit() and then use the SQLAlchemy get() method to query for that id pulling the value from the hidden fields data attribute.
def post(self):
if self.delete_form.validate_on_submit():
entry_to_delete = YourSQLAlchemyTableClassObject.query.get(self.delete_form.delete_id.data)
db.session.delete(entry_to_delete)
db.session.commit()
You could if you wanted to combine the entry_to_delete line with the db.session.delete() by just putting the query to your table using the hidden field data property into the call to the delete() method. I like to separate those pieces in case I need to do anything additional to the found entry before I delete it but it doesn't matter.
Here is a screenshot of one of my apps I built for my family that allows my kids to add suggestions for our Disney Vacations. I didn't use a cool trash can icon like you did but you get the point.
I'm new to Django and I'm trying to develop an apllication that deals with learning objects metadata. One of the functions of the system is to view the L.O. metadata in browser.
I have an HTML template that lists the result of the query from the database. Each result come along with a "Visualize Metadata" button, that when clicked, should display the metadata of that object in browser. So I want my button to pass the object ID back to my view, so i can make another query by the specific ID and print the results on the screen.
This is my template .html
{% if objects %}
<ul>
{% for object in objects %}
<li>
{{ object.General.title }}
<form action='visualize' method='POST' name='id' value="{{object.General.id}}">
{% csrf_token %}
<button type="submit" >Visualize Metadata </button>
</form>
</li>
{% endfor %}
</ul>
{% else %}
<p>No results found.</p>
{% endif %}
And this is my views.py function
def visualize_lom_metadata(request):
if request.method == 'POST':
objID = request.POST.get('id')
return HttpResponse(objID)
For now i just want to see if that's possible by printing the objID in the screen. But when I try to do that,it just returns "None". Anyone knows how to retrieve data from template.html to my Django views.py?
I believe that all your forms should have different names and ids, and that submit button must be bound to that form.
It would also help if you in your visualize_lom_data would print entire request.POST to see what you get back from template.
I am looking to build a multiple choice quiz using python/flask/flask-wtf. I am successfully able to pull random questions from a database as well as pull random choices as the possible answers. I am using a for loop on the template page that first displays the question and then the possible answers.
Here is my template.
<div class="quote-block">
{% for quote in quotes %}
{{ quote.line_text }}
<form method="POST">
{{ form.hidden_tag() }}
{{ form.question }}
<input type="submit">
</form>
{% endfor %}
</div>
My problem is that the possible answers are the same for each question and in the same order. I understand why this is happening. The query into the database for the RadioField choices is only happening once. And then the results of that query are being pulled for each instance of the for loop.
Here is my view route.
#app.route('/quiz/', methods=['POST', 'GET'])
def quiz():
quotes = Quotes.query.order_by(func.rand()).limit(5)
form = Quiz()
form.question.choices = [(choice.speaker_id, choice.speaker) for choice in Characters.query.order_by(func.rand()).limit(4)]
return render_template('television/quiz.html', form=form, quotes=quotes, options=options)
And my form.
class Quiz(FlaskForm):
question = RadioField('Label', choices=[])
Like I said, all of this works. I just can't figure out how to start a new choices query for each question. Any help would be greatly appreciated. Thanks!
You could try dynamically extending your FlaskForm:
def Quiz(number_of_questions):
class _Quiz(FlaskForm):
pass
for n in range(number_of_questions):
setattr(_Quiz, RadioField('Label', choices=[]), 'question_' + str(n))
return _Quiz()
Now your form has a question_[n] attribute for each question so you can iterate it in jijna2:
<div class="quote-block">
<form method="POST">
{{ form.hidden_tag() }}
{% for q_num in range(n) %}
{{ quotes[q_num].line_text }}
{{ form['question_' + q_num|string }}
{% endfor %}
<input type="submit">
</form>
</div>
After a lot of reading and research, I realized I was trying to do too much too soon. So I took a step back and built the blocks of what I was trying to do and then it finally came together. Of course, I am having new issues with the form validation portion, but I'll include that in a new question.
My biggest roadblock was that I was trying to pull random choice options for the RadioField. Instead, I added three columns to the database table and supplied the options in the table. That made it easier to pull the options and the question with one query.
Basically, I am running a for loop over the fields in the form. If the field is a RadioField, it runs a query against the database and pulls a random row. I then use another for loop over the pulled row and assign different elements to the pieces of the RadioField (label, choices).
If you know of a more elegant way to do this, I'd love to hear it. But for now it works.
My form and template stayed the same. But here is my new route view.
#app.route('/quiz/', methods=['GET', 'POST'])
def quiz():
form = Quiz()
for field in form:
if field.type != "RadioField":
continue
else:
pulls = Quotes.query.order_by(func.rand()).limit(1)
for pull in pulls:
answer = pull.speaker
option_two = pull.option_two
option_three = pull.option_three
option_four = pull.option_four
question = pull.line_text
field.label = pull.line_text
field.choices = [(answer, answer), (option_two, option_two), (option_three, option_three), (option_four, option_four)]
return redirect(url_for('you_passed'))
return render_template('television/quiz.html', form=form)
I have been trying to figure out why my Flask form will not properly validate my select field choices even though the choices are coming from the select field options.
My assumption is that the select option when passed back from the server is unicode and is being compared to the choice which is a string, however, I thought coerce=str would fix that. I printed out the form data and request data which is the output below. Why isn't it working?
My code is attached below, removed csrf token key from the output dict. It seems like a very simple thing, but I can't figure it out.
forms.py
class PlatformForm(FlaskForm):
platform_options = [('test', 'Test'), ('test2','Test2')]
platforms = wtforms.SelectField('Platforms', choices=platform_options, coerce=str, validators=[DataRequired()])
views.py
#app.route('/', methods=['POST', 'GET'])
def index():
form = forms.PlatformForm()
if form.is_submitted():
print form.data
print request.form
if form.errors:
print form.errors
return render_template('home.html', form=form)
index.html
{% extends "base.html" %}
{% block content %}
<h4>Select a Platform</h4>
<form method="POST">
{{ form.csrf_token }}
<select class="custom-select" name="platform">
{% for value, text in form.platforms.choices %}<br>
<option value="{{ value }}">{{ text }}</option>
{% endfor %}
</select>
<button id="submit_inputs" type="submit" class="btn btn-default">Submit</button>
</form>
{% endblock %}
output
{'platforms': 'None'}
ImmutableMultiDict([('platform', u'test')])
{'platforms': [u'Not a valid choice']}
EDIT:
I figured out the problem. It's the way I'm creating the Select drop down through HTML and Jinja. Iterating through the choices and creating option tags doesn't seem to instantiate anything in the form data itself when passed back into Python. Changing that whole for loop to just
{{form.platforms}}
created a select drop down field that actually works.
You have a name mismatch. In the form, you named your select field platforms (plural). In the HTML, you use platform (singular).
I recommend that instead of manually rendering the fields in your template, you let WTForms generate the HTML for you. For the form label, you can use {{ form.platforms.label }}, and for the actual field {{ form.platforms() }}. You can pass any attributes you want to field to have as keyword arguments.
I think something might be going wrong because of the way you are rendering the form in your html file. If my hunch is right, try this:
{% extends "base.html" %}
{% block content %}
<h4>Select a Platform</h4>
<form method="POST">
{{ form.hidden_tag() }}
Select: {{ form.plaforms}}
{{ form.submit(class="btn btn-default") }}
</form>
{% endblock %}
and then try if form.validate_on_submit() in your views.py file
taken from this stack overflow answer by pjcunningham:
"validate_on_submit() is a shortcut for is_submitted() and validate().
From the source code, line 89, is_submitted() returns True if the form
submitted is an active request and the method is POST, PUT, PATCH, or
DELETE.
Generally speaking, it is used when a route can accept both GET and
POST methods and you want to validate only on a POST request."