Django - Как провести модульное тестирование представления, управляющего M2M-отношениями

У меня есть представление, которое реализует m2m отношения, и я хотел бы протестировать его, что мне пока не удалось. Представление вроде бы работает со страницей, которую я определил, но любые предложения также приветствуются.
Контекст следующий: Я хочу управлять группами пользователей в приложении Django, и, конечно, поскольку мне нужны дополнительные поля, я создал модель, предназначенную для управления пользователями в моем приложении. Я определил страницу с несколькими полями выбора, одно для списка пользователей, другое для пользователей, выбранных для участия в группе. Между ними находятся иконки действий для перемещения пользователей из одной группы в другие. На данном этапе нет контроля, если пользователь не принадлежит более чем к одной группе, отображаются все пользователи, которые не принадлежат к текущей группе (я предполагаю, что это просто вопрос фильтрации данных).
. В настоящее время моя страница выглядит следующим образом (btw, если у вас есть предложения по отображению заголовков над полями выбора, я буду благодарен, даже если это не относится к данной теме.
). enter image description here

Я хотел бы протестировать содержание каждой группы и, позже, влияние добавления или удаления пользователя из группы.
На данном этапе я смог только проверить, отображается ли пользователь в базе данных, но на самом деле я понятия не имею, входит ли он в ту или иную группу. Если я добавлю какие-то правила, например, проверю, не состоит ли пользователь уже в группе (или не предлагаю ли его добавить в группу в этом случае), мне нужно будет построить более точный тест, а я пока не знаю, как это сделать.

Вот мой текущий рабочий тестовый код:

class TestAdmGroups(TestCase):
    def setUp(self):
        self.company = create_dummy_company("Société de test")

        self.group1 = EventGroup.create_group({"company": self.company, "group_name": "Groupe 1", "weight": 40})
        self.group2 = EventGroup.create_group({"company": self.company, "group_name": "Groupe 2", "weight": 60})
        # self.group2 = EventGroup.objects.create(company=self.company, group_name="Groupe 2", weight=60)

        self.user_staff = create_dummy_user(self.company, "staff", group=self.group1, admin=True)
        self.usr11 = create_dummy_user(self.company, "user11", group=self.group1)
        self.usr12 = create_dummy_user(self.company, "user12", group=self.group1, admin=True)
        self.usr13 = create_dummy_user(self.company, "user13")
        self.usr14 = create_dummy_user(self.company, "user14")
        self.usr21 = create_dummy_user(self.company, "user21", group=self.group2)
        self.usr22 = create_dummy_user(self.company, "user22", group=self.group2)

    def test_adm_update_group(self):
        self.client.force_login(self.user_staff.user)
        url = reverse("polls:adm_group_detail", args=[self.company.comp_slug, self.group1.id])
        response = self.client.get(url)
        self.assertEqual(response.status_code, 200)
        self.assertContains(response, "user11")
        self.assertContains(response, "user14")
        self.assertContains(response, "user21")

Я хотел бы разделить результаты и убедиться, что user11 является частью правого списка, а остальные значения должны быть частью левого.

Вид следующий:

def adm_group_detail(request, comp_slug, grp_id=0):
    company = Company.get_company(comp_slug)
    if grp_id > 0:
        current_group = EventGroup.objects.get(id=grp_id)
        group_form = GroupDetail(request.POST or None, instance=current_group)
    else:
        group_form = GroupDetail(request.POST or None)
        group_form.fields['all_users'].queryset = UserComp.objects.\
                                                    filter(company=company).\
                                                    order_by('user__last_name', 'user__first_name')


    if request.method == 'POST':
        # Convert the string in a list of user IDs
        usr_list = [int(elt) for elt in request.POST['group_list'].split('-') if elt != ""]

        group_form.fields['users'].queryset = UserComp.objects.filter(id__in=usr_list).\
                                                        order_by('user__last_name', 'user__first_name')
        group_form.fields['all_users'].queryset = UserComp.objects.exclude(id__in=usr_list)

        if group_form.is_valid():
            if grp_id == 0:
                # Create empty group
                group_data = {
                    "company": company,
                    "group_name": group_form.cleaned_data["group_name"],
                    "weight": group_form.cleaned_data["weight"],
                }
                new_group = EventGroup.create_group(group_data)
            else:
                # Update group
                new_group = group_form.save()

                # Remove all users
                group_usr_list = UserComp.objects.filter(eventgroup=new_group)
                for usr in group_usr_list:
                    new_group.users.remove(usr)

            # Common part for create and update : add users according to new/updated list
            for usr in usr_list:
                new_group.users.add(usr)
            new_group.save()

            # Update form according to latest changes
            group_form.fields['all_users'].queryset = UserComp.objects.\
                                                            exclude(id__in=usr_list).\
                                                            order_by('user__last_name', 'user__first_name')
            group_form.fields['group_list'].initial = "-".join([str(elt.id) for elt in new_group.users.all()])

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

Мне удалось заставить представление работать, когда оба списка являются частью одной формы, но я могу изменить это, если у вас есть какие-либо предложения.
Используя это представление, я заметил, что могу получать значения из одного списка или другого следующим образом: response.context["group_form"]["all_users"] или response.context["group_form"]["users"], но к сожалению, похоже, что невозможно ввести одно из этих значений в качестве параметра assertContains() (self.assertContains(response.context["group_form"]["users"], self.user11.user.username) не работает, так как первый параметр должен быть response) или assertInHTML(), в этом случае я имею следующее сообщение об ошибке с теми же предыдущими параметрами:

======================================================================
ERROR: test_adm_update_group (polls.tests_admin.TestAdmGroups)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "D:\Mes documents\Informatique\Developpement\Votes AG\projet_votes\polls\tests_admin.py", line 264, in test_adm_update_group
    self.assertInHTML(response.context["group_form"]["users"], self.usr11.user.username)
  File "C:\Users\Christophe\.virtualenvs\projet_votes-onIieQ0I\lib\site-packages\django\test\testcases.py", line 791, in assertInHTML        
    needle = assert_and_parse_html(self, needle, None, 'First argument is not valid HTML:')
  File "C:\Users\Christophe\.virtualenvs\projet_votes-onIieQ0I\lib\site-packages\django\test\testcases.py", line 62, in assert_and_parse_html
    dom = parse_html(html)
  File "C:\Users\Christophe\.virtualenvs\projet_votes-onIieQ0I\lib\site-packages\django\test\html.py", line 220, in parse_html
    parser.feed(html)
  File "c:\program files\python37\Lib\html\parser.py", line 110, in feed
    self.rawdata = self.rawdata + data
TypeError: can only concatenate str (not "BoundField") to str

----------------------------------------------------------------------

Как видно на скриншоте, я хочу проверить, находится ли пользователь в том или ином списке, а не только отображается на странице, как я уже сделал.

Вот определение модели:

class EventGroup(models.Model):
    """
    Groups of users
    The link with events is supported by the Event
    (as groups can be reused in several Events)
    """
    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)

    def __str__(self):
        return self.group_name

    class Meta:
        verbose_name = "Groupe d'utilisateurs"
        verbose_name_plural = "Groupes d'utilisateurs"

    @classmethod
    def create_group(cls, group_info):
        new_group = EventGroup(company=group_info["company"], group_name=group_info["group_name"], weight=group_info["weight"])
        new_group.save()
        return new_group

Если это может помочь, вот HTML код:

Кажется, я нашел ответ, который искал, и способ сделать мои тесты.
Идея заключалась в том, чтобы найти способ разделить содержание каждой формы, и это было сделано благодаря правильному применению атрибутов: выделите главную форму как ключ диктанта context, затем используйте атрибут fields для фильтрации и, наконец, примените атрибут queryset, чтобы иметь возможность управлять связанными данными соответствующим образом.
. Затем возник вопрос: "Как сделать сравнение с этим конкретным форматом?". Я нашел ответ, отфильтровав этот объект, воспользовавшись тем, что .filter() получит пустой список, если значение не найдено, тогда как .get() вызвал бы ошибку.

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

class TestAdmGroups(TestCase):
    def setUp(self):
        self.company = create_dummy_company("Société de test")

        self.group1 = EventGroup.create_group({"company": self.company, "group_name": "Groupe 1", "weight": 40})
        self.group2 = EventGroup.create_group({"company": self.company, "group_name": "Groupe 2", "weight": 60})
        # self.group2 = EventGroup.objects.create(company=self.company, group_name="Groupe 2", weight=60)

        self.user_staff = create_dummy_user(self.company, "staff", group=self.group1, admin=True)
        self.usr11 = create_dummy_user(self.company, "user11", group=self.group1)
        self.usr12 = create_dummy_user(self.company, "user12", group=self.group1, admin=True)
        self.usr13 = create_dummy_user(self.company, "user13")
        self.usr14 = create_dummy_user(self.company, "user14")
        self.usr21 = create_dummy_user(self.company, "user21", group=self.group2)
        self.usr22 = create_dummy_user(self.company, "user22", group=self.group2)

    def test_adm_update_group(self):
        self.client.force_login(self.user_staff.user)
        url = reverse("polls:adm_group_detail", args=[self.company.comp_slug, self.group1.id])
        response = self.client.get(url)
        self.assertEqual(response.status_code, 200)
        group_users = response.context["group_form"].fields["users"].queryset
        test_user = group_users.filter(id=self.usr11.id)
        self.assertEqual(len(test_user), 1)
        test_user = group_users.filter(id=self.usr14.id)
        self.assertEqual(len(test_user), 0)
Вернуться на верх