diff options
-rw-r--r-- | ctrack/organisations/admin.py | 6 | ||||
-rw-r--r-- | ctrack/organisations/forms.py | 58 | ||||
-rw-r--r-- | ctrack/organisations/migrations/0006_incidentreport.py | 35 | ||||
-rw-r--r-- | ctrack/organisations/models.py | 62 | ||||
-rw-r--r-- | ctrack/organisations/templates/organisations/incidentreport_form.html | 32 | ||||
-rw-r--r-- | ctrack/organisations/tests/test_models.py | 7 | ||||
-rw-r--r-- | ctrack/organisations/tests/test_views.py | 2 | ||||
-rw-r--r-- | ctrack/organisations/views.py | 3 | ||||
-rw-r--r-- | ctrack/users/tests/test_functional.py | 13 |
9 files changed, 194 insertions, 24 deletions
diff --git a/ctrack/organisations/admin.py b/ctrack/organisations/admin.py index f85ddad..ce2b21b 100644 --- a/ctrack/organisations/admin.py +++ b/ctrack/organisations/admin.py @@ -3,6 +3,7 @@ from django.contrib import admin from .models import ( Address, AddressType, + IncidentReport, Mode, Organisation, Person, @@ -21,6 +22,10 @@ def get_organisation_name(person): get_organisation_name.short_description = "Organisation" +class IncidentReportAdmin(admin.ModelAdmin): + model = IncidentReport + + class AddressTypeAdmin(admin.ModelAdmin): pass @@ -72,3 +77,4 @@ admin.site.register(Person, PersonAdmin) admin.site.register(Mode, ModeAdmin) admin.site.register(Submode, SubmodeAdmin) admin.site.register(Stakeholder, StakeholderAdmin) +admin.site.register(IncidentReport, IncidentReportAdmin) diff --git a/ctrack/organisations/forms.py b/ctrack/organisations/forms.py index 473bc67..d98e9f0 100644 --- a/ctrack/organisations/forms.py +++ b/ctrack/organisations/forms.py @@ -3,7 +3,7 @@ from crispy_forms.layout import Layout, Submit from django import forms from django.forms import inlineformset_factory -from ctrack.organisations.models import Organisation, Address +from ctrack.organisations.models import Address, IncidentReport, Organisation class OrganisationCreateForm(forms.ModelForm): @@ -20,12 +20,17 @@ class OrganisationCreateForm(forms.ModelForm): class Meta: model = Organisation - fields = ["name", "submode", "oes", "designation_type", - "registered_company_name", "registered_company_number", - "comments", "active"] - labels = { - "oes": "OES" - } + fields = [ + "name", + "submode", + "oes", + "designation_type", + "registered_company_name", + "registered_company_number", + "comments", + "active", + ] + labels = {"oes": "OES"} help_texts = { "name": "Name of the organisation", "submode": "e.g. Rail Maintenance, TOC, etc...", @@ -49,15 +54,40 @@ class AddressCreateForm(forms.ModelForm): class Meta: model = Address - fields = ('type', 'line1', 'line2', 'line3', 'city', 'county', 'postcode', - 'country', 'other_details') + fields = ( + "type", + "line1", + "line2", + "line3", + "city", + "county", + "postcode", + "country", + "other_details", + ) # https://dev.to/zxenia/django-inline-formsets-with-class-based-views-and-crispy-forms-14o6 # good advice on setting up the inlineformset - with crispy forms too -AddressInlineFormSet = inlineformset_factory(Organisation, Address, - fields=("type", "line1", "line2", "line3", "city", - "county", "postcode", "country", "other_details"), - form=AddressCreateForm, - extra=2) +AddressInlineFormSet = inlineformset_factory( + Organisation, + Address, + fields=( + "type", + "line1", + "line2", + "line3", + "city", + "county", + "postcode", + "country", + "other_details", + ), + form=AddressCreateForm, + extra=2, +) + +class IncidentReportForm(forms.Form): + class Meta: + model = IncidentReport diff --git a/ctrack/organisations/migrations/0006_incidentreport.py b/ctrack/organisations/migrations/0006_incidentreport.py new file mode 100644 index 0000000..0528aaa --- /dev/null +++ b/ctrack/organisations/migrations/0006_incidentreport.py @@ -0,0 +1,35 @@ +# Generated by Django 2.2.12 on 2020-05-29 12:56 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('organisations', '0005_auto_20200525_1502'), + ] + + operations = [ + migrations.CreateModel( + name='IncidentReport', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('role', models.CharField(help_text='Please identify your role', max_length=100)), + ('phone_number', models.CharField(max_length=30)), + ('email', models.EmailField(max_length=254)), + ('internal_incident_number', models.CharField(blank=True, max_length=30)), + ('date_time_incident_detected', models.DateTimeField(help_text='This can be approximate', verbose_name='Date/Time incident detected')), + ('date_time_incident_reported', models.DateTimeField(verbose_name='Date/Time incident reported')), + ('incident_type', models.CharField(choices=[('cyber', 'Cyber'), ('non-cyber', 'Non-Cyber'), ('both', 'Both'), ('power', 'Power Outage')], help_text='This can be appoximate', max_length=10)), + ('incident_status', models.CharField(choices=[('detected', 'Detected'), ('suspected', 'Suspected'), ('resolved', 'Resolved')], max_length=10)), + ('incident_stage', models.CharField(choices=[('ongoing', 'Ongoing'), ('ended', 'Ended'), ('managed', 'Ongoing but managed')], max_length=10)), + ('summary', models.TextField(help_text='Please provide a summary of your understanding of the incident, including any impact to services and/or users.')), + ('mitigations', models.TextField(help_text='What investigations and/or mitigations have you or a third party performed or plan to perform?', verbose_name='Investigations or mitigations')), + ('others_informed', models.TextField(help_text='Who else has been informed about this incident?(CSIRT, NCSC, NCA, etc)', verbose_name='Others parties informed')), + ('next_steps', models.TextField(help_text='What are your planned next steps?', verbose_name='Planned next steps')), + ('organisation', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='organisations.Organisation')), + ('reporting_person', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='organisations.Person', verbose_name='Person reporting the incident')), + ], + ), + ] diff --git a/ctrack/organisations/models.py b/ctrack/organisations/models.py index 00e4db6..a107829 100644 --- a/ctrack/organisations/models.py +++ b/ctrack/organisations/models.py @@ -184,4 +184,64 @@ class Stakeholder(models.Model): class IncidentReport(models.Model): - pass + INCIDENT_TYPES = ( + ("cyber", "Cyber"), + ("non-cyber", "Non-Cyber"), + ("both", "Both"), + ("power", "Power Outage"), + ) + INCIDENT_STATUS = ( + ("detected", "Detected"), + ("suspected", "Suspected"), + ("resolved", "Resolved"), + ) + INCIDENT_STAGE = ( + ("ongoing", "Ongoing"), + ("ended", "Ended"), + ("managed", "Ongoing but managed"), + ) + organisation = models.ForeignKey( + Organisation, blank=False, on_delete=models.CASCADE + ) + reporting_person = models.ForeignKey( + Person, + blank=False, + on_delete=models.CASCADE, + verbose_name="Person " "reporting the incident", + ) + role = models.CharField( + max_length=100, blank=False, help_text="Please identify your role" + ) + phone_number = models.CharField(max_length=30, blank=False) + email = models.EmailField(blank=False) + internal_incident_number = models.CharField(max_length=30, blank=True) + date_time_incident_detected = models.DateTimeField( + verbose_name="Date/Time incident detected", + auto_now=False, + help_text="This can be approximate", + ) + date_time_incident_reported = models.DateTimeField( + verbose_name="Date/Time incident reported" + ) + incident_type = models.CharField( + choices=INCIDENT_TYPES, help_text="This can be appoximate", max_length=10 + ) + incident_status = models.CharField(choices=INCIDENT_STATUS, max_length=10) + incident_stage = models.CharField(choices=INCIDENT_STAGE, max_length=10) + summary = models.TextField( + help_text="Please provide a summary of your understanding of the incident, including" + " any impact to services and/or users." + ) + mitigations = models.TextField( + verbose_name="Investigations or mitigations", + help_text="What investigations and/or mitigations have you or a third" + " party performed or plan to perform?", + ) + others_informed = models.TextField( + verbose_name="Others parties informed", + help_text="Who else has been informed about this incident?" + "(CSIRT, NCSC, NCA, etc)", + ) + next_steps = models.TextField( + verbose_name="Planned next steps", help_text="What are your planned next steps?" + ) diff --git a/ctrack/organisations/templates/organisations/incidentreport_form.html b/ctrack/organisations/templates/organisations/incidentreport_form.html new file mode 100644 index 0000000..5387f56 --- /dev/null +++ b/ctrack/organisations/templates/organisations/incidentreport_form.html @@ -0,0 +1,32 @@ +{% extends "base.html" %} + +{% block title %} + Submit a new NIS Incident Report to DfT +{% endblock title %} + +{% load crispy_forms_tags %} + +{% block content %} + <div class="container mt-3"> + <div class="row"> + <div class="col-md-12 pl-0 my-2"> + <h4>Submit an Incident Report</h4> + </div> + </div> + <div class="row"> + <div class="col-md-7 pt-2 border bg-light"> + {% crispy form %} + </div> + <div class="col-sm-5"> + <div class="card" style="width: 18rem;"> + <div class="card-body"> + <h5 class="card-title">Help submitting a form</h5> + <p class="card-text">An organisation is the parent/operating company. There is probably advice and + proper definitions for this somewhere. Maybe if you click the link below, it will take you there?</p> + <a href="#" class="btn btn-primary">Go somewhere</a> + </div> + </div> + </div> + </div> + </div> +{% endblock content %} diff --git a/ctrack/organisations/tests/test_models.py b/ctrack/organisations/tests/test_models.py index 6c41c49..6f79cb4 100644 --- a/ctrack/organisations/tests/test_models.py +++ b/ctrack/organisations/tests/test_models.py @@ -1,7 +1,7 @@ import pytest from slugify import slugify -from ctrack.organisations.models import Organisation +from ctrack.organisations.models import IncidentReport, Organisation pytestmark = pytest.mark.django_db @@ -29,8 +29,3 @@ def test_update_organisation(org_with_people): def test_new_address(addr): # The address "has" an organisation assert addr.organisation.name - - -def test_incident_report(org_with_people): - breakpoint() - pass diff --git a/ctrack/organisations/tests/test_views.py b/ctrack/organisations/tests/test_views.py index 1e2476d..cebcda9 100644 --- a/ctrack/organisations/tests/test_views.py +++ b/ctrack/organisations/tests/test_views.py @@ -36,5 +36,5 @@ def test_incident_report_create_view(): factory = RequestFactory() request = factory.get(f"{org.name}/create-incident-report") request.user = user - response = IncidentReportCreateView.as_view()(request) + response = IncidentReportCreateView.as_view()(request, org.slug) assert response.status_code == 200 diff --git a/ctrack/organisations/views.py b/ctrack/organisations/views.py index 3363a33..e73792a 100644 --- a/ctrack/organisations/views.py +++ b/ctrack/organisations/views.py @@ -9,7 +9,7 @@ from django.db import transaction from django.urls import reverse_lazy from django.views.generic import CreateView, DetailView, ListView -from .forms import AddressInlineFormSet, OrganisationCreateForm +from .forms import AddressInlineFormSet, IncidentReportForm, OrganisationCreateForm from .models import IncidentReport, Organisation @@ -76,3 +76,4 @@ class OrganisationDetailView(LoginRequiredMixin, DetailView): class IncidentReportCreateView(LoginRequiredMixin, CreateView): model = IncidentReport fields = "__all__" + form = IncidentReportForm diff --git a/ctrack/users/tests/test_functional.py b/ctrack/users/tests/test_functional.py index 882c974..55f8de3 100644 --- a/ctrack/users/tests/test_functional.py +++ b/ctrack/users/tests/test_functional.py @@ -127,9 +127,20 @@ def test_stakeholder_logs_into_system_and_submits_incident_form( time.sleep(1) current_url = browser.current_url assert current_url == live_server + "/" + time.sleep(1) + + browser.get(current_url) # Clicks the Report a NIS incident button - browser.find_element_by_id("id_submit_incident_button").submit() + # browser.find_element_by_id("id_submit_incident_button").submit() + button = browser.find_element_by_xpath('//*[@id="id_submit_incident_button"]') + + button.click() + time.sleep(1) + + current_url = browser.current_url + + browser.get(current_url) # Gets to the correct page assert "Submit a new NIS Incident Report to DfT" in browser.title |