Sorry, but your answer doesn’t help at all :-/
Anyway, I found the solution by myself, and I would like to share it here, hoping this could help if anybody encounter the same problems.
As far as I included some context data, I moved all the stuff in my view. It was not part of the question, but to give a complete overview, the new form is defined like this:
class EventDetail(forms.ModelForm):
groups = forms.ModelMultipleChoiceField(
label = "Liste des groupes",
queryset = None,
widget = forms.CheckboxSelectMultiple,
required = False
)
class Meta:
model = Event
fields = ['event_name', 'event_date', 'quorum', 'rule']
Let’s come to the main subject: initialize the form and being able to validate it.
Initialization: defining the queryset is not enough, it only define the list of values to be displayed. To put the initial values, you need to use the initial attribute. It might be evident for most of you, but reading the doc I thought it was a difference between a form linked to a model and a one that is not, in other words, I thought we might choose between queryset and initial attribute. I was wrong, here I need both.
Then, to validate the form, I need to manage data related to this M2M relationship separately. This means, a form.save() statement is not enough and will save nothing for this field. To manage this field, I need to add() each value… but do not forget to remove the ones that could be no longer be linked to the main object!
According to this, here is my final working view:
@user_passes_test(lambda u: u.is_superuser or (u.id is not None and u.usercomp.is_admin))
def adm_event_detail(request, comp_slug, evt_id=0):
company = Company.get_company(comp_slug)
if evt_id > 0:
current_event = Event.objects.get(id=evt_id)
event_form = EventDetail(request.POST or None, instance=current_event)
event_form.fields['groups'].initial= current_event.groups.all()
else:
event_form = EventDetail(request.POST or None)
event_form.fields['groups'].queryset= UserGroup.objects.\
filter(company=company, hidden=False).\
order_by('group_name')
if request.method == 'POST':
if event_form.is_valid():
if evt_id == 0:
# Create new event
event_data = {
"company": company,
"groups": event_form.cleaned_data["groups"],
"event_name": event_form.cleaned_data["event_name"],
"event_date": event_form.cleaned_data["event_date"],
"quorum": event_form.cleaned_data["quorum"],
"rule":event_form.cleaned_data["rule"]
}
new_event = Event.create_event(event_data)
else:
new_event = event_form.save()
new_event.groups.clear()
new_event = event_form.save()
new_event.groups.add(*event_form.cleaned_data['groups'])
else:
print("****** FORMULAIRE NON VALIDE *******")
print(event_form.errors)
return render(request, "polls/adm_event_detail.html", locals())
And I updated the template, as the loop is actually not necessary (unless anyone could propose me another idea to display group details); the equivalent of what I put in the first post is now this single line of code:
{{ event_form.groups }}
I just added a CSS property to hide list’s bullet points:
#id_groups {
list-style-type: none;
}
It might be possible to make the view simpler with other functions - I would be very interested to know! - but at least it works for me =)