Using Select2 with flask-wtforms - javascript

After select2 manipulates the dropdown field the regular use of form.owner_id.data yields None. How can I extract the selected option from a select2 field.
If I disable the select2 javascript, wtforms will work just fine and I will be able to use form.owner_id.data. (but it looks ugly)
screenshot of rendered form
forms.py
class ProjectForm(FlaskForm):
owner_id = SelectField('Owner:', [validators.Required()], choices=[], render_kw={"placeholder": "Owner company *"})
(rest of form has been truncated for simplicity)
views.py
#app.route('/new_project/<int:company_id>', methods=('GET', 'POST'))
def new_project(company_id):
membership = Membership.query.filter_by(user_id=current_user.id, company_id=company_id).first()
company = Company.query.filter_by(id=company_id).first_or_404()
form = ProjectForm()
form.owner_id.choices = [(str(comp.id), repr(comp)) for comp in Company.query.all()]
form.client_id.choices = [(str(comp.id), repr(comp)) for comp in Company.query.all()]
form.contractor_id.choices = [(str(comp.id), repr(comp)) for comp in Company.query.all()]
form.membership_id.choices = [(str(comp.id), repr(comp)) for comp in Company.query.all()]
if request.method == 'POST':
flash(str(form.owner_id.data), 'success')
if form.validate_on_submit():
project = Project()
connection = Assignment()
# PROJECT DETAILS
project.title = form.title.data
project.description = form.description.data
project.owner_id = int(form.owner_id.data)
_macro
{% macro render_select_field(field, placeholder='Select...', label='Select an option below') %}
<div class="form-group">
<label>{{ label }}</label>
<select data-placeholder="{{ placeholder }}" class="select-size-xs">
<option></option>
{% for choice in field.choices %}
<option value="{{ choice[0] }}">{{ choice[1] }}</option>
{% endfor %}
</select>
{% if field.errors %}
{% for error in field.errors %}
<span class="help-block text-danger"><i class="icon-cancel-circle2 position-left"></i>{{ error }}</span>
{% endfor %}
{% endif %}
</div>
{% endmacro %}
html
<form method="POST" action="{{ url_for('new_project', company_id=company.id) }}" enctype="multipart/form-data" role="form">
<div class="panel panel-body login-form">
<div class="text-center">
<div class="icon-object text-muted"><i class="icon-plus2"></i></div>
</div>
{{ form.hidden_tag() }}
<div class="text-center form-group"><span>Project details</span></div>
{{ new_render_field(form.title, icon="icon-quill2", class_='form-control') }}
{{ new_render_field(form.description, icon="icon-stack-text", class_='form-control', rows=10) }}
{{ render_select_field(form.owner_id, placeholder="Who's doing the work?", label="Select the owner company") }}
{{ render_select_field(form.client_id, placeholder="Where do the bills go?", label="Select the client company") }}
{{ new_render_field(form.default_client_rate, icon="icon-price-tag", class_='form-control') }}
{{ new_render_field(form.default_contractor_rate, icon="icon-price-tag", class_='form-control') }}
<!-- THE REST OF FORM HAS BEEN TRUNCATED FOR SIMPLICITY -->
<div class="form-group">
<button type="submit" class="btn bg-pink-400 btn-block">Create</button>
</div>
</div>
</form>

So after analyzing the select field in the inspect window, it became apparent that the select field is missing the name="{{ field.name }}" that wtforms requires in order to validate the form. The simple change made in my _macro was from this:
<select data-placeholder="{{ placeholder }}" class="select-size-xs">
To this:
<select data-placeholder="{{ placeholder }}" class="select-size-xs" name="{{ field.name }}">
With this addition wtforms can now validate, and find the selected option returning the proper id for form.owner_id.data.

Related

Converting inline javascript to Alpine.js

I'm trying to avoid inline javascript and would like to convert it to Alpine.js code. Is there a way to rewrite the following piece of code in Alpine.js?
<script type="text/javascript">
window.addEventListener('DOMContentLoaded', function () {
const message = "Do you really want to remove the selected e-mail address?";
const actions = document.getElementsByName('action_remove');
if (actions.length) {
actions[0].addEventListener("click", function (e) {
if (!confirm(message)) {
e.preventDefault();
}
});
}
});
document.addEventListener('DOMContentLoaded', function () {
$('.form-group').removeClass('row');
})
</script>
Here is the full context (I'm working with Django templates):
{% extends "account/base.html" %}
{% load tailwind_filters %}
{% load crispy_forms_tags %}
{% block head_title %}
Account
{% endblock %}
{% block inner %}
<h1>E-mail Addresses</h1>
{% if user.emailaddress_set.all %}
<p>The following e-mail addresses are associated with your account:</p>
<form action="{% url 'account_email' %}" class="email_list" method="post">
{% csrf_token %}
<fieldset class="blockLabels">
{% for emailaddress in user.emailaddress_set.all %}
<div class="radio">
<label for="email_radio_{{forloop.counter}}" class="{% if emailaddress.primary %}primary_email{%endif%}">
<input id="email_radio_{{forloop.counter}}" type="radio" name="email" {% if emailaddress.primary or user.emailaddress_set.count == 1 %}checked="checked" {%endif %} value="{{emailaddress.email}}" />
{{ emailaddress.email }}
{% if emailaddress.verified %}
<span class="verified">Verified</span>
{% else %}
<span class="unverified">Unverified</span>
{% endif %}
{% if emailaddress.primary %}<span class="primary">Primary</span>
{% endif %}
</label>
</div>
{% endfor %}
<div class="form-group">
<button class="secondaryAction btn btn-primary" type="submit" name="action_primary">Make Primary</button>
<button class="secondaryAction btn btn-primary" type="submit" name="action_send">Re-send Verification</button>
<button class="primaryAction btn btn-primary" type="submit" name="action_remove">Remove</button>
</div>
</fieldset>
</form>
{% else %}
<p><strong>Sad news:</strong>You currently do not have any e-mail address set up. You should add an e-mail address so you can receive notifications, reset your password, etc.</p>
{% endif %}
<h2>Add E-mail Address</h2>
<form method="post" action="{% url 'account_email' %}" class="add_email">
{% csrf_token %}
{{ form|crispy }}
<button class="btn btn-primary" name="action_add" type="submit">
Add E-mail
</button>
</form>
{% endblock %}
{% block inline_javascript %}
{{ block.super }}
<script type="text/javascript">
window.addEventListener('DOMContentLoaded', function () {
const message = "Do you really want to remove the selected e-mail address?";
const actions = document.getElementsByName('action_remove');
if (actions.length) {
actions[0].addEventListener("click", function (e) {
if (!confirm(message)) {
e.preventDefault();
}
});
}
});
document.addEventListener('DOMContentLoaded', function () {
$('.form-group').removeClass('row');
})
</script>
{% endblock %}
You can try something like this:
Initialize the parent form element with x-data and set the state variable confirmMsg to null.
On form submit you prevent the actual submit with #submit.prevent and check whether a confirm message (confirmMsg) was set. If yes, you prompt the user to confirm the set message. If the users confirms, you reset the confirmMsg to null and submit the form with $el.submit().
On the buttons, you can just set the respective confirmMsg with #click = "confirmMsg = 'Are you sure?'".
Here is a code example:
<script src="//unpkg.com/alpinejs" defer></script>
<form
x-data="{confirmMsg: null}"
#submit.prevent="
if (confirmMsg && !confirm(confirmMsg)) return;
confirmMsg = null;
alert('Submitting form...'); $el.submit()"
>
<button
#click="confirmMsg = 'Do you really want to remove the selected e-mail address?'"
type="submit"
name="action_remove"
>
Remove
</button>
</form>

Appending string to dictionary values using jinja template

I have the following code for accessing the values in dictionary using jinja template
names={'ant':['abc',2456789,28-02-1991]}
{% for key,value in names.items %}
{% for val in value %}
<p class="lead">{{ val }} </p>
{% endfor %}
{% endfor %}
For every value,it is printed I want to append a string.My values are "abc","2456789","28-02-1991".I want the following output:
Name:abc
Id:2456789
Date: 28-02-1991
How could I do it using jinja template?
You can use it like this with your current data structure.
{% for key, value in names.items %}
<p class="lead">Name: {{ value.0 }}</p>
<p class="lead">Id: {{ value.1 }}</p>
<p class="lead">Date: {{ value.2 }}</p>
{% endfor %}
But it would be more readable if change your data structure into this:
my_dict = {
'ant': {
'id': 2,
'name': 'abc',
'date' : datetime.now()
}
}
then your template would look like this:
{% for key, person in my_dict.items %}
<p class="lead">Id: {{ person.id }} </p>
<p class="lead">Name: {{ person.name }} </p>
<p class="lead">Date: {{ person.date }} </p>
{% endfor %}
In Jinja-2 you can subscript in the variables:
{% for key,vals in names.items %}
<p class="lead">Name: {{ vals[0] }}</br>
Id: {{ vals[1] }}</br>
Date: {{ vals[2] }}</p>
{% endfor %}

How to handle JavaScript event inside a inlineformset_factory with formset_media_js

I have an inlineformset_factory implemented with formset_media_js, these two by itself are working ok. What I need to implement is to be able to handle the enable and disable state of some checkboxes and input fields that are inside the inlineformset_factory.
I have a javascript that works on the first group of formset created on page load, but when a new formset is added by the user the javascript is not working.
How can I handle the new formsets input fields added by the user with javascript?
If "is chapter" is checked then "is subchapter" and "quantity" are disabled, by default the inlineformset_fatory creates 1 formset on page load, on this formset the javascript works. But when the user adds another formset with button "Add another Budget Item" the javascript is no longer working. If for example, I configure the inlineformser_factory to create 3 formset on page load the javascript works on those 3 formset but not on the formsets added by the user.
forms.py : at this forms.py i have the inlineformset_factory that is created every time the user adds a formset.
from django import forms
from django.forms import inlineformset_factory
from djangoformsetjs.utils import formset_media_js
from accounts.models import ProjectManager
from projects.models import Project, BudgetModel, BudgetModelItems
class CreateBudgetItem(forms.ModelForm):
class Media(object):
js = formset_media_js
class Meta:
model = BudgetModelItems
fields = ('budget_model',)
widgets = {
'budget_item_description': forms.Textarea(attrs={'rows': 2, 'cols': 50}),
'budget_item_item': forms.NumberInput(attrs={'size': 6}),
'budget_item_quantity': forms.NumberInput(attrs={'size': 6}),
}
BudgetItemFormset = inlineformset_factory(BudgetModel, BudgetModelItems,
form=CreateBudgetItem,
fields=('budget_model', 'budget_item_item',
'budget_item_description', 'budget_item_unit',
'budget_item_quantity', 'budget_item_is_chapter',
'budget_item_is_subchapter'),
extra=1,
can_delete=True,
can_order=True
)
views.py
from django.shortcuts import render, redirect
from django.forms import formset_factory
from accounts.models import ProjectManager
from projects.forms.create_project import CreateProjectForm
from projects.forms.create_budgetmodel import BudgetFormset, ProjectForBudgetModel
from projects.forms.create_budgetitem import CreateBudgetItem, BudgetItemFormset
from projects.models import BudgetModel, Project
def create_budget_item(request):
user = request.user.projectmanager
projects = Project.objects.filter(project_manager_id=user)
models = BudgetModel.objects.none()
project_form = ProjectForBudgetModel(user)
budget_item_form = CreateBudgetItem()
formset = BudgetItemFormset()
for project in projects:
models |= BudgetModel.objects.filter(project_id=project.pk)
budget_item_form.fields['budget_model'].queryset = models
if request.method == 'POST':
project_form = ProjectForBudgetModel(user, request.POST)
budget_item_form = CreateBudgetItem(request.POST)
if project_form.is_valid() and budget_item_form.is_valid():
# project_id = project_form.cleaned_data['project']
budget_model_id = budget_item_form.cleaned_data['budget_model']
formset = BudgetItemFormset(request.POST, instance=budget_model_id)
if formset.is_valid():
formset.save()
context = {'project_form': project_form,
'bi_form': budget_item_form,
'formset': formset,
'models': models}
return render(request, 'projects/create_budget_items.html', context)
budget_item_form.html: this form is called (included) at create_budget_items.html
<div data-formset-form>
<div class="card">
<div class="card-body">
<div class="row">
<div class="col">
<table class="table">
<thead class="thead-light">
<tr>
<th scope="col">Item</th>
<th scope="col">Description</th>
<th scope="col">Unit</th>
<th scope="col">Quantity</th>
<th scope="col">Is Chapter</th>
<th scope="col">Is SubChapter</th>
</tr>
</thead>
<tbody>
<tr>
<th>{{ form.budget_item_item }}</th>
<td>{{ form.budget_item_description }}</td>
<td>{{ form.budget_item_unit }}</td>
<td>{{ form.budget_item_quantity }}</td>
<td>{{ form.budget_item_is_chapter }}</td>
<td>{{ form.budget_item_is_subchapter }}</td>
</tr>
</tbody>
</table>
</div>
<div class="col-md-auto">
{% if form.ORDER %}
<div class="row mt-1">
<div class="d-none">{{ form.ORDER }}</div>
<button class="btn btn-info btn-block" type="button" data-formset-move-up-button>
{% trans 'Move up' %}
</button>
</div>
<div class="row mt-1">
<button class="btn btn-info btn-block" type="button" data-formset-move-down-button>
{% trans 'Move down' %}
</button>
</div>
{% endif %}
</div>
<div class="col col-lg-2 mt-1">
{% if form.DELETE %}
<div class="d-none">{{ form.DELETE }}</div>
<button type="button" class="btn btn-danger btn-block h-100" data-formset-delete-button>
{% trans 'Delete' %}
</button>
{% endif %}
</div>
</div>
</div>
</div>
</div>
create_budget_items.html: On this template I have the javascript where I control the enable and disable states of checkboxes and input fields. I thought that by calling the script inside the for loop where the formset is being iterated I was going to be able to control the input fields of the formsets added by the user. Is only working on the formsets created on page load.
{% block dashboard_head %}
{{ formset.media }}
<script type="text/javascript">
function trackDisabled(trigger_id, ...targets) {
const checkbox = document.getElementById(trigger_id);
checkbox.addEventListener('change', e => {
console.log(e.target.checked);
{#console.log(trigger_id);#}
{#console.log(...targets);#}
if (e.target.checked === true) {
targets.forEach(x => {
const element = document.getElementById(x);
element.disabled = true;
element.checked = false;
element.value = ''
})
} else {
targets.forEach(x => document.getElementById(x).disabled = false)
}
})
}
</script>
{% endblock dashboard_head %}
{% block dashboard_content %}
<h1>Create Budget Items</h1>
<form method="post">
{% csrf_token %}
{{ project_form.project }}
<select name="budget_model" id="id_budget_model" class="form-control">
{% with value=bi_form.budget_model.value %}
{% for model in models %}
<option value="{{ model.pk }}" class="{{ model.project.pk }}"
{% if model.pk|slugify == value|slugify %}selected="selected"{% endif %}>
{{ model.budget_name }}
</option>
{% endfor %}
{% endwith %}
</select>
{% load formset_tags %}
<div id="formset" data-formset-prefix="{{ formset.prefix }}">
{{ formset.management_form }}
<div data-formset-body>
{% for form in formset %}
{% include "projects/budget_item_form.html" with form=form only %}
<script>
trackDisabled(
'{{ form.budget_item_is_chapter.auto_id }}',
'{{ form.budget_item_is_subchapter.auto_id }}',
'{{ form.budget_item_quantity.auto_id }}'
);
console.log('{{ form.budget_item_is_chapter.auto_id }}');
</script>
{{ form.errors }}
{% endfor %}
</div>
<script type="form-template" data-formset-empty-form>
{% escapescript %}
{% include "projects/budget_item_form.html" with form=formset.empty_form only %}
{% endescapescript %}
</script>
<div class="row mt-3 mr-1 ml-1">
<!-- This button will add a new form when clicked -->
<div class="col text-center">
<input class="w-75 btn btn-info" type="button"
value="{% trans 'Add another Budget Item' %}" data-formset-add>
</div>
<div class="col text-center">
<button class="w-75 btn btn-success" type="submit">
{% trans 'Create Models' %}
</button>
</div>
</div>
</div>
</form>
{% endblock dashboard_content %}
This is the javascript that finally worked out, was written by a friend.. Thank you FunkyBob!
<script>
function isChapter() {
const root = document.getElementById('formset');
const prefix = root.dataset.formsetPrefix;
console.log({root, prefix});
// listen for all input changes
root.addEventListener('change', ev => {
// check if it matches out name pattern
console.log(ev.target.name);
console.log(ev.target.checked, !ev.target.checked);
console.log(`${prefix}-(\\d+)-budget_item_is_chapter`);
let m = ev.target.name.match(RegExp(`${prefix}-(\\d+)-budget_item_is_chapter`));
// if it's not {prefix}-*-budget_item_is_chapter ignore
if (!m) return;
console.log(m);
let idx = m[1]; // the matched regex group
// Find the related fields, and set them as enabled/disabled
root.querySelector(`[name="${prefix}-${idx}-budget_item_is_subchapter"]`).disabled = ev.target.checked;
root.querySelector(`[name="${prefix}-${idx}-budget_item_is_subchapter"]`).checked = false;
root.querySelector(`[name="${prefix}-${idx}-budget_item_unit"]`).disabled = ev.target.checked;
root.querySelector(`[name="${prefix}-${idx}-budget_item_unit"]`).value = ev.target.checked;
root.querySelector(`[name="${prefix}-${idx}-budget_item_quantity"]`).disabled = ev.target.checked;
root.querySelector(`[name="${prefix}-${idx}-budget_item_quantity"]`).value = ev.target.checked;
console.log("Done!")
});
}
isChapter();
</script>

value gets submitted through form, then even after the value got submitted and gets displayed, in a form the value got submitted is shown

Sorry for the long title, but this is what happens:
I submit word "hello", then hello gets displayed. Then in a form, it still says "hello". I'm not sure why this is happening. Is this django problem or some javascript code is needed for this...?Here's my full code I think is causing this problem.
in my views.py
def post(request, slug):
user = get_object_or_404(User,username__iexact=request.user)
try:
profile = MyProfile.objects.get(user_id=request.user.id)
# if it's a OneToOne field, you can do:
# profile = request.user.myprofile
except MyProfile.DoesNotExist:
profile = None
post = get_object_or_404(Post, slug=slug)
post.views += 1 # increment the number of views
post.save() # and save it
path = request.get_full_path()
comments = Comment.objects.filter(path=path)
#comments = post.comment_set.all()
comment_form = CommentForm(request.POST or None)
if comment_form.is_valid():
parent_id = request.POST.get('parent_id')
parent_comment = None
if parent_id is not None:
try:
parent_comment = Comment.objects.get(id=parent_id)
except:
parent_comment = None
comment_text = comment_form.cleaned_data['comment']
new_comment = Comment.objects.create_comment(
user=MyProfile.objects.get(user=request.user),
path=request.get_full_path(),
text=comment_text,
post = post,
parent = parent_comment)
for c in comments:
c.get_children()
context_dict = {
'post' :post,
'profile' :profile,
'comments' : comments,
'comment_form':comment_form
}
return render(request, 'main/post.html', context_dict)
and in my post.html I have
<table class='table'>
{% for comment in comments %}
<tr><td>{{ comment.get_comment }}
<br/><small>via {{ comment.user }} | {{ comment.timestamp|timesince }} ago </small>
{% if not comment.is_child %}
<ul>
{% for child in comment.get_children %}
<li>{{ child.get_comment }}
<small>via {{ child.user }}</small>
</li>
{% endfor %}
</ul>
<a href='#' class='reply_btn'>Reply</a>
<div class='reply_comment'>
<form method="POST" action=''>{% csrf_token %}
<input type='hidden' name='parent_id' value='{{ comment.id }}' />
{{ comment_form.as_p }}
<input type='submit' class='btn btn-default' value='Add reply'/>
</form>
</div>
{% endif %}
</td></tr>
{% endfor %}
</table>
</div>
<div class = "col-sm-3">
</div>
{% include 'footer.html' %}
<script>
{% block jquery %}
$('.reply_btn').click(function(e){
e.preventDefault();
$(this).next(".reply_comment").fadeToggle();
// $(".reply_comment").fadeToggle();
})
{% endblock %}
</script>
{% endblock %}
Can someone please direct me where I should look.....thank you
Before you check if a form is valid or not you assign the form to include the POST data so it will still include this when you return back to the form (in case it needs to show errors). The easiest fix would be to reassign the form after your valid logic is done.
comment_form = CommentForm(request.POST or None)
if comment_form.is_valid():
.. Valid logic ..
comment_form = CommentForm()

django javascript template conditional test

I am looking to do conditional tests in the html part of my code
html
<p class= "rep3" width="100" id="val1" data1={{ family }} style= "display:none;">{{ family }}</p>
<p class= "rep4" width="100" id="val2" data2="" style= "display:none;"></p>
{% if data2 == "general" %}
criteria1
{% else %}
{% if data2 == "ceramic" %}
criteria2
{% else %}
criteria
{% endif %}
{% endif %}
javascript
<script type="text/javascript">
$(document).ready(function() {
var element = document.getElementById('val1');
laval1 = element.getAttribute('data1');
var element2 = document.getElementById('val2');
element2.setAttribute('data2', laval1);
});
</script>
I read data1 in the script
I would like to send this data in data2 and after to do the test
but its not working
How to do that ?
This code won't work because:
JavaScript code is run by the client, long after the template tags ({% if data2 == "general" %} etc. ) are compiled in the server.
You can't send data from JS to django like that for data2. (Although you could use AJAX), but that is not required in this case.
Why don't you could do something like this, instead of relying on Javascript?
<p class= "rep3" width="100" id="val1" data1={{ family }} style= "display:none;">{{ family }}</p>
<p class= "rep4" width="100" id="val2" data2="" style= "display:none;"></p>
{% if family == "general" %}
criteria1
{% else %}
{% if family == "ceramic" %}
criteria2
{% else %}
criteria
{% endif %}
{% endif %}

Categories