Using different 'Forms' for different methods - python

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)

Related

Flask WTForm: add new field based on previous input

I have a form with a SelectField and if a specific option in there is selected I want to show a different field where a user can input more options.
So my form class looks like
class QuestionForm(FlaskForm):
type = SelectField("Question Type")
options = SelectField("options", validators=[Optional()])
Then my views\questions.py blueprint renders the form like
def question():
...
form = QuestionCreationForm()
form.type.choices = [
(type, type) for type in ["Type 1", "Type 2"]
]
form.options.choices = ["option 1", "option 2"]
if form.validate_on_submit():
...
else:
flash_form_errors(form)
return render_template(
"question.html",
form=form,
)
And finally the html:
{% extends "base.html" %}
{% block page_title %}Questionnaire{% endblock %}
{% block content %}
...
<form method="POST" action="{{ url_for('questionnaire.questionnaire') }}">
<div id="type_selection" class="select is-medium is-expanded" onchange="WHAT HERE?">
{{ form.type }}
</div>
<div id="option_selection" class="select is-medium is-expanded DISPLAY NONE HERE?" onchange="WHAT HERE?">
{{ form.options }}
</div>
</form>
....
{% endblock %}
<script>
function myFunction() {
// ??
}
</script>
So my guess is to have an onchange for my typesform field and check if the selected value is let's say Type 2 in order to change the rendering of the optionsfield to be displayed. However, I wasn`t able to stick together all the parts correctly.
This questions is a potential duplicate, but I cannot comment there and the answer provided was not specific enough the help a newby like me. Thanks!
Flask WTF forms: can a new field be shown if a user selects a specific choice in SelectMultipleField?
To not make you all fuzzy about the answer on this one. You will need some javascript too. There is pretty straight to the point tutorial on youtube that is no longer than 10 - 15 mins , so search for flask dynamic select field. Hope it will help you out.

Python Flask pagination submits only last page

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

Flask-admin batch action with argument via pop-up modal window

Is there any way to initiate a pop-up window from a Flask function?
I have a flask-admin + flask-sqlalchemy app. A table in the DB contains a field foo with some values. I have a UserAdmin view and I'm trying to create a batch action with some external argument.
I.e I want to:
select several elements from my DB and
substitute the old foo values for each of the element with the one new user-defined value and
the way I want to receive this new value is a modal window.
So the model is:
class User(db.Model):
# some code
foo = Column(Integer)
def change_foo(self, new_foo):
self.foo = int(new_foo)
db.session.commit()
return True
class UserAdmin(sqla.ModelView):
# some code
#action('change_foo', 'Change foo', 'Are you sure you want to change foo for selected users?')
def action_change_foo(self, ids):
query = tools.get_query_for_ids(self.get_query(), self.model, ids)
count = 0
# TODO: pop-up modal window here to get new_foo_value
# new_foo_value = ???
for user in query.all():
if user.change_foo(new_foo_value):
count += 1
flash(ngettext('Foo was successfully changed.',
'%(count)s foos were successfully changed.',
count, count=count))
except Exception as e:
flash(ngettext('Failed to change foo for selected users. %(error)s', error=str(e)), 'error')
I admit that the whole approach is not optimal, so I'd be glad to be advised with the better one.
There are some related questions: «Batch Editing in Flask-Admin» (yet unanswered) and «Flask-Admin batch action with form» (with some workaround using WTF forms).
Here's one way of achieving this. I've put the complete self-contained example on Github, flask-admin-modal.
Update 28 May 2018. The Github project has been enhanced by another user to handle form validation nicely.
In this example the SQLite database model is a Project with name (string) and cost (Integer) attributes and we will update the cost value for selected rows in the Flask-Admin list view. Note that the database is populated with random data when the Flask application is started.
Here's the model:
class Project(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(255), nullable=False, unique=True)
cost = db.Column(db.Integer(), nullable=False)
def __str__(self):
return unicode(self).encode('utf-8')
def __unicode__(self):
return "Name: {name}; Cost : {cost}".format(name=self.name, cost=self.cost)
Define a form with an integer cost field that accepts the new cost. This form also has a hidden field to keep track of the selected row ids.
class ChangeForm(Form):
ids = HiddenField()
cost = IntegerField(validators=[InputRequired()])
Override the list template for the Project view model. We do this so we can inject a Bootstrap modal form, with an id changeModal, within the {% block body %}, making sure we call {{ super() }} first.
We also add a jQuery document ready function that will show the modal form if a template variable (change_modal) evaluates to true. The same variable is used in the modal-body to display the change_form. We use the Flask-Admin lib macros render_form to render the form in a Bootstrap style.
Note the value of the action parameter in render_form - it is a route that we define in our Project view where we can process the form's data. Also note the the "Close" button has been replaced with a link, but still styled as a button. The link is the original url (including page and filter details) where the action was initiated from.
{% extends 'admin/model/list.html' %}
{% block body %}
{{ super() }}
<div class="modal fade" tabindex="-1" role="dialog" id="changeModal">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<span aria-hidden="true">×</span>
<h4 class="modal-title">Change Project Costs</h4>
</div>
<div class="modal-body">
{% if change_modal %}
{{ lib.render_form(change_form, action=url_for('project.update_view', url=url)) }}
{% endif %}
</div>
</div><!-- /.modal-content -->
</div><!-- /.modal-dialog -->
</div><!-- /.modal -->
{% endblock body %}
{% block tail %}
{{ super() }}
<script>
{% if change_modal %}
$(document).ready(function(){
$("#changeModal").modal('show');
});
{% endif %}
</script>
{% endblock tail %}
The Project view class needs to modify the behaviour of the batch action method and define a couple of routes that accepts POST requests.
#action('change_cost', 'Change Cost', 'Are you sure you want to change Cost for selected projects?')
def action_change_cost(self, ids):
url = get_redirect_target() or self.get_url('.index_view')
return redirect(url, code=307)
Instead of processing the ids directly the batch action gets the url that posted the action, this url will include any page number and filter details. It then does a redirect back to the list view with a 307. This ensures the selected rows ids are carried along in the body as well as the fact that it was a POST request.
Define a POST route to process this redirect, get the ids and url from the request body, instance a ChangeForm, set the hidden ids form field to an encoded list of the ids. Add the url, change_form and change_model variables to the template args and then render the list view again - this time the modal popup form will be shown in the view.
#expose('/', methods=['POST'])
def index(self):
if request.method == 'POST':
url = get_redirect_target() or self.get_url('.index_view')
ids = request.form.getlist('rowid')
joined_ids = ','.join(ids)
encoded_ids = base64.b64encode(joined_ids)
change_form = ChangeForm()
change_form.ids.data = encoded_ids
self._template_args['url'] = url
self._template_args['change_form'] = change_form
self._template_args['change_modal'] = True
return self.index_view()
Define a POST route to process the modal form's data. This is standard form/database processing and when finished redirect back to the original url that initiated the action.
#expose('/update/', methods=['POST'])
def update_view(self):
if request.method == 'POST':
url = get_redirect_target() or self.get_url('.index_view')
change_form = ChangeForm(request.form)
if change_form.validate():
decoded_ids = base64.b64decode(change_form.ids.data)
ids = decoded_ids.split(',')
cost = change_form.cost.data
_update_mappings = [{'id': rowid, 'cost': cost} for rowid in ids]
db.session.bulk_update_mappings(Project, _update_mappings)
db.session.commit()
return redirect(url)
else:
# Form didn't validate
# todo need to display the error message in the pop-up
print change_form.errors
return redirect(url, code=307)

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.

Loading page in Django based on its ID

I've been working with Django in order to make my portfolio and I've managed to make a simple page manager. The problem is, it does not work how I want it to work:
I create the page.
It loads the content I gave it.
With jQuery, I load only that content (as formatted HTML).
It shows itself without reloading or moving to another page.
The problem is with the last two steps, I can't get the view and template to only load one.
Views.py:
def paginas(request, title):
get_page = Page.objects.all() # I can't think of a way to make a proper filter
return render_to_response('template.html', {'get_page': get_page}, context_instance=RequestContext(request), mimetype="text/html")
Template.html:
{% if get_page %}
{% for page in get_page %}
{{ page.content|safe }}
<p>Full path is {{ request.get_full_path }} and page id is {{ page.id }}</p>
{% endfor %}
{% else %}
<p>Nothing.</p>
{% endif %}
I know I should filter it, but I don't know how.
I appreciate your help.
tbh, the django tutorial explains urls, parameters and forms very clear, but here's the idea:
url(r'^/someapp/(?P<page_id>\d+)/$', paginas),
def paginas(request, **kwargs):
id = kwargs.pop('page_id')
page = get_object_or_404(Page, id=id)
# etcetera
class Page(models.Model):
# regular stuff
def get_absolute_url(self):
return "/someapp/%d/" % self.id
In paginas you are obviously getting all Pages.
To get one page you can use the get function
def paginas(request, title):
try:
your_page = Page.objects.get(title=title)
except Page.DoesNotExist:
# error no page for that title
# could use built in get_object_or_404 as ArgsKwargs suggested
It's also important to consider using a slug to make sure encoding is correct. The page id would be even better to use

Categories