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>
Related
This is my html form in which i have a text field for a word and i am running a for loop which created checkbox for the list of document a user has and displays it as form.
<body>
<form id="form" method = "POST">
{% csrf_token %}
<fieldset id="User">
<legend>Your details:</legend>
<p><label for="word">Enter your word</label>
<input type="text" id="word" name="word">
</p>
{% for doc in docs %}
<p>
<input type="checkbox" id="{{ doc.document_id }}" name="docid" value="{{ doc.path }}">
<label for="{{ doc.document_id }}"> {{ doc.document_name }} </label><br>
</p>
{% endfor%}
<input type="submit" value="Submit" >
</fieldset>
</form>
</body>
In views.py I have below method which loads this html page
def client_home(request):
client_pr = Client_Profile.objects.get(user_id=request.user)
events = Event.objects.filter(client_id=client_pr.pk)
name = request.POST.get('word')
id = request.POST.get('docid')
print(name)
print(id)
docs = []
for eve in events:
docs = Document.objects.filter(event_id=eve)
context = {'events': events, 'docs': docs}
return render(request, 'Elasticsearch/form.html', context)
My question is as i am using a for loop create checkbox field based on number of documents a user has and when I try to print them in my Views.py file it is only priniting last document id whose checkbox is created and not all the documents.
From seeing your view, I assume you want to pass a list of documents specific to a user to your template. Currently you are overwriting docs in every iteration of your for loop.
Change
docs = Document.objects.filter(event_id=eve)
to
docs += Document.objects.filter(event_id=eve)
You need to use getlist to get all the selected values from your checkbox, so:
id_list = request.POST.getlist('docid')
Otherwise using get will only return the last one.
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);
}
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 %}
My Goal
I have a django project with a form, and I want to display a preview page before the user submits.
The problem
I can display a preview page using a Django FormPreview, but not all form data is displayed properly. Specifically, if I have a field with choices, the string values of these choices aren't displayed. I'm also having problems applying template filters to date fields. The end result is that some data on the preview page is visible but other data is blank:
However, if I display the same data for posts that have actually been submitted, then everything displays properly:
My Code
models.py:
class Game(models.Model):
# Game Choices
FOOTBALL = 0
BASKETBALL = 1
TENNIS = 2
OTHER = 3
GAME_CHOICES = (
(FOOTBALL, 'Football'),
(BASKETBALL, 'Basketball'),
(TENNIS, 'Tennis'),
(OTHER, 'Other')
)
game_id = models.AutoField(primary_key=True)
location = models.CharField(max_length=200, verbose_name="Location")
game = models.IntegerField(choices=GAME_CHOICES, default=FOOTBALL)
game_date = models.DateField(verbose_name='Game Date')
forms.py
class GameForm(ModelForm):
class Meta:
model = Game
fields = (
'location',
'game',
'game_date'
)
I'm pretty sure that the problem is in my views.py: I'm not sure that I'm processing the POST request the right way to feed all data to the preview page.
views.py
def form_upload(request):
if request.method == 'GET':
form = GameForm()
else:
# A POST request: Handle Form Upload
form = GameForm(request.POST) # Bind data from request.POST into a GameForm
# If data is valid, proceeds to create a new game and redirect the user
if form.is_valid():
game = form.save()
return render(request, 'games/success.html', {})
return render(request, 'games/form_upload.html', {
'form': form,
})
preview.py
class GameFormPreview(FormPreview):
form_template = 'games/form_upload.html'
preview_template = 'games/preview.html'
def done(self, request, cleaned_data):
# Do something with the cleaned_data, then redirect
# to a "success" page.
return HttpResponseRedirect('/games/success')
form_upload.html
...
<form method="post">
{% csrf_token %}
<ul><li>{{ form.as_p }}</li></ul>
<button type="submit">Preview your post</button>
</form>
...
preview.html
{% load humanize %}
...
<h1>Preview your submission</h1>
<div>
<p>Location: {{ form.data.location }}</p>
<p>Game Date: {{ form.data.game_date|date:"l, F d, Y" }}</p>
<p>Game Type: {{ form.data.get_game_display }}</p>
</div>
<div>
<form action="{% url 'form_upload' %}" method="post">
{% csrf_token %}
{% for field in form %}
{{ field.as_hidden }}
{% endfor %}
<input type="hidden" name="{{ stage_field }}" value="2" />
<input type="hidden" name="{{ hash_field }}" value="{{ hash_value }}" />
<!-- Submit button -->
<button type="submit">Submit your post</button>
<!-- Go back button -->
<button type="submit">
<a href="{% url 'form_upload' %}"
onClick="history.go(-1);return false;" >
Go back and edit your post
</a>
</button>
</div>
</form>
</div>
...
Two issues
Essentially, I'm having these two issues:
String values for choices are not displayed. If I use the get_FOO_display() method in my preview.html template, it returns blank. However, if I use this in a page after the post has been submitted, it displays properly.
The humanize date filter doesn't work. If I apply a humanize filter ({{ form.data.game_date|date:"l, F d, Y" }}) in preview.html, it also displays blank. Again, this works for submitted posts.
My question essentially is: what's the right way to use the FormPreview here?
form.data does not have get_FOO_display attributes. When you access {{ form.data.get_game_display }} in the template, it fails silently and doesn't display anything.
The get_FOO_display are methods of the instance, so try this instead.
{{ form.instance.get_game_display }}
Wherever possible you should access data from form.cleaned_data (which is validated and 'cleaned') instead of form.data, which is the raw data submitted to the form.
The filters don't work with form.data.game_date because it's a raw string. They should work with form.cleaned_data.game_date, which has been converted to a python date object.
Finally, you haven't implemented anything in your done method, you've just copied the comment from the docs. You could create a new game using cleaned_data as follows:
def done(self, request, cleaned_data):
game = Game.objects.create(**cleaned_data)
return HttpResponseRedirect('/games/success')
I am new with django and I try update some data passing the id_provider from form1 to form2
The form2 have to display the data of the provider
I have an html page with a little form(form1):
Is a a simple input text where the user write a number.
the number is passed to other form as an argument.
My forms.py:
class ConfigForm(forms.ModelForm):
def __init__(self,idprov,*args,**kwargs):
super(ConfigForm,self).__init__(*args,**kwargs)
self.id_provider = idprov
class Meta:
model = Config
And my views.py:
#csrf_exempt
def configView(request):
prov = get_object_or_404(Config, id_proveedor=id)
if request.method == 'POST':
form = ConfigForm(request.post, instance=prov)
if form.is_valid():
form.save()
return HttpResponseRedirect('/monitor/')
else:
form = ConfigForm(Config.id_proveedor,instance=prov)
return render_to_response('config.html',{'form':form},RequestContext(request))
This is the form(form2) where I try to display the data:
{% block content %}
<div class="container">
<div class="row">
<div class="col-md-4">
<form method='POST' action='' class='form'>
<div class="form-group">
{% csrf_token %}
{{ form.as_p }}
</div>
<button type='submit' class="btn btn-primary">Grabar</button>
</form>
</div>
</div>
</div>
{% endblock %}
I receive the error:
TypeError at /config/
id() takes exactly one argument (0 given)
I don't know if my error are in the method of the form where I try to update the data (form2) or I have some error in the view of the form.
I think I am not getting the value of the input text int the right way.
Any advice, link or snippet will be very helpful
Thanks in advance
id is a Python function and you didn't create it
>>> id('test')
35092128
Try
#csrf_exempt
def configView(request):
# Get your ID in another way for example this one
pk = request.POST.get('id', None)
if pk is None:
# Handle error
prov = get_object_or_404(Config, id_proveedor=pk)
# ^^
should works
BTW using csrf_exempt is generally not a good idea.