Populate text fields on select field update in Flask / WTForms - javascript

Folks, scratching my head at this, there's a kind of an answer to this here, but having difficulty implementing it.
I currently have a recipe and styles table, and when submit an "add recipe" form, it copies data from the styles table into the recipe. What I would like to do is to select a style in the add recipe form and have this data populate form fields. So I'd like style-type for example to be populated in the form on updating the style select dropdown.
My set up:
Routes:
#app.route('/recipe/new', methods=['GET', 'POST'])
#login_required
def addrecipe():
form = RecipeForm()
if form.validate_on_submit():
recipe = Recipe(recipe_name=form.recipe_name.data,
recipe_style=form.style.data.id,
style_name=form.style.data.name,
style_type = form.style.data.type)
db.session.add(recipe)
db.session.commit()
flash('You added your recipe, get brewing!', 'success')
return redirect(url_for('recipes'))
return render_template('add_recipe.html', title = 'Add Recipe', form=form, legend='Add Recipe')
Models:
class Recipe(db.Model):
id = db.Column(db.Integer, primary_key=True)
recipe_name = db.Column(db.String(100), nullable=False)
recipe_style = db.Column(db.Text, db.ForeignKey('styles.id'))
style_name = db.Column(db.String(100))
style_type = db.Column(db.String(100))
# used for query_factory
def getStyles():
return Styles.query.order_by(Styles.name.asc())
Forms:
class RecipeForm(FlaskForm):
recipe_name = StringField('Recipe Name', validators=[DataRequired(), Length(min=2, max=20)])
style = QuerySelectField(query_factory=getStyles,
get_label="name")
style_type = StringField('Style Type')
The Form HTML:
<form method="POST" action="">
{{ form.hidden_tag() }}
<legend class="border-bottom mb-4">{{ legend }}</legend>
<fieldset class="form-group card p-3 bg-light">
<h5 class="card-title">Overview</h5>
<div class="form-row">
<div class="form-group col-md-3">
{{ form.recipe_name.label(class="form-control-label") }}
{% if form.recipe_name.errors %}
{{ form.recipe_name(class="form-control form-control-sm is-invalid") }}
<div class="invalid-feedback">
{% for error in form.recipe_name.errors %}
<span>{{ error }}</span>
{% endfor %}
</div>
{% else %}
{{ form.recipe_name(class="form-control form-control-sm") }}
{% endif %}
</div>
</fieldset>
<fieldset class="form-group card p-3 bg-light">
<h5 class="card-title">Style</h5>
<div class="form-row">
<div class="form-group col-md-3">
{{ form.style.label(class="form-control-label") }}
<input class="form-control form-control-sm" type="text" placeholder="Search Styles" id="myInput" onkeyup="filterFunction()">
{% if form.style.errors %}
{{ form.style(class="form-control form-control-sm is-invalid") }}
<div class="invalid-feedback">
{% for error in form.style.errors %}
<span>{{ error }}</span>
{% endfor %}
</div>
{% else %}
{{ form.style(class="form-control form-control-sm", id="style_name") }}
{% endif %}
</div>
<div class="form-group col-md-2">
{{ form.style_type.label(class="form-control-label") }}
{% if form.style_type.errors %}
{{ form.style_type(class="form-control form-control-sm is-invalid") }}
<div class="invalid-feedback">
{% for error in form.style_type.errors %}
<span>{{ error }}</span>
{% endfor %}
</div>
{% else %}
{{ form.style_type(class="form-control form-control-sm", id="styletype", style_type_tag='{{ form.style.data.type }}' ) }}
{% endif %}
</div>
</div>
</fieldset>
My Javascript so far:
style_name.oninput = function(o) {
// style = document.getElementById('styletype')
styletype.value = $(o).attr('style_type_tag')
}
I can get some basic stuff working with the JS function. So when I update the dropdown, it'll populate the field with some text. What I can't figure out is how to pull the style_type info from the database. The link at the top here loads that info into the html tags of the text box, but it's a little different to what I'm doing. The poster has looped through some items and it isn't a form. My style_type_tag is just showing up as the raw text. I'm guessing that the loop here is crucial but I can't quite make the step to getting into my setup.
Any help much appreciated!

So the answer to this was building a simple API. I'm sure there are simpler methods, but I wanted a bit of practice here and thought it would be useful in building other features into the project.
I followed Brad Traversy's vid on the subject and used the GET section to make this. His project was a simple single file project, so I had to get a bit more involved with imports etc in my project.
Grab postman to interact with the API
Install Marshmallow, To requirements.txt, add the lines:
flask-marshmallow
marshmallow-sqlalchemy
then run
pip install -r requirements.txt
Import and initialise marshmallow. In init.py:
from flask_marshmallow import Marshmallow
ma = Marshmallow(app)
Add style schema To models.py
# import module
from flaskblog import ma
# create schema with the fields required
class StyleSchema(ma.Schema):
class Meta:
fields = ("id", "name", "origin", "type")
# initialise single style schema
style_schema = StyleSchema()
# initialise multiple style schema
styles_schema = StyleSchema(many=True)
Note that the strict=True isn't necessary any more with Marshmallow.
Create endpoints/routes, to routes.py:
# Get All Styles
#app.route('/styleget', methods=['GET'])
def styles_get():
all_styles = Styles.query.all()
result = styles_schema.dump(all_styles)
# return jsonify(result.data) - note that this line is different to the video, was mentioned in the comments. Was originally "return jsonify(result.data)"
return styles_schema.jsonify(all_styles)
# Get Single Product
# passes the id into the URL
#app.route('/styleget/<id>', methods=['GET'])
def style_get(id):
style = Styles.query.get(id)
return style_schema.jsonify(style)
Update JS script:
style_name.onchange = function() {
// creates a variable from the dropdown
style = document.getElementById('style_name').value
// uses the variable to call the API and populate the other form fields
fetch('/styleget/' + style).then(function(response) {
response.json().then(function(data) {
// this takes the 'type' data from the JSON and adds it to the styleType variable
styleType = data.type;
// adds the data from the variable to the form field using the ID of the form.
styletype.value = styleType
});
});
}
Hope this helps anyone who runs into the same challenge!

Related

The form to change email/profile pictures doesn't appear?

Any guidance on this issue will be greatly appreciated. I entered the necessary fields so that on the profile page the user would be able to edit the profile picture and the username and email. when I created the form, it only displayed the update button and the "Profile Info" text. How can fix this so that the form to edit the user information appears?
in views.py
'''
#login_required
def profile(response):
if response.method == "POST":
u_form = UserUpdateForm(response.POST, instance=response.user)
p_form = ProfileUpdateForm(response.POST, response.FILES, instance=response.user.profile)
if u_form.is_valid() and p_form.is_valid():
u_form.save()
p_form.save()
messages.success(response, f'Your account has been updated!')
return redirect("/profile")
else:
u_form = UserUpdateForm(instance=response.user)
p_form = ProfileUpdateForm(instance=response.user.profile)
args = {}
args['u_form']= u_form,
args['p_form']= p_form
return render(response, 'register/profile.html')
'''
in forms.py
'''
from django import forms
#from django.contrib.auth. import login, authenticate
from django.contrib.auth.forms import UserCreationForm, UserChangeForm
from django.contrib.auth.models import User
from .models import Profile
from django.forms import ModelForm
class RegisterForm(UserCreationForm):
email = forms.EmailField()
class Meta:
model = User
fields = ["username", "email", "password1", "password2"]
class UserUpdateForm(forms.ModelForm):
email = forms.EmailField()
class Meta:
model = User
fields = ["username", "email"]
class ProfileUpdateForm(forms.ModelForm):
class Meta:
model = Profile
fields = ['image']
'''
in profile.html
{% extends "register/base.html" %}
{% load crispy_forms_tags %}
{% block content %}
<div class="content-section">
<div class="media">
<img class="rounded-circle account-img" src="{{ user.profile.image.url }}">
<div class="media-body">
<h2 class="account-heading">{{ user.username }}</h2>
<p class="text-secondary">{{ user.email }}</p>
</div>
</div>
<div class="container">
<form method="post" enctype="multipart/form-data">
{% csrf_token %}
<fieldset class="form-group">
<legend class="border-bottom mb-4">Profile Info</legend>
{{ u_form|crispy }}
{{ p_form|crispy }}
</fieldset>
<div class="form-group">
<button class="btn btn-outline-info" type="submit">Update</button>
</div>
</form>
</div>
{% endblock content %}
To render some variable in the template it must be passed as the context to the template. In the line:
return render(response, 'register/profile.html')
You never pass it the context. render accepts the optional third argument as the context. So the line should be:
return render(response, 'register/profile.html', args)
Note: You accept the request parameter in the view as response! This is a great misnomer! The request is what the user makes to the server and the response is what the server returns. You should accept the parameter as request instead.

How to update a single field from modal form in Django?

I have a form that keeps track of when people enter/leave different areas. Whenever there is a discrepancy, for example, someone forgets to "leave" an area before entering a new one, the user is prompted an "estimate" of the time they believe they left the previous area at(edited_timestamp). The only two required fields on the main form are the employee number and the work area, as these are used to verify/keep track of data.
When I try to reproduce the situation that would make the modal show up, it works, but when I attempt to submit it, I get these messages:
and these are the errors that are being returned.
Now, while I don't understand why the "Enter valid date/time" error is showing, I'm guessing the other two errors are due to the main form requiring the employee_number and the work_area and probably for this request, even though it is updating by the ID, it still wants the other two fields.
I guess my question is, how could I modify this so that these two fields are not required for the modal?
models.py
class EmployeeWorkAreaLog(TimeStampedModel, SoftDeleteModel, models.Model):
employee_number = models.ForeignKey(Salesman, on_delete=models.SET_NULL, help_text="Employee #", null=True, blank=False)
work_area = models.ForeignKey(WorkArea, on_delete=models.SET_NULL, null=True, blank=False, help_text="Work Area", related_name="work_area")
station_number = models.ForeignKey(Station, on_delete=models.SET_NULL, null=True, help_text="Station", related_name="stations", blank=True)
edited_timestamp = models.DateTimeField(null=True, blank=True)
time_exceptions = models.CharField(max_length=2, blank=True, default='', choices=EXCEPTION_STATUS)
time_in = models.DateTimeField(help_text="Time in", null=True, blank=True)
time_out = models.DateTimeField(blank=True, help_text="Time out", null=True)
forms.py
class WarehouseForm(AppsModelForm):
class Meta:
model = EmployeeWorkAreaLog
widgets = {
'employee_number': ForeignKeyRawIdWidget(EmployeeWorkAreaLog._meta.get_field('employee_number').remote_field, site, attrs={'id':'employee_number_field'}),
}
fields = ('employee_number', 'work_area', 'station_number', 'edited_timestamp')
urls.py
urlpatterns = [
url(r'enter-exit-area/$', views.enter_exit_area, name='enter_exit_area'),
url(r'update-timestamp-modal/(?P<main_pk>\d+)/$', UpdateTimestampModal.as_view(), name='update_timestamp_modal'),
]
(Took out leave_area code for redundancy)
views.py
def enter_exit_area(request):
form = WarehouseForm()
enter_without_exit = None
exit_without_enter = None
if request.method == 'POST':
temp = request.POST.copy()
form = WarehouseForm(temp)
if form.is_valid():
emp_num = form.cleaned_data['employee_number']
area = form.cleaned_data['work_area']
station = form.cleaned_data['station_number']
if 'enter_area' in request.POST:
new_entry = form.save()
EmployeeWorkAreaLog.objects.filter((Q(employee_number=emp_num) & Q(work_area=area) & Q(time_out__isnull=True) & Q(time_in__isnull=True)) & (Q(station_number=station) | Q(station_number__isnull=True))).update(time_in=datetime.now())
# If employee has an entry without an exit and attempts to enter a new area, mark as an exception 'N', meaning they're getting the modal
enters_without_exits = EmployeeWorkAreaLog.objects.filter(Q(employee_number=emp_num) & Q(time_out__isnull=True) & Q(time_exceptions="")).exclude(pk=new_entry.pk).order_by("-time_in")
if len(enters_without_exits) > 0:
enter_without_exit = enters_without_exits[0]
enters_without_exits.update(time_exceptions='N')
message = 'You have entered %(area)s' % {'area': area}
if station is not None:
message += ': %(station)s' % {'station': station}
messages.success(request, message)
form = WarehouseForm()
return render(request, "operations/enter_exit_area.html", {
'form': form,
'enter_without_exit': enter_without_exit,
})
class UpdateTimestampModal(CreateUpdateModalView):
main_model = EmployeeWorkAreaLog
model_name = "EmployeeWorkAreaLog"
form_class = WarehouseForm
template = 'operations/modals/update_timestamp_modal.html'
modal_title = 'Update Missing Time'
enter_exit_area.html
{% extends "base.html" %}
{% load core_tags staticfiles %}
{% block head %}
<script src="{% static "js/operations/warehouse_enter_exit.js" %}"></script>
{% endblock head %}
{% block main %}
{% if enter_without_exit %}
<div id="auto-open-ajax-modal" data-modal="#create-update-modal" data-modal-url="{% url "operations:update_timestamp_modal" enter_without_exit.id %}" class="hidden"></div>
{% endif %}
<form id="warehouseForm" action="" method="POST" novalidate >
{% csrf_token %}
<div>
<div>
<div style="color: red">{{ form.employee_number.errors.as_text }}</div>
<label>Employee</label>
{{ form.employee_number }}
</div>
<div>
<div style="color: red">{{ form.work_area.errors.as_text }}</div>
<label>Work Area</label>
{{ form.work_area }}
</div>
<div style="color: red">{{ form.station_number.errors.as_text }}</div>
<div>
<label>Station</label>
{{ form.station_number }}
</div>
</div>
<div>
<div>
<button type="submit" name="enter_area" value="Enter">Enter Area</button>
<button type="submit" name="leave_area" value="Leave">Leave Area</button>
</div>
</div>
</form>
{% modal id="create-update-modal" title="Update Timestamp" primary_btn="Submit" default_submit=True %}
update_timestamp_modal.html
{% load core_tags %}
<form id="create-update-form" method="post" action="{% url "operations:update_timestamp_modal" main_object.id %}">
{% csrf_token %}
<label>Update</label>
<div class="row">
<div class="form-group col-xs-6">
{% standard_input form.edited_timestamp datetimepicker=True hide_label=True %}
</div>
</div>
</form>
warehouse_enter_exit.js
$(function () {
// Submit the edited timestamp form when they click the "Submit" button in the modal
$(document).on('click', '#update-timestamp-modal-btn-primary', function (e) {
e.preventDefault();
forms.ajaxSubmit($('#create-update-form'), function (data) {
if (data.success && data.redirect) {
window.location.href = data.redirect;
} else {
if (data.warning) {
messages.warning(data.warning);
} else {
messages.error("An error occurred when saving this timestamp, please try again.");
}
}
});
});
});
Could I maybe edit the JS to only update the edited_timestamp field? Or maybe a way I can edit the views so that only that ID is accessed to update that field? The URL to the modal access it based on ID so I thought there could be a way to edit only based on this field.
You'll need to override those fields in your form code.
Like this:
class WarehouseForm(AppsModelForm):
employee_number = forms.ModelChoiceField(queryset=EmployeeWorkAreaLog.objects.all(), required=False)
work_area = forms.CharField(required=False)
class Meta...

How do I populate dynamically a StringField with the value of a SelectField in Wtforms

in a wtforms, I would like my SelectField to fill up with its selected value a StringField.
I use flask, bootstrap, and python 3.7
My HTML code is as follow:
{% block body %}
<h3>Edit Bloomberg ticker details</h3>
{% from "includes/forms/_form_helpers.html" import render_field %}
<div class="form-group" id="company_id" onchange="myFunction(event)">
{{render_field(form.company_names, class_="form-control")}}
</div>
<div class="form-group" id="isin_id">
{{render_field(form.isin_id, class_="form-control")}}
</div>
<script>
function myFunction(e) {
document.getElementById("isin_id").value = e.target.value
}
</script>
{% endblock %}
And the pyhon behind is as follow:
class DefTickerForm(_Form):
choices_companies = [(1,'Facebook'), (2, 'Google') ]
company_names = _SelectField(label='Company names:', choices=choices_companies, coerce=int)
isin_id = _StringField(label='isin_id', validators=[_validators.DataRequired], default=-1)
I would like that when the user select 'Facebook', the isin SelectField to be equal to 1. But so far it does nothing.
Note that if if code:
alert(e.target.value)
I get the wanted value. so issue is to set the TextField value.
my render field code is as followed (from a youtube tutorial):
{% macro render_field(field) %}
{{ field.label }}
{{ field(**kwargs)|safe }}
{% if field.errors %}
{% for error in field.errors %}
<span class="help-inline"> {{ error }}</span>
{% endfor %}
{% endif %}
{% endmacro %}
Any help would be much apreciated as google isn't so good on these.
Best
apparently TextField only accepts strings (I guess obvious if you are used to javascript)
so code working is as follow in case someone get he same problem:
<div class="form-group" onchange="myFunction(event)">
{{render_field(form.company_names, class_="form-control")}}
</div>
<div class="form-group">
{{render_field(form.isin_id, class_="form-control")}}
</div>
<script>
function myFunction(e) {
var x = e.target.value;
alert(x);
document.getElementById("isin_id").value = x.toString();
}
</script>
As a note, Jinja or anyway my render, use the fields names as default IDs (wich i realised using Chrome inpector. Meaning I didn't have to add an id for each Div. Anyway that is the thoughts of a beginenr in Javascripts.

How to pass a modified sortable list from html to another html?

I am writing web application by Flask. I made a sortable list in HTML using jquery-sortable.js. I want to pass the new sequences of the list after making modification from one HTML to another HTML.
I have tried to use GET/POST methods. However, it didn't work. Nothing was passed to another HTML.
home.html
<script>
...
$('#sortedList').val(sorted_list);
...
</script>
<form action="http://localhost:5000/about" method="POST">
{{ form.hidden_tag() }}
<div class="form-group" style="display: none;">
{{ form.sortedList.label(class="form-control-label") }}
{{ form.sortedList(class="form-control form-control-lg") }}
</div>
<div class="form-group">
{{ form.submit(class="btn btn_outline-info") }}
</div>
</form>
forms.py
from flask_wtf import FlaskForm
from wtforms import StringField, SubmitField
from wtforms.validators import DataRequired
class SortedForm(FlaskForm):
sortedList = StringField('Sorted List', validators=[DataRequired()])
submit = SubmitField('Submit')
main.py
#app.route("/about", methods=['GET', 'POST'])
def about():
form = SortedForm()
if form.validate_on_submit():
flash(f'Sorted list passed {form.sortedList.data}~', 'success')
return render_template('about.html', title='About', form=form)
about.html
{% extends "layout.html" %}
{% block content %}
{{ form.sortedList.data }}
{% endblock content %}

Toggle language of labels in flask wtforms form

I have a series of wtforms written in a flask app. One of my pages in my app will be used by people speaking English and Spanish. I would like to find a clean way of toggling the form element labels between English and Spanish with a toggle button on the webpage.
I found a nice solution to toggle languages using the HTML lang attribute here (2nd answer down, by J Grover):
Using Javascript to change website language
This encloses each language in a <span> element and then simply hides the one we don't want to see.
My problem is that the labels for the fields on my forms are coming from wtforms objects. I am unsure how to include multiple languages in my current setup. See below for an example of where I am now:
Form
class RoofCheck(FlaskForm):
"""
Class holding the form inputs for a roof check
"""
roofstate = RadioField('Roof Status',
validators=[validators.DataRequired('Please select roof closed or open')],
choices=[('closed', 'Closed'), ('open', 'Open')])
comments = TextAreaField('Comments on roof status',
[validators.optional(), validators.length(max=390)],
render_kw={"placeholder": "Enter comments here",
"rows": 4,
"style": "min-width: 100%"})
submit = SubmitField('Submit')
HTML
<div id='roof_check' class='col-md-6 padding-0'>
<form id="roof_check_form" action={{ url_for('roof_check')}} method="post" name="roof">
<fieldset class='form_group'>
{{ form1.hidden_tag() }}
<legend class='text-center'>
Daily visual check of the roof
</legend>
{% for subfield in form1.roofstate %}
<div class='form-check'>
{{ subfield }}
{{ subfield.label(class_="form-check-label") }} <br/>
</div>
{% endfor %}
<div class='form-check'>
{{ form1.comments }}
</div>
<div class='form-check'>
{% with messages = get_flashed_messages(category_filter=["form1"]) %}
{% if messages %}
{% for message in messages %}
<div> {{ message }} </div>
{% endfor %}
{% endif %}
{% endwith %}
{% for message in form1.roofstate.errors %}
<div> {{ message }} </div>
{% endfor %}
<div style="padding-top: 5px;">
{{ form1.submit(class_="btn btn-primary") }}
</div>
</div>
</fieldset>
</form>
</div>
I am not sure where to begin adding multiple labels to the RoofCheck class form objects. Any advice on how to insert support for two languages here would be great. Thanks in advance.
So it turns out that I can just add html directly to the label and place the language attribute there, then use the javascript solution in the link above to toggle the language. For example:
roof_status_text = ("<span lang='en'>Roof Status</span>"
"<span lang='es'>Estado del Techo</span>")
open_text = ("<span lang='en'>Open</span>"
"<span lang='es'>Abierto</span>")
closed_text = ("<span lang='en'>Closed</span>"
"<span lang='es'>Cerrado</span>")
roofstate = RadioField(roof_status_text,
validators=[validators.DataRequired('Please select roof closed or open')],
choices=[('closed', closed_text),
('open', open_text)])

Categories

Resources