A Django ManyToManyField
renders something like this in HTML:
<form action="" method="post"> <select name="answers" multiple="multiple"> <option value="1" >Question 1, Answer 1</option> <option value="2">Question 1, Answer 2</option> <option value="3">Question 1, Answer 3</option> <option value="4">Question 2, Answer 1</option> <option value="5">Question 2, Answer 2</option> </select> <input type="submit"> </form>
I can select one item manually from the question 1 group, and one item from the question 2 group. When this is sent in a POST request, I get a POST array containing answers = [1, 3]
or similar.
I want to get the same behavior from groups of radio buttons, as this is a problem better suited to radio buttons. For example, if I do the following:
<form action="" method="post"> <fieldset> <legend>Question 1</legend> <input name="answers" id="id_1" value="1" type="radio"> <label for="id_1">Answer 1</label> <br> <input name="answers" id="id_2" value="2" type="radio"> <label for="id_2">Answer 2</label> <br> <input name="answers" id="id_3" value="3" type="radio"> <label for="id_3">Answer 3</label> <br> </fieldset> <fieldset> <legend>Question 2</legend> <input name="answers" id="id_4" value="4" type="radio"> <label for="id_4">Answer 1</label> <br> <input name="answers" id="id_5" value="5" type="radio"> <label for="id_5">Answer 2</label> <br> </fieldset> </form>
It doesn't actually let the user select more than one radio button at once. On the other hand, if I name the radio buttons answers[0]
and answers[1]
, in POST, it sends two separate entities with those names instead of sending a combined answers
.
I ask because on the back-end, I have a Django ManyToManyField
and a ModelForm
with a custom widget, and I'm trying to save the data into a ManyToManyField
from this custom widget without resorting to too much trickery on the back end, but I keep getting the error "Enter a list of values."
Edit: JavaScript is acceptable so long as it doesn't send the original data as well, as is Django inheritance/custom parsing.
Edit 2: Here is my widget for the ManyToManyField as it stands now.
{% if questions %} {% for question in questions %} <fieldset> <legend>{{ question.question }}</legend> {% if question.options %} {% for option in question.options %} <input type="checkbox" class="form-check-input" name="answers" id="id_{{ option.id }}" value="{{ option.id }}"> <label for="id_{{ option.id }}">{{ option.text }}</label> <br> {% endfor %} {% else %} <p>No options for this question.</p> {% endif %} </fieldset> {% endfor %} {% else %} <p>No questions in test.</p> {% endif %}
2 Answers
Answers 1
It doesn't actually let the user select more than one radio button at once. On the other hand, if I name the radio buttons answers[0] and answers[1], in POST, it sends two separate entities with those names instead of sending a combined answers.
This doesn't fullfil your requirements, but atleast resolve one mystery. When you use answers[]
as name, backend will recognize it as array and join all values into array. This is possible with any input type except single (radio/select).
And this is js solution you can use, but your situation shows that model you are using is not optimal for this case. Ofc course it can be solved hacky way like bellow, but you will always have to solve workarounds when you meet this code..
So my advice: change approach in backend
document.getElementById('answers').onsubmit = function(e) { e.preventDefault(); //prevent submit; let clone = this.cloneNode(true); let inputs = clone.querySelectorAll('input[type=radio]'); let formData = new FormData(clone); // HTML5 // dumb check for validity if (Array.from(formData.values()).length != clone.querySelectorAll('fieldset').length) { alert('Invalid form!') return false; } for (let i=0;i<inputs.length;i++) { inputs[i].type = "checkbox"; inputs[i].name = "answers[]"; } // only for snippet formData = new FormData(clone); console.log(Array.from(formData.entries())); // commented in snippet //clone.submit(); } /* This is cleaner submit by JS without reload document.getElementById('answers').onsubmit = function(e) { let formData = new FormData(this); // HTML5 let answers = Array.from(formData.values()); // dumb check for validity if (answers.length != this.querySelectorAll('fieldset').length) { alert('Invalid form!') return false; } let newFormData = new FormData(); newFormData.set('answers', answers); let request = new XMLHttpRequest(); request.open(this.method || "POST", this.action || '/default/post/link'); request.send(newFormData); return false; } */
<form action="" method="post" id="answers"> <fieldset> <legend>Question 1</legend> <input name="question_1" id="id_1" value="1" type="radio"> <label for="id_1">Answer 1</label> <br> <input name="question_1" id="id_2" value="2" type="radio"> <label for="id_2">Answer 2</label> <br> <input name="question_1" id="id_3" value="3" type="radio"> <label for="id_3">Answer 3</label> <br> </fieldset> <fieldset> <legend>Question 2</legend> <input name="question_2" id="id_4" value="4" type="radio"> <label for="id_4">Answer 1</label> <br> <input name="question_2" id="id_5" value="5" type="radio"> <label for="id_5">Answer 2</label> <br> </fieldset> <button type="submit">Submit</button> </form>
Answers 2
Changing the radio buttons to checkboxes should allow you to select multiple values.
<form action="" method="post"> <fieldset> <legend>Question 1</legend> <input name="answers" id="id_1" value="1" type="checkbox"> <label for="id_1">Answer 1</label> <br> <input name="answers" id="id_2" value="2" type="checkbox"> <label for="id_2">Answer 2</label> <br> <input name="answers" id="id_3" value="3" type="checkbox"> <label for="id_3">Answer 3</label> <br> </fieldset> <fieldset> <legend>Question 2</legend> <input name="answers" id="id_4" value="4" type="checkbox"> <label for="id_4">Answer 1</label> <br> <input name="answers" id="id_5" value="5" type="checkbox"> <label for="id_5">Answer 2</label> <br> </fieldset> </form>
The HTTP POST generated by this form should be exactly the same as the example you gave above with the select
tag.
0 comments:
Post a Comment