Flask with Jinja issue with pagination after the first page - python

I have created a dropdown menu to search for parts by project number from an SQL database. The first page loads with the correct parts but any pages subsequent give the error of:
TypeError: show_compound() missing 1 required positional argument: 'search_string'
From what I've seen online it seems I may need to use *args or pass the search_string to the template but I am unsure of how to use *args or where to insert the search_string value on the template.
#parts_database.route('/searchcompound', methods=['GET', 'POST'])
#login_required
def compounds_search():
form = ProjectSearch(request.form)
if form.validate_on_submit():
search_string = form.select.data.project_number
return show_compound(search_string)
return render_template('parts_database/search_compounds.html', form=form)
#parts_database.route('/showcompound', methods=['GET'])
#login_required
def show_compound(search_string):
page = request.args.get('page', 1, type=int)
pagination = PartsTable.query.filter_by(project_number=search_string).order_by(PartsTable.part_number).paginate(page, per_page=15, error_out=False)
compound = pagination.items
page_10 = pagination.next_num+9
if page_10 > pagination.pages:
pageincrement = pagination.pages
else:
pageincrement = page_10
page_decrement = page - 10
if page_decrement < 1:
page_decrement = 1
return render_template('parts_database/showpartstable.html', compound=compound, pagination=pagination, pageincrement=pageincrement, page_decrement=page_decrement)
template :
{% extends "base.html" %}
{% import "bootstrap/wtf.html" as wtf %}
{% import "_macros.html" as macros %}
{% block title %}Amos{% endblock %}
{% block page_content %}
<div class="page-header">
<h1>Parts</h1>
{% include 'parts_database/_showpartstable.html' %}
{% if pagination %}
<div class="pagination">
{{ macros.pagination_widget(page_decrement, pageincrement, pagination, '.show_compound') }}
</div>
{% endif %}
</div>
{% endblock %}

If a view takes an argument, you must include that variable in the route.
In your case, you are missing search_string in your show_compound route definition. Try something like this:
#parts_database.route('/showcompound/<search_string>', methods=['GET'])
#login_required
def show_compound(search_string):
(...)
EDIT:
Also, I'd sugest to redirect instead of calling another view's function.
Replace this:
if form.validate_on_submit():
search_string = form.select.data.project_number
return show_compound(search_string)
with this:
You'll have to import redirect before that:
from flask import redirect # (Add this at the top)
(...)
if form.validate_on_submit():
search_string = form.select.data.project_number
return redirect(url_for('parts_database.show_compound', search_string=search_string))

Related

Why am I receiving a NoReverseError message when having two django paths that take string inputs? (I am working on CS50 Project 1.)

I am working on CS50 Project 1 dealing with Django. In urls.py I have two paths that take strings as input, but neither work, and I receive a NoReverseError message.
urls.py code
urlpatterns = [
path("", views.index, name="index"),
path("edit_entry/<str:title>/", views.edit_entry, name = "edit_entry"),
path("search/", views.search, name = "search"),
path("wiki/<str:title>/", views.get_entry, name = "get_entry"),
path("create_entry", views.create_entry, name = "create_entry")
]
views.get_entry code
def get_entry(request, title):
exists = util.get_entry(title)
if exists is None:
return render(request, "encyclopedia/get_entry.html", {
"entry": "Entry Does Not Exist"
})
else:
entry = markdown(util.get_entry(title))
return render(request, "encyclopedia/get_entry.html", {
"entry": entry
})
views.edit_entry code (edit_entry actually has some more work that needs to be done to it)
def edit_entry(request, title):
if request.method == "POST":
form = NewEditEntryForm(request.POST)
if form.is_valid():
title = form.cleaned_data["title"]
content = form.cleaned_data["content"]
util.save_entry(title, content)
return HttpRespnseRedirect("/wiki/" + title)
else: return render(request, "encyclopedia/edit_entry.html",{
"form": NewEditEntryForm() })
The error message
NoReverseMatch at /wiki/Css/
Reverse for 'edit_entry' with keyword arguments '{'title': ''}' not found. 1 pattern(s) tried: ['edit_entry/(?P<title>[^/]+)/$']
Your help would greatly be appreciated. Thank you!
Templates
get_entry.html
{% extends "encyclopedia/layout.html" %}
{% block title %}
Encyclopedia
{% endblock %}
{% block body %}
<p>
{{ entry|safe }}
</p>
Edit This Entry
{% endblock %}
edit_entry.html
{% extends "encyclopedia/layout.html" %}
{% block title %}
Edit Entry
{% endblock %}
{% block body %}
<h1>Edit {{ title }}</h1>
<form action = "{% url 'edit_entry' title=title %}" method = "POST"></form>
{% csrf_token %}
{{ form }}
<input type = submit value = "Save">
</form>
{% endblock %}
I figured it out. I had to remove title=title from the hrefs in the templates and make it a hard coded url with a variable.

Adding parameter to render call when extending flask admin template

I am trying to add content to a Flask-admin list view.
I want to add my own content on top of the list view.
What I have done so far is to extend the default list view and added my own content like so :
{% extends 'admin/model/list.html' %}
{% block body %}
<h3>Submit report</h3>
{% if report_form.errors %}
<ul class="errors">
{% for field_name, field_errors in report_form.errors|dictsort if field_errors %}
{% for error in field_errors %}
<li>{{ form[field_name].label }}: {{ error }}</li>
{% endfor %}
{% endfor %}
</ul>
{% endif %}
<form action="{{ url_for('report.index') }}" method="post" enctype="multipart/form-data">
{{ report_form.file }}
{{ report_form.csrf_token }}
<button type="submit">Send</button>
</form>
{{ super() }}
{% endblock %}
And my custom model view for this template is :
class ReportAdmin(ModelView):
#expose('/', methods=('GET', 'POST'))
def index(self):
report_form = ReportFileForm()
if form.validate_on_submit():
file = form.file.data
#Check for report duplicate
if Report.query.filter(Report.filename == file.filename).all():
flash('Could not add report because a report with filename {} already exists.'.format(file.filename), 'error')
else:
try:
report = parser_factory(file)
flash('Report was submitted succesfully')
return redirect(url_for('report.index_view'))
except ValueError as e:
form.file.errors.append(e)
return self.render('report/index.html', report_form=report_form)
Now my problem is that the list view expects a certain number of parameters to be set (to handle displaying the list).
Those parameters are defined inside base.py
return self.render(
self.list_template,
data=data,
form=form,
delete_form=delete_form,
# List
list_columns=self._list_columns,
sortable_columns=self._sortable_columns,
editable_columns=self.column_editable_list,
# Pagination
count=count,
pager_url=pager_url,
num_pages=num_pages,
page=view_args.page,
# Sorting
sort_column=view_args.sort,
sort_desc=view_args.sort_desc,
sort_url=sort_url,
# Search
search_supported=self._search_supported,
clear_search_url=clear_search_url,
search=view_args.search,
# Filters
filters=self._filters,
filter_groups=self._filter_groups,
active_filters=view_args.filters,
# Actions
actions=actions,
actions_confirmation=actions_confirmation,
# Misc
enumerate=enumerate,
get_pk_value=self.get_pk_value,
get_value=self.get_list_value,
return_url=self._get_list_url(view_args),
)
So of course when trying to display the page, I get an error :
jinja2.exceptions.UndefinedError: 'num_pages' is undefined
My question is : how do I include my parameter into the render call of the parent view?
Thanks!
Try overriding the view's render method where you will be able to inject your variables into the kwargs argument.
Example (untested)
class ReportAdmin(ModelView):
list_template = 'report/index.html'
# Override to allow POSTS
#expose('/', methods=('GET', 'POST'))
def index_view(self):
return super(ReportAdmin, self).index_view(self)
def render(self, template, **kwargs):
# we are only interested in our custom list page
if template == 'report/index.html':
report_form = ReportFileForm()
if report_form.validate_on_submit():
file = report_form.file.data
#Check for report duplicate
if Report.query.filter(Report.filename == file.filename).all():
flash('Could not add report because a report with filename {} already exists.'.format(file.filename), 'error')
else:
try:
report = parser_factory(file)
flash('Report was submitted succesfully')
except ValueError as e:
report_form.file.errors.append(e)
kwargs['report_form'] = report_form
return super(ReportAdmin, self).render(template, **kwargs)

Django: How to have multiple "add another field" buttons in a form

I'm new to django and I'm having a lot of trouble with forms.
I'm making a calculation-based tool and I need to be able to have an arbitrary number of inputs.
As a really basic example, let's say I want to make a calculator that will sum and subtract any number of inputs. Each number to be added or subtracted is in its own number field. Both the list of "adding" fields and the list of "subtracting" fields has its own "add another field" button.
For starters, here's something that adds two inputs (since I can't figure out how to implement even 1 "add another field button" or understand the answer to it).
views.py
from __future__ import unicode_literals
from django.http import HttpResponse, HttpResponseRedirect
from django.shortcuts import render
from django.views.decorators.csrf import csrf_exempt
from .forms import AddForm
def _from_str(s):
try:
s = int(s)
except ValueError:
try:
s = float(s)
except ValueError:
pass
return s
#csrf_exempt
def web_adder(request):
if request.method == 'POST':
form = AddForm(request.POST)
# form = MyForm(request.POST, extra=request.POST.get('extra_field_count'))
if form.is_valid():
return web_adder_out(request, _from_str(form.cleaned_data['addend0']), _from_str(form.cleaned_data['addend1']))
else:
form = AddForm()
# form = MyForm()
return render(request, 'addercontent.html', {'form': form})
def web_adder_out(request, a, b):
return render(request, 'addercontentout.html', {'content':[a + b]})
forms.py
from django import forms
class AddForm(forms.Form):
addend0 = forms.CharField(label='first addend', max_length=100)
addend1 = forms.CharField(label='second addend', max_length=100)
addercontent.html
{% block content %}
<p>This is a web adder</p>
<form action="" method="post">
{% csrf_token %}
{{ form.as_p }}
<button type="submit" class="btn btn-default">Enter</button>
</form>
{% endblock %}
addercontentout.html
{% block content %}
{% for c in content%}
Result: {{c}}
<br>
Return
{% endfor %}
{% endblock %}
Don't use Django for the field generation. I would do all of it via HTML. Run your setup that you currently have, and you should be able to look at the page source to see how the inputs are structured. Then you can manually write the form in HTML, with JavaScript adding fields in as needed.
Something like this? (not tested, I haven't implement add button)
forms.py
class CalcForm(forms.Form)
first = forms.IntegerField()
second = forms.IntegerField()
def add(self):
first = self.cleaned_data['first']
second = self.cleaned_data['second']
return first + second
views.py
def index(request):
if request.method == "POST":
form = CalcForm(request.POST)
if form.is_valid():
result = form.add()
return render(request, 'your_result_template.html', {'result': result})
else:
form = CalcForm()
return render(request, 'your_template.html', {'form': form})
your_template.html
{% block content %}
<p>This is a web adder</p>
<form method="post">
{% csrf_token %}
{{ form.as_p }}
<button type="submit" class="btn btn-default">Enter</button>
</form>
{% endblock %}
your_result_template.html
{% block content %}
<p>Sum:</p>
<h2>{{ result }}</h2>
{% endblock %}
Edit: For field generation you may need javascript.
I don't know why you want to use django for this kind of app.

Python Flask cannot get element from form

Im having trouble getting anything from the shown HTML form
I always get "ValueError: View function did not return a response"
Can somebody help me out here please? I have tried every variation of request.get that I can find on the web. Also if I specify my form should use post it uses get anyway - anybody know why this is?
Im new to flask so forgive my ignorance!
Thanks in advance.
The python file (routes.py)
from flask import Flask, render_template, request
import os
app = Flask(__name__)
musicpath = os.listdir(r"C:\Users\Oscar\Music\iTunes\iTunes Media\Music")
lsize = str(len(musicpath))
looper = len(musicpath)
#app.route('/')
def home():
return render_template('home.html', lsize=20, looper=looper, musicpath=musicpath)
#app.route('/pop', methods=['POST', 'GET'])
def pop():
if request.method == "GET":
text = request.args.get('som')
return text
#Have tried every variation of request.get
#app.route('/about')
def about():
name = "Hello!"
return render_template('about.html', name=name)
if __name__ == '__main__':
app.run(debug=True)
The html file (home.html)
{% extends "layout.html" %}
{% block content %}
<div class="jumbo">
<h2>A Music app!<h2>
</div>
<div>
{% if lsize %}
<form action="/pop">
<select id="som" size="20">
{% for i in range(looper):%}
<option value="{{i}}">{{ musicpath[i] }}</option>
{% endfor %}
</select>
</form>
{% endif %}
</div>
Select,
{% endblock %}
You don't have a name attribute on your select element. That is the attribute that browsers use to send information in forms; without it no data will be sent.
Note also that your pop handler does not do anything if the method is POST, even though you explicitly say you accept that method.

How to render placeholders from a django-cms custom templatetag?

I've made a simple template tag to list child pages in a django-cms site.
It's working fine except for the fact that I have not been able to render the child pages placeholders in the template tag itself.
The following is my code for the template tag.
subpages.py
from cms.models import Page
from cms.utils.page_resolver import get_page_from_path
from django import template
register = template.Library()
#register.inclusion_tag('subpages.html', takes_context = True)
def get_news_items( context ):
request = context['request']
subpages = request.current_page.children.filter(published=True)
return {'subpages':subpages}
subpages.html
{% load cms_tags menu_tags placeholder_tags %}
<ul>
{% for item in subpages %}
<li>{{ item.get_title }}
{% render_placeholder subtitle %}
</li>
{% endfor %}
</ul>
I've tried some alternatives to *render_placeholder* but without luck.
How would be the correct way to get the placeholder rendered?
This is only a (untested) suggestion, but try passing the context along to the template:
#register.inclusion_tag('subpages.html', takes_context = True)
def get_news_items( context ):
request = context['request']
subpages = request.current_page.children.filter(published=True)
context.update({'subpages':subpages})
return context

Categories