barseghyanartur / django-fobi

Form generator/builder application for Django done right: customisable, modular, user- and developer- friendly.
https://pypi.python.org/pypi/django-fobi
486 stars 112 forks source link

[question] Where is the validation of the required fields is done? #184

Closed mansonul closed 6 years ago

mansonul commented 6 years ago

Hi, I'm trying to implement conditionals in fobi and so far everything is going well. I've made new plugins and added custom fields and tailored much of the code to suit my needs.

The problem that I'm facing right now is with the required fields that are hidden until another response is triggering the css to show them. I've googled (https://stackoverflow.com/questions/28421896/dont-require-field-based-on-another-fields-value) the problem and it seems that it is possible to override the 'require' attribute in the clean method of django's forms.Form but I'm unable to find the code that manages validation before submit in fobi.

As a workaround until I'll get some help, I've added an initial 'N/A' response to all of the hidden ("conditioned" elements).

Thank you!

barseghyanartur commented 6 years ago

@mansonul:

Regarding conditionals, one way of doing that would be to create a couple of custom fields for your needs. One thing that you might not know (as I'm not sure if that part is specifically documented at all) is that a single field plugin might consist of multiple form fields. That feature has been made specifically for such cases in mind (I have implemented a couple of custom complex fobi setups using that feature; sadly enough I can't share the code, since it's non-open-source).

Take this an example. The list is returned. That list consists of a single tuple, but might consist of muliple tuples.

Another solution, would be similar approach outside fobi, using standard Django (as they might be complex as well). See the MultiValueField.

mansonul commented 6 years ago

Thank you for your quick reply! I don't have a problem implementing conditionals, I did that (as you said).

radio/base.py:

...
field_kwargs = {
            'label': self.data.label,
            'help_text': self.data.help_text,
            'initial': self.data.initial,
            'required': self.data.required,
            'conditional': self.data.conditional,
            'conditioned_data': conditioned_data,
            'is_conditioned': self.data.is_conditioned,
            'choices': choices,
            'widget': RadioSelect(attrs=widget_attrs),
        }
...

I have a problem with the fields that are required. As an example:

  1. What food do you like?
  1. Are you a vegan?

In my (stupid) example above question 2 is required, but is hidden in the frontend until 'None of the above' is selected. If the responder selects 'Spam' or 'Eggs' question 2 is not shown but the form cannot be send because it throws 'This filed is required'. Given the examples that I found on SO a solution will be to put the logic in the 'init' and 'clean' methods to accept the form if 'None of the above' from the 1st question is not selected. My problem is that I don't know where the validation of the responder is done in fobi so I can put the code there. Thank you!

barseghyanartur commented 6 years ago

I see. So, actually, your use case is multiple forms. I would suggest to use form-wizards for that (you can combine as many forms as you like).

mansonul commented 6 years ago

No, it's one form. The questions are form element plugins, in the example they are a modified version of the Radio plugin.

mansonul commented 6 years ago

Hi, I think I might have not been clear getting into details earlier. My actual question is - where I can find in the code the part for the fields validation?

What I do: I use fobi to ask some questions. I am creating a sort-of survey with 2-3 questions that are dependent on each-other (depending on what you answer on the first, you will have or not the possibility to answer a second).

How it works: my use case is two mandatory questions conditioned by each other. So I have two required questions in the same form and the second question will only be displayed if a certain answer is selected in the first question:

  1. Do you like Django?*

This is a required question.

CASE I If you answer Yes, another required question will be displayed:

  1. Do you like Django?
  1. Then what you think about fobi?

This all works well.

CASE II If you answer 'No' to the first question then the second question won’t be displayed. This is intended, so all good so far. The issue is with it being required (which has to be). If I try to submit the form like this:

  1. Do you like Django?

I will get a 'This is required' error because I haven’t selected any answer for the second question.

What I did to fix: I have added a 'N/A' default answer to the second question and it works fine, but it doesn’t look good and certainly is not what I want - to display a 'N/A' answer to each question (it pretty much beats the purpose of it being required…).

What I want to do: So my question is how can I create a question that is not required and set it as required only if I show it?

Based on the example, I want to create a required 'Do you like Django?' and then a regular 'Then what you think about fobi?'. In Case I, I will need to display the second question AND make it required. In case II only the first question will be displayed and the error won’t appear as the second question is not required.

Can you please tell me where can I find in the code the fields validations part? I have tried to figure it out myself, but with no luck so far.

barseghyanartur commented 6 years ago

@mansonul:

I get it. You can do hell a lot in the get_form_field_instances of the plugin. Among others, it takes request, the form entry.

Validation isn't a part of fobi, since we delegate it to the standard Django form fields. So, whatever validation shall be implemented, it shall be done there. Thus, you either go with MultiValueField as I mentioned earlier, or you return multiple simple form fields per plugin.

Here comes the interesting part.

See the implementation of the get_form_field_instances method for BooleanField plugin.

    def get_form_field_instances(self, request=None, form_entry=None,
                                 form_element_entries=None, **kwargs):
        """Get form field instances."""
        field_kwargs = {
            'label': self.data.label,
            'help_text': self.data.help_text,
            'initial': self.data.initial,
            'required': self.data.required,
        }

        return [(self.data.name, BooleanField, field_kwargs)]

Since we have request there and the form assembled, based on your earlier POST/request data you can conditionally modify the field_kwargs (these are kwargs that a Django form field is constructed with.

barseghyanartur commented 6 years ago

Closing this. Feel free to ask more questions.