I have a custom Flask WTForm where I want to have a portion of that form that includes a list of button type inputs that are created based on the number of entries in a table but have been having difficulties having them show up the way I want and passing the form validation. My goal for the look of this field is to have it show up as an Inline Button Group with a Checkbox type input. Below is an example of my route method.
#bp.route('/new_channel', methods=['GET', 'POST'])
def new_channel():
# Pre-populate the NewChannelForm
newChannelForm = NewChannelForm()
newChannelForm.required_test_equipment.choices = [(equip.id, equip.name) for equip in TestEquipmentType.query.order_by('name')]
test_equipment_types = TestEquipmentType.query.all()
return render_template('new_channel.html', title='Add New Channel', form=newChannelForm,
test_equipment_types=test_equipment_types)
I have tried using a FieldList with a FormField containing a custom form with a BooleanField and managed to get the styling right but the form validation didn't work. From looking into it further, BooleanField isn't compatible with a FieldList.
My next step is to use Flask WTForm example of a MultiSelectField with a custom widget for the Field and a custom widget for the option. The default is shown below:
class MultiCheckboxField(SelectMultipleField):
"""
A multiple-select, except displays a list of checkboxes.
Iterating the field will produce subfields, allowing custom rendering of
the enclosed checkbox fields.
"""
widget = widgets.ListWidget(prefix_label=False)
option_widget = widgets.CheckboxInput()
My goal is to modify this to make a custom widget called InLineButtonGroupWidget which will use the styling for a list of in-line buttons like my picture included before. Additionally, I am looking to create a custom option_widget called CheckboxButtonInput to get the styling of each individual button where I can pass info to the field. This is what I have as the goal for the both:
InLineButtonGroupWidget:
<div class="btn-group-toggle" role="group" data-toggle="buttons"></div>
CheckboxButtonInput:
<label class="btn btn-outline-info" for="check-1">Calibrator
<input type="checkbox" id="check-1">
</label>
The documentation for how to create custom widgets is a bit over my head and doesn't explain it the best so I'm looking for some
Edit:
Used Andrew Clark's suggestions and here is my final implementation:
routes.py
#bp.route('/new_channel', methods=['GET', 'POST'])
def new_channel():
class NewChannelForm(FlaskForm):
pass
test_equipment_types = TestEquipmentType.query.all()
for test_equipment_type in test_equipment_types:
# Create field(s) for each query result
setattr(NewChannelForm, f'checkbox_{test_equipment_type.name}', BooleanField(label=test_equipment_type.name, id=f'checkbox-{test_equipment_type.id}'))
newChannelForm = NewChannelForm()
if newChannelForm.validate_on_submit():
print('Form has been validated')
for test_equipment_type in test_equipment_types:
if newChannelForm.data[f'checkbox_{test_equipment_type.name}']:
channel.add_test_equipment_type(test_equipment_type)
return redirect(url_for('main.index'))
print(newChannelForm.errors.items())
return render_template('new_channel.html', title='Add New Channel', form=newChannelForm, units_dict=ENG_UNITS,
test_equipment_types=test_equipment_types)
new_channel.html
<!-- Test Equipment Selection -->
<div class="row">
<legend>Test Equipment Selection:</legend>
<div class="col-md-12">
<div class="btn-group-toggle mb-3" role="group" data-toggle="buttons">
{% for test_equipment_type in test_equipment_types %}
<label class="btn btn-outline-info" for="checkbox-{{ test_equipment_type.id }}">
{{ test_equipment_type.name }}
{{ form['checkbox_{}'.format(test_equipment_type.name)] }}
</label>
{% endfor %}
</div>
</div>
</div>
I usually tackle form building doing something like this:
def buildNewChannelForm():
class NewChannelForm(FlaskForm):
# put any non dynamic fields here
pass
test_equipment_types = TestEquipmentType.query.all()
for test_equipment_object in test_equipment_types:
# create field(s) for each query result
setattr(NewChannelForm, f'field_name_{test_equipment_object.id}', SelectField(label='label name', choices=[(equip.id, equip.name) for equip in TestEquipmentType.query.order_by('name')]))
return NewChannelForm()
Edit 1:
I'm not sure if there are better ways to do it, but I usually do something like this to handle data submission
def buildNewChannelForm():
new_channel_form_variable_list = []
class NewChannelForm(FlaskForm):
# put any non dynamic fields here
pass
test_equipment_types = TestEquipmentType.query.all()
for test_equipment_object in test_equipment_types:
# create field(s) for each query result
setattr(NewChannelForm, f'field_name_{test_equipment_object.id}', SelectField(label='label name', choices=[(equip.id, equip.name) for equip in TestEquipmentType.query.order_by('name')]))
# append variable name
new_channel_form_variable_list.append(f'field_name_{test_equipment_object.id}')
return NewChannelForm(), new_channel_form_variable_list
Then you can render your form using your variable list, just include in your render_template statement
{% for variable_name in new_channel_form_variable_list %}
{{ form[variable_name] }}
{% endfor %}
Then on submission of form in route, it's just a dictionary. So you can do something like this
result_dictionary = form.data
# either loop through your variable list or handle each one individually
for variable_name in new_channel_form_variable_list:
print(f'variable name: {variable_name}, value: {result_dictionary[variable_name]}')
Related
I have a django template in which I'm dynamically rendering multiple fields (using ajax)
Below is a Django form (which has been rendered in a template) whose fields have same names. I want to use the cleaned_data method to clean form data in views.py before storing them in the database.
index.html
<div class="form-container">
<!-- ASSUMING I HAVE ALREADY ADDED FIELDS DYNAMICALLY -->
<form id = "orderForm" action="newPickupOrder/" method="post" name="processForm">
<input type='text' name='this_field'>
<input type='text' name='this_field'>
<button type="submit">Submit</button>
</form>
</div>
<form id="addItemForm">
{% csrf_token %}
<!-- BUTTON TO ADD MORE FIELDS DYNAMICALLY -->
<button id = "addItemButton">Add item</button>
</form>
<script>
var addItemButton = document.querySelector('#addItemButton');
addItemButton.onclick = function(){
$.ajax({
type: 'POST',
url: 'addItem/',
data: addItemForm.serialize(),
success: function (response) {
$("#orderForm").append(response);
console.log('Success');
},
error: function (response) {
console.log('Error = '+response);
}
});
};
</script>
forms.py
class ItemForm(forms.Form):
this_field = forms.CharField()
urls.py
urlpatterns = [
url(r'^newPickupOrder/$', views.pickup_order_view, name='new_pickup_order'),
]
views.py
def add_item(request):
if request.method == 'POST':
itemForm = ItemForm()
return HttpResponse(itemForm.as_p())
def pickup_order_view(request):
if request.method == 'POST':
form = ItemForm(request.POST)
same_name_fields = request.POST.getlist('this_field')
# WANT TO CLEAN DATA IN same_name_fields
if form.is_valid():
print(form.cleaned_data)
# ONLY PRINTS THE LAST FIELD's DATA
return HttpResponseRedirect('/viewPickupRequests')
The problem I'm facing is that if I use form.cleaned_data['this_field'], only the last field's data is fetched i.e. in this example, the field with value anotherTestValue is fetched and cleaned. If I fetch the data using request.POST.getlist('this_field'), all the fields' data is fetched and stored as a list, but, I don't know how to clean it using cleaned_data method. Is there a way to apply the cleaned_data method to the list of field data?
I'm sorry, I can't test if this works so this is not really an answer - but the comment system is not suitable for larger code chunks so I'm posting here.
Django forms lack a field type that renders to multiple text inputs with the same name. The proper thing to do would be to write a new form field class and a new widget. Since you are not rendering the form in the template (you are using it only for validation) I will omit the widget part.
class AcceptAnythingMultipleChoiceField(forms.MultipleChoiceField):
def validate(self, value):
if self.required and not value:
raise ValidationError(
self.error_messages['required'],
code='required'
)
Then use this field class instead of forms.CharField() (you may need to pass an empty choices parameter).
[update]
So essentially what you're saying is that I need to create new form field class and then render it to the template each time the user wants to add a new field? What if user has to add 15 fields, I'll need to create 15 classes then! I think this method won't be suitable in scenarios where number of fields required to be generated is large. I feel there should be some elegant way to do this which i'm not aware of – The OP
No, it is not what I'm saying. You probably want to subclass something like MultipleHiddenInput and set AcceptAnythingMultipleChoiceField.widget to it. You will have to create a new template based on the template for MultipleHiddenInput and replace input type="hidden" for type="text" (the original template is django/forms/widgets/multiple_hidden.html).
class AcceptAnythingWidget(MultipleHiddenInput):
template_name = 'django/forms/widgets/multiple_visible.html'
class AcceptAnythingMultipleChoiceField(forms.MultipleChoiceField):
widget = AcceptAnythingWidget
def validate(self, value):
if self.required and not value:
raise ValidationError(
self.error_messages['required'],
code='required'
)
This should render as many <input name='this_field'> as needed for instantiated forms at the frontend if you use:
{{ form.this_field }}
in the template, but will not add/remove them dynamically.
In order to do that you must plug in the JavaScript required to add/remove inputs dynamically in the widget but I will left this as an exercise for you. Look at Form Assets (the Media class) in the docs in order to figure out how to do that.
I think that what you are looking for is formsets. https://docs.djangoproject.com/en/2.0/topics/forms/formsets/
from django.forms import formset_factory
ItemFormSet = formset_factory(ItemForm, extra=2)
You can the essentialy use ItemFormSet in the way you would use a normal form except that this objects is iterable.
You will also have to change your jquery if you want to dynamically add items. There are many examples online on how to do this. In short what you do is
clone one of the forms in the formset
clear all the values from the copied form
update the input's (prefixes of) id's
Using Formsets doesn't solve the problem of fetching and validating
fields with same name. The issue still remains
It does however generate the end result you wanted (see below). My question would be why you need to have inputs with the same name? If there is some jquery stuff that uses these names I dont see any reason why you wouldn't be able to use name like... or assign a class to the inputs instead.
def pickup_order_view(request):
if request.method == 'GET':
ItemFormSet = formset_factory(ItemForm, extra=5)
item_formset = ItemFormSet()
template = "some_template.html"
template_context = {'item_formset': item_formset}
return render(request, template, template_context)
if request.method == 'POST':
ItemFormSet = formset_factory(ItemForm)
item_formset = ItemFormSet(request.POST)
same_name_fields=[]
if item_formset.is_valid():
for item_form in item_formset:
same_name_fields.append(item_form.cleaned_data['this_field'])
print(same_name_fields)
Template
<form id = "orderForm" action="newPickupOrder/" method="post" name="processForm">
{% csrf_token %}
{{ item_formset.management_form }}
{{ for item_form in item_formset }}
{{ item_form.as_p }}
{{ endfor }}
<input type='submit' value='submit'>
</form>
Go to newPickupOrder/ , fill in the 5 fields, hit submit, and watch it print your list.
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)
I am developing a question-and-answer web application with Flask and Flask-WTForms. Questions can have 1 to 5 tags, and I made a tag editor for users to input their tags:
I've written JavaScript code to serialize the tags into an array. So, in the image above that array looks like this: ["tag1","tag2"], and when tag3 gets added, it looks like this: ["tag1","tag2","tag3"].
I need a way to get that list of tag names to my Flask view function, but I can't figure out how to do that. Any help would be greatly appreciated.
The form:
class AskQuestionForm(Form):
title = StringField('Title', validators=[Required(), Length(min=15, max=200)])
content = TextAreaField('Content', validators=[Required(), Length(min=50, max=30000)])
submit = SubmitField('Submit')
The view function:
#main.route('/ask/', methods=['GET', 'POST'])
def ask_question():
form = AskQuestionForm()
if form.validate_on_submit():
# Code to add a new question
else:
return render_template('ask.html', form=form)
The template (simplified):
<form id="ask-question-form" method="post" action="{{ url_for('main.ask_question') }}">
{{ form.csrf_token }}
<label for="title">Title</label>
<input type="text" name="title" id="title">
<label for="content">Content</label>
<textarea name="content" id="content"></textarea>
<label for="tags">Tags</label>
<input type="text" name="tags" id="add-tag-input">
<span>
<button id="add-tag-button" type="button">Add</button>
</span>
<button type="submit">Submit question</button>
</form>
How can I get the list of tags to my Flask view function? Any help would be appreciated.
Use the TagListField which is used an example of creating custom fields in the wtforms documentation. It will take care of what you are trying to do here. I have slightly modified it because in the original example they are using a comma separated string of tags:
class TagListField(Field):
widget = TextInput()
def _value(self):
if self.data:
return u', '.join(self.data)
else:
return u''
def process_formdata(self, valuelist):
if valuelist:
self.data = [x.strip() for x in valuelist[0].split(' ')]
else:
self.data = []
Burhan's answer was part of my solution, but here is what I had to do:
I added a hidden field to my form and left the tag input without a name so it wouldn't get submitted.
Using JavaScript, I intercepted the form submit and put a comma-separated list of tag names into the hidden field.
I created a TagListField with a process_formdata(self, valuelist) function similar to what was shown above.
In my Flask view function, I retrieved the value of the hidden field and proceeded to use it in making the new question.
If any future readers have any questions about what I did specifically, post a comment.
I'm trying to create a simple WTForms-based admin interface for an SQLAlchemy app, using Jinja2 templates.
I've read the docs of WTForms-Alchemy and I understand that it can auto-generate a form from my model just via a few lines of code, like:
class UserForm(ModelForm):
class Meta:
model = User
My problem is that even though I have this form auto-generated, I found no resource anywhere about how can I make it into a functional HTML page. There are a few snippets about rendering errors for fields, as well as some SO answers mentioning macros for rendering whole fields, but I found absolutely no resource about how to generate a full, functional form automatically.
// I understand that this is something what Flask-Admin might do already, I'm not using Flask so this is not a possibility unfortunately.
WTForms leaves it up to you to figure out how to you want to render out your form after you pass it into your template. The simplest way to render a form would be to just iterate through your form and render the fields. When a field (or its label) is called, it emits HTML.
<form action="/some_url" method="POST">
{% for field in form %}
{{ field.label() }}
{{ field() }}
{% endfor %}
<button type="submit" />
</form>
The macros provided here provide an automated way to generate HTML surrounding these fields.
You can use wtf.quick_form like this, in which case you'll have a totally generic form template. Mark up your db.Model members with info{} properties to set field display names etc
<form method="post" action="/{{route}}">
<fieldset>
{{ wtf.quick_form(form, button_map={'submit':'success'}) }}
<input class="btn btn-success" type="submit" value="Submit" />
<button type="button" class="btn">Cancel</button>
</fieldset>
</form>
Your form definition:
class MyobjectForm(BaseModelForm):
class Meta:
model = Myobject
Then your route handler looks like this:
#app.route('/myobject', methods=('GET', 'POST'))
def myobject_route():
obj = Myobject()
form = MyobjectForm(obj = obj)
if form.validate_on_submit():
form.populate_obj(obj)
db.session.add(obj)
db.session.commit()
return redirect(url_for('index'))
return render_template('form.j2', form=form, title='My Object', route='myobject')
I am trying to pass some dynamic values from my form, but so far I am only getting empty values.
I have an autocomplete plugin, where I search for "names" from a JSON object, each time I select one they will pass into a list with value="the ID".
I couldn't find a unordered list in WTForms, so I am using SelectMultipleField instead, since it is able to pass many values as an array/list
my form class is looking like this:
class ClassForm(Form):
function_name = StringField('names')
studentid = SelectMultipleField('studentid')
submit = SubmitField('submit')
then in my template I am using it like this
<form id="function_search_form" method="post" action="">
{{ form.csrf_token }}
{{form.function_name.label()}}
{{form.function_name()}}
<!-- then I am not using studentid directly,
but just normal html, so each time you pass
in a name from the json object it will come in like this.
-->
<ol class='student-list'>
<li value="1" name="studentid" id="studentid">test</li>
</ol>
{{ form.submit()}}
</form>
My problem is that it wont get the value from the list, even if I hard code the values directly, instead from the jquery script.
Here is how my view looks like
#app.route('/index', methods=['GET', 'POST'])
def index():
form = ClassForm()
if request.method == 'POST' and form.validate_on_submit():
flash('valid form')
st = form.studentid.data
print(st)#debug
return render_template('index.html', form=form)
Everytime I submit I am printing form.studentid.data I am getting [], which is an empty list.
Even when I try to pass single values and make studentid to a StringField I am still getting an empty value.
I have also tried the request.form['studentid'] but then I am getting Bad Request What am I doing wrong and is there another way to pass "custom" values ?
The reason its not working is because <li> is not a form control, so its data is not sent back with the form request.
You are never rendering the studentid field from your form class, so the form control is never rendered. It would be like expecting the following to work:
<form>
<p name="foo" data="hello">This should be sent</p>
<input type="submit">
</form>
To get the data back to your view method, you need to use a form control - you can test it out like this:
<form id="function_search_form" method="post" action="">
{{ form.csrf_token }}
{{form.function_name.label()}}
{{form.function_name()}}
<select name="studentid" class='student-list'>
<option value="1">test</option>
</select>
{{ form.submit()}}
</form>
Or, simply render the field correctly:
<form id="function_search_form" method="post" action="">
{{ form.csrf_token }}
{{form.function_name.label()}}
{{form.function_name()}}
{{form.studentid.label()}}
{{form.studentid}}
{{ form.submit()}}
</form>