I have created a quiz with wagtail form builder, but I need the form to contain some fields which are constant. For example, I have the following:
Name
Email
Employee id
[form builder fields]
How do I integrate fields [1-3] in the form builder to show up on all quizzes?
A simple way to do this is to override the get_form_fields method on your FormPage model.
This method simply returns a query for all stored FormFields on the FormPage model. We can add instances of FormField as needed and this will make the fields available in the form (view). They will also be available in form reports and emails.
1 - Override get_form_fields in your FormPage class.
In myapp/models.py wherever you have defined your FormPage model, add the following:
class FormPage(AbstractEmailForm):
# ...additional FormPage fiels and content_panels
def get_form_fields(self):
# get form_fields as defined by the super class & convert to list
# otherwise returns queryset
fields = list(super(FormPage, self).get_form_fields())
# append instances of FormField - not actually stored in the db
# field_type can only be one of the following:
# 'singleline', 'multiline', 'email', 'number', 'url', 'checkbox',
# 'checkboxes', 'dropdown', 'multiselect', 'radio', 'date',
# 'datetime', 'hidden'
# Important: Label MUST be unique in each form
# `insert(0` will prepend these items, so here ID will be first
fields.insert(0, FormField(
label='Employee Name',
field_type='singleline',
required=False,
help_text="Employee's Name"))
fields.insert(0, FormField(
label='Employee Email',
field_type='email',
required=False,
help_text="Employee's Email"))
fields.insert(0, FormField(
label='Employee ID',
field_type='number',
required=False,
help_text="Employee's ID"))
return fields
Note: When creating our FormField instances, these are not stored in the database, but their responses will be. You should provide the attributes label, field_type, required and help_text.
2 - Ensure there are no label conflicts on existing Forms
If the form label 'Employee ID' is used already in a form - not sure what will happen but it will likely break things so check you do not have conflicts in existing form pages.
Note: If the label is the same, you will not loose your data from previous submissions. All data is stored for past submissions even if you change the FormFields in use.
3 - Override the label field on FormField with some validation
In myapp/models.py wherever you have defined your FormField model, add the following:
from django.core.exceptions import ValidationError
# ... other imports and then models
RESERVED_LABELS = ['Employee Name', 'Employee Email', 'Employee ID']
def validate_label(value):
if value in RESERVED_LABELS:
raise ValidationError("'%s' is reserved." % value)
class FormField(AbstractFormField):
# page = ...
# redefine 'label' field so we can ensure there will be no conflicts with constant fields
label = models.CharField(
verbose_name='label',
max_length=255,
help_text='The label of the form field, cannot be one of the following: %s.'
% ', '.join(RESERVED_LABELS),
validators=[validate_label]
)
RESERVED_VALUES should match the labels used in your FormPage model.
This creates a simple Django Field Validator to check that you will not have conflicting labels. It also adds some nice help text so users do not get confused.
4 - Migrate
You should also do a migrations as you have now changed the label field on the FormField model.
Related
Is it possible to validate only present fields of the form..
For example, I have a declarationForm, I give rules in the validation.yml for 3 fields : name, reference, commantary.. sometime I dont add commentary field in my builder. Anyway when I validate the form it says that commentary cannot be empty (but there is no field commentary)
Yes, but you will have to remove the NotBlank conditions from your validation.yml for the commentary field and add the requirement back into the builder whenever you include it in your FormBuilderInterface:
$builder
// ... other fields using add()
->add('commentary', 'text', array(
'required' => true,
))
;
Note however that this does indeed only add an HTML5 required attribute to both the input and label tags.
The better solution thanks to #Cerad is to add validation groups to your validation.yml for the field:
AcmeBundle\Entity\Declaration: # change to your entity class
properties:
# ...
commentary:
- NotBlank: { groups: [commentaryReq] }
And then use the Form Builder with this validation group when needed:
$form = $this->createFormBuilder($declaration, array(
'validation_groups' => array('commentaryReq'),
))
//->add(...)
;
or if you're using Form Classes:
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'validation_groups' => array('commentaryReq'),
));
}
You can also specify groups based on submitted data or specify groups based on the button that was clicked if that helps.
If you want to allow empty commentary field, add nullable=true to its column definition in your entity:
http://doctrine-orm.readthedocs.org/en/latest/reference/annotations-reference.html#column
http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/basic-mapping.html#property-mapping
My Product entity has the following structure:
private $id;
private $title;
/**
* #ManyToOne(targetEntity="Category")
* #JoinColumn(name="cat_id", referencedColumnName="id")
*/
private $category;
Category have nested structure. And each level of nesting is shown in 5 separate fields:
In class form code, I solve it in this way:
$builder
->add('cat_1', 'entity', array(
...
'query_builder' => function() { return someSelectLogic1(); }
))
->add('cat_2', 'entity', array(
...
'query_builder' => function() { return someSelectLogic2(); }
))
->add('cat_3', 'entity', array(
...
'query_builder' => function() { return someSelectLogic3(); }
))
->add('cat_4', 'entity', array(
...
'query_builder' => function() { return someSelectLogic4(); }
))
->add('cat_5', 'entity', array(
...
'query_builder' => function() { return someSelectLogic5(); }
))
Now I need to know which field is filled in the last turn and pass the value of that field in the entity property.
In all that I do not like:
complex logic to determine which field with category was filled at the end
each of these fields is not tied to the entity 'mapped' => false
1) What the right way to organize code of my form?
2) And is there a way to bring these fields into a separate class which will deal with the logic of determining which category was chosen in the end?
I would suggest the following:
1) Create a new custom form field type and put all those entity in there.
This process is not much different from ordinary creation of form type. Just enclose those fields in it's own buildForm() and that should do the trick. Docs.
2) Mark all those entity fields with property "property_path => false".
Clearly you wont be storing these values inside your model.
3) Add two more fields: chosen and lastOne.
Now, this might be tricky: I would either set the chosen to text type (basically, generic type) or would use entity as well. If you go for entity you would need to include all possible answers from all entity fields. As for the lastOne set it to text as it will reflect which field (by name) was selected last.
Either way, those two fields will be invisible. Don't forget to set property_path to false for lastOne field.
4) Finally, add ValueTransformer (docs) which will contain logic to "see" which field was selected last.
Now, I dealt with it only once and don't understand it just quite yet, so your best bet would be trial and error with examples from official docs, unfortunately.
What basically you should do is to, within value-transformer, read the value of field lastOne. This will give you the name of field which was selected last. Then, using that value, read the actual last value selected. Last, set that value (object, if you've went for entity type, or it's ID otherwise) to chosen field.
That should basically do the thing.
As for the JS, I don't know if you're using any framework but I will assume jQuery. You will need to set lastOne field as your selecting items in your form.
$(function(){
$('#myform').find('select').on('change', function(){
var $this = $(this);
$this.closest('form').find('#__ID_OF_YOUR_LASTONE_FIELD').val($this.attr('name'));
});
});
I'm sorry I cannot provide you with code samples for PHP right now. It's a bit late here and will do my best to further update this answer tomorrow.
I need to store a record in First Model. It has around 10 fields. Now, I need to apply required rule for one field which i'm storing in SECOND model and I'm getting these values for this field from Third model.(it is a drop down field)
how can i make this field mandatory in yii??
can any one help please..
Many Thanks,
You can add additional attributes and rules to a model. You don't have to only use attributes that directly relate to fields in your database table. Let's look at a basic example. You have a user (User) table which has the following fields:
id
email
password
And you have another table which stores user profile (UserProfile) information which has the structure:
id
user_id
country
When the user creates their account, you would have a form that captures their information. Your User model would have rules like:
array('email, password', 'required'),
array('email', 'unique', 'message'=>'Email already in use'),
...
You can add an attribute for country to your User model like so:
class User extends CActiveRecord {
public $country;
...
And then in your rules you can add the new attribute:
array('email, password, country', 'required'),
array('email', 'unique', 'message'=>'Email already in use'),
...
The country attribute will now be a part of your User model. You can now add that to your form:
<?php echo $form->dropDownList($model,'country',CHtml::listData(Country::model()->findAll(),'id','country_name'),array('empty'=>'-- select a country --')); ?>
Now on your form submit, the $model->validate() method will validate the country field. You can save this manually to your second model (UserProfile), something like:
if(isset($_POST['User'])){
if ($model->validate()){
$user_profile = new UserProfile;
$user_profile->user_id = $model->id;
$user_profile->country = $model->country;
$user_profile->save();
...
Hopefully that answers your question.
Suppose to have an entity in Symfony2 that has a field bestfriend, which is a User entity selected from a list of User entities that satisfy a complex requirement.
You can render this field in a form by specifying that it is an entity field type, i.e.:
$builder->add('bestfriend', 'entity', array(
'class' => 'AcmeHelloBundle:User',
'property' => 'username',
));
This form field is rendered as a <select>, where each one of the displayed values is in the form:
<option value="user_id">user_username</option>
So, one would render the field by using the <optgroup> tags to highlight such special feature of the friends.
Following this principle, I created a field type, namely FriendType, that creates the array of choices as in this answer, which is rendered as follows:
$builder->add('bestfriend', new FriendType(...));
The FriendType class creates a <select> organized with the same <option>s but organized under <optgroup>s.
Here I come to the problem! When submitting the form, the framework recognize that the user field is not an instance of User, but it is an integer. How can I let Symfony2 understand that the passed int is the id of an entity of type User?
Here follows my solution.
Notice that it is not mentioned in the Symfony2 official docs, but it works! I exploited the fact that the entity field type is child of choice.
Hence, you can just pass the array of choices as a param.
$builder->add('bestfriend', 'entity', array(
'class' => 'AcmeHelloBundle:User',
'choices' => $this->getArrayOfEntities()
));
where the function getArrayOfEntities() is a function that fills the choice list with the friends of my friends, organized by my friends:
private function getArrayOfEntities(){
$repo = $this->em->getRepository('AcmeHelloBundle:User');
$friends = $repo->findAllFriendByComplexCriteria(...);
$list = array();
foreach($friends as $friend){
$name = $friend->getUsername();
if(count($friend->getFriends())>0){
$list[$name] = array();
foreach($friend->getFriends() as $ff){
$list[$name][$ff->getUsername()] = $ff;
}
}
}
return $list;
}
I know the example could be meaningless, but it works...
PS: You need to pass the entity manager to let it working...
I have few fields on the form like name, description, timestamp.
Now in the form I am only displaying name and description but not timestamp.
public function __construct()
{
$this->setTimestamp(new \DateTime());
}
Now in my database, it is coming as null.
Either doctrine is not executing constructor or those fields are set to null when displayed in form.
Even though I am not displaying them.
You need put the timestamp field in your FormType.
If you don't need to show it, just hide the field and set default value.
Something like that:
$builder->add('timestamp', 'hidden', array(
'data' => new \DateTime(),
));
Don't forget {{form_rest(form)}} at end of the twig template to send all hidden fields.