Using an HTML form with Python Flask search a MongoDB collection - python

I 'ld like for a user to drop a random search in a search form for a book title and find get results if the book is in the db. Below is part of the code block.
I 'm having some issues searching for a single document in my mongodb using a search field and search strings. Below is the code. I'm trying to get the search result via the find_book route.
The code above with the /find_book/<book_id> returns errors.
Below is a part of my code in the app.py file and the search form.
I get the following errors.
werkzeug.routing.BuildError
werkzeug.routing.BuildError: Could not build url for endpoint 'find_book'. Did you forget to specify values ['book_title']?
Traceback (most recent call last)
# create an instance of py_mongo with app as argument
mongo = PyMongo(app)
#app.route('/')
def home():
return render_template('home.html')
# define the various menu options
#app.route('/get_books')
def get_books():
return render_template('books.html', books=mongo.db.books.find())
# Add a book
#app.route('/add_book')
def add_book():
return render_template('add_book.html',
faculties=mongo.db.faculties.find())
# Add submit button for Books
#app.route('/insert_book', methods=['POST'])
def insert_book():
book = mongo.db.books
book.insert_one(request.form.to_dict())
return redirect(url_for('get_books'))
# wire the edit button
#app.route('/edit_book/<book_id>')
# description task, name, due date, is urgent fields will be
# pre-populated based on the information returned in the task.
def edit_book(book_id):
a_book = mongo.db.books.find_one({"_id": ObjectId(book_id)})
# category names will be prepolulated based on the collection
# # of categories returned in the categories cursor
all_faculties = mongo.db.faculties.find()
return render_template('edit_book.html',
book=a_book, faculties=all_faculties)
#app.route('/update_book/<book_id>', methods=['POST'])
def update_book(book_id):
# access the database collection
book = mongo.db.books
# call the update function, specify an id
book.update({'_id': ObjectId(book_id)},
{
'faculty_name': request.form.get('faculty_name'),
'subject_name': request.form.get('subject_name'),
'book_title': request.form.get('book_title'),
'book_author': request.form.get('book_author'),
'book_description': request.form.get('task_description'),
'lender_name': request.form.get('lender_name'),
'due_date': request.form.get('due_date'),
'is_available': request.form.get('is_urgent')
})
return redirect(url_for('get_books'))
# specify the form fields to match the keys on the task collection
# delete a book
#app.route('/delete_book/<book_id>')
def delete_book(book_id):
mongo.db.books.remove({'_id': ObjectId(book_id)})
return redirect(url_for('get_books'))
# find a book by text search
#app.route('/find_book/<book_title>', methods=['GET'])
def find_book(book_title):
book_title = mongo.db.books
book_title.find_one(
{
'book_title': request.form.get('book_title'),
})
return render_template('find.html', book_title=book_title)
# categories function
#app.route('/get_faculties')
def get_faculties():
return render_template('faculties.html',
faculties=mongo.db.faculties.find())
if __name__ == '__main__':
app.run(host=os.environ.get('IP'),
port=int(os.environ.get('PORT')),
debug=True)
<form action="{{ url_for('find_book') }}" method="GET">
<input type="text" placeholder="Book Title" id="book_title" name="book_title" >
<button type="submit"><i class="fa fa-search">Search</i></button>
</form>

Your find_book route is expecting an argument book_title
But you are not passing that in {{ url_for('find_book') }}
You could just change this route to #app.route('/find_book') and get the value from request.form or if you are using this route in another place of your application you could use the approach from this question and use this way:
#app.route('/find_book/', defaults={'book_title': None})
#app.route('/find_book/<book_title>')
def find_book(book_title):
books = mongo.db.books
if book_title is None:
book_title = request.form.get('book_title')
book = books.find_one({
'book_title': book_title
})
return render_template('find.html', book=book)
I could not run this snippet of code now, so let me know if dont work.

Related

How to build a dynamic url using get parameters in Flask [duplicate]

This question already has answers here:
Get a variable from the URL in a Flask route
(2 answers)
Closed 1 year ago.
i am using this code to get filter value from template by GET method:
#app.route('/', methods=['GET'])
def filters():
if request.args.get('country') is None:
results = Table.query.all()
return render_template('index.html', results=results)
else:
country = request.args.get('country')
industry = request.args.get('industry')
results = Table.query.filter(Table.country == country, Table.industry == industry).all()
return render_template('index.html', results=results, country=country, industry=industry)
as a result, I get a url like this:
http://127.0.0.1:5000/?country=USA&industry=REIT
how can I get with GET or POST url requests of this kind:
http://127.0.0.1:5000/USA/REIT
It looks like the feature you are looking for is called "Variable (Routing) Rules". From the Flask documentation:
You can add variable sections to a URL by marking sections with <variable_name>. Your function then receives the <variable_name> as a keyword argument. Optionally, you can use a converter to specify the type of the argument like <converter:variable_name>.
#app.route('/post/<int:post_id>')
def show_post(post_id):
# show the post with the given id, the id is an integer
return 'Post %d' % post_id
or in your case:
#app.route('/<country>/<industry>')
def show_post(country, industry):
results = Table.query.filter(Table.country == country, Table.industry == industry).all()
return render_template('index.html', results=results, country=country, industry=industry)

Updating a specific row with Flask SQLAlchemy

I have this app with a profile and I want to update only a specific row based on its account id. Inserting the data works, but updating a specific row doesn't and I'm not sure which part is wrong with my code.
#app.route('/edit_parent/<int:acc_id>', methods=['GET','POST'])
def edit_parent(acc_id):
myParent = Parent.query.filter_by(acc_id=int(acc_id)).first()
if request.method == "POST":
myParent.fname_p = request.form['fname_p']
myParent.lname_p = request.form['lname_p']
myParent.bday_p = request.form['bday_p']
myParent.add_p = request.form['add_p']
db.session.commit()
print "hello success"
return redirect(url_for('parent', acc_id=int(acc_id)))
if request.method == "GET":
return render_template('edit_p.html', acc_id=int(acc_id))
It prints the "hello success" and redirects to the parent url but returns an error 302 and still no changes in the db.
I don't think you are updating a specific row at all, but instead you are just inserting new one each time with:
myParent = Parent(request.form['fname_p'], request.form['lname_p'],
request.form['bday_p'], request.form['add_p']).where(acc_id=acc_id)
db.session.add(myParent)`
So, what you are supposed to do instead is:
myParent = Parent.query.filter_by(acc_id=acc_id)
assuming your Parent db has the following attributes:
myParent.fname = request.form['fname_p']
myParent.lname = request.form['lname_p']
myParent.bday = request.form['bday_p']
myParent.add = request.form['add_p']
db.session.commit()
solved it by adding:
myParent = db.session.merge(myParent)
this way it merges the current session with the previous one. It still returns a 302 but the data on the db has been successfully updated.

How to correctly Update Odoo / Openerp website context?

I trying to adapt the module https://www.odoo.com/apps/modules/9.0/website_sale_product_brand/ to have a select box on the shop page, and filter by brand and category, and not to have to go to a diferent page and select the brand.
In that module they update context with the brand_id so the sale_product_domain function could append to the domain. In the module, it filter it as a charm, but in my code not....
Any guest?
When I debug self.env.context in the sale_product_domain function not brand if append, but in the website_sale_product_brand yes, with the exactly same code
controller.py
class WebsiteSale(website_sale):
#http.route(['/shop',
'/shop/page/<int:page>',
'/shop/category/<model("product.public.category"):category>',
'/shop/category/<model("product.public.category"):category>/page/<int:page>',
],type='http',auth='public',website=True)
def shop(self, page=0, category=None, search='', brand=None, **post):
# Update context to modify sale_product_domain function from website model
if brand:
context = dict(request.env.context)
context.setdefault('brand', int(brand))
request.env.context = context
result = super(WebsiteSale, self).shop(page=page, category=category,
brand=brand, search=search,
**post)
#append brand to keep so links mantain brand filter
keep = QueryURL('/shop',
brand=brand,
category=category and int(category),
search=search,)
#attrib=attrib_list TODO
#Update result
result.qcontext['keep'] = keep
result.qcontext['brands'] = http.request.env['product.brand'].search([]) #use to populate template select box
result.qcontext['sel_brand_id'] = brand #use to select the selected brand on brand select box
return result
models.py
class WebSite(models.Model):
_inherit = 'website'
#api.multi
def sale_product_domain(self):
domain = super(WebSite, self).sale_product_domain()
print self.env.context
if 'brand' in self.env.context:
domain.append(
('product_brand_id', '=', self.env.context['brand']))
return domain
ctx = dict (request.context)
ctx.update ({'bin_size': True})
request.context = ctx
That's it!

How to access data via dot-notation

Goal: Pull video_id from assessment_obj, assign to variable
DB Type: SQLAlchemy
Model: Assessment holds video_id in a Many-to-One relationship (multiple assessments can use a video)
1.) I am trying to use dot.notation to pull the video_id from the assessment 2.) so that I can use the retrieve method to pull the video information (such as url).
Purpose: The video url will be used to enter into a video player src via a JINJA2 template/HTML code. (I apologize if I am using jargon incorrectly as I am new and am trying to better understand the concepts.)
I was trying to follow a OOP dot.notation tutorial, but was uncertain on how to pull the data from a table and assign to a variable without accidently reassigning the variable.
views.py
The form view config.
#view_config(route_name='save_assessment_result', renderer='templates/completedpage.jinja2')
def save_assessment_result(request):
with transaction.manager:
assessment_id = int(request.params['assessment_id'])
assessment_obj = api.retrieve_assessment(assessment_id) # the assessment
assessment_obj.video_id = video # this right?
thevid = api.retrieve_video(video) #retrieves the video via ID
template_video = int(request.params['video'])
# how do I get the retrieved video 'thevid' into the template?
# more code
transaction.commit()
return HTTPCreated()
template: templates/assessment_form.jinja2
<div class="embed-responsive embed-responsive-16by9">
<iframe width="640" height="360" class="embed-responsive-item" allowfullscreen name='video' src="{{ url }}"></iframe>
</div>
NEW: Return for assessment_obj. New View_Config.
Note: 'videoname': video.videoname, 'length': video.length, 'url': video.url
#view_config(route_name='assessment', request_method='GET', renderer='templates/assessment_form.jinja2')
def assessment_form_view(request):
with transaction.manager:
assessment_id = int(request.matchdict['id'])
assessment = api.retrieve_assessment(assessment_id)
if not assessment:
raise HTTPNotFound()
video_id = int(request.matchdict['id']) # <--- this gives the assessmnet_id and not the video
video = api.retrieve_video(video_id)
return {'assessment_id': assessment_id, 'assessment_name': assessment.name, 'assessment_text': assessment.text, 'user_id': assessment.user_id, 'video_id':assessment.video_id, 'categories': ','.join([str(i) for i in assessment.categories]), 'video_id':video_id, 'videoname': video.videoname, 'length': video.length, 'url': video.url}
In the line assessment_obj.video_id = video - where does the video variable come from?
Seeing that you're trying "pull the data from a table and assign to a variable" I'm wondering if you actually tried to assign assessment_obj.video_id to a variable called video. In this case it should be another way round:
video = assessment_obj.video_id
Then you code almost starts to make sense. To pass the data to the template you simply return a dictionary from your view function:
#view_config(route_name='save_assessment_result', renderer='templates/assessment_form.jinja2')
def save_assessment_result(request):
with transaction.manager:
assessment_id = int(request.params['assessment_id'])
assessment_obj = api.retrieve_assessment(assessment_id) # the assessment
video_id = assessment_obj.video_id
thevid = api.retrieve_video(video_id)
template_video = int(request.params['video'])
return {
'thevid': thevid,
'template_video': template_video
}
Then in your template you can reference those variables and their members:
<iframe ... src="{{ thevid.url }}"></iframe>
Per Sergey's amazing guidance I created a new view configuration that loaded the assessment and the video. I then returned the values needed.
Working code:
#view_config(route_name='assessment', request_method='GET', renderer='templates/assessment_form.jinja2')
def assessment_form_view(request):
with transaction.manager:
assessment_id = int(request.matchdict['id'])
assessment = api.retrieve_assessment(assessment_id)
if not assessment:
raise HTTPNotFound()
video = assessment.video_id
print 'test_video_id', video
video_obj = api.retrieve_video(video)
print 'test_video_obj', video_obj
return {'assessment_id': assessment_id, 'assessment_name': assessment.name, 'assessment_text': assessment.text, 'user_id': assessment.user_id, 'categories': ','.join([str(i) for i in assessment.categories]), 'url': video_obj.url, 'video_id':video_obj.video_id, 'videoname': video_obj.videoname, 'length': video_obj.length}

web2py ajax issue onclick

I am trying to write a controller method and a corresponding view which will call the controller on web2py using Ajax. The idea is to make a small update on the database and return a simple updated message on a target div using Ajax. Below is contoller method:
def deleteajax():
recid1 = request.vars.recid
reptype1 = request.vars.reptype
if recid1 == None:
out = 'Missing Param : recid'
return out
if reptype1 == None:
reptype1 = 'pr'
if reptype1 == 'pr':
row = db2(db2.prs_mailed.id==recid1).select().first()
return str(row.id)
elif reptype1 == 'tb':
row = db2(db2.tbs_mailed.id==recid1).select().first()
else:
return 'Please provide the parameter : rep'
if row['action'] == 'D':
out = 'Already deleted'
return out
else:
row.update_record(action='D')
out = 'Deleted Successfully!'
return out
and this is how I am calling the same from view:
<form>{{
response.write('<input type="hidden" name="recid" value="'+str(response._vars['prs']['id'][k])+'"/>',escape=False)}}
<input type ='button' name="del" value = "D" onclick="ajax('deleteajax', ['reid'], 'target')" />
<div id="target"></div>
</form>
I have tested the controller individually using a POST call and that works. Even the AJAX call works and displays error messages like 'Missing Param : recid' on the target div. I even tried modifying the controller to show more messages after finishing each statement. However, post any database operation, no commands from the controller are getting executed, nor is anything showed on the target div. Where am I going wrong?
First, instead of this:
{{response.write('<input type="hidden" name="recid" value="' +
str(response._vars['prs']['id'][k])+'"/>',escape=False)}}
Just do this:
<input type="hidden" name="recid" value="{{=prs['id'][k])}}"/>
There's no need to use response.write or to access the "prs" object through response._vars (all the items in response._vars are available globally in the view environment).
Regarding the Ajax problem, your input element name is "recid", but your ajax() call refers to "reid". Change the latter to "recid" and see if it works.
UPDATE:
To create multiple unique recid's, you could do:
name="{{='recid%s' % prs['id'][k]}}"
Then, in the controller, check for request.vars that start with "recid":
recid = [v for v in request.post_vars if v.startswith('recid')]
if recid:
recid = int(recid[0][5:])
[code to delete record with id==recid]

Categories