Django - Как провести модульное тестирование представления, управляющего M2M-отношениями
У меня есть представление, которое реализует m2m отношения, и я хотел бы протестировать его, что мне пока не удалось. Представление вроде бы работает со страницей, которую я определил, но любые предложения также приветствуются.
Контекст следующий: Я хочу управлять группами пользователей в приложении Django, и, конечно, поскольку мне нужны дополнительные поля, я создал модель, предназначенную для управления пользователями в моем приложении. Я определил страницу с несколькими полями выбора, одно для списка пользователей, другое для пользователей, выбранных для участия в группе. Между ними находятся иконки действий для перемещения пользователей из одной группы в другие. На данном этапе нет контроля, если пользователь не принадлежит более чем к одной группе, отображаются все пользователи, которые не принадлежат к текущей группе (я предполагаю, что это просто вопрос фильтрации данных).
.
В настоящее время моя страница выглядит следующим образом (btw, если у вас есть предложения по отображению заголовков над полями выбора, я буду благодарен, даже если это не относится к данной теме.
).
Я хотел бы протестировать содержание каждой группы и, позже, влияние добавления или удаления пользователя из группы.
На данном этапе я смог только проверить, отображается ли пользователь в базе данных, но на самом деле я понятия не имею, входит ли он в ту или иную группу. Если я добавлю какие-то правила, например, проверю, не состоит ли пользователь уже в группе (или не предлагаю ли его добавить в группу в этом случае), мне нужно будет построить более точный тест, а я пока не знаю, как это сделать.
Вот мой текущий рабочий тестовый код:
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)