Package: dynamic-django-forms
Dynamic Django Formsdynamic-django-formsis a simple, reusable app that allows you to build (and respond to) dynamic forms, i.e. forms that have variable numbers and types of fields. A few examples of uses include:Building and sending out surveysJob applications where each job might have a different application formsInstallationInstall via pip:pip install dynamic-django-formsAdd to settings.INSTALLED_APPS:INSTALLED_APPS={"...","dynamic_forms","..."}ComponentsThe main functionality ofdynamic-django-formsis contained within 2 model fields:Form Builderdynamic_forms.models.FormFieldallows you to build and edit forms via a convenient UI, and stores them in JSON-Schema form. It is easy to use both through the admin panel and in any custom template webpage.Example Setup:fromdynamic_forms.modelsimportFormFieldclassSurvey:# Other Fields Hereform=FormField()Please note that JSON data can saved into the model field as a pythondictor a valid JSON string. When the value is retrieved from the database, it will be provided as alistcontainingdicts for each dynamic form field.Form Responsedynamic_forms.models.ResponseFieldallows you to render, and collect responses to, forms built with the Form Builder. It is currently only supported through custom views. All form responses are stored as a dict where the key is the question label, and the value is the user's input.Example Setup:Model Config:fromdjango.dbimportmodelsfromdynamic_forms.modelsimportResponseFieldfromotherapp.modelsimportSurveyclassSurveyResponse:# Other Fields Heresurvey=models.ForeignKey(Survey,on_delete=models.CASCADE)# Optionalresponse=ResponseField()Please note that including a ForeignKey link from the model containing responses to the model containing forms isnt technically required; however, it is highly recommended and will make linking the two much easierConfiguring ResponseFields with formsYou must provide a valid JSON Schema to ResponseField's associated FormField at runtime. This is best done in the view where the dynamic form will be used. Generally speaking, this means you should:Obtain the JSON Schema Form DataGet an instance of a model containing a FormField that has already been built ORProvide the form data as a constantIntercept the Form instance used in the view where the dynamic form will be shown. This could be an automatically generated ModelForm (via a generic Class Based View), or a form instance you have made yourself.Provide the JSON form data to the form field:form_instance.fields['response_field_name_in_form'].add_fields(JSON_DATA) will add the fields in JSON_DATA to the existing fields in the dynamic form.form_instance.fields['response_field_name_in_form].replace_fields(JSON_DATA) will remove any fields currently in the dynamic form and replace the with the fields in JSON_DATAAn example of how to do this can be found in the DynamicFormMixin explained in the next section:fromdjango.views.generic.editimportFormMixinclassDynamicFormMixin(FormMixin):form_field="form"form_pk_url_kwarg="pk"response_form_fk_field=Noneresponse_field="response"def_get_object_containing_form(self,pk):returnself.form_model.objects.get(pk=pk)defget_form(self,*args,**kwargs):form=super().get_form(*args,**kwargs)# Get instance of model containing form used for this response. Save this object as an instance variable for use in form_valid methodform_instance_pk=self.kwargs[self.form_pk_url_kwarg]self.form_instance=self._get_object_containing_form(form_instance_pk)# Get json form configuration from form-containing objectjson_data=getattr(self.form_instance,self.form_field)# Add fields in JSON to dynamic form rendering field.form.fields[self.response_field].add_fields(json_data)returnformdefform_valid(self,form):action=form.save(commit=False)action.survey=self.form_instanceaction.save()returnsuper().form_valid(form)Configuration ShortcutThe process of configuring ResponseFields with forms is somewhat complicated, so a shortcut is provided.dynamic_forms.views.DynamicFormMixincan be added to Class Based Views extending fromdjango.views.generic.edit.CreateViewanddjango.views.generic.edit.UpdateView, and will automatically complete configure the dynamic form provided that:The model containing the ResponseField has a ForeignKey link to a model containing the FormField.The following attributes are provided:form_model: The relevant model (not instance) containing a FormField with the wanted dynamic form configuration. I.e. which model is the survey defined in? Default:Noneform_field: The attribute ofform_modelthat contains the FormField. I.e. Which field in the Survey model contains the form? Default:formform_pk_url_kwargTheURL Keyword Argumentcontaining the primary key of the instance ofform_modelthat contains the dynamic form we want? I.e. Which survey are we responding to? Default:pkresponse_form_fk_fieldThe attribute of the model which contains the ResponseField that links via ForeignKey to the model containing the FormField. I.e. Which attribute of the Survey Response model links to the Survey model? Default:Noneresponse_fieldThe attribute of the Response model which contains the ResponseField. I.e. which attribute of the Survey Response model contains the actual responses? Default:responseExample:classRespondView(DynamicFormMixin,CreateView):model=SurveyResponsefields=['response']template_name="example/respond.html"form_model=Surveyform_field="form"form_pk_url_kwarg="survey_id"response_form_fk_field="survey"response_field="response"defget_success_url(self):returnreverse('survey_detail',kwargs={"survey_id":self.form_instance.pk})Django Crispy Forms SupportIf you are usingDjango Crispy Formsto make your forms look awesome, set use the following setting:USE_CRISPY = True(false by default)Please note that you are responsible for importing any CSS/JS libraries needed by your chosen crispy template pack into the templates where (e.x. bootstrap, uni-form, foundation).Fields Supported and Limitationsdynamic-django-formscurrently supports the following field types:Checkbox GroupDate FieldHidden InputNumberRadio GroupSelectText FieldEmail FieldText AreaThe only major limitation ofdynamic-django-forms, which is also one of its major features, is the dissociation of dynamic form questions and responses.Pros:Responses cannot be changed after submissionDynamic forms can be edited, removing, changing, or adding questions, without affecting prior responsesCons:Responses cannot be changed after submissionCustom JS for FormBuilderOnsettings.pyyou can use a variable to inject custom JS code before the form builder is initialized. Note that theoptionsvariable. Note that when this custom JS runs, the following variables are available:textArea: This is a hidden textarea input used to submit the JSON form schema.options: This is aFormBuilder Options objectthat you can override and modify to change how the form is displayed.DYNAMIC_FORMS_CUSTOM_JS = 'console.log(1)'Example SiteTo run an example site, runcd example && docker-compose up. If you do not use docker, you can manually install the requirements withpip install -r example/requirements.txtand run the site withpython example/manage.py runserver.Planned ImprovementsSupport for some HTML ElementsHeadersParagraph TextDividersSupport for the following fields:Color FieldTelephone FieldStar RatingSupport for "Other" option on radio groups, checkbox groups, and select dropdownsUser can select "other", at which point an inline text-type input will appear where they can put a custom choiceAbility to provide default JSON form config via:StringDictFileRemote URLDynamicFormMixin should support slugsAbility to customize JSONBuilder settings through Django settingsExtensive Automated TestingPossible ImprovementsSupport File Upload FieldCreditsHuge thanks to Kevin Chappell & Team for developing the awesome open sourceForm Builder UI!