Combine flask form field with html input - python

I have a FieldList form that allows users to enter in an origin and destination for routes they have travelled.
I am trying to add Google's autocomplete API to make it easier for users to enter in addresses into the fields.
forms.py
from flask_wtf import Form
from wtforms import (
StringField,
FormField,
FieldList
)
from wtforms.validators import (
Length,
Optional
)
class RouteForm(Form):
origin = StringField([Optional(), Length(1, 256)])
destination = StringField([Optional(), Length(1, 256)])
class JourneysForm(Form):
ids = []
journeys = FieldList(FormField(RouteForm))
edit.html
{% import 'macros/form.html' as f with context %}
<tbody>
<tr>
{% for journey, route in zip(form.journeys, routes) %}
<td>
{% call f.location_search(journey.origin,
css_class='sm-margin-bottom hide-label') %}
{% endcall %}
</td>
</tr>
{% endfor %}
</tbody>
<div class="col-md-6">
<button type="submit" class="btn btn-primary btn-block">
'Save'
</button>
</div>
macros/forms.html
<head>
<script src="https://maps.googleapis.com/maps/api/js?libraries=places&key=<KEY>&callback=initMap" async defer></script>
<script>
function initMap() {
var input = document.getElementById('searchInput');
var autocomplete = new google.maps.places.Autocomplete(input);
}
</script>
</head>
{# Render a form for searching a location. #}
{%- macro location_search(form, css_class='') -%}
<input type="text" class="form-control"
id="searchInput" placeholder="Enter location">
{{ form(class=css_class, **kwargs) }}
{{ caller () }}
{%- endmacro -%}
routes.py
#route('/edit', methods=['GET', 'POST'])
def routes_edit():
routes = get_routes()
journeys_form = JourneysForm()
if journeys_form.validate_on_submit():
for i, entry in enumerate(journeys_form.journeys.entries):
origin = entry.data['origin']
However, this renders two fields. One which contains the Google autocomplete input, but does not submit the value (top). And another field which does not have the Google autocomplete input but submits the value to the db via routes.py (bottom).
Is it possible to combine this into a single field that both contains the Google autocomplete and submits the input value to the db?

WTForms actually renders an input class itself:
<input class="form-control" id="journeys-0-origin" name="journeys-0-origin" type="text" value="" placeholder="Enter a location" autocomplete="off">
Therefore I was unnecessarily duplicating the input element. In order to get Google autocomplete to work I simply just had to pass the input id into my js function:
function initMap() {
var input = document.getElementById('journeys-0-origin');
var autocomplete = new google.maps.places.Autocomplete(input);
}

Related

Store dynamic choice field form from api call to be able to pass the verification in post request

I'm working on an app that can display the events coming from a google calendar.
To be able to do this, the user need to fill a form with the start date, end date, timezone and select a calendar.
I'm new to Django and it's ok to make a form with date, text, checkbox but regarding the choice field, it fail because the values are not present in the choice list.
Select a valid choice. johndoe#gmail.com is not one of the available choices.
This is normal because the values will change according to the user.
For that I'm calling the google calendar api before showing the page at GET request.
I tried to add it to the form but of course, it doesn't stay while the post method is called.
Is there a way to store the value without using the session or database?
How do you manage dynamic choice field that isn't coming from database?
Here is my code:
form.py
from django import forms
class events_filters(forms.Form):
start = forms.DateField(label='Start date')
end = forms.DateField(label='End date')
calendar = forms.ChoiceField(label='Select calendar')
timezone = forms.BooleanField(label="Show timezone")
view.py
from django.shortcuts import render
from django.contrib.auth.decorators import login_required
from googleapiclient.discovery import build
from google.oauth2.credentials import Credentials
from allauth.socialaccount.models import SocialApp, SocialAccount
import csv
from django.http import HttpResponse
from .forms import events_filters
# Create your views here.
# Main page
#login_required(login_url='/accounts/google/login/')
def index(request):
if request.method == "GET":
creds = create_credentials(request)
calendars = getCalendars(creds) #function to get the calendars
form = events_filters()
form.fields["calendar"].choices = calendars #tried to add the calendars to the form, it work but of course doesn't stay for the post request
return render(request, "ts_index.html", context={"calendars":calendars, 'form': form})
if request.method == "POST":
form = events_filters(request.POST)
if form.is_valid(): # Failed here because the form doesn't know the values to be validated. I would like to be able to validate the data without passing by the client to be sure that the user use an email in the list. I would also like to avoid to call the calendar api again.
parameters = {
"start_date" : form["start"] + "T00:00:00.000Z",
"end_date" : form["end"] + "T00:00:00.000Z",
"calendar_id" : form["calendar"],
}
# Use the access token to authenticate with the Google Calendar API
creds = create_credentials(request)
events = getEvents(creds, parameters["start_date"], parameters["end_date"], parameters["calendar_id"])
return render(request, "ts_input_data.html", context={'events':events, "parameters": parameters})
else :
print("Data not valid")
Html page
{% extends 'head.html' %}
{% load socialaccount %}
<!--Block content goes below-->
{% block content %}
<h1>Input data</h1>
<!-- Select the dates -->
<form method="POST" class="d-flex flex-column justify-content-center align-items-center" >
<!-- Key for CSRF -->
{% csrf_token %}
<!-- Select the agenda -->
<div class="mb-3 w-100">
<label for="calendar" class="form-label">Select calendar</label>
<select name="calendar" id="calendar_id" class="form-control">
{% for c in calendars %}
<option value=" {{ c.0 }} ">{{ c.1 }}</option>
{% endfor%}
</select>
</div>
<!-- Start date -->
<div class="mb-3 w-100">
<label for="start_id" class="form-label">Start date</label>
<input type="date" class="form-control" id="start_id" name="start">
</div>
<!-- End date -->
<div class="mb-3 w-100">
<label for="end" class="form-label">End date</label>
<input type="date" class="form-control" id="end_id" name="end">
</div>
<!-- End date -->
<div class="mb-3 w-100">
<label for="timezone" class="form-label">Show time-zone</label>
<input type="checkbox" class="form-check-input" id="timezone_id" name="timezone" checked>
</div>
<!-- Submit -->
<button type="submit" class="btn btn-primary w-100">Submit</button>
</form>
{% endblock %}

How to make Ajax delete Django's object instance?

There is a list generated in HTML, that represents all objects (Cards).
There is already a delete button, but it's using Django functionality, and it requires a page to reload to take effect.
Is there a simple way to include AJAX into the program?
I am a beginner to JavaScript and AJAX. I have tried some copy-paste solutions. I even tried to deconstruct a simple Django Ajax CRUD app, but it has too many functionalities, and it seemed like an overkill for my app (i would have to rewrite all the views, templates and urls).
So I decided to ask a question over here with my own code.
views.py (List objects view)
def all_cards(request):
cards = Card.objects.all()
return render(request, 'all_cards.html', {'cards':cards})
all_cards.html
<body>
{% if cards %}
<table class="table" id="card-table">
<tr>
<th>Card owner name</th>
<th>Card balance</th>
</tr>
{% for card in cards %}
<tr>
<td>{{ card.cardholders_name }}</td>
<td>{{ card.card_balance }}€</td>
<td><form action="{% url 'card_delete' card.id %}" method="post">
{% csrf_token %}
<input type="submit" value='Delete'>
</form></td>
</tr>
{% endfor %}
{% else %}
<p>There are no cards registered.</p>
{% endif %}
</table>
</body>
urls.py
url(r'(?P<id>\d+)/$', views.card_delete, name='card_delete'),
views.py (Delete object view)
def card_delete(request, id):
card_that_is_ready_to_be_deleted = get_object_or_404(Card, id=id)
if request.method == 'POST':
card_that_is_ready_to_be_deleted.delete()
return HttpResponseRedirect('/all_cards')
As you can see, the form's input(
<input type="submit" value='Delete'>
)calls Django's view via URL.
I expect the delete button to call an AJAX functionality, that will do a similar thing.
How should I go about writing that functionality?
P.S.: This is my first StackOVerflow question, I'm open for constructive criticism.
You should add id to your form and table row first
<form action="{% url 'card_delete' card.id %}" method="post" id="delete_form_{{ card.id }}">
.
<tr id="card_{{card.id}}">
And change button code to:
<input type="button" onclick="submit_delete({{ card.id }})" value="delete">
And use this function to send AJAX request:
<script>
function submit_delete(id) {
$.ajax({
type: $('#delete_form_'+id).attr('method'),
url: $('#delete_form_'+id).attr('action'),
data: $('#delete_form_'+id).serialize(),
success: function (data) {
$('#card'+id).remove()
}
});
}
</script>

Django search as input is entered into form

I currently have a working search form in my project that passes through form data to the GET request. Pretty standard.
What I'm wanting to do is search as data is entered into the search form, so that results will display in real time with search data. This is much like what Google does with the instant desktop results. Is this something that's possible with Django?
Below is my current (simple) search
#views.py
def ProductView(request):
title = 'Products'
all_products = Product.objects.all().order_by("product_Name")
query = request.GET.get("q")
if query:
products = all_products.filter(
Q(product_Name__contains=query) |
Q(manufacturer__contains=query)
).distinct()
return render(request, 'mycollection/details.html', { 'all_products' : products })
-
<!-- HTML -->
<!-- SEARCH BAR -->
<form class="navbar-form navbar-left" role="search" method="get" action="{% url 'mycollection:products' %}">
<div class="form-group">
<input type="text" class="form-control" name="q" value="{{ request.GET.q }}">
</div>
<button type="submit" class="btn btn-default">Search</button>
</form>
you can save the request.data in to session and if any data is associated with session search data you can put in to value of search box.
request.session['search'] = request.GET.get('q','')
templete :
{% if request.session.search %} {{request.session.search}} {% endif %}

Pre-Populate an edit form with WTForms and Flask

I am able to add a new entry to my database using WTForms and Flask and I can edit too, the problem is that I need to display the already existing information in the database in an edit form.
I have the following code:
A Class For the Edit Post Form
class editPostForm(Form):
postTitle = TextField('postTitle', validators.Required()])
postSubtitle = TextField('postSubtitle', validators.Required()])
A Route for the Edit Post Template
#app.route('/editpost/<postId>', methods = ['GET','POST'])
def editpost_page(postId):
try:
form = editPostForm(form)
if request.method == "POST" and form.validate():
postTitle = form.postTitle.data
postSubtitle = form.postSubtitle.data
c, conn = connection()
query = c.execute("SELECT * FROM posts WHERE post_id = (%s)",
[noinjection(postId)])
c.execute("UPDATE posts SET post_title=%s, post_subtitle=%s WHERE post_id = %s",
[
noinjection(postTitle),
noinjection(postSubtitle),
noinjection(postId)
])
conn.commit()
flash("Post Edited", 'success')
c.close()
conn.close()
gc.collect()
return redirect(url_for('posts'))
return render_template("editpost.html", form = form, POST_ID = postId)
except Exception as e:
return(str(e))
The Edit Post Template {jinja}
{% extends "header.html" %}
{% block body %}
<body>
<div class="container-fluid">
<br />
<h4>Edit Post</h4>
<br />
{% from "_formhelpers.html" import render_field %}
<form action="/editpost/{{ POST_ID }}" class="form-horizontal" method="post">
{% from "_formhelpers.html" import render_field %}
<form action="/editpost/" method="post">
<div class="form-group">
<label for="postTitle">Post Title</label>
<input type="text" class="form-control" id="postTitle" name="postTitle" placeholder="Post Title" value="{{request.form.postTitle}}">
</div>
<div class="form-group">
<label for="postSubtitle">Post Subtitle</label>
<input type="text" class="form-control" id="postSubtitle" name="postSubtitle" placeholder="Post Subtitle" value="{{request.form.postSubtitle}}">
</div>
<button type="submit" class="btn btn-default">Submit</button>
</form>
{% if error %}
<p class="error"><strong>Error: </strong>{{error}}</p>
{% endif %}
</form>
{% if error %}
<p class="error"><strong>Error: </strong>{{error}}</p>
{% endif %}
</div>
</body>
{% endblock %}
With the following code, I am getting a selected post to update in the database, but the editpost template is not showing the values which are already in the database and all the fields are blank.
How can I pre-populate the form before editing?
You can populate each field separately like this:
form = editPostForm(form)
form.postTitle.data = postTitle_from_database
form.postSubtitle.data = postSubtitle_from_database
or you can populate your form fields from a given object using process method:
process(formdata=None, obj=None, **kwargs)
Take form, object data, and keyword arg input and have the fields
process them.
Parameters:
formdata – Used to pass data coming from the enduser, usually request.POST or equivalent.
obj – If formdata has no data for a field, the form will try to get it from the passed object.
**kwargs – If neither formdata or obj contains a value for a field, the form will assign the value of a matching keyword argument
to the field, if provided.
Since BaseForm does not take its data at instantiation, you must call
this to provide form data to the enclosed fields. Accessing the
field’s data before calling process is not recommended.
I was able to pre-populate HTML input and textarea fields from a SQL database with Python and Jinja as follows:
1. Store relevant data from database in a variable:
name = db.execute("""SELECT name FROM users WHERE id = :id""", id=session["user_id"])
about = db.execute("""SELECT about FROM users WHERE id = :id""", id=session["user_id"])
2. Render template (with render_template function) and pass in the relevant variables:
return render_template("edit.html", name = name, about = about)
3. Pass variables via jinja to html input and textarea elements. Index into the object that has been passed as follows:
For an input tag use the value attribute as below:
<input type="text" class="form-control" name="name" value="{{ name[0]["name"] }}">
For a textarea element:
<textarea class="form-control" name="about">{{ about[0]["about"] }}</textarea>

How to use a WTForms FieldList of FormFields?

I'm building a website using Flask in which I use WTForms. In a Form I now want to use a FieldList of FormFields as follows:
class LocationForm(Form):
location_id = StringField('location_id')
city = StringField('city')
class CompanyForm(Form):
company_name = StringField('company_name')
locations = FieldList(FormField(LocationForm))
so to give people the ability to enter a company with two locations (dynamic adding of locations comes later) I do this on the front side:
<form action="" method="post" role="form">
{{ companyForm.hidden_tag() }}
{{ companyForm.company_name() }}
{{ locationForm.location_id() }}
{{ locationForm.city() }}
{{ locationForm.location_id() }}
{{ locationForm.city() }}
<input type="submit" value="Submit!" />
</form>
So on submit I print the locations:
print companyForm.locations.data
but I get
[{'location_id': u'', 'city': u''}]
I can print the values of the first location using the locationForm (see below), but I still don't know how to get the data of the second location.
print locationForm.location_id.data
print locationForm.city.data
So the list of locations does have one dict with empty values, but:
Why does the list of locations have only one, and not two dicts?
And why are the values in the location dict empty?
Does anybody know what I'm doing wrong here? All tips are welcome!
For starters, there's an argument for the FieldList called min_entries, that will make space for your data:
class CompanyForm(Form):
company_name = StringField('company_name')
locations = FieldList(FormField(LocationForm), min_entries=2)
This will setup the list the way you need. Next you should render the fields directly from the locations property, so names are generated correctly:
<form action="" method="post" role="form">
{{ companyForm.hidden_tag() }}
{{ companyForm.company_name() }}
{{ companyForm.locations() }}
<input type="submit" value="Submit!" />
</form>
Look at the rendered html, the inputs should have names like locations-0-city, this way WTForms will know which is which.
Alternatively, for custom rendering of elements do
{% for l in companyForms.locations %}
{{ l.form.city }}
{% endfor %}
(in wtforms alone l.city is shorthand for l.form.city. However, that syntax seems to clash with Jinja, and there it is necessary to use the explicit l.form.city in the template.)
Now to ready the submitted data, just create the CompanyForm and iterate over the locations:
for entry in form.locations.entries:
print entry.data['location_id']
print entry.data['city']
This is an old question, but still a good one.
I'd like to add a working Flask based example of a toy database (just a list of strings) with focus on the Python part - how to initialize the form with variable number of subforms and how to process the posted data.
This is the example.py file:
import flask
import wtforms
import flask_wtf
app = flask.Flask(__name__)
app.secret_key = 'fixme!'
# not subclassing from flask_wtf.FlaskForm
# in order to avoid CSRF on subforms
class EntryForm(wtforms.Form):
city = wtforms.fields.StringField('city name:')
delete = wtforms.fields.BooleanField('delete?')
class MainForm(flask_wtf.FlaskForm):
entries = wtforms.fields.FieldList(wtforms.fields.FormField(EntryForm))
submit = wtforms.fields.SubmitField('SUBMIT')
city_db = "Graz Poprad Brno Basel Rosenheim Torino".split() # initial value
#app.route("/", methods=['POST'])
def demo_view_function_post():
global city_db
form = MainForm()
if form.validate_on_submit():
city_db = [
entry['city'] for entry in form.entries.data
if entry['city'] and not entry['delete']]
return flask.redirect(flask.url_for('demo_view_function_get'))
# handle the validation error, i.e. flash a warning
return flask.render_template('demo.html', form=form)
#app.route("/")
def demo_view_function_get():
form = MainForm()
entries_data = [{'city': city, 'delete': False} for city in city_db]
entries_data.append({'city': '', 'delete': False}) # "add new" placeholder
form.process(data={'entries': entries_data})
return flask.render_template('demo.html', form=form)
This is the demo.html file:
<!DOCTYPE html>
<html lang="en">
<head>
<title>Demo</title>
</head>
<body>
<h1>Subform demo</h1>
<p>Edit names / mark for deletion / add new</p>
<form method="post">
{{ form.csrf_token() }}
{% for entry in form.entries %}
{% if loop.last %}
<div>Add new:</div>
{% endif %}
<div>
{{ entry.city.label }} {{ entry.city() }}
{{ entry.delete() }} {{ entry.delete.label }}
</div>
{% endfor %}
{{ form.submit() }}
</form>
</body>
Run with: FLASK_APP=example flask run

Categories

Resources