web2py FORM - lookup value by string field instead of ID - python

I have a model like this in web2py:
db.define_table('courses',
Field('course_name','string'))
db.define_table('auth_user',
....
....
Field('course_name',db.courses,label=T('Course Name'),
required=True,
requires=IS_IN_DB(db,db.courses.id,'%(course_name)s'),
If I display a form based on the auth_user table, the auth_user.course_name field is represented by a dropdown menu containing all of the courses in courses table. As expected, it displays them using the contents of the courses.course_name field, and not the courses.ID field (because of the string format representation in the IS_IN_DB requirement.
However, I'm trying to modify it such that instead of using a dropdown select menu, it just displays a text field. I'm expecting the user to be able to enter the name of a course, and the form will work properly as long as that name is a valid course_name.
To do that, I added a widget=SQLFORM.widgets.string.widget property to the auth_user.course_name field. That correctly displays a text box instead of the dropdown, but doesn't allow the user to enter a course_name. It works fine if a valid courses.id is entered (and displays the expected error message if it's not a valid ID).
However, I can't figure out how to make it accept a course_name instead of the ID. I could theoretically use the autocomplete plugin (which does indeed work), but the purpose of this is to allow the user to only submit the form if they know the valid course_name (it's kind of like a password).
Is this possible?

I figured this out by using a custom validation class (replacing the IS_IN_DB validator). For reference, this is what my validator looks like:
class COURSE_NAME_VALIDATOR:
def __init__(self, error_message='Unknown course name. Please see your instructor.'):
self.e = error_message
def __call__(self, value):
if db(db.courses.course_name == value).select():
return (db(db.courses.course_name == value).select()[0].id, None)
return (value, self.e)
I got the template for the validation class from the web2py manual (http://www.web2py.com/book/default/chapter/07#Custom-validators)

Related

Django ModelChoiceField with empty_label as valid input

I'm using the following form to generate a dropdown list with my registered users:
class TaskAssignUserForm(forms.Form):
user = forms.ModelChoiceField(queryset=User.objects.all().order_by('username'))
Inside my template I render the above form together with a submit button. The application user can choose from the registered users to select one and assign him/her to a task. This is working, if a user and not the empty label (--------) was selected.
But, I want the empty label as a valid option too, to cancel the assignment between the task and the user. I'm working with the following views.py and looking for an option to check if the empty label or an empty choice was made.
if form_TaskAssignUserForm.is_valid():
task.user_assigned = form_TaskAssignUserForm.cleaned_data['user']
task.save()
else:
if # check if emtpy label is set
task.user_assigned = None
task.save()
I found out that checking if form_TaskAssignUserForm.cleaned_data['user'] exists could be an option but I feel not comfortable with that. There should be a way that works together with the .is_valid() check.
Is there a djangonian way solving that problem?
Bye, aronadaal
You should just need to set required=False on the field.
user = forms.ModelChoiceField(required=False, queryset=User.objects.all().order_by('username'))
This means that no selection is required for the form to be valid.

wtforms add value dynamically

I'm working on an app that uses WTForms in combination with an templating engine.
The forms that are being shown to the user are not pre defined, but can be dynamically configured by the admin. So the WTForm needs to be able to dynamically add fields, I have achieved this by specifying this subclass of Form (as suggested by):
class DynamicForm(Form):
#classmethod
def append_field(cls, name, field):
setattr(cls, name, field)
return cls
This works fine, but it seems that via this method you cannot populate the fields through Form(obj=values_here). This is my current instantiation of the Form subclass mentioned above:
values = {}
values['password'] = 'Password123'
form = DynamicForm(obj=values)
form.append_field("password", PasswordField('password'))
So this won't work, and this makes sense when you consider the field to be added after the Form's init has been called. So I have been searching for a way to also assign the value of a field dynamically, but I haven't had any luck so far.
Does anyone know of a way to set the password field in the example to a certain value? Would be greatly appreciated.
Try this:
>>> form = DynamicForm().append_field('password',
PasswordField('password'))(
password="Password123")
>>> form.data
{'password': 'Password123'}
The solution mentioned here by dkol works like a charm, but it does require you to actually know the name of the field (in other words, it can't be computed). This is because the named parameter's key which you're passing along in (password="Password123") can't be an expression, or an variable. Meaning that an for loop like this won't work:
form = DynamicForm()
for field in fields:
form = form.append_field(field.label, StringField(field.label))(field.label=field.value)
So after some fiddling around with the former solution provided by dkol, I came up with this:
form = DynamicForm()
class formReferenceObject:
pass
for field in fields:
form = form.append_field(field.label, StringField(field.label))
setattr(formReferenceObject, field.label, field.value)
form = form(obj=formReference)
This solution may require a bit more code, but is still really readable and is very flexible. So when using an for loop / an computed <input /> name, this will help you.

Flask + WTForms, dynamically generated list of fields

I am making a Flask application that is essentially form-based and so I'm using WTForms and Flask-wtf.
I am currently refactoring my code so my whole form uses WTForms and there is a very dynamic part of one of the forms that I am unable to implement using WTForms. I have no clue how to do it, my initial ideas didn't work, I can't find references or tutorials covering my problem and so this is why I ask for help.
So, the form in question allows users to submit objects that consist of:
A label (StringField, easy)
A Description (TextAreaField, also easy; although I had trouble to make a default value work)
A list of property of the form (predicate, object), where predicate is taken from a pre-built list and object can basically be anything but each predicate will generate a specific object (for instance, the predicate "related to" will expect another object (that comes from a dropdown list) and the predicate "resource" will expect a http link of some sort). This list can be empty.
As you can guess I have trouble with the list. The way the code works right now, I get the label and description using wtforms, and the property list is generated using a config constant (that is used throughout the code so I only have one place to edit if I want to add new properties) and a dynamic menu in javascript that creates (here, for predicates) fields, that I can then get using flask.request.form object in the view function. All the hidden fields for predicates have the same name attribute and all the hidden fields for objects have the same name attribute.
Here is what the view of the form looks like, initialized with a few properties:
http://i.imgur.com/bfMG95s.png
Under the "Propriétés" label you have a dropdown to select the predicate, the second field is displayed or hidden depending on the selected predicate (can be a dropdown or a text field), and it is only when you click on "Ajouter propriété" ("Add property") that a new line is added in the tab below and the fields are generated.
I'd like not to have to change anything on this side because it works very well, makes the form very intuitive and is basically exactly what I want it to be from the user's end.
This is what my custom Form looks like right now (it doesn't work and properties stays empty whatever the number of fields I submit with the form):
class PropertyForm(Form):
property_predicate = HiddenField(
validators=[AnyOf(values=app.config["PROPERTY_LIST"].keys())]
)
property_object = HiddenField(
validators=[DataRequired()]
)
class CategoryForm(Form):
"""
Custom form class for creating a category with the absolute minimal
attributes (label and description)
"""
label = StringField(
"Nom de la categorie (obligatoire)",
validators=[DataRequired()]
)
description = TextAreaField(
"Description de la categorie (obligatoire)",
validators=[DataRequired()]
)
properties = FieldList(FormField(PropertyForm),validators=[Optional()])
And here is what I'd love to do in my views.py code (that I am currently refactoring):
def cat_editor():
cat_form = CategoryForm()
if request.method == "GET":
# Do GET stuff and display the form
return render_template("cateditor.html", form=cat_form, varlist=template_var_list)
else if request.method == "POST":
if cat_form.validate_on_submit():
# Get values from form
category_label = cat_form.label.data
category_description = cat_form.description.data
category_properties = cat_form.properties.data
# Do POST stuff and compute things
return redirect(url_for("index"))
else:
# form didn't validate so we return the form so the template can display the errors
return render_template("cateditor.html", form=cat_form,
template_var_list = template_var_list)
The basic structure works perfectly, it's just that damn dynamic list I can't get to work properly.
Getting label and description from the WTForms CategoryForm instance works fine, but properties always return an empty list. Ideally I'd love to be able to get a list of the form [(predicate1, property1), (predicate2, object2) ... ] when calling cat_form.properties.data (this is why I have a FieldList of FormFields with two HiddenField in each) but I'd have no problem having to build such a list from two list as long as it's using WTForms. Any idea? Thanks a lot :)
I found out what the problem was by playing around with FieldList objects and append_entry() to see what HTML code would Flask-wtf generate if I was to make a prepopulated property list.
My Javascript was generating hidden fields with all the same name, as from what I understood that WTForms is able to aggregate fields with the same name to create lists. Problem is, those similarly named fields were part of a FormField itself nested in a FieldList object name properties.
In order for the WTForms Form object to discern a set of hidden fields from another, when you nest FormFields inside a FieldList it prefixes the FormFields field names with "FieldList_name-index-". Which means what WTForms was expecting was something like
<input type="hidden", name="properties-0-property_predicate" value=...>
<input type="hidden", name="properties-0-property_object" value=...>
<input type="hidden", name="properties-1-property_predicate" value=...>
<input type="hidden", name="properties-1-property_object" value=...>
<input type="hidden", name="properties-2-property_predicate" value=...>
<input type="hidden", name="properties-2-property_object" value=...>
I modified my javascript so it generates the appropriate names. Now when I call cat_form.properties.data I have something that looks like:
[{"property_predicate": "comment", "property_object":"bleh"},
{"property_predicate": "comment", "property_object": "bleh2"}]
And that is exactly what I need. For some reason the form doesn't validate but at least I know how to make WTForms extract data my javascript-generated hidden fields, which is what the problem was.
Edit: Form validation happens because you have to insert a CSRF hidden input with your csrf to every subform you generate with the FormField.
Use jQuery for the more dynamic elements/ behavior in your form(s). Note that form fields have a hidden property (or method, depending e.g., if you're using bootstrap), allowing you to render everything you might need, but only show fields when these are necessary, and hiding them otherwise. Dynamically adding fields is a bit harder, but not really impossible. Is there a limit to the number of fields associated with properties? if yes, i'd just render the maximum number of fields (as long as it's reasonable, up to 5 seems OK, when you get to double digits as a maximum number of properties a user can add, rendering a bunch of fields you'll never use gets to be inelegant).
Here's a good place to see how that would work. Of course, you have another problem of choosing when to hide or show relevant fields, but that can also be handled by a javascript/jQuery script, using jQuery's .change() event. Something like this:
$("#dropdown").change(function () {
var chosen_val = $(this).val();
if (chosen_val == 'banana'){$('#property1').show();} else {$('#property1').hide();}
});
This code will probably not work, and is definitely lacking proper logic but should give you an idea of how to approach this issue using jQuery. Note that 'property1' field is always there, waiting to be shown if the user chooses the right dropdown value.

How to properly validate a MultipleChoiceField in a Django form

I have a MultipleChoiceField representing US states, and passing a GET request to my form like ?state=AL%2CAK results in the error:
Select a valid choice. AL,AK is not one of the available choices.
However, these values are definitely listed in the fields choices, as they're rendered in the form field correctly.
I've tried specifying a custom clean_state() method in my form, to convert the value to a list, but that has no effect. Printing the cleaned_data['state'] seems to show it's not even being called with the data from request.GET.
What's causing this error?
from django import forms
class MyForm(forms.Form):
state = forms.MultipleChoiceField(
required=False,
choices=[('AL','Alabama'),('AK','Alaska')],
)
MultipleChoiceFields don't pass all of the selected values in a list, they pass several different values for the same key instead.
In other words, if you select 'AL' and 'AK' your querystring should be ?state=AL&state=AK instead of ?state=AL%2CAK.
Without seeing your custom clean_state() method I can't tell you what's going wrong with it, but if the state field isn't valid because the querystring is wrong then 'state' won't be in cleaned_data (because cleaned_data only holds valid data).
Hopefully that helps. If you're still stuck try adding a few more details and I can try to be more specific.

Implementing a Multiple (Checkbox) Select + "Other" widget in Django

I need to populate a CharField with the result of a set of checkboxes plus an "other" [please specify] option (the value would be 'option-a,other' for the form state below). The form can basically look like:
[x] option a
[ ] option b
[x] other
If you selected "other", please specify. [__________]
I've managed to get most of the way there by Implementing a subclass of MultipleChoiceField:
class CommaStringMultipleChoiceField(MultipleChoiceField):
def to_python(self, value):
return [val.rstrip().lstrip() for val in value.split(',')]
def clean(self, value):
return ",".join([val.rstrip().lstrip() for val in value])
In my form class, I assign that field to my form field:
TYPE_CHOICES = [
(u'option-a',u"Option A"),
(u'option-b',u"Option B"),
(u'other', u"Other"),
]
type = CommaStringMultipleChoiceField(
choices=TYPE_CHOICES,
widget=CheckboxSelectMultiple
)
This renders the form properly, and my custom clean() method is called, but when I save the form I get a validation error:
Value u'education,other' is not a valid choice.
I've played with adding a custom validator, but that hasn't made any difference so far. What am missing?
As the error only occurs once you save a model instance out of the form, check if the “Value u'education,other' is not a valid choice” error is coming from model validation instead of form validation. That can happen if you also have a choices set on the model field when you really want to store a free-form string there.
You will probably need to override the Validate method on your class. In the code at https://code.djangoproject.com/browser/django/trunk/django/forms/fields.py#L682 you'll see where it is raising the error message.

Categories