From c133ba5f6ee61465263bad7ae0b89d952eb73265 Mon Sep 17 00:00:00 2001 From: berk76 Date: Wed, 13 Nov 2024 23:00:57 +0100 Subject: [PATCH] Fault report on behalf of (#112) * Fault report on behalf of * Translations * Tests --- svjis/articles/forms.py | 16 +- .../articles/locale/cs/LC_MESSAGES/django.po | 8 +- .../articles/locale/en/LC_MESSAGES/django.po | 8 +- svjis/articles/templates/faults_create.html | 6 + svjis/articles/templates/faults_edit.html | 1 + svjis/articles/tests/factories/__init__.py | 4 + svjis/articles/tests/factories/company.py | 18 + svjis/articles/tests/factories/preferences.py | 11 + svjis/articles/tests/test_articles.py | 195 +++++++++++ svjis/articles/tests/test_faults.py | 75 +++++ svjis/articles/tests/test_menu.py | 72 ++++ svjis/articles/tests/test_migrations.py | 17 + svjis/articles/tests/test_views.py | 308 ------------------ svjis/articles/tests/testdata/__init__.py | 2 + .../tests/testdata/preferences_data.py | 14 + svjis/articles/tests/testdata/user_data.py | 17 +- svjis/articles/utils.py | 2 +- svjis/articles/views_faults.py | 68 ++-- 18 files changed, 493 insertions(+), 349 deletions(-) create mode 100644 svjis/articles/tests/factories/company.py create mode 100644 svjis/articles/tests/factories/preferences.py create mode 100644 svjis/articles/tests/test_articles.py create mode 100644 svjis/articles/tests/test_faults.py create mode 100644 svjis/articles/tests/test_menu.py create mode 100644 svjis/articles/tests/test_migrations.py delete mode 100644 svjis/articles/tests/test_views.py create mode 100644 svjis/articles/tests/testdata/preferences_data.py diff --git a/svjis/articles/forms.py b/svjis/articles/forms.py index 0555cc8..b1d4922 100644 --- a/svjis/articles/forms.py +++ b/svjis/articles/forms.py @@ -245,7 +245,7 @@ def label_from_instance(self, obj): class BoardForm(forms.ModelForm): - member = MemberModelChoiceField(queryset=User.objects.filter(is_active=True).order_by('last_name')) + member = MemberModelChoiceField(queryset=User.objects.filter(is_active=True).order_by('last_name', 'first_name')) class Meta: model = models.Board @@ -311,7 +311,7 @@ class Meta: } -class AssignedUserChoiceField(forms.ModelChoiceField): +class UserChoiceField(forms.ModelChoiceField): def label_from_instance(self, obj): return f"{obj.last_name} {obj.first_name}" @@ -323,24 +323,30 @@ class FaultReportForm(forms.ModelForm): help_text=_(SELECT_ENTRANCE_TEXT), label=_("Entrance"), ) - assigned_to_user = AssignedUserChoiceField( + assigned_to_user = UserChoiceField( queryset=User.objects.filter(groups__permissions__codename='svjis_fault_resolver') .exclude(is_active=False) .distinct() - .order_by('last_name'), + .order_by('last_name', 'first_name'), required=False, label=_("Resolver"), ) + created_by_user = UserChoiceField( + queryset=User.objects.exclude(is_active=False).distinct().order_by('last_name', 'first_name'), + required=False, + label=_("On Behalf Of"), + ) class Meta: model = models.FaultReport - fields = ("subject", "entrance", "description", "assigned_to_user", "closed") + fields = ("subject", "entrance", "description", "created_by_user", "assigned_to_user", "closed") widgets = { 'subject': forms.widgets.TextInput(attrs={'class': 'common-input', 'size': '80'}), 'entrance': forms.widgets.Select(attrs={'class': 'common-input'}), 'description': forms.widgets.Textarea( attrs={'class': 'common-textarea', 'rows': '5', 'cols': '80', 'wrap': True} ), + 'created_by_user': forms.widgets.Select(attrs={'class': 'common-input'}), 'assigned_to_user': forms.widgets.Select(attrs={'class': 'common-input'}), 'closed': forms.widgets.CheckboxInput(attrs={'class': 'common-input', 'size': '50'}), } diff --git a/svjis/articles/locale/cs/LC_MESSAGES/django.po b/svjis/articles/locale/cs/LC_MESSAGES/django.po index 9b25000..e413101 100644 --- a/svjis/articles/locale/cs/LC_MESSAGES/django.po +++ b/svjis/articles/locale/cs/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-10-10 10:18+0200\n" +"POT-Creation-Date: 2024-11-11 19:21+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -32,6 +32,10 @@ msgstr "Vchod" msgid "Resolver" msgstr "Řešitel" +#: articles/forms.py:337 +msgid "On Behalf Of" +msgstr "Za uživatele" + #: articles/models.py:14 articles/models.py:130 articles/models.py:440 #: articles/templates/redaction_useful_link.html:13 msgid "Header" @@ -318,7 +322,7 @@ msgstr "Smazat" #: articles/templates/admin_preferences_edit.html:27 #: articles/templates/admin_user_edit.html:94 #: articles/templates/advert_edit.html:41 -#: articles/templates/faults_create.html:31 +#: articles/templates/faults_create.html:37 #: articles/templates/faults_edit.html:34 #: articles/templates/personal_settings_edit.html:58 #: articles/templates/personal_settings_password.html:26 diff --git a/svjis/articles/locale/en/LC_MESSAGES/django.po b/svjis/articles/locale/en/LC_MESSAGES/django.po index be61439..793eace 100644 --- a/svjis/articles/locale/en/LC_MESSAGES/django.po +++ b/svjis/articles/locale/en/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-10-10 10:18+0200\n" +"POT-Creation-Date: 2024-11-11 19:20+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -31,6 +31,10 @@ msgstr "Entrance" msgid "Resolver" msgstr "Resolver" +#: articles/forms.py:337 +msgid "On Behalf Of" +msgstr "On Behalf Of" + #: articles/models.py:14 articles/models.py:130 articles/models.py:440 #: articles/templates/redaction_useful_link.html:13 msgid "Header" @@ -317,7 +321,7 @@ msgstr "Delete" #: articles/templates/admin_preferences_edit.html:27 #: articles/templates/admin_user_edit.html:94 #: articles/templates/advert_edit.html:41 -#: articles/templates/faults_create.html:31 +#: articles/templates/faults_create.html:37 #: articles/templates/faults_edit.html:34 #: articles/templates/personal_settings_edit.html:58 #: articles/templates/personal_settings_password.html:26 diff --git a/svjis/articles/templates/faults_create.html b/svjis/articles/templates/faults_create.html index f471c32..8b84c05 100644 --- a/svjis/articles/templates/faults_create.html +++ b/svjis/articles/templates/faults_create.html @@ -22,6 +22,12 @@

{% trans 'Fault report' %}

{{ form.description }}

{% if perms.articles.svjis_fault_resolver %} +

+ {{ form.created_by_user.label }}
+ {{ form.created_by_user }} +

+ {% endif %} + {% if perms.articles.svjis_fault_resolver %}

{{ form.assigned_to_user.label }}
{{ form.assigned_to_user }} diff --git a/svjis/articles/templates/faults_edit.html b/svjis/articles/templates/faults_edit.html index e4832fc..3da19a0 100644 --- a/svjis/articles/templates/faults_edit.html +++ b/svjis/articles/templates/faults_edit.html @@ -9,6 +9,7 @@

#{{ form.instance.pk }} - {{ form.instance.
{% csrf_token %} +

{{ form.subject.label }}
{{ form.subject }} diff --git a/svjis/articles/tests/factories/__init__.py b/svjis/articles/tests/factories/__init__.py index e7d1463..6fa8671 100644 --- a/svjis/articles/tests/factories/__init__.py +++ b/svjis/articles/tests/factories/__init__.py @@ -4,6 +4,8 @@ from .user import UserFactory from .article_menu import ArticleMenuFactory from .article import ArticleFactory +from .preferences import PreferencesFactory +from .company import CompanyFactory __all__ = [ "ContentTypeFactory", @@ -12,4 +14,6 @@ "UserFactory", "ArticleMenuFactory", "ArticleFactory", + "PreferencesFactory", + "CompanyFactory", ] diff --git a/svjis/articles/tests/factories/company.py b/svjis/articles/tests/factories/company.py new file mode 100644 index 0000000..09c5eae --- /dev/null +++ b/svjis/articles/tests/factories/company.py @@ -0,0 +1,18 @@ +import factory + +from ...models import Company + + +class CompanyFactory(factory.django.DjangoModelFactory): + name = factory.Faker("word") + address = factory.Faker("word") + city = factory.Faker("word") + post_code = factory.Faker("word") + phone = factory.Faker("word") + email = factory.Faker("word") + registration_no = factory.Faker("word") + vat_registration_no = factory.Faker("word") + internet_domain = factory.Faker("word") + + class Meta: + model = Company diff --git a/svjis/articles/tests/factories/preferences.py b/svjis/articles/tests/factories/preferences.py new file mode 100644 index 0000000..7318fcf --- /dev/null +++ b/svjis/articles/tests/factories/preferences.py @@ -0,0 +1,11 @@ +import factory + +from ...models import Preferences + + +class PreferencesFactory(factory.django.DjangoModelFactory): + key = factory.Faker("word") + value = factory.Faker("word") + + class Meta: + model = Preferences diff --git a/svjis/articles/tests/test_articles.py b/svjis/articles/tests/test_articles.py new file mode 100644 index 0000000..fd40097 --- /dev/null +++ b/svjis/articles/tests/test_articles.py @@ -0,0 +1,195 @@ +from django.test import TestCase +from django.urls import reverse + +from .testdata import ArticleDataMixin + + +class ArticleListTest(ArticleDataMixin, TestCase): + + def do_user_test(self, username, password, for_all, for_owners, for_board, article_list): + # Login user + if username == 'anonymous': + self.client.logout() + else: + logged_in = self.client.login(username=username, password=password) + self.assertTrue(logged_in) + + # Article for all + response = self.client.get(reverse('article', kwargs={'slug': self.article_for_all.slug})) + self.assertEqual(response.status_code, for_all) + + # Article for Owners + response = self.client.get(reverse('article', kwargs={'slug': self.article_for_owners.slug})) + self.assertEqual(response.status_code, for_owners) + + # Article for Board + response = self.client.get(reverse('article', kwargs={'slug': self.article_for_board.slug})) + self.assertEqual(response.status_code, for_board) + + # Main page + response = self.client.get(reverse('main')) + self.assertEqual(response.status_code, 200) + + # List of Articles + res_articles = response.context['article_list'] + self.assertEqual([a.header for a in res_articles], article_list) + + def test_admin_user(self): + self.do_user_test( + 'jarda', + 'jarda', + 200, + 200, + 200, + ['For Board', 'For Owners and Board', 'For Owners', 'For All'], + ) + + def test_board_user(self): + self.do_user_test( + 'jiri', + 'jiri', + 200, + 200, + 200, + ['For Board', 'For Owners and Board', 'For Owners', 'For All'], + ) + + def test_owner_user(self): + self.do_user_test( + 'petr', + 'petr', + 200, + 200, + 404, + ['For Owners and Board', 'For Owners', 'For All'], + ) + + def test_vendor_user(self): + self.do_user_test( + 'karel', + 'karel', + 200, + 404, + 404, + ['For All'], + ) + + def test_anonymous_user(self): + self.do_user_test('anonymous', '', 200, 404, 404, ['For All']) + + def test_top_articles(self): + # Login board user + logged_in = self.client.login(username='jiri', password='jiri') + self.assertTrue(logged_in) + + # Article for all + response = self.client.get(reverse('article', kwargs={'slug': self.article_for_all.slug})) + self.assertEqual(response.status_code, 200) + + # Article for Board + response = self.client.get(reverse('article', kwargs={'slug': self.article_for_board.slug})) + self.assertEqual(response.status_code, 200) + response = self.client.get(reverse('article', kwargs={'slug': self.article_for_board.slug})) + self.assertEqual(response.status_code, 200) + + # Main page + response = self.client.get(reverse('main')) + self.assertEqual(response.status_code, 200) + + # Top Articles + res_top = response.context['top_articles'] + self.assertEqual(len(res_top), 2) + self.assertEqual(res_top[0]['article_id'], self.article_for_board.pk) + self.assertEqual(res_top[0]['total'], 2) + self.assertEqual(res_top[1]['article_id'], self.article_for_all.pk) + self.assertEqual(res_top[1]['total'], 1) + + # Login owner user + logged_in = self.client.login(username='petr', password='petr') + self.assertTrue(logged_in) + + # Main page + response = self.client.get(reverse('main')) + self.assertEqual(response.status_code, 200) + + # Top Articles + res_top = response.context['top_articles'] + self.assertEqual(len(res_top), 1) + self.assertEqual(res_top[0]['article_id'], self.article_for_all.pk) + self.assertEqual(res_top[0]['total'], 1) + + # Logout user + self.client.logout() + + # Main page + response = self.client.get(reverse('main')) + self.assertEqual(response.status_code, 200) + + # Top Articles + res_top = response.context['top_articles'] + self.assertEqual(len(res_top), 1) + self.assertEqual(res_top[0]['article_id'], self.article_for_all.pk) + self.assertEqual(res_top[0]['total'], 1) + + def test_send_article_notifications(self): + # Login board user + logged_in = self.client.login(username='jiri', password='jiri') + self.assertTrue(logged_in) + + # Send notifications for article not published + response = self.client.get( + reverse('redaction_article_notifications', kwargs={'pk': self.article_not_published.pk}) + ) + self.assertEqual(response.status_code, 200) + res_recipients = response.context['object_list'] + self.assertEqual(len(res_recipients), 0) + + # Send notifications for no one + response = self.client.get( + reverse('redaction_article_notifications', kwargs={'pk': self.article_for_no_one.pk}) + ) + self.assertEqual(response.status_code, 200) + res_recipients = response.context['object_list'] + self.assertEqual(len(res_recipients), 0) + + # Send notifications for all + response = self.client.get(reverse('redaction_article_notifications', kwargs={'pk': self.article_for_all.pk})) + self.assertEqual(response.status_code, 200) + res_recipients = response.context['object_list'] + self.assertEqual(len(res_recipients), 4) + self.assertEqual(res_recipients[0].last_name, 'Beran') + self.assertEqual(res_recipients[1].last_name, 'Brambůrek') + self.assertEqual(res_recipients[2].last_name, 'Lukáš') + self.assertEqual(res_recipients[3].last_name, 'Nebus') + + # Send notifications for owners + response = self.client.get( + reverse('redaction_article_notifications', kwargs={'pk': self.article_for_owners.pk}) + ) + self.assertEqual(response.status_code, 200) + res_recipients = response.context['object_list'] + self.assertEqual(len(res_recipients), 3) + self.assertEqual(res_recipients[0].last_name, 'Beran') + self.assertEqual(res_recipients[1].last_name, 'Brambůrek') + self.assertEqual(res_recipients[2].last_name, 'Nebus') + + # Send notifications for board + response = self.client.get( + reverse('redaction_article_notifications', kwargs={'pk': self.article_for_board.pk}) + ) + self.assertEqual(response.status_code, 200) + res_recipients = response.context['object_list'] + self.assertEqual(len(res_recipients), 2) + self.assertEqual(res_recipients[0].last_name, 'Beran') + self.assertEqual(res_recipients[1].last_name, 'Brambůrek') + + # Send notifications for board + response = self.client.get( + reverse('redaction_article_notifications', kwargs={'pk': self.article_for_owners_and_board.pk}) + ) + self.assertEqual(response.status_code, 200) + res_recipients = response.context['object_list'] + self.assertEqual(len(res_recipients), 3) + self.assertEqual(res_recipients[0].last_name, 'Beran') + self.assertEqual(res_recipients[1].last_name, 'Brambůrek') + self.assertEqual(res_recipients[2].last_name, 'Nebus') diff --git a/svjis/articles/tests/test_faults.py b/svjis/articles/tests/test_faults.py new file mode 100644 index 0000000..3b0d5bc --- /dev/null +++ b/svjis/articles/tests/test_faults.py @@ -0,0 +1,75 @@ +from django.test import TestCase +from django.urls import reverse + +from .testdata import UserDataMixin + + +class FaultsTest(UserDataMixin, TestCase): + + def create_fault_and_get_created_by(self, username, password, fault_form): + logged_in = self.client.login(username=username, password=password) + self.assertTrue(logged_in) + response = self.client.post( + reverse('faults_fault_create_save'), + fault_form, + follow=True, + ) + self.assertEqual(response.status_code, 200) + return response.context['obj'] + + # Created by user + def test_owner_created_by_user(self): + fault = self.create_fault_and_get_created_by( + "petr", "petr", {'pk': 0, 'subject': 'test', 'description': 'test', 'created_by_user': ''} + ) + self.assertEqual(fault.created_by_user, self.u_petr) + + def test_owner_on_behalf_of(self): + fault = self.create_fault_and_get_created_by( + "petr", "petr", {'pk': 0, 'subject': 'test', 'description': 'test', 'created_by_user': self.u_jarda.pk} + ) + self.assertEqual(fault.created_by_user, self.u_petr) + + def test_board_on_behalf_of(self): + fault = self.create_fault_and_get_created_by( + "jiri", "jiri", {'pk': 0, 'subject': 'test', 'description': 'test', 'created_by_user': self.u_jarda.pk} + ) + self.assertEqual(fault.created_by_user, self.u_jarda) + + def test_board_created_by_user_missing(self): + fault = self.create_fault_and_get_created_by( + "jiri", "jiri", {'pk': 0, 'subject': 'test', 'description': 'test'} + ) + self.assertEqual(fault.created_by_user, self.u_jiri) + + # Assigned to user + def test_owner_assigned_to_user_empty(self): + fault = self.create_fault_and_get_created_by( + "petr", "petr", {'pk': 0, 'subject': 'test', 'description': 'test', 'assigned_to_user': ''} + ) + self.assertEqual(fault.assigned_to_user, None) + + def test_owner_assigned_to_user(self): + fault = self.create_fault_and_get_created_by( + "petr", "petr", {'pk': 0, 'subject': 'test', 'description': 'test', 'assigned_to_user': self.u_jarda.pk} + ) + self.assertEqual(fault.assigned_to_user, None) + + def test_board_assigned_to_user(self): + fault = self.create_fault_and_get_created_by( + "jiri", "jiri", {'pk': 0, 'subject': 'test', 'description': 'test', 'assigned_to_user': self.u_jarda.pk} + ) + self.assertEqual(fault.assigned_to_user, self.u_jarda) + + # Closed + def test_owner_closed(self): + fault = self.create_fault_and_get_created_by( + "petr", "petr", {'pk': 0, 'subject': 'test', 'description': 'test', 'closed': True} + ) + self.assertEqual(fault.closed, False) + + def test_board_closed(self): + fault = self.create_fault_and_get_created_by( + "jiri", "jiri", {'pk': 0, 'subject': 'test', 'description': 'test', 'closed': True} + ) + self.assertEqual(fault.closed, True) diff --git a/svjis/articles/tests/test_menu.py b/svjis/articles/tests/test_menu.py new file mode 100644 index 0000000..3c94ab6 --- /dev/null +++ b/svjis/articles/tests/test_menu.py @@ -0,0 +1,72 @@ +from django.test import TestCase +from django.urls import reverse +from .. import ( + views, + views_contact, + views_personal_settings, + views_redaction, + views_faults, + views_adverts, + views_admin, +) + +from .testdata import ArticleDataMixin + + +class MenuTest(ArticleDataMixin, TestCase): + + def do_menu_test(self, username, password, menu_list): + menu = [ + {'item': 'Articles', 'link': reverse(views.main_view)}, + {'item': 'Contact', 'link': reverse(views_contact.contact_view)}, + {'item': 'Personal settings', 'link': reverse(views_personal_settings.personal_settings_edit_view)}, + {'item': 'Redaction', 'link': reverse(views_redaction.redaction_article_view)}, + {'item': 'Fault reporting', 'link': reverse(views_faults.faults_list_view) + '?scope=open'}, + {'item': 'Adverts', 'link': reverse(views_adverts.adverts_list_view) + '?scope=all'}, + {'item': 'Administration', 'link': reverse(views_admin.admin_company_edit_view)}, + ] + # Login user + if username == 'anonymous': + self.client.logout() + else: + logged_in = self.client.login(username=username, password=password) + self.assertTrue(logged_in) + + # Main page + response = self.client.get(reverse('main')) + self.assertEqual(response.status_code, 200) + + # Menu + res_tray_menu = response.context['tray_menu_items'] + self.assertEqual( + [m['description'] for m in res_tray_menu], + menu_list, + ) + + # Menu access + for m in menu: + response = self.client.get(m['link']) + if m['item'] in menu_list: + self.assertEqual(response.status_code, 200) + else: + self.assertEqual(response.status_code, 302) + self.assertTrue(response.url.startswith('/?next=')) + + def test_admin_user(self): + self.do_menu_test( + 'jarda', + 'jarda', + ['Articles', 'Contact', 'Personal settings', 'Redaction', 'Fault reporting', 'Administration'], + ) + + def test_board_user(self): + self.do_menu_test('jiri', 'jiri', ['Articles', 'Contact', 'Personal settings', 'Redaction', 'Fault reporting']) + + def test_owner_user(self): + self.do_menu_test('petr', 'petr', ['Articles', 'Contact', 'Personal settings', 'Fault reporting']) + + def test_vendor_user(self): + self.do_menu_test('karel', 'karel', ['Articles', 'Contact', 'Personal settings', 'Fault reporting']) + + def test_anonymous_user(self): + self.do_menu_test('anonymous', '', ['Articles', 'Contact']) diff --git a/svjis/articles/tests/test_migrations.py b/svjis/articles/tests/test_migrations.py new file mode 100644 index 0000000..06d697c --- /dev/null +++ b/svjis/articles/tests/test_migrations.py @@ -0,0 +1,17 @@ +from io import StringIO +from django.core.management import call_command +from django.test import TestCase + + +class PendingMigrationsTests(TestCase): + def test_no_pending_migrations(self): + out = StringIO() + try: + call_command( + "makemigrations", + "--check", + stdout=out, + stderr=StringIO(), + ) + except SystemExit: # pragma: no cover + raise AssertionError("Pending migrations:\n" + out.getvalue()) from None diff --git a/svjis/articles/tests/test_views.py b/svjis/articles/tests/test_views.py deleted file mode 100644 index 02e8b98..0000000 --- a/svjis/articles/tests/test_views.py +++ /dev/null @@ -1,308 +0,0 @@ -from io import StringIO -from django.core.management import call_command -from django.test import TestCase -from django.urls import reverse - -from .testdata import ArticleDataMixin - - -class ArticleListTest(ArticleDataMixin, TestCase): - - def test_admin_user(self): - # Login user - logged_in = self.client.login(username='jarda', password='jarda') - self.assertTrue(logged_in) - - # Article for all - response = self.client.get(reverse('article', kwargs={'slug': self.article_for_all.slug})) - self.assertEqual(response.status_code, 200) - - # Article for Board - response = self.client.get(reverse('article', kwargs={'slug': self.article_for_board.slug})) - self.assertEqual(response.status_code, 200) - - # Main page - response = self.client.get(reverse('main')) - self.assertEqual(response.status_code, 200) - - # Menu - res_tray_menu = response.context['tray_menu_items'] - self.assertEqual(len(res_tray_menu), 5) - self.assertEqual(res_tray_menu[0]['description'], 'Articles') - self.assertEqual(res_tray_menu[1]['description'], 'Contact') - self.assertEqual(res_tray_menu[2]['description'], 'Personal settings') - self.assertEqual(res_tray_menu[3]['description'], 'Redaction') - self.assertEqual(res_tray_menu[4]['description'], 'Administration') - - # List of Articles - res_articles = response.context['article_list'] - self.assertEqual(len(res_articles), 4) - self.assertEqual(res_articles[0].header, 'For Board') - self.assertEqual(res_articles[1].header, 'For Owners and Board') - self.assertEqual(res_articles[2].header, 'For Owners') - self.assertEqual(res_articles[3].header, 'For All') - - def test_board_user(self): - # Login user - logged_in = self.client.login(username='jiri', password='jiri') - self.assertTrue(logged_in) - - # Article for all - response = self.client.get(reverse('article', kwargs={'slug': self.article_for_all.slug})) - self.assertEqual(response.status_code, 200) - - # Article for Board - response = self.client.get(reverse('article', kwargs={'slug': self.article_for_board.slug})) - self.assertEqual(response.status_code, 200) - response = self.client.get(reverse('article', kwargs={'slug': self.article_for_board.slug})) - self.assertEqual(response.status_code, 200) - - # Main page - response = self.client.get(reverse('main')) - self.assertEqual(response.status_code, 200) - - # Menu - res_tray_menu = response.context['tray_menu_items'] - self.assertEqual(len(res_tray_menu), 4) - self.assertEqual(res_tray_menu[0]['description'], 'Articles') - self.assertEqual(res_tray_menu[1]['description'], 'Contact') - self.assertEqual(res_tray_menu[2]['description'], 'Personal settings') - self.assertEqual(res_tray_menu[3]['description'], 'Redaction') - - # List of Articles - res_articles = response.context['article_list'] - self.assertEqual(len(res_articles), 4) - self.assertEqual(res_articles[0].header, 'For Board') - self.assertEqual(res_articles[1].header, 'For Owners and Board') - self.assertEqual(res_articles[2].header, 'For Owners') - self.assertEqual(res_articles[3].header, 'For All') - - def test_owner_user(self): - # Login user - logged_in = self.client.login(username='petr', password='petr') - self.assertTrue(logged_in) - - # Article for all - response = self.client.get(reverse('article', kwargs={'slug': self.article_for_all.slug})) - self.assertEqual(response.status_code, 200) - - # Article for Owners - response = self.client.get(reverse('article', kwargs={'slug': self.article_for_owners.slug})) - self.assertEqual(response.status_code, 200) - - # Article for Board - response = self.client.get(reverse('article', kwargs={'slug': self.article_for_board.slug})) - self.assertEqual(response.status_code, 404) - - # Main page - response = self.client.get(reverse('main')) - self.assertEqual(response.status_code, 200) - - # Menu - res_tray_menu = response.context['tray_menu_items'] - self.assertEqual(len(res_tray_menu), 3) - self.assertEqual(res_tray_menu[0]['description'], 'Articles') - self.assertEqual(res_tray_menu[1]['description'], 'Contact') - self.assertEqual(res_tray_menu[2]['description'], 'Personal settings') - - # List of Articles - res_articles = response.context['article_list'] - self.assertEqual(len(res_articles), 3) - self.assertEqual(res_articles[0].header, 'For Owners and Board') - self.assertEqual(res_articles[1].header, 'For Owners') - self.assertEqual(res_articles[2].header, 'For All') - - def test_vendor_user(self): - # Login user - logged_in = self.client.login(username='karel', password='karel') - self.assertTrue(logged_in) - - # Article for all - response = self.client.get(reverse('article', kwargs={'slug': self.article_for_all.slug})) - self.assertEqual(response.status_code, 200) - - # Article for Owners - response = self.client.get(reverse('article', kwargs={'slug': self.article_for_owners.slug})) - self.assertEqual(response.status_code, 404) - - # Article for Board - response = self.client.get(reverse('article', kwargs={'slug': self.article_for_board.slug})) - self.assertEqual(response.status_code, 404) - - # Main page - response = self.client.get(reverse('main')) - self.assertEqual(response.status_code, 200) - - # Menu - res_tray_menu = response.context['tray_menu_items'] - self.assertEqual(len(res_tray_menu), 3) - self.assertEqual(res_tray_menu[0]['description'], 'Articles') - self.assertEqual(res_tray_menu[1]['description'], 'Contact') - self.assertEqual(res_tray_menu[2]['description'], 'Personal settings') - - # List of Articles - res_articles = response.context['article_list'] - self.assertEqual(len(res_articles), 1) - self.assertEqual(res_articles[0].header, 'For All') - - def test_anonymous_user(self): - # Logout user - self.client.logout() - - # Article for all - response = self.client.get(reverse('article', kwargs={'slug': self.article_for_all.slug})) - self.assertEqual(response.status_code, 200) - - # Article for Owners - response = self.client.get(reverse('article', kwargs={'slug': self.article_for_owners.slug})) - self.assertEqual(response.status_code, 404) - - # Article for Board - response = self.client.get(reverse('article', kwargs={'slug': self.article_for_board.slug})) - self.assertEqual(response.status_code, 404) - - # Main page - response = self.client.get(reverse('main')) - self.assertEqual(response.status_code, 200) - - # Menu - res_tray_menu = response.context['tray_menu_items'] - self.assertEqual(len(res_tray_menu), 2) - self.assertEqual(res_tray_menu[0]['description'], 'Articles') - self.assertEqual(res_tray_menu[1]['description'], 'Contact') - - # List of Articles - res_articles = response.context['article_list'] - self.assertEqual(len(res_articles), 1) - self.assertEqual(res_articles[0].header, 'For All') - - def test_top_articles(self): - # Login board user - logged_in = self.client.login(username='jiri', password='jiri') - self.assertTrue(logged_in) - - # Article for all - response = self.client.get(reverse('article', kwargs={'slug': self.article_for_all.slug})) - self.assertEqual(response.status_code, 200) - - # Article for Board - response = self.client.get(reverse('article', kwargs={'slug': self.article_for_board.slug})) - self.assertEqual(response.status_code, 200) - response = self.client.get(reverse('article', kwargs={'slug': self.article_for_board.slug})) - self.assertEqual(response.status_code, 200) - - # Main page - response = self.client.get(reverse('main')) - self.assertEqual(response.status_code, 200) - - # Top Articles - res_top = response.context['top_articles'] - self.assertEqual(len(res_top), 2) - self.assertEqual(res_top[0]['article_id'], self.article_for_board.pk) - self.assertEqual(res_top[0]['total'], 2) - self.assertEqual(res_top[1]['article_id'], self.article_for_all.pk) - self.assertEqual(res_top[1]['total'], 1) - - # Login owner user - logged_in = self.client.login(username='petr', password='petr') - self.assertTrue(logged_in) - - # Main page - response = self.client.get(reverse('main')) - self.assertEqual(response.status_code, 200) - - # Top Articles - res_top = response.context['top_articles'] - self.assertEqual(len(res_top), 1) - self.assertEqual(res_top[0]['article_id'], self.article_for_all.pk) - self.assertEqual(res_top[0]['total'], 1) - - # Logout user - self.client.logout() - - # Main page - response = self.client.get(reverse('main')) - self.assertEqual(response.status_code, 200) - - # Top Articles - res_top = response.context['top_articles'] - self.assertEqual(len(res_top), 1) - self.assertEqual(res_top[0]['article_id'], self.article_for_all.pk) - self.assertEqual(res_top[0]['total'], 1) - - def test_send_article_notifications(self): - # Login board user - logged_in = self.client.login(username='jiri', password='jiri') - self.assertTrue(logged_in) - - # Send notifications for article not published - response = self.client.get( - reverse('redaction_article_notifications', kwargs={'pk': self.article_not_published.pk}) - ) - self.assertEqual(response.status_code, 200) - res_recipients = response.context['object_list'] - self.assertEqual(len(res_recipients), 0) - - # Send notifications for no one - response = self.client.get( - reverse('redaction_article_notifications', kwargs={'pk': self.article_for_no_one.pk}) - ) - self.assertEqual(response.status_code, 200) - res_recipients = response.context['object_list'] - self.assertEqual(len(res_recipients), 0) - - # Send notifications for all - response = self.client.get(reverse('redaction_article_notifications', kwargs={'pk': self.article_for_all.pk})) - self.assertEqual(response.status_code, 200) - res_recipients = response.context['object_list'] - self.assertEqual(len(res_recipients), 4) - self.assertEqual(res_recipients[0].last_name, 'Beran') - self.assertEqual(res_recipients[1].last_name, 'Brambůrek') - self.assertEqual(res_recipients[2].last_name, 'Lukáš') - self.assertEqual(res_recipients[3].last_name, 'Nebus') - - # Send notifications for owners - response = self.client.get( - reverse('redaction_article_notifications', kwargs={'pk': self.article_for_owners.pk}) - ) - self.assertEqual(response.status_code, 200) - res_recipients = response.context['object_list'] - self.assertEqual(len(res_recipients), 3) - self.assertEqual(res_recipients[0].last_name, 'Beran') - self.assertEqual(res_recipients[1].last_name, 'Brambůrek') - self.assertEqual(res_recipients[2].last_name, 'Nebus') - - # Send notifications for board - response = self.client.get( - reverse('redaction_article_notifications', kwargs={'pk': self.article_for_board.pk}) - ) - self.assertEqual(response.status_code, 200) - res_recipients = response.context['object_list'] - self.assertEqual(len(res_recipients), 2) - self.assertEqual(res_recipients[0].last_name, 'Beran') - self.assertEqual(res_recipients[1].last_name, 'Brambůrek') - - # Send notifications for board - response = self.client.get( - reverse('redaction_article_notifications', kwargs={'pk': self.article_for_owners_and_board.pk}) - ) - self.assertEqual(response.status_code, 200) - res_recipients = response.context['object_list'] - self.assertEqual(len(res_recipients), 3) - self.assertEqual(res_recipients[0].last_name, 'Beran') - self.assertEqual(res_recipients[1].last_name, 'Brambůrek') - self.assertEqual(res_recipients[2].last_name, 'Nebus') - - -class PendingMigrationsTests(TestCase): - def test_no_pending_migrations(self): - out = StringIO() - try: - call_command( - "makemigrations", - "--check", - stdout=out, - stderr=StringIO(), - ) - except SystemExit: # pragma: no cover - raise AssertionError("Pending migrations:\n" + out.getvalue()) from None diff --git a/svjis/articles/tests/testdata/__init__.py b/svjis/articles/tests/testdata/__init__.py index 26b0ddb..e29826d 100644 --- a/svjis/articles/tests/testdata/__init__.py +++ b/svjis/articles/tests/testdata/__init__.py @@ -1,7 +1,9 @@ from .user_data import UserDataMixin from .article_data import ArticleDataMixin +from .preferences_data import PreferencesDataMixin __all__ = ( "UserDataMixin", "ArticleDataMixin", + "PreferencesDataMixin", ) diff --git a/svjis/articles/tests/testdata/preferences_data.py b/svjis/articles/tests/testdata/preferences_data.py new file mode 100644 index 0000000..bea01e9 --- /dev/null +++ b/svjis/articles/tests/testdata/preferences_data.py @@ -0,0 +1,14 @@ +from ..factories import PreferencesFactory, CompanyFactory + + +class PreferencesDataMixin: + + @classmethod + def setUpTestData(cls): + cls.company = CompanyFactory(name="Testing company", internet_domain="www.test.cz") + cls.p_fault_notif = PreferencesFactory( + key="mail.template.fault.notification", value="Uživatel {} vložil novou závadu {}:


{}" + ) + cls.p_fault_assigned = PreferencesFactory( + key="mail.template.fault.assigned", value="Uživatel {} vám přiřadil tiket {}:


{}" + ) diff --git a/svjis/articles/tests/testdata/user_data.py b/svjis/articles/tests/testdata/user_data.py index a8c861e..ec69959 100644 --- a/svjis/articles/tests/testdata/user_data.py +++ b/svjis/articles/tests/testdata/user_data.py @@ -1,10 +1,13 @@ from ..factories import GroupFactory, UserFactory, PermissionFactory +from .preferences_data import PreferencesDataMixin -class UserDataMixin: +class UserDataMixin(PreferencesDataMixin): @classmethod def setUpTestData(cls): + super().setUpTestData() + cls.g_owner = GroupFactory( name="owner", permissions=[ @@ -12,6 +15,9 @@ def setUpTestData(cls): PermissionFactory(codename="svjis_view_personal_menu"), PermissionFactory(codename="svjis_view_phonelist"), PermissionFactory(codename="svjis_answer_survey"), + PermissionFactory(codename="svjis_view_fault_menu"), + PermissionFactory(codename="svjis_fault_reporter"), + PermissionFactory(codename="svjis_add_fault_comment"), ], ) cls.g_board_member = GroupFactory( @@ -20,6 +26,7 @@ def setUpTestData(cls): PermissionFactory(codename="svjis_add_article_comment"), PermissionFactory(codename="svjis_view_personal_menu"), PermissionFactory(codename="svjis_view_phonelist"), + PermissionFactory(codename="svjis_fault_resolver"), ], ) cls.g_vendor = GroupFactory( @@ -27,6 +34,10 @@ def setUpTestData(cls): permissions=[ PermissionFactory(codename="svjis_add_article_comment"), PermissionFactory(codename="svjis_view_personal_menu"), + PermissionFactory(codename="svjis_view_fault_menu"), + PermissionFactory(codename="svjis_fault_reporter"), + PermissionFactory(codename="svjis_fault_resolver"), + PermissionFactory(codename="svjis_add_fault_comment"), ], ) cls.g_redactor = GroupFactory( @@ -55,6 +66,10 @@ def setUpTestData(cls): PermissionFactory(codename="svjis_edit_admin_company"), PermissionFactory(codename="svjis_edit_admin_building"), PermissionFactory(codename="svjis_view_phonelist"), + PermissionFactory(codename="svjis_view_fault_menu"), + PermissionFactory(codename="svjis_fault_reporter"), + PermissionFactory(codename="svjis_fault_resolver"), + PermissionFactory(codename="svjis_add_fault_comment"), ], ) diff --git a/svjis/articles/utils.py b/svjis/articles/utils.py index a36d51b..f3ce4d9 100644 --- a/svjis/articles/utils.py +++ b/svjis/articles/utils.py @@ -148,7 +148,7 @@ def adjust_worksheet_columns_width(ws, max_width=1000): def send_mails(recipient_list: list, subject: str, html_body: str, immediately: bool) -> None: if settings.EMAIL_HOST == '': - logger.error("Error: It seems E-Mail system is not configured yet.") + logger.error("Warning: It seems E-Mail system is not configured yet.") return if immediately: diff --git a/svjis/articles/views_faults.py b/svjis/articles/views_faults.py index 4f4154d..556fbf6 100644 --- a/svjis/articles/views_faults.py +++ b/svjis/articles/views_faults.py @@ -146,7 +146,7 @@ def faults_fault_edit_view(request, pk): @permission_required("articles.svjis_fault_reporter") @require_GET def faults_fault_create_view(request): - form = forms.FaultReportForm + form = forms.FaultReportForm(initial={'created_by_user': request.user.pk}) ctx = utils.get_context() ctx['aside_menu_name'] = _("Fault reporting") ctx['form'] = form @@ -162,38 +162,42 @@ def faults_fault_create_save_view(request): pk = int(request.POST['pk']) form = forms.FaultReportForm(request.POST) - if form.is_valid(): - obj = form.save(commit=False) - if pk == 0: - obj.created_by_user = request.user - obj.save() - - # Set watching users - if pk == 0: - obj.watching_users.add(request.user) - resolvers = ( - User.objects.filter(groups__permissions__codename='svjis_fault_resolver') - .exclude(is_active=False) - .distinct() - ) - for u in resolvers: - obj.watching_users.add(u) - - # Send notifications - recipients = [u for u in obj.watching_users.all() if u != request.user] - utils.send_new_fault_notification(recipients, f"{request.scheme}://{request.get_host()}", obj) - - # Send assigned notification - if obj.assigned_to_user is not None and request.user != obj.assigned_to_user: - utils.send_fault_assigned_notification( - obj.assigned_to_user, request.user, f"{request.scheme}://{request.get_host()}", obj - ) - return redirect(fault_view, slug=obj.slug) - else: + if not form.is_valid() or pk != 0: for error in form.errors: messages.error(request, error) return redirect(reverse(faults_list_view) + '?scope=open') + obj = form.save(commit=False) + if ( + "created_by_user" not in form.data + or form.data["created_by_user"] == '' + or not request.user.has_perm('articles.svjis_fault_resolver') + ): + obj.created_by_user = request.user + if not request.user.has_perm('articles.svjis_fault_resolver'): + obj.assigned_to_user = None + obj.closed = False + obj.save() + + # Set watching users + obj.watching_users.add(obj.created_by_user) + resolvers = ( + User.objects.filter(groups__permissions__codename='svjis_fault_resolver').exclude(is_active=False).distinct() + ) + for u in resolvers: + obj.watching_users.add(u) + + # Send notifications + recipients = [u for u in obj.watching_users.all() if u != obj.created_by_user] + utils.send_new_fault_notification(recipients, f"{request.scheme}://{request.get_host()}", obj) + + # Send assigned notification + if obj.assigned_to_user is not None and request.user != obj.assigned_to_user: + utils.send_fault_assigned_notification( + obj.assigned_to_user, request.user, f"{request.scheme}://{request.get_host()}", obj + ) + return redirect(fault_view, slug=obj.slug) + @permission_required("articles.svjis_fault_resolver") @require_POST @@ -214,7 +218,11 @@ def faults_fault_update_view(request): instance.watching_users.add(request.user) # Send assigned notification - if original_resolver != instance.assigned_to_user and request.user != instance.assigned_to_user: + if ( + original_resolver != instance.assigned_to_user + and request.user != instance.assigned_to_user + and None is not instance.assigned_to_user + ): utils.send_fault_assigned_notification( instance.assigned_to_user, request.user, f"{request.scheme}://{request.get_host()}", instance )