Django форма - Отображение вариантов M2M в списке флажков multiselect

Я пытаюсь создать форму, в которой флажки с несколькими вариантами выбора позволят пользователю сделать выбор относительно полей, связанных с моделью формы с помощью M2M-отношения ship.
. Мне это удалось, но я сделал много тестов для отображения дополнительной информации, и теперь, когда я выбрал решение (основанное на методе __str__() моей модели), я не могу отобразить список всех групп, где отмечены те, которые уже связаны с моим событием
. Опять же, возможно, я упустил какую-то мелкую деталь, но я ничего не вижу и буду признателен за помощь.

Некоторые фрагменты кода на данном этапе.

Модели:

class UserGroup(models.Model):
    company = models.ForeignKey(
        Company, on_delete=models.CASCADE, verbose_name="société"
    )
    users = models.ManyToManyField(UserComp, verbose_name="utilisateurs", blank=True)
    group_name = models.CharField("nom", max_length=100)
    weight = models.IntegerField("poids", default=0)
    hidden = models.BooleanField(default=False)

    @property
    def nb_users(self):
        return UserComp.objects.filter(usergroup=self).count()

    def __str__(self):
        return self.group_name + " (Poids : " + str(self.weight) + " / " + str(self.nb_users) + " utilisateurs)"

class Event(models.Model):
    company = models.ForeignKey(
        Company, on_delete=models.CASCADE, verbose_name="société"
    )
    groups = models.ManyToManyField(UserGroup, verbose_name="groupes", blank=True)
    rules = [("MAJ", "Majorité"), ("PROP", "Proportionnelle")]
    event_name = models.CharField("nom", max_length=200)
    event_date = models.DateField("date de l'événement")
    slug = models.SlugField()
    current = models.BooleanField("en cours", default=False)
    quorum = models.IntegerField(default=33)
    rule = models.CharField(
        "mode de scrutin", max_length=5, choices=rules, default="MAJ"
    )

    class Meta:
        # Constraint(s) : an event_slug can be linked to only one company
        #       This allow several companies to use the same event_slugs,
        #       unless one sngle event_slug is used by company
        verbose_name = "Evénement"
        constraints = [
            models.UniqueConstraint(fields=["company_id", "slug"], name="unique_event_slug")
        ]

    def __str__(self):
        return self.event_name

Form (я совершенно уверен, что у меня уже был этот метод __init__() в моих первых рабочих тестах):

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']

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        instance = kwargs.get('instance', None)
        self.fields['groups'].queryset= UserGroup.objects.\
                                                filter(company=instance.company, hidden=False).\
                                                order_by('group_name')

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):
    '''
        Manage events creation and options
    '''
    company = Company.get_company(request.session['comp_slug'])
    # all_groups = list(UserGroup.objects.filter(company=company, hidden=False).order_by('group_name').values())

    if evt_id > 0:
        current_event = Event.objects.get(id=evt_id)
        event_form = EventDetail(request.POST or None, instance=current_event)

    else:
        event_form = EventDetail(request.POST or None)

    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()

        else:
            print("****** FORMULAIRE NON VALIDE *******")
            print(event_form.errors)

    return render(request, "polls/adm_event_detail.html", locals())

HTML (извлечение):

{% if evt_id %}
<form action="{% url 'polls:adm_event_detail' company.comp_slug evt_id %}" method="post">
{% else %}
<form action="{% url 'polls:adm_create_event' company.comp_slug %}" method="post">
{% endif %}
    {% csrf_token %}

    <p>Sélectionnez le(s) groupe(s) d'utilisateurs participants à l'événement :</p>
    <ul style="list-style-type: none">
    {% for grp in event_form.groups %}
        <li>{{ grp }}</li>
    {% endfor %}
    </ul>
</form>

Соответствующий дисплей (поле Группа 1 должно быть отмечено):
enter image description here

Я немного разочарован, что не было хотя бы комментария, но я наконец нашел, как со всем этим справиться и поделюсь этим здесь.
Во-первых, мне нужно было переместить все вещи в vie, поэтому форма стала очень простой:

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']

Для управления созданием и обновлением значений ключевыми моментами являются следующие:

  • Я только что понял, что модель на основе модели и без модели не эквивалентны queryset и initial, в плане создания формы. Я могу предположить, что для многих из вас это очевидно, но для меня это было еще не так! Таким образом, для правильного отображения мне на самом деле нужно было и то, и другое: queryset атрибут отобразит весь список, initial проверит те, которые связаны с моим объектом
  • . Метода
  • save() недостаточно, когда поле связано с M2M-отношением. Я понял из документации (но я явно ошибался!), что этого будет достаточно, но я должен управлять этим файлом отдельно. В основном это означает следующее: добавьте значения в новую форму, и не забудьте удалить те, которые пользователи больше не хотят быть связанными.

Итак, вот мой окончательный рабочий вид:

@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)
        # question_set = QuestionFormset()

    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())

Если существует более простой подход, я был бы очень признателен узнать =)

Чтобы обеспечить полный ответ, я также изменил шаблон и использовал только одну строку:

    {{ event_form.groups }}

и специальное CSS свойство для скрытия буллитов списка:

#id_groups {
  list-style-type: none;
}
Вернуться на верх