Python Flask pagination submits only last page - python

i have a sqlalchemy query which renders a template with a couple of settings.
below you can find very simplified code to give an idea of what is going on. This code puts a checkbox field for a setting on every page, and there is no fixed nr of settings at the moment, it depends on the size of the table. As far as the pagination goes, this works fine. I can go to next and previous page.
The submit button on the page only posts the checkbox value of the last page. Is it possible to also remember and/or save the input from all pages, not just the last page?
#app.route('/settings')
def settings():
page = request.args.get('page', 1, type=int)
settings = Settings.query.paginate(page, 1, False)
next_url = url_for('settings', page=settings.next_num) \
if settings.has_next else None
prev_url = url_for('settings', page=settings.prev_num) \
if settings.has_prev else None
inputtype = 'checkbox'
return render_template("settings.html",
settings = settings,
inputtype = inputtype,
next_url = next_url,
prev_url = prev_url
)
template would be something like this.
<div class="form-check">
{% for setting in settings %}
<input type="{{ inputtype }}" value="{{ setting }}" {{ setting }}
{% endfor %}
<div class=pagination>
{% if prev_url %}
Previous
(% endif %}
{% if next_url %}
Next
{% endif %}
</div>
<div class="panel-footer">
<input class="btn btn-primary" role="button" type="submit" value="Submit">
</div>

I get the feeling that if you submit you only submit the settings on the current page. Only the current settings are on the page and it would not make much sense to add all of them to the page.
I think that what you want is not possible on multiple pages if you use links to got to the previous and next settings.
If you make a change on page 1 and then click next the changes made on page 1 are not saved anywhere so they are lost.
Maybe it is possible to make previous and next also post to settings. This way you get the settings from that page and can make a temporary settings object that you can process when you click commit.

I fixed this without using javascript. I came across this answer and seems to do the trick. It simply does a post request and jumps to the next page.
#hugo, thanks for your answers, it certainly helped me looking in the right direction.
Cannot Generate a POST Request on Flask using url_for

Related

Django Pagination Where User Selects/Types Desired Page

I am looking to create a pagination like either of the following examples:
https://www.martindale.com/search/attorneys/?term=Bankruptcy%20near%20New%20York%2C%20USA (top right of search results)
https://www.adidas.com/us/superstar?start=0 (scroll to the bottom)
Where a user could either type in the page they want to go to and press enter or have a dropdown selection showing all the possible pages to choose from and then once selecting, automatically go to that page (the dropdown probably seems better to prevent users from typing a random set of numbers of characters into the input field). Otherwise the input field where a user types will need forms.py validation.
When attempting to do this, I ran into an issue not being able to use a template tag within forms.py. I was able to make a template for loop dropdown menu that loops through all the possible page numbers. But this for loop is not using forms.py which I don't think is desirable (because no validation). My pagination code is currently separate from forms.py or the form tag HTML.
I would still like to keep the current next, previous, first, and last links to appease those types of users. But if there are lots of pages of results, I want to give users freedom to quickly go to a desired page they want without clicking next or previous a bunch of times to get there.
How would I get the desired result similar to both of the links above?
Current HTML Pagination:
<div class="pagination">
<span class="step-links">
{% if page_obj.has_previous %}
« first
previous
{% endif %}
<span class="current">
Page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }}
</span>
{% if page_obj.has_next %}
next
last »
{% endif %}
</span>
</div>
App_tags.py:
from urllib.parse import urlencode
from django import template
register = template.Library()
#register.simple_tag(takes_context=True)
def url_replace(context, next_page):
query = context['request'].GET.copy().urlencode()
if '&page=' in query:
url = query.rpartition('&page=')[0] # equivalent to .split('page='), except more efficient
elif 'page=' in query:
url = query.rpartition('page=')[0]
else:
url = query
return f'{url}&page={next_page}'
Attempt Template For Loop Showing All Possible Pages:
<select class="form-control col-md-1" autocomplete="off">
{% for i in "x"|rjust:page_obj.paginator.num_pages %}
<option> {{ forloop.counter }} </option>
{% endfor %}
</select>
Attempt Forms.py:
class CourseForm(forms.Form):
page_number = forms.CharField(widget=forms.TextInput(attrs={'class':'form-control col-md-1', 'autocomplete':'off', 'id':'title_contains', 'type':'search', 'placeholder': {{ page_obj.number }} }), required=False)
def __init__(self, *args, **kwargs):
super(CourseForm, self).__init__(*args, **kwargs)
self.fields['page_number'].widget.attrs['placeholder'] = {{ page_obj.number }}

Additional action button doesn't work on flask-admin

I'm trying to add one more action to flask-admin forms.
It has to increment rating (+1) and it works with batch action, but not with single. Please help me find the bug, I've spent a lot of time trying to make this thing work properly.
Here's the code:
I made an html template in templates folder - custom_lists.html
{% extends 'admin/model/list.html' %}
{% block list_row_actions %}
{{ super() }}
<form class="icon" method="POST" action="/admin/user/action/">
<input id="action" name="action" value="approve" type="hidden">
<input name="rowid" value="{{ get_pk_value(row) }}" type="hidden">
<button onclick="return confirm('Are you sure you want to approve selected items?');" title="Approve">
<span class="fa fa-ok glyphicon glyphicon-ok"></span>
</button>
</form>
{% endblock %}
this succeeded with an icon on the list, but if i click to it - it says
Not Found
The requested URL was not found on the server. If you entered the URL
manually please check your spelling and try again.
added to templates folder and added to AdidasView class this:
list_template = 'custom_list.html'
#action('approve', 'Approve', 'Are you sure you want to approve selected items?')
def action_approve(self, ids):
try:
query = Adidas.query.filter(Adidas.id.in_(ids))
count = 0
for image in query.all():
image.rating += 1
count += 1
db.session.commit()
flash(ngettext('Item was successfully approved.',
'%s items were successfully approved.'%count,count))
except Exception as ex:
if not self.handle_view_exception(ex):
raise
flash(gettext('Failed to approve items. %(error)s', error=str(ex)), 'error')
I have not changed the template but I have done it differently as following by setting the column_extra_row_actions variable and defining the action_play function
column_extra_row_actions = [
EndpointLinkRowAction('glyphicon glyphicon-play', 'event.action_play')
]
#expose('/action/play', methods=('GET',))
def action_play(self, *args, **kwargs):
return self.handle_action()
This solution does not seem to apply to this example, but I also struggled with a case where I received a 404 when I using an action on one item via the button, while the batch action worked as expected.
After taking a look at the JS for the batch action I realized that the two HTML forms for individual actions and batch actions are practically identical. The only difference is that when using batch actions there may be more input fields in the form. That implies that if you get a 404 on one, but not the other, there must be an error in your HTML.
In my case I was not aware that Flask-Admin addresses models_with_underscores_in_their_name as modelswithunderscoresintheirname. Therefore instead of
<form class="icon" method="POST" action="/admin/mymodel/action/">
my erroneous code was
<form class="icon" method="POST" action="/admin/my_model/action/">
Note the difference in the action field.
With this change I was able to use the #action API as explained in the Flask-Admin docs.

how can i reverse back to home page without reload it

In my project, I have 2 models Article and Quote. Every time, a user click a button in the home page, it will add the related quote into user's article.
The Article.models look like this:
class Article(models.Model):
user = models.OneToOneField(User, null=True)
quote = models.ManyToManyField(Quote, blank=True, null=True)
def __unicode__(self):
return self.user.username
Here is the view.py
def add_quote(request, id):
u = User.objects.get(username=request.user)
a = Article.objects.get(user=u)
q = Quote.objects.get(id=id)
a.quote.add(q)
a.save()
return HttpResponseRedirect(reverse("home"))
Home.html:
{% for quote in quotes %}
<p>{{ quote.description }}</p>
<p><a class="btn btn-primary" href="{{ quote.get_absolute_url }}"
role="button" data-container="body" data-toggle="popover"
data-placement="top" data-content="added">collect »</a></p>
It does work. However, it also reloads home page. So when I scroll down and click the button, the page goes back to the top and doesnt stay where i click.
I did some research finding out that dajax might help but just have no idea how to solve my problem with it or another efficient way?
There are two ways to go about this, and I recommend you implement them both.
1. Javascript-free method.
Include anchors for each quote, so when you redirect you can redirect like so:
HttpResponseRedirect(reverse("home")+"#quote_id_%s"%(q.id))
Which will look like:
http:example.com#quote_id_123
And jumps to the element with that id, like:
<blockquote id="quote_id_123">Four score and seven years ago...</blockquote>
This means that users who don't have Javascript (which is still a surprising amount) get the functionality of jumping to the right location.
To do this you can alter your for loop like so:
{% for quote in quotes %}
<p id="quote_id_{{quote.id}}">{{ quote.description }}</p>
<p><a class="btn btn-primary" href="{{ quote.get_absolute_url }}"
<!-- etc ... -->
{% endfor %}
2. Use progressive enhancement to add Javascript
This is less straight-forward, and will require writing a lot more code than above, but basically will require submitting the form via some ajax method, capturing the response (or error) correctly, and updating the page.
Dajax and jQuery will probably help in this regard, but will be very specific to your site.

How do I use the same view with two different forms and don’t loose the data provided by the user with Flask?

I have a form with several input fields about a meeting.
#app.route('/Alt_Reuniao/<WCodigoChaveP>', methods=['GET', 'POST'])
def Alt_Reuniao(WCodigoChaveP):
# This function returns a list with de data from the Database about a specific meeting
WReuniao = Executa_Query_Leitura("0005", WCodigoChaveP, "O")
if not WReuniao:
flash('Error.......')
return redirect(url_for('Cad_Reunioes'))
else:
if WReuniao[8]:
# this function returns a list with de data from the Database about the topic of the meeting
WAssunto = Executa_Query_Leitura("0002", WReuniao[8], "O")
Wform = Cad_Reunioes_Form()
if request.method == "POST":
if Wform.validate():
# save the data ......
return redirect(url_for('Cad_Reunioes'))
else:
for Werro in Wform.errors.values():
flash(Werro[0])
return render_template('Reuniao.html', Wformulario=Wform)
else:
Wform.WCPO_Reuniao.data = WReuniao[1]
Wform.WCPO_Desc_Reuniao.data = WReuniao[2]
Wform.WCPO_Nro_Part.data = WReuniao[3]
Wform.WCPO_Cod_Assunto.data = WReuniao[8]
if WReuniao[8]:
if WAssunto:
Wform.WCPO_Assunto.data = WAssunto[1]
return render_template('Reuniao.html', Wformulario=Wform)
This is my Reuniao.html template:
{% extends "Base_Cad_2.html" %}
{% block dados %}
{{ Wformulario.WCPO_Reuniao.label(id="WCPO_Reuniao", class="lab1") }} {{ Wformulario.WCPO_Reuniao(size = 100, maxlength=30, id="WCPO_Reuniao") }}
<p id="PL"> {{ Wformulario.WCPO_L_Desc_Reuniao(id="WCPO_L_Desc_Reuniao", class="lab1") }} </p>
{{ Wformulario.WCPO_Desc_Reuniao(rows=5, cols=100, id="WCPO_Desc_Reuniao") }}
{{ Wformulario.WCPO_Nro_Part.label(id="WCPO_Nro_Part", class="lab1") }} {{ Wformulario.WCPO_Nro_Part(size = 5, id="WCPO_Nro_Part") }}
{{ Wformulario.WCPO_Cod_Assunto.label(id="WCPO_Cod_Assunto", class="lab1") }} {{ Wformulario.WCPO_Cod_Assunto(size=10, readonly='readonly', id="WCPO_Cod_Assunto") }}
{{ Wformulario.WCPO_Assunto.label(id="WCPO_Assunto", class="lab1") }} {{ Wformulario.WCPO_Assunto(size=95, readonly='readonly', id="WCPO_Assunto") }}
<button id="Selec_Assunto" name="Selec_Assunto" value="Selec_Assunto" type="button"> <a class="botoes" href="{{ url_for('Selec_Assuntos_Inicio', WRotChama = "001", WCodorig = Wformulario.WCPO_Cod_Reuniao ) }}" hreflang="pt-br"> Seleciona </a> </button>
{% endblock %}
{% block botoes %}
<button id="gravar" name="gravar" value="Gravar" type="submit" class="botoes" > Gravar </button>
{% endblock %}
Basically, this view works fine.
When I select a meeting from a list in a previous template the view method is a GET and the data from the database is passed to the form and the template renders correctly.
When the method is a POST the data from the form is saved correctly in the Database,…
On the form there is a button Selec_Assunto. When the user click on that button I point to a view witch renders a template with a list of all possible topics for the meeting. These topics come from the database. There can be a lot of them so I can’t just use a combo. That’s why I use a template.
When the user select an topic from the list I have to render Alt_Reuniao view again and I have to pass to the view the selected topic.
This is working fine.
My problem is this: the method again is a GET. If before hitting the Selec_Assunto button the user alter or input data on the other fields in the form I loose these data from the user when the topic is selected. The view render the template with the data from the database again.
Everything seems to be working fine. I just want to maintain the data the user already change in the form before clicking on the Selec_Assunto button.
As you can see I’m new in Web developing, Python, Flask,…
Thanks a lot in advance for your help.
In this case you could update the "Selec_Assunto" button behavior to post the form data back to the server, but also include a redirect variable. When the redirect variable is included, the server would save the form changes and then redirect to the "Selec_Assuntos_Inicio" view, whereas it would follow the previous/normal form posting behavior if the redirect variable isn't present. For example:
if request.method == "POST":
if Wform.validate():
# save the data ……
if Wform.redirect.data:
return redirect(Wform.redirect.data)
else:
return redirect(url_for('Cad_Reunioes'))
else:
for Werro in Wform.errors.values():
flash(Werro[0])
return render_template('Reuniao.html', Wformulario=Wform)
It's worth noting that this approach requires you to use JavaScript to override the "Selec_Assunto" button behavior (as you'd be forcing it to perform a form submission essentially). Here's one way you could do that using jQuery:
$('button#Selec_Assunto').on('click', function() {
$('form#formId').append('<input type="hidden" name="redirect" value="{{ url_for('Selec_Assuntos_Inicio', WRotChama = "001", WCodorig = Wformulario.WCPO_Cod_Reuniao ) }}">');
$('form#formId').submit();
});
That said, a potentially better option from both a coding and usability perspective would be to asynchronously load the topic data into the existing page so that you don't have to reload the view at all. That would enable you to avoid needing to do an interim save of the form data.

Using different 'Forms' for different methods

I hope i can express myself right, here it goes.
I have a handler for a html page that has the objective of editing a "chapter". In this page the chapters are listed and you can use a button to add more chapters. So the first time you open the page, the chapters are listed and a button to add more. If you click 'add', it should present you the same page but with a form for the chapter information.
My problem is passing the information of what chapters are we editing when we reload the page, because a can't pass the 'tut_key' - the reference to the chapters.
editTut.html:
{% for chap in chaps %}
Title: {{ chap.title}}<br>
{% endfor %}
{% if not editMode or editMode == 0 %}
<form ????????>
<input id="tutBtnNext" type="submit" value="Add">
</form>
{% endif %}
{% if editMode == 1 %}
<form method="post">
<!-- form stuff -->
</form>
{% endif %}
the class:
class EditTut(FuHandler):
def get(self):
tutID = self.request.get('tut_key')
tut = db.Key.from_path('Tutorial', tutID)
chaps = db.GqlQuery("SELECT * FROM Chapter " +
"WHERE tutorial = :1", tut)
self.render('editTut.html', chaps=chaps)
def post(self):
tutID = self.request.get("tut_key")
tutorial = db.Key.from_path('Tutorial', tutID)
title = self.request.get("chapTitle")
content = self.request.get("content")
note = self.request.get("note")
chap = Chapter(tutorial=tutorial, title=title,
content=content, note=note)
chap.put()
self.redirect('/editTut?tut_key=%s' % tutID)
#should i use something like this?
#I tried but i can't find a way to call this function on the html
def addChap(self):
tutID = self.request.get("tut_key")
self.redirect('/editTut?tut_key=%s' % tutID)
Setting a cookie would likely be a good option here. When they select a chapter you could send a cookie that specifies that option. Then, each time the page is loaded you just check for that cookie. If the cookie exists and is valid, you should know what chapter they are editing.
You can set cookies in Google App engine like this:
self.response.set_cookie('name', 'value', expires=expire_time, path='/', domain='example.com')
value could be something that references the chapter you're working with. If you leave expires blank, it should expire when they close the browser (which you may want).
You can get the cookie with:
self.request.cookies.get('name','')
Then you could just assign that cookie to a variable, and check it for the chapter info.
I hope I understand you correctly, I had to load forms based on criteria and did it like so:
if not editMode:
params = {}
form = 'add_mode.html'
else:
params = {"data", data}
form = 'edit_mode.html'
return self.render_template(form, **params)

Categories