Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

work on form and formset without jquery #43

Merged
merged 31 commits into from
Oct 23, 2023
Merged

Conversation

B-Alica
Copy link
Contributor

@B-Alica B-Alica commented Feb 24, 2023

Hello,

I reworked the formsets without jQuery.
So here are some modification suggestions in order to work more easily with forms and formset.

Here are what I suggest adding :

  • two new dependancies : django-widget-tweaks and django-crispy-forms
  • In dsfr/static : a script "formsets.js" to use formsets dynamically.
  • In dsfr/templates/django/forms/widgets : I modified the custom widget templates : checkbox_option.html, input_option.html, multiple_input.html and radio_option.html, because they weren't displaying correctly.
  • In dsfr/templates/dsfr : two templates : form-base.html and formset-base.html

I also modified the readme to include these changes.
And I also modified the example_app to include an example of form and formset, where user adds an author and related books (several books possible, so we use formset).

form-base.html

It extends base.html.
It creates the form with the <form> balise and the csrf token
It makes a loop on the fields of the form passed in context (from views.py and defined in forms.py) and checks the widget class. Then it display the field in the dsfr way.
It contains several blocks where user can add information or buttons for example. Two of these blocks are called extra-formset, this is where you can include a formset if you need, before or after the main form.

With form-base.html, your template looks like this :

{% extends 'dsfr/form-base.html' %}
{% load static dsfr_tags %}

{% block head-form %}
    <h2>Test de formulaire</h2>
{% endblock head-form %}

{% block foot-form %}
    {% dsfr_button btn_submit %}
{% endblock foot-form %}

formset-base.html

It doesn't extend anything, so it has to be included somewhere in a template extending form-base.html
It also doesn't contain <form> balise nor csrf token : it is made to be included in a template extending form-base.html, which contains <form> and csrf token.
It works the same as form-base.html (loop on the fields)
It contains several blocks to add things
It contains a script allowing to display formsets dynamically (add a new form to the formset, delete a form, etc.)

The templates look like these :
The one which extends formset-base.html

{% extends "dsfr/formset-base.html" %}

{% block formset-title %}
	{% if formset %}
		<h3>Livres</h3>
	{% endif %}
{% endblock formset-title %}

Is included in the one which extends form-base.html

{% extends 'dsfr/form-base.html' %}
{% load static dsfr_tags %}

{% block head-form %}
    <h1>Exemple de formulaire</h1>
{% endblock head-form %}

{% block extra-formset-after %}
    {% include "example_app/example-formset.html" %}
{% endblock extra-formset-after %}

{% block foot-form %}
    {% dsfr_button btn_submit %}
{% endblock foot-form %}

checkbox_option.html, input_option.html, multiple_input.html and radio_option.html

I had to modify these widget templates in order to display the checkboxes and radio buttons in the dsfr way correctly, with the possibility to add help_texts individually.

HOW TO :

  • Display the radio buttons/checkboxes horizontally ?
    => In your forms.py, select the right widget (CheckboxSelectMultiple or RadioSelect) and add : attrs={'class':'fr-fieldset--inline'}
test_radio_select = forms.ChoiceField(
        label="test_radio_select",
        choices=(('A', 'Option A'), ('B', 'Option B')),
        widget=forms.RadioSelect(attrs={'class':'fr-fieldset--inline'}),
        required=False,
)
  • Display individual help_text under one checkbox/radio button ?
    => In your forms.py, in the choices, you can write the choice with a dict, like this : ('A', {'label': 'label for A', 'help_text': 'help_text for A'})
    It is important to respect the syntax 'label' and 'help_text'. You can mix some choices without help_text and some with help_text.
test_chebox_select_multiple = forms.MultipleChoiceField(
        label="test_chebox_select_multiple",
        help_text = 'This is the general help_text under the label of the field',
        choices=(
                ('A', {'label':"Choix A", "help_text":"This is the help_text under checkbox A"}), 
                ('B', {'label':"Choix B", "help_text":"This is the help_text under checkbox B"})
        ),
        widget=forms.CheckboxSelectMultiple(),
)

If you want to work with a Model instead of a fixed choices list, there is a manipulation to be made :
ModelMultipleChoiceField won't display individual help_text under each checkbox/radio button. Therefore, we will use MultipleChoiceField anyway and format out queryset into a choices list accepted by MultipleChoiceField.
MultipleChoiceField takes a list of choices like so :
choices=(
('A', "Choix A"),
('B', {'label':"Choix B", "help_text":"This is the help_text under checkbox B"})
),
Let's say our model Genre (litterary genres) contains a field "help_text" which we want to display as an help_text under each objects (so, each checkbox). We will have to do like so :
in forms.py

GENRE=list()
for genre in Genre.objects.all():
    if not genre.help_text:
        to_add = (genre.pk, genre.designation)  # If no help_text, a tuple (pk, designation)
    else:
        to_add = (genre.pk, {'label':genre.designation, 'help_text':genre.help_text})  # If help_text, a tuple (pk, {'label':designation, 'help_text':help_text})
    GENRE.append(to_add)

Then when creating the field in the form class, do like this :

class BookCreateForm(ModelForm):
    class Meta:
        model = Book
        exclude=[]
        widgets = {
            'title': forms.TextInput(),
            'number_of_pages': forms.NumberInput(),
        }

    genre = forms.MultipleChoiceField(
        label="Genre",
        choices=GENRE,
        widget=forms.CheckboxSelectMultiple(),
        required=False,
    )

You can try this in the example_app for an example

Working with formset

See example_app for an example

In forms.py, there needs to be :

  • a form for the main object (for example Author)
  • a form for the related object which will be in the formset (for example Book)
  • a crispy FormHelper for this second form (Book)
  • a formset factory for this second form (Book)

In views.py :
The main form, the formset and the formhelper need to be passed to context in get_context_data.
The main form must be saved before the formset. Then, the formset must be linked to the main form with formset.instance = self.object. Then the formset can be saved.

In templates :
There needs to be :

  • A template for the main form, which extends form-base.html and includes a second template :
  • A second template for the formset, which extends formset-base.html

I hope everything works well !

@Ash-Crow
Copy link
Collaborator

Hi, sorry for the late answer!

The CI is failing because of an outdated dependency to python 3.7, which I have just fixed on the main branch. Could you please rebase and try again?

@B-Alica
Copy link
Contributor Author

B-Alica commented Apr 7, 2023

Hello,
Did you have time to look at the PR ?
Thank you.

@Ash-Crow
Copy link
Collaborator

Sorry, I was offline last week and in a bit of a rush the weeks before that. I'll try to check why there is a check that still doesn't pass tomorrow or on Friday.

@B-Alica
Copy link
Contributor Author

B-Alica commented Apr 13, 2023 via email

@Ash-Crow
Copy link
Collaborator

OK, I tried to recreate the migration, which seemed to work on my local installation as well as on the main CI, but still not on the deploy-doc script, not sure what is going on here

@Ash-Crow
Copy link
Collaborator

OK, it now "normally fails" (because your PR doesn't have the right to actually deploy to the branch), so OK on that part. I'll review the code ASAP.

@B-Alica
Copy link
Contributor Author

B-Alica commented Apr 17, 2023

Ok, thank you. I hope it will work !

@B-Alica
Copy link
Contributor Author

B-Alica commented Jun 20, 2023

Hello,
Did you had time to review the code ?
Thank you

@Ash-Crow Ash-Crow closed this Oct 23, 2023
@Ash-Crow Ash-Crow reopened this Oct 23, 2023
Ash-Crow and others added 4 commits October 23, 2023 12:28
Fix dateinput field : the DSFR has been updated and a div wraper is no longer needed.
Fix dateinput field : the DSFR has been updated and a div wraper is no longer needed.
@B-Alica
Copy link
Contributor Author

B-Alica commented Oct 23, 2023

Hello,
Have you had time to look at this PR ?

@Ash-Crow Ash-Crow merged commit 36d5a95 into numerique-gouv:main Oct 23, 2023
5 of 6 checks passed
@Ash-Crow Ash-Crow added the enhancement New feature or request label Oct 23, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants