diff --git a/intervention/forms/modalForms.py b/intervention/forms/modalForms.py index a069267..cf996f9 100644 --- a/intervention/forms/modalForms.py +++ b/intervention/forms/modalForms.py @@ -182,7 +182,10 @@ class NewRevocationModalForm(BaseModalForm): return revocation -class RunCheckModalForm(BaseModalForm): +class CheckModalForm(BaseModalForm): + """ The modal form for running a check on interventions and their compensations + + """ checked_intervention = forms.BooleanField( label=_("Checked intervention data"), label_suffix="", diff --git a/intervention/templates/intervention/detail/includes/controls.html b/intervention/templates/intervention/detail/includes/controls.html index bb94042..f41c8b8 100644 --- a/intervention/templates/intervention/detail/includes/controls.html +++ b/intervention/templates/intervention/detail/includes/controls.html @@ -16,7 +16,7 @@ {% fa5_icon 'share-alt' %} {% if is_zb_member %} - {% endif %} diff --git a/intervention/tests/test_views.py b/intervention/tests/test_views.py index 70a7077..62f8d33 100644 --- a/intervention/tests/test_views.py +++ b/intervention/tests/test_views.py @@ -30,7 +30,7 @@ class InterventionViewTestCase(BaseViewTestCase): cls.remove_url = reverse("intervention:remove", args=(cls.intervention.id,)) cls.share_url = reverse("intervention:share", args=(cls.intervention.id, cls.intervention.access_token,)) cls.share_create_url = reverse("intervention:share-create", args=(cls.intervention.id,)) - cls.run_check_url = reverse("intervention:run-check", args=(cls.intervention.id,)) + cls.run_check_url = reverse("intervention:check", args=(cls.intervention.id,)) cls.record_url = reverse("intervention:record", args=(cls.intervention.id,)) cls.report_url = reverse("intervention:report", args=(cls.intervention.id,)) diff --git a/intervention/tests/test_workflow.py b/intervention/tests/test_workflow.py index 6f8bac6..8cc461b 100644 --- a/intervention/tests/test_workflow.py +++ b/intervention/tests/test_workflow.py @@ -7,13 +7,12 @@ Created on: 10.11.21 """ import datetime -from django.contrib.auth.models import Group from django.core.exceptions import ObjectDoesNotExist from django.urls import reverse from compensation.models import Payment, EcoAccountDeduction from intervention.models import Intervention -from konova.settings import DEFAULT_GROUP +from konova.settings import DEFAULT_GROUP, ETS_GROUP, ZB_GROUP from konova.tests.test_views import BaseWorkflowTestCase from user.models import UserActionLogEntry, UserAction @@ -26,11 +25,19 @@ class InterventionWorkflowTestCase(BaseWorkflowTestCase): @classmethod def setUpTestData(cls): super().setUpTestData() - cls.new_url = reverse("intervention:new", args=()) - # Add user to the default group -> give default permissions - default_group = Group.objects.get(name=DEFAULT_GROUP) - cls.superuser.groups.set([default_group]) + # Give the user shared access to the dummy intervention + cls.intervention.users.add(cls.superuser) + + def setUp(self) -> None: + """ Setup data before each test run + + Returns: + + """ + # Set the default group as only group for the user + default_group = self.groups.get(name=DEFAULT_GROUP) + self.superuser.groups.set([default_group]) def test_new(self): """ @@ -45,6 +52,8 @@ class InterventionWorkflowTestCase(BaseWorkflowTestCase): # Define the intervention identifier for easier handling on the next lines test_id = "Test_IDENTIFIER" + new_url = reverse("intervention:new", args=()) + # Expect the new intervention does not exist yet obj_exists = Intervention.objects.filter( identifier=test_id @@ -58,7 +67,7 @@ class InterventionWorkflowTestCase(BaseWorkflowTestCase): "geometry": "", } response = self.client_user.post( - self.new_url, + new_url, post_data ) @@ -80,6 +89,118 @@ class InterventionWorkflowTestCase(BaseWorkflowTestCase): self.assertIn(self.superuser, obj.users.all()) self.assertEqual(1, obj.users.count()) + def test_checkability(self): + """ Tests that the intervention can only be checked if all required data has been added + + Returns: + + """ + check_url = reverse("intervention:check", args=(self.intervention.id,)) + post_data = { + "checked_intervention": True, + "checked_comps": True, + } + + # First of all, the intervention should not be checked, yet + if self.intervention.checked: + self.intervention.checked.delete() + self.intervention.refresh_from_db() + + # Make sure the dummy compensation is currently not linked to the intervention, + # since the system would check on it's quality as well (and it would fail) + self.intervention.compensations.set([]) + + # Run request with an incomplete intervention and missing user privileges --> expect to fail + self.client_user.post(check_url, post_data) + + # We expect that the intervention is still not checked now + self.intervention.refresh_from_db() + self.assertIsNone(self.intervention.checked) + + # Now give the user the required privileges by adding to the registration office group + group = self.groups.get(name=ZB_GROUP) + self.superuser.groups.add(group) + + # Now fill in the missing data, so the intervention is 'valid' for checking + self.intervention = self.fill_out_intervention(self.intervention) + + # Then add a dummy payment, so we pass the quality check (Checks whether any kind of valid compensation exists) + payment = Payment.objects.create(amount=10.00, due_on=None, comment="No due date because test") + self.intervention.payments.add(payment) + + # Run request again + self.client_user.post(check_url, post_data) + + # Update intervention from db + self.intervention.refresh_from_db() + + # We expect the intervention to be checked now and contains the proper data + # Attention: We check the timestamp only on the date, not the time, since the microseconds delay would result + # in an unwanted assertion error + checked = self.intervention.checked + self.assertIsNotNone(checked) + self.assertEqual(self.superuser, checked.user) + self.assertEqual(datetime.date.today(), checked.timestamp.date()) + self.assertEqual(UserAction.CHECKED, checked.action) + + # Expect the user action now to live in the log + self.assertIn(checked, self.intervention.log.all()) + + def test_recordability(self): + """ Tests that the intervention can only be recorded if all required data has been added + + Returns: + + """ + record_url = reverse("intervention:record", args=(self.intervention.id,)) + post_data = { + "confirm": True, + } + + # Make sure the dummy compensation is currently not linked to the intervention, + # since we would check on it's quality as well then + self.intervention.compensations.set([]) + + # First of all, the intervention should not be recorded, yet + if self.intervention.recorded: + self.intervention.recorded.delete() + self.intervention.refresh_from_db() + + # Run request with an incomplete intervention and missing user privileges --> expect to fail + self.client_user.post(record_url, post_data) + + # We expect that the intervention is still not recorded now + self.intervention.refresh_from_db() + self.assertIsNone(self.intervention.recorded) + + # Now give the user the required privileges by adding to the ETS group + group = self.groups.get(name=ETS_GROUP) + self.superuser.groups.add(group) + + # Now fill in the missing data, so the intervention is 'valid' for recording + self.intervention = self.fill_out_intervention(self.intervention) + + # Then add a dummy payment, so we pass the quality check (Checks whether any kind of valid compensation exists) + payment = Payment.objects.create(amount=10.00, due_on=None, comment="No due date because test") + self.intervention.payments.add(payment) + + # Run request again + self.client_user.post(record_url, post_data) + + # Update intervention from db + self.intervention.refresh_from_db() + + # We expect the intervention to be recorded now and contains the proper data + # Attention: We check the timestamp only on the date, not the time, since the microseconds delay would result + # in an unwanted assertion error + self.assertIsNotNone(self.intervention.recorded) + self.assertEqual(self.superuser, self.intervention.recorded.user) + self.assertEqual(datetime.date.today(), self.intervention.recorded.timestamp.date()) + self.assertEqual(UserAction.RECORDED, self.intervention.recorded.action) + + # Expect the user action now to live in the log + self.assertIn(self.intervention.recorded, self.intervention.log.all()) + def subtest_add_payment(self): """ Subroutine for 'normal' payment tests @@ -89,9 +210,9 @@ class InterventionWorkflowTestCase(BaseWorkflowTestCase): Returns: """ - ## Attention: Despite the fact, this url refers to a compensation app route, we test it here for the interventions. - ## Reason: A payment is some kind of compensation for an intervention. Therefore it lives inside the compensation app. - ## BUT: Payments are added on the intervention detail page. Therefore it's part of a regular intervention workflow. + # Attention: Despite the fact, this url refers to a compensation app route, we test it here for the interventions. + # Reason: A payment is some kind of compensation for an intervention. Therefore it lives inside the compensation app. + # BUT: Payments are added on the intervention detail page. Therefore it's part of a regular intervention workflow. new_payment_url = reverse("compensation:pay-new", args=(self.intervention.id,)) # Make sure there are no payments on the intervention, yet diff --git a/intervention/urls.py b/intervention/urls.py index 022c3c9..df1e0d7 100644 --- a/intervention/urls.py +++ b/intervention/urls.py @@ -8,7 +8,7 @@ Created on: 30.11.20 from django.urls import path from intervention.views import index_view, new_view, detail_view, edit_view, remove_view, new_document_view, share_view, \ - create_share_view, remove_revocation_view, new_revocation_view, run_check_view, log_view, new_deduction_view, \ + create_share_view, remove_revocation_view, new_revocation_view, check_view, log_view, new_deduction_view, \ record_view, remove_document_view, get_document_view, get_revocation_view, new_id_view, report_view app_name = "intervention" @@ -22,7 +22,7 @@ urlpatterns = [ path('/remove', remove_view, name='remove'), path('/share/', share_view, name='share'), path('/share', create_share_view, name='share-create'), - path('/check', run_check_view, name='run-check'), + path('/check', check_view, name='check'), path('/record', record_view, name='record'), path('/report', report_view, name='report'), diff --git a/intervention/views.py b/intervention/views.py index c04594b..a1e45ce 100644 --- a/intervention/views.py +++ b/intervention/views.py @@ -5,7 +5,7 @@ from django.shortcuts import render from intervention.forms.forms import NewInterventionForm, EditInterventionForm from intervention.forms.modalForms import ShareInterventionModalForm, NewRevocationModalForm, \ - RunCheckModalForm, NewDeductionModalForm + CheckModalForm, NewDeductionModalForm from intervention.models import Intervention, Revocation, InterventionDocument, RevocationDocument from intervention.tables import InterventionTable from konova.contexts import BaseContext @@ -413,7 +413,7 @@ def create_share_view(request: HttpRequest, id: str): @login_required @registration_office_group_required @shared_access_required(Intervention, "id") -def run_check_view(request: HttpRequest, id: str): +def check_view(request: HttpRequest, id: str): """ Renders check form for an intervention Args: @@ -424,7 +424,7 @@ def run_check_view(request: HttpRequest, id: str): """ intervention = get_object_or_404(Intervention, id=id) - form = RunCheckModalForm(request.POST or None, instance=intervention, user=request.user) + form = CheckModalForm(request.POST or None, instance=intervention, user=request.user) return form.process_request( request, msg_success=_("Check performed"), diff --git a/konova/tests/test_views.py b/konova/tests/test_views.py index c493799..a3e9369 100644 --- a/konova/tests/test_views.py +++ b/konova/tests/test_views.py @@ -5,13 +5,15 @@ Contact: michel.peltriaux@sgdnord.rlp.de Created on: 26.10.21 """ -from abc import abstractmethod +import datetime from django.contrib.auth.models import User, Group +from django.contrib.gis.geos import MultiPolygon, Polygon from django.core.exceptions import ObjectDoesNotExist from django.test import TestCase, Client from django.urls import reverse +from codelist.models import KonovaCode from compensation.models import Compensation, CompensationState, CompensationAction, EcoAccount from intervention.models import LegalData, ResponsibilityData, Intervention from konova.management.commands.setup_data import GROUPS_DATA @@ -32,17 +34,22 @@ class BaseTestCase(TestCase): eco_account = None comp_state = None comp_action = None + codes = None superuser_pw = "root" user_pw = "root" + class Meta: + abstract = True + @classmethod def setUpTestData(cls): cls.create_users() cls.create_groups() - cls.create_dummy_intervention() - cls.create_dummy_compensation() - cls.create_dummy_eco_account() + cls.intervention = cls.create_dummy_intervention() + cls.compensation = cls.create_dummy_compensation() + cls.eco_account = cls.create_dummy_eco_account() + cls.codes = cls.create_dummy_codes() @classmethod def create_users(cls): @@ -69,9 +76,6 @@ class BaseTestCase(TestCase): ) cls.groups = Group.objects.all() - class Meta: - abstract = True - @classmethod def create_dummy_intervention(cls): """ Creates an intervention which can be used for tests @@ -91,7 +95,7 @@ class BaseTestCase(TestCase): responsibility_data = ResponsibilityData.objects.create() geometry = Geometry.objects.create() # Finally create main object, holding the other objects - cls.intervention = Intervention.objects.create( + intervention = Intervention.objects.create( identifier="TEST", title="Test_title", responsible=responsibility_data, @@ -100,8 +104,8 @@ class BaseTestCase(TestCase): geometry=geometry, comment="Test", ) - cls.intervention.generate_access_token(make_unique=True) - return cls.intervention + intervention.generate_access_token(make_unique=True) + return intervention @classmethod def create_dummy_compensation(cls): @@ -111,7 +115,7 @@ class BaseTestCase(TestCase): """ if cls.intervention is None: - cls.create_dummy_intervention() + cls.intervention = cls.create_dummy_intervention() # Create dummy data # Create log entry action = UserActionLogEntry.objects.create( @@ -120,7 +124,7 @@ class BaseTestCase(TestCase): ) geometry = Geometry.objects.create() # Finally create main object, holding the other objects - cls.compensation = Compensation.objects.create( + compensation = Compensation.objects.create( identifier="TEST", title="Test_title", intervention=cls.intervention, @@ -128,8 +132,7 @@ class BaseTestCase(TestCase): geometry=geometry, comment="Test", ) - cls.intervention.generate_access_token(make_unique=True) - return cls.compensation + return compensation @classmethod def create_dummy_eco_account(cls): @@ -149,7 +152,7 @@ class BaseTestCase(TestCase): lega_data = LegalData.objects.create() responsible_data = ResponsibilityData.objects.create() # Finally create main object, holding the other objects - cls.eco_account = EcoAccount.objects.create( + eco_account = EcoAccount.objects.create( identifier="TEST", title="Test_title", legal=lega_data, @@ -158,7 +161,7 @@ class BaseTestCase(TestCase): geometry=geometry, comment="Test", ) - return cls.eco_account + return eco_account @classmethod def create_dummy_states(cls): @@ -185,6 +188,47 @@ class BaseTestCase(TestCase): ) return cls.comp_action + @classmethod + def create_dummy_codes(cls): + """ Creates some dummy KonovaCodes which can be used for testing + + Returns: + + """ + codes = KonovaCode.objects.bulk_create([ + KonovaCode(id=1, is_selectable=True, long_name="Test1"), + KonovaCode(id=2, is_selectable=True, long_name="Test2"), + KonovaCode(id=3, is_selectable=True, long_name="Test3"), + KonovaCode(id=4, is_selectable=True, long_name="Test4"), + ]) + return codes + + @staticmethod + def fill_out_intervention(intervention: Intervention) -> Intervention: + """ Adds all required (dummy) data to an intervention + + Args: + intervention (Intervention): The intervention which shall be filled out + + Returns: + intervention (Intervention): The modified intervention + """ + intervention.responsible.registration_office = KonovaCode.objects.get(id=1) + intervention.responsible.conservation_office = KonovaCode.objects.get(id=2) + intervention.responsible.registration_file_number = "test" + intervention.responsible.conservation_file_number = "test" + intervention.responsible.handler = "handler" + intervention.responsible.save() + intervention.legal.registration_date = datetime.date.fromisoformat("1970-01-01") + intervention.legal.binding_date = datetime.date.fromisoformat("1970-01-01") + intervention.legal.process_type = KonovaCode.objects.get(id=3) + intervention.legal.save() + intervention.legal.laws.set([KonovaCode.objects.get(id=(4))]) + intervention.geometry.geom = MultiPolygon(Polygon.from_bbox([-4.526367, 18.354526, -1.801758, 20.591652])) + intervention.geometry.save() + intervention.save() + return intervention + class BaseViewTestCase(BaseTestCase): """ Wraps basic test functionality, reusable for every specialized ViewTestCase @@ -348,6 +392,9 @@ class BaseWorkflowTestCase(BaseTestCase): client_user = None client_anon = None + class Meta: + abstract = True + @classmethod def setUpTestData(cls): super().setUpTestData()