diff options
author | Matthew Lemon <matt@matthewlemon.com> | 2020-09-02 12:04:19 +0100 |
---|---|---|
committer | Matthew Lemon <matt@matthewlemon.com> | 2020-09-02 12:04:19 +0100 |
commit | edd91cb77191b386af95dfaca0b2706c7ee143b9 (patch) | |
tree | b94edf8e069911d0aea4e1acfc23a72a180cb2cf | |
parent | 15c89a78c097dc00ad7b8ca3314581ed3b058187 (diff) | |
parent | c406d3da83d20d65c2fc4da7d5d4d5db0f6ad115 (diff) |
Merge branch 'db_rewrite' into master
54 files changed, 477 insertions, 853 deletions
diff --git a/config/settings/base.py b/config/settings/base.py index c1702d7..c699798 100644 --- a/config/settings/base.py +++ b/config/settings/base.py @@ -68,6 +68,7 @@ DJANGO_APPS = [ "django.contrib.sessions", "django.contrib.sites", "django.contrib.messages", + "django_pdb", ## this needs to be before django.contrib.staticfiles according to its docs "django.contrib.staticfiles", # "django.contrib.humanize", # Handy template tags "django.contrib.admin", diff --git a/ctrack/assessments/migrations/0001_initial.py b/ctrack/assessments/migrations/0001_initial.py index 94afdf9..3789ad7 100644 --- a/ctrack/assessments/migrations/0001_initial.py +++ b/ctrack/assessments/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 2.2.9 on 2020-04-03 14:07 +# Generated by Django 2.2.12 on 2020-08-27 09:40 from django.db import migrations, models import django.db.models.deletion @@ -9,20 +9,46 @@ class Migration(migrations.Migration): initial = True dependencies = [ + ('organisations', '0001_initial'), + ('caf', '0001_initial'), ] operations = [ migrations.CreateModel( + name='AchievementLevel', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('descriptor', models.CharField(max_length=50)), + ('colour_description', models.CharField(max_length=100)), + ('colour_hex', models.CharField(max_length=8)), + ], + ), + migrations.CreateModel( name='CAFAssessment', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('comments', models.TextField(max_length=500)), + ('caf', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='caf.CAF')), + ('completer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='organisations.Person')), ], options={ 'verbose_name': 'CAF Assessment', }, ), migrations.CreateModel( + name='CAFContributingOutcome', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('designation', models.CharField(help_text='e.g. A1.a, B3.c, etc', max_length=5)), + ('name', models.CharField(help_text='e.g. Board Direction', max_length=100)), + ('description', models.TextField(max_length=1000)), + ('order_id', models.IntegerField()), + ], + options={ + 'verbose_name': 'CAF Contributing Outcome', + }, + ), + migrations.CreateModel( name='CAFObjective', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), @@ -35,33 +61,36 @@ class Migration(migrations.Migration): }, ), migrations.CreateModel( - name='CAFPrinciple', + name='IGP', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('designation', models.CharField(help_text='e.g. A1, B3, etc', max_length=5)), - ('title', models.CharField(max_length=50)), - ('description', models.TextField(max_length=1000)), - ('order_id', models.IntegerField()), - ('caf_objective', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='assessments.CAFObjective')), + ('descriptive_text', models.CharField(max_length=2000)), + ('achievement_level', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='assessments.AchievementLevel')), + ('contributing_outcome', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='assessments.CAFContributingOutcome')), ], options={ - 'verbose_name': 'CAF Principle', + 'verbose_name': 'IGP', }, ), migrations.CreateModel( - name='CAFContributingOutcome', + name='CAFPrinciple', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('designation', models.CharField(help_text='e.g. A1.a, B3.c, etc', max_length=5)), - ('name', models.CharField(help_text='e.g. Board Direction', max_length=100)), + ('designation', models.CharField(help_text='e.g. A1, B3, etc', max_length=5)), + ('title', models.CharField(max_length=50)), ('description', models.TextField(max_length=1000)), ('order_id', models.IntegerField()), - ('principle', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='assessments.CAFPrinciple')), + ('caf_objective', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='assessments.CAFObjective')), ], options={ - 'verbose_name': 'CAF Contributing Outcome', + 'verbose_name': 'CAF Principle', }, ), + migrations.AddField( + model_name='cafcontributingoutcome', + name='principle', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='assessments.CAFPrinciple'), + ), migrations.CreateModel( name='CAFAssessmentOutcomeScore', fields=[ diff --git a/ctrack/assessments/migrations/0002_cafassessment_caf.py b/ctrack/assessments/migrations/0002_cafassessment_caf.py deleted file mode 100644 index db08459..0000000 --- a/ctrack/assessments/migrations/0002_cafassessment_caf.py +++ /dev/null @@ -1,22 +0,0 @@ -# Generated by Django 2.2.9 on 2020-04-03 14:07 - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - initial = True - - dependencies = [ - ('assessments', '0001_initial'), - ('caf', '0001_initial'), - ] - - operations = [ - migrations.AddField( - model_name='cafassessment', - name='caf', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='caf.CAF'), - ), - ] diff --git a/ctrack/assessments/migrations/0003_cafassessment_completer.py b/ctrack/assessments/migrations/0003_cafassessment_completer.py deleted file mode 100644 index 63b3b18..0000000 --- a/ctrack/assessments/migrations/0003_cafassessment_completer.py +++ /dev/null @@ -1,22 +0,0 @@ -# Generated by Django 2.2.9 on 2020-04-03 14:07 - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - initial = True - - dependencies = [ - ('organisations', '0001_initial'), - ('assessments', '0002_cafassessment_caf'), - ] - - operations = [ - migrations.AddField( - model_name='cafassessment', - name='completer', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='organisations.Person'), - ), - ] diff --git a/ctrack/assessments/migrations/0004_achievementlevel_igp.py b/ctrack/assessments/migrations/0004_achievementlevel_igp.py deleted file mode 100644 index 581a115..0000000 --- a/ctrack/assessments/migrations/0004_achievementlevel_igp.py +++ /dev/null @@ -1,32 +0,0 @@ -# Generated by Django 3.0.5 on 2020-05-12 14:00 - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('assessments', '0003_cafassessment_completer'), - ] - - operations = [ - migrations.CreateModel( - name='AchievementLevel', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('descriptor', models.CharField(max_length=50)), - ('color_description', models.CharField(max_length=100)), - ('color_hex', models.CharField(max_length=8)), - ], - ), - migrations.CreateModel( - name='IGP', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('descriptive_text', models.CharField(max_length=2000)), - ('achievement_level', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='assessments.AchievementLevel')), - ('contributing_outcome', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='assessments.CAFContributingOutcome')), - ], - ), - ] diff --git a/ctrack/assessments/migrations/0005_auto_20200512_1438.py b/ctrack/assessments/migrations/0005_auto_20200512_1438.py deleted file mode 100644 index 31a741a..0000000 --- a/ctrack/assessments/migrations/0005_auto_20200512_1438.py +++ /dev/null @@ -1,27 +0,0 @@ -# Generated by Django 3.0.5 on 2020-05-12 14:38 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('assessments', '0004_achievementlevel_igp'), - ] - - operations = [ - migrations.AlterModelOptions( - name='igp', - options={'verbose_name': 'IGP'}, - ), - migrations.RenameField( - model_name='achievementlevel', - old_name='color_description', - new_name='colour_description', - ), - migrations.RenameField( - model_name='achievementlevel', - old_name='color_hex', - new_name='colour_hex', - ), - ] diff --git a/ctrack/assessments/models.py b/ctrack/assessments/models.py index d3cd4dd..89d83b8 100644 --- a/ctrack/assessments/models.py +++ b/ctrack/assessments/models.py @@ -13,16 +13,17 @@ class CAFAssessment(models.Model): verbose_name = "CAF Assessment" def get_title(self): - return f"CAF Assessment for {self.caf.applicable_systems.first().organisation.name} - version {self.caf.version}" + return f"CAF Assessment for {self.caf.organisation.name} - version {self.caf.version}" def __str__(self): - return f"CAF Assessment for {self.caf.applicable_systems.first().organisation.name} - version {self.caf.version}" + return f"CAF Assessment for {self.caf.organisation.name} - version {self.caf.version}" class CAFObjective(models.Model): """ One of 4 as set out in the framework... """ + name = models.CharField(max_length=100, help_text="e.g. Managing Risk") description = models.TextField(max_length=500) order_id = models.IntegerField() @@ -38,6 +39,7 @@ class CAFPrinciple(models.Model): """ One of 14 as set out in the framework. """ + caf_objective = models.ForeignKey(CAFObjective, on_delete=models.CASCADE) designation = models.CharField(max_length=5, help_text="e.g. A1, B3, etc") title = models.CharField(max_length=50) @@ -55,6 +57,7 @@ class CAFContributingOutcome(models.Model): """ One of the 39 as set out in the framework. """ + designation = models.CharField(max_length=5, help_text="e.g. A1.a, B3.c, etc") name = models.CharField(max_length=100, help_text="e.g. Board Direction") description = models.TextField(max_length=1000) @@ -73,20 +76,32 @@ class CAFAssessmentOutcomeScore(models.Model): Details the assessment for an Outcome, and the baseline assessment. Completed by an OES initially, but can be completed by anyone. """ + ASSESSMENT_SCORE = ( ("Achieved", "Achieved"), ("Partially Achieved", "Partially Achieved"), ("Not Achieved", "Not Achieved"), ) - caf_assessment = models.ForeignKey(CAFAssessment, on_delete=models.CASCADE, - verbose_name="CAF Assessment") - caf_contributing_outcome = models.ForeignKey(CAFContributingOutcome, on_delete=models.CASCADE, - verbose_name="CAF Contributing Outcome") - assessment_score = models.CharField(max_length=20, choices=ASSESSMENT_SCORE, - help_text="Choose an assessment score", - verbose_name="Assessment Score") - baseline_assessment_score = models.CharField(max_length=20, choices=ASSESSMENT_SCORE, - help_text="Choose an assessment score", verbose_name="Baseline Score") + caf_assessment = models.ForeignKey( + CAFAssessment, on_delete=models.CASCADE, verbose_name="CAF Assessment" + ) + caf_contributing_outcome = models.ForeignKey( + CAFContributingOutcome, + on_delete=models.CASCADE, + verbose_name="CAF Contributing Outcome", + ) + assessment_score = models.CharField( + max_length=20, + choices=ASSESSMENT_SCORE, + help_text="Choose an assessment score", + verbose_name="Assessment Score", + ) + baseline_assessment_score = models.CharField( + max_length=20, + choices=ASSESSMENT_SCORE, + help_text="Choose an assessment score", + verbose_name="Baseline Score", + ) class Meta: verbose_name = "CAF Assessment Outcome Score" @@ -98,7 +113,9 @@ class CAFAssessmentOutcomeScore(models.Model): class AchievementLevel(models.Model): descriptor = models.CharField(max_length=50) colour_description = models.CharField(max_length=100) - colour_hex = models.CharField(max_length=8) # CSS hex code or simple word descriptor + colour_hex = models.CharField( + max_length=8 + ) # CSS hex code or simple word descriptor def __str__(self): return f"{self.descriptor}" @@ -106,7 +123,9 @@ class AchievementLevel(models.Model): class IGP(models.Model): achievement_level = models.ForeignKey(AchievementLevel, on_delete=models.CASCADE) - contributing_outcome = models.ForeignKey(CAFContributingOutcome, on_delete=models.CASCADE) + contributing_outcome = models.ForeignKey( + CAFContributingOutcome, on_delete=models.CASCADE + ) descriptive_text = models.CharField(max_length=2000) class Meta: diff --git a/ctrack/caf/admin.py b/ctrack/caf/admin.py index f19e8f4..5513781 100644 --- a/ctrack/caf/admin.py +++ b/ctrack/caf/admin.py @@ -1,31 +1,56 @@ from django.contrib import admin -from .models import CAF, FileStore, DocumentFile, Grading, ApplicableSystem +from .models import ( + CAF, + FileStore, + DocumentFile, + Grading, + ApplicableSystem, + EssentialService, +) + + +def get_system_org(obj): + es = obj.essentialservice_set.first() # just get the first if there are many + return es.organisation.name + + +get_system_org.short_description = "Organisation" + + +class EssentialServiceAdmin(admin.ModelAdmin): + model = EssentialService class ApplicableSystemListAdmin(admin.ModelAdmin): model = ApplicableSystem - list_display = ["name", "organisation", "caf"] + list_display = ["name", get_system_org, "function"] -class ApplicableSystemAdmin(admin.StackedInline): - model = ApplicableSystem - max_num = 3 - extra = 1 +# FIXME +# class ApplicableSystemAdmin(admin.StackedInline): +# model = ApplicableSystem +# max_num = 3 +# extra = 1 -def get_caf_name(obj): - ass = ApplicableSystem.objects.filter(caf=obj).first() - return f"{ass.organisation.name}_v{obj.version}" +# FIXME - NOT NEEDED +# def get_caf_name(obj): +# ass = ApplicableSystem.objects.filter(caf=obj).first() +# return f"{ass.organisation.name}_v{obj.version}" +# FIXME class CAFAdmin(admin.ModelAdmin): model = CAF - inlines = [ApplicableSystemAdmin] - list_display = [get_caf_name, "quality_grading", "confidence_grading", "file"] + # inlines = [ApplicableSystemAdmin] + + +# list_display = ["quality_grading", "confidence_grading", "file"] admin.site.register(CAF, CAFAdmin) +admin.site.register(EssentialService, EssentialServiceAdmin) admin.site.register(FileStore) admin.site.register(DocumentFile) admin.site.register(Grading) diff --git a/ctrack/caf/forms.py b/ctrack/caf/forms.py index 7367ccd..8e3d8ee 100644 --- a/ctrack/caf/forms.py +++ b/ctrack/caf/forms.py @@ -12,12 +12,13 @@ from django import forms from django.forms import inlineformset_factory from django.urls import reverse -from ctrack.caf.models import CAF, ApplicableSystem +from ctrack.caf.models import CAF, ApplicableSystem, EssentialService from ctrack.organisations.models import Organisation -CAFCreateInlineFormset = inlineformset_factory( - CAF, ApplicableSystem, fields=("name", "organisation"), extra=2 -) +# TODO - Replace this to get inlineformet working +# CAFCreateInlineFormset = inlineformset_factory( +# CAF, ApplicableSystem, fields=("name", "organisation"), extra=2 +# ) class ApplicableSystemCreateFromCafForm(forms.Form): @@ -77,13 +78,14 @@ class ApplicableSystemCreateFromCafForm(forms.Form): class ApplicableSystemCreateFromOrgForm(forms.Form): name = forms.CharField(max_length=255) function = forms.CharField(widget=forms.Textarea) - organisation = forms.ModelChoiceField(queryset=Organisation.objects.all()) - caf = forms.ModelChoiceField(queryset=CAF.objects.all()) - essential_service = forms.CharField( - widget=forms.Textarea, - max_length=255, - help_text="Description of the essential service which the system suppports.", - ) + # organisation = forms.ModelChoiceField(queryset=Organisation.objects.all()) + # caf = forms.ModelChoiceField(queryset=CAF.objects.all()) + # essential_service = forms.CharField( + # widget=forms.Textarea, + # max_length=255, + # help_text="Description of the essential service which the system suppports.", + # ) + essential_service = forms.ModelChoiceField(queryset=EssentialService.objects.all()) dft_categorisation = forms.ChoiceField( choices=ApplicableSystem.SYSTEM_CATEGORISATION, help_text="Refer to documentation for description of these criteria", @@ -93,16 +95,19 @@ class ApplicableSystemCreateFromOrgForm(forms.Form): help_text="Categorisation based on OES' own internal prioritisation process.", ) - def __init__(self, org_id, slug, org_name, org_cafs, *args, **kwargs): + def __init__(self, org_id, slug, org_name, *args, **kwargs): super().__init__(*args, **kwargs) cancel_redirect = reverse("organisations:detail", args=[slug]) # we need to create the choices we can use for the CAF dropdown in the form - self.fields["caf"].queryset = CAF.objects.filter( - pk__in=[caf.pk for caf in org_cafs] - ) - self.fields["caf"].label = "CAF" + # self.fields["caf"].queryset = CAF.objects.filter( + # pk__in=[caf.pk for caf in org_cafs] + # ) + # self.fields["caf"].label = "CAF" self.fields["dft_categorisation"].label = "DfT Categorisation" self.fields["oes_categorisation"].label = "OES Categorisation" + self.fields["essential_service"].queryset = EssentialService.objects.filter( + pk=org_id + ) self.helper = FormHelper(self) self.helper.layout = Layout( Fieldset( @@ -113,7 +118,6 @@ class ApplicableSystemCreateFromOrgForm(forms.Form): "dft_categorisation", "oes_categorisation", Hidden("organisation", org_id), - "caf", ), ButtonHolder( Submit("submit", "Submit", css_class="btn-primary"), diff --git a/ctrack/caf/managers.py b/ctrack/caf/managers.py index cba8c83..4d8c134 100644 --- a/ctrack/caf/managers.py +++ b/ctrack/caf/managers.py @@ -1,8 +1,8 @@ from django.db import connection from django.db import models -import ctrack.caf.models # to deal with circular import -from ctrack.organisations.models import Organisation, Person +import ctrack.caf.models as caf_models # to deal with circular import +import ctrack.organisations.models as org_models class ApplicableSystemManager(models.Manager): @@ -16,14 +16,16 @@ class ApplicableSystemManager(models.Manager): Using Custom Managers Django docs for an example. """ with connection.cursor() as cursor: - cursor.execute(""" + cursor.execute( + """ SELECT a.id, a.name, o.id, c.id, sm.id, p.id, o.name FROM caf_applicablesystem a, organisations_organisation o, organisations_person p, caf_caf c, organisations_submode sm WHERE a.organisation_id = o.id AND a.caf_id = c.id AND p.organisation_id = o.id AND o.submode_id = sm.id AND p.primary_nis_contact = True; - """) + """ + ) result_list = [] for row in cursor.fetchall(): - org = Organisation.objects.get(pk=row[2]) + org = org_models.Organisation.objects.get(pk=row[3]) caf = ctrack.caf.models.CAF.objects.get(pk=row[3]) ass = self.model(id=row[0], name=row[1], organisation=org, caf=caf) ass.nis_contact = Person.objects.get(pk=row[5]) diff --git a/ctrack/caf/migrations/0001_initial.py b/ctrack/caf/migrations/0001_initial.py index 5e184df..ca65af6 100644 --- a/ctrack/caf/migrations/0001_initial.py +++ b/ctrack/caf/migrations/0001_initial.py @@ -1,6 +1,8 @@ -# Generated by Django 2.2.9 on 2020-04-03 14:07 +# Generated by Django 2.2.12 on 2020-08-27 09:40 +import ctrack.caf.models from django.db import migrations, models +import django.db.models.deletion class Migration(migrations.Migration): @@ -8,6 +10,7 @@ class Migration(migrations.Migration): initial = True dependencies = [ + ('organisations', '0001_initial'), ] operations = [ @@ -15,49 +18,68 @@ class Migration(migrations.Migration): name='ApplicableSystem', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(max_length=256)), - ('description', models.TextField(max_length=1000)), + ('name', models.CharField(help_text='System name assigned by OES', max_length=256)), + ('function', models.TextField(blank=True, help_text='How the system is relevant to delivering or supporting the essential service', max_length=1000, null=True)), + ('dft_categorisation', models.CharField(choices=[('CR', 'Critical'), ('IM', 'Important (Legacy use only)')], default='CR', help_text='Refer to documentation for description of these criteria', max_length=2, verbose_name='DfT Categorisation')), + ('oes_categorisation', models.CharField(default='NA', help_text="Categorisation based on OES' own internal prioritisation process.", max_length=255, verbose_name='OES Categorisation')), ], options={ - 'verbose_name': 'Applicable System', + 'verbose_name': 'NIS System', }, ), migrations.CreateModel( - name='CAF', + name='Grading', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('version', models.CharField(blank=True, max_length=10, null=True)), - ('triage_review_date', models.DateField(blank=True, null=True)), - ('comments', models.TextField(max_length=1000)), + ('descriptor', models.CharField(help_text='Q1, C1, etc', max_length=2)), + ('description', models.TextField(max_length=250)), + ('type', models.CharField(choices=[('CONFIDENCE', 'Confidence'), ('QUALITY', 'Quality'), ('MISC', 'Misc')], help_text='Type of grading', max_length=20)), ], - options={ - 'verbose_name': 'CAF', - }, ), migrations.CreateModel( - name='DocumentFile', + name='FileStore', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(max_length=255)), - ('type', models.IntegerField(choices=[(1, 'Excel'), (2, 'Word'), (3, 'PDF'), (4, 'Hard Copy')], default=1)), + ('descriptor', models.CharField(max_length=100)), + ('virtual_location', models.CharField(help_text='USB, Rosa, email, etc', max_length=100)), + ('physical_location', models.CharField(blank=True, help_text='Cupboard, room, building, etc', max_length=100)), + ('physical_location_organisation', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='organisations.Organisation')), ], ), migrations.CreateModel( - name='FileStore', + name='EssentialService', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('descriptor', models.CharField(max_length=100)), - ('virtual_location', models.CharField(help_text='USB, Rosa, email, etc', max_length=100)), - ('physical_location', models.CharField(blank=True, help_text='Cupboard, room, building, etc', max_length=100)), + ('name', models.CharField(max_length=256)), + ('description', models.CharField(max_length=512)), + ('organisation', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='organisations.Organisation')), + ('systems', models.ManyToManyField(to='caf.ApplicableSystem')), ], ), migrations.CreateModel( - name='Grading', + name='DocumentFile', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('descriptor', models.CharField(help_text='Q1, C1, etc', max_length=2)), - ('description', models.TextField(max_length=250)), - ('type', models.CharField(choices=[('CONFIDENCE', 'Confidence'), ('QUALITY', 'Quality'), ('MISC', 'Misc')], help_text='Type of grading', max_length=20)), + ('name', models.CharField(max_length=255)), + ('type', models.IntegerField(choices=[(1, 'Excel'), (2, 'Word'), (3, 'PDF'), (4, 'Hard Copy')], default=1)), + ('file_store_location', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='caf.FileStore')), ], ), + migrations.CreateModel( + name='CAF', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('version', models.CharField(blank=True, max_length=10, null=True)), + ('triage_review_date', models.DateField(blank=True, null=True)), + ('comments', models.TextField(max_length=1000)), + ('confidence_grading', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='confidence_grading', to='caf.Grading')), + ('file', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='caf.DocumentFile')), + ('organisation', models.ForeignKey(on_delete=models.SET(ctrack.caf.models.CAF.get_sentinel_org), to='organisations.Organisation')), + ('quality_grading', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='quality_grading', to='caf.Grading')), + ('triage_review_inspector', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='organisations.Person')), + ], + options={ + 'verbose_name': 'CAF', + }, + ), ] diff --git a/ctrack/caf/migrations/0002_auto_20200403_1407.py b/ctrack/caf/migrations/0002_auto_20200403_1407.py deleted file mode 100644 index e5963d5..0000000 --- a/ctrack/caf/migrations/0002_auto_20200403_1407.py +++ /dev/null @@ -1,58 +0,0 @@ -# Generated by Django 2.2.9 on 2020-04-03 14:07 - -import ctrack.caf.models -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - initial = True - - dependencies = [ - ('caf', '0001_initial'), - ('organisations', '0001_initial'), - ] - - operations = [ - migrations.AddField( - model_name='filestore', - name='physical_location_organisation', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='organisations.Organisation'), - ), - migrations.AddField( - model_name='documentfile', - name='file_store_location', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='caf.FileStore'), - ), - migrations.AddField( - model_name='caf', - name='confidence_grading', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='confidence_grading', to='caf.Grading'), - ), - migrations.AddField( - model_name='caf', - name='file', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='caf.DocumentFile'), - ), - migrations.AddField( - model_name='caf', - name='quality_grading', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='quality_grading', to='caf.Grading'), - ), - migrations.AddField( - model_name='caf', - name='triage_review_inspector', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='organisations.Person'), - ), - migrations.AddField( - model_name='applicablesystem', - name='caf', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='applicable_systems', to='caf.CAF'), - ), - migrations.AddField( - model_name='applicablesystem', - name='organisation', - field=models.ForeignKey(on_delete=models.SET(ctrack.caf.models.ApplicableSystem.get_sentinel_org), to='organisations.Organisation'), - ), - ] diff --git a/ctrack/caf/migrations/0002_caf_systems.py b/ctrack/caf/migrations/0002_caf_systems.py new file mode 100644 index 0000000..9e0f76a --- /dev/null +++ b/ctrack/caf/migrations/0002_caf_systems.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.12 on 2020-08-27 12:44 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('caf', '0001_initial'), + ] + + operations = [ + migrations.AddField( + model_name='caf', + name='systems', + field=models.ManyToManyField(to='caf.ApplicableSystem'), + ), + ] diff --git a/ctrack/caf/migrations/0003_auto_20200424_1924.py b/ctrack/caf/migrations/0003_auto_20200424_1924.py deleted file mode 100644 index 34613c6..0000000 --- a/ctrack/caf/migrations/0003_auto_20200424_1924.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 2.2.9 on 2020-04-24 19:24 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('caf', '0002_auto_20200403_1407'), - ] - - operations = [ - migrations.AlterField( - model_name='applicablesystem', - name='description', - field=models.TextField(blank=True, max_length=1000, null=True), - ), - ] diff --git a/ctrack/caf/migrations/0004_auto_20200813_0953.py b/ctrack/caf/migrations/0004_auto_20200813_0953.py deleted file mode 100644 index ad1ca96..0000000 --- a/ctrack/caf/migrations/0004_auto_20200813_0953.py +++ /dev/null @@ -1,31 +0,0 @@ -# Generated by Django 2.2.12 on 2020-08-13 09:53 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('caf', '0003_auto_20200424_1924'), - ] - - operations = [ - migrations.AlterModelOptions( - name='applicablesystem', - options={'verbose_name': 'NIS System'}, - ), - migrations.RemoveField( - model_name='applicablesystem', - name='description', - ), - migrations.AddField( - model_name='applicablesystem', - name='function', - field=models.TextField(blank=True, help_text='How the system is relevant to delivering or supporting the essential service', max_length=1000, null=True), - ), - migrations.AlterField( - model_name='applicablesystem', - name='name', - field=models.CharField(help_text='System name assigned by OES', max_length=256), - ), - ] diff --git a/ctrack/caf/migrations/0005_applicablesystem_oes_categorisation.py b/ctrack/caf/migrations/0005_applicablesystem_oes_categorisation.py deleted file mode 100644 index dc8c928..0000000 --- a/ctrack/caf/migrations/0005_applicablesystem_oes_categorisation.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 2.2.12 on 2020-08-13 11:17 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('caf', '0004_auto_20200813_0953'), - ] - - operations = [ - migrations.AddField( - model_name='applicablesystem', - name='oes_categorisation', - field=models.CharField(choices=[('CR', 'Critical'), ('IM', 'Important')], default='CR', max_length=2), - ), - ] diff --git a/ctrack/caf/migrations/0006_auto_20200813_1125.py b/ctrack/caf/migrations/0006_auto_20200813_1125.py deleted file mode 100644 index 1e97dff..0000000 --- a/ctrack/caf/migrations/0006_auto_20200813_1125.py +++ /dev/null @@ -1,22 +0,0 @@ -# Generated by Django 2.2.12 on 2020-08-13 11:25 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('caf', '0005_applicablesystem_oes_categorisation'), - ] - - operations = [ - migrations.RemoveField( - model_name='applicablesystem', - name='oes_categorisation', - ), - migrations.AddField( - model_name='applicablesystem', - name='dft_categorisation', - field=models.CharField(choices=[('CR', 'Critical'), ('IM', 'Important')], default='CR', help_text='Refer to documentation for description of these criteria', max_length=2, verbose_name='DfT Categorisation'), - ), - ] diff --git a/ctrack/caf/migrations/0007_auto_20200814_1230.py b/ctrack/caf/migrations/0007_auto_20200814_1230.py deleted file mode 100644 index 68a12bd..0000000 --- a/ctrack/caf/migrations/0007_auto_20200814_1230.py +++ /dev/null @@ -1,23 +0,0 @@ -# Generated by Django 2.2.12 on 2020-08-14 12:30 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('caf', '0006_auto_20200813_1125'), - ] - - operations = [ - migrations.AddField( - model_name='applicablesystem', - name='oes_categorisation', - field=models.CharField(default='', help_text="Categorisation based on OES' own internal prioritisation process.", max_length=255, verbose_name='OES Categorisation'), - ), - migrations.AlterField( - model_name='applicablesystem', - name='dft_categorisation', - field=models.CharField(choices=[('CR', 'Critical'), ('IM', 'Important (Legacy use only)')], default='CR', help_text='Refer to documentation for description of these criteria', max_length=2, verbose_name='DfT Categorisation'), - ), - ] diff --git a/ctrack/caf/migrations/0008_auto_20200814_1318.py b/ctrack/caf/migrations/0008_auto_20200814_1318.py deleted file mode 100644 index 90f188f..0000000 --- a/ctrack/caf/migrations/0008_auto_20200814_1318.py +++ /dev/null @@ -1,23 +0,0 @@ -# Generated by Django 2.2.12 on 2020-08-14 13:18 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('caf', '0007_auto_20200814_1230'), - ] - - operations = [ - migrations.AddField( - model_name='applicablesystem', - name='essential_service', - field=models.CharField(default='NA', help_text='Name of the essential service which the system suppports.', max_length=255, verbose_name='Essential Service'), - ), - migrations.AlterField( - model_name='applicablesystem', - name='oes_categorisation', - field=models.CharField(default='NA', help_text="Categorisation based on OES' own internal prioritisation process.", max_length=255, verbose_name='OES Categorisation'), - ), - ] diff --git a/ctrack/caf/models.py b/ctrack/caf/models.py index 27220bc..5fd586f 100644 --- a/ctrack/caf/models.py +++ b/ctrack/caf/models.py @@ -45,13 +45,6 @@ class DocumentFile(models.Model): class ApplicableSystem(models.Model): - CRITICAL = "CR" - IMPORTANT = "IM" - SYSTEM_CATEGORISATION = ( - (CRITICAL, "Critical"), - (IMPORTANT, "Important (Legacy use only)"), - ) - def get_sentinel_org(): """ We need this so that we can ensure models.SET() is applied with a callable @@ -60,6 +53,13 @@ class ApplicableSystem(models.Model): """ return Organisation.objects.get_or_create(name="DELETED ORGANISATION")[0] + CRITICAL = "CR" + IMPORTANT = "IM" + SYSTEM_CATEGORISATION = ( + (CRITICAL, "Critical"), + (IMPORTANT, "Important (Legacy use only)"), + ) + name = models.CharField(max_length=256, help_text="System name assigned by OES") function = models.TextField( max_length=1000, @@ -68,22 +68,6 @@ class ApplicableSystem(models.Model): help_text="How the system is relevant to delivering or supporting the " "essential service", ) - organisation = models.ForeignKey( - Organisation, on_delete=models.SET(get_sentinel_org) - ) - caf = models.ForeignKey( - "CAF", - on_delete=models.CASCADE, - blank=True, - null=True, - related_name="applicable_systems", - ) - essential_service = models.CharField( - max_length=255, - default="NA", - verbose_name="Essential Service", - help_text="Description of the essential service which the system suppports.", - ) dft_categorisation = models.CharField( max_length=2, choices=SYSTEM_CATEGORISATION, @@ -105,12 +89,20 @@ class ApplicableSystem(models.Model): return self.organisation.person_set.filter(primary_nis_contact=True) def __str__(self): - return f"{self.organisation.name} | {self.name}" + return self.name objects = ApplicableSystemManager() class CAF(models.Model): + def get_sentinel_org(): + """ + We need this so that we can ensure models.SET() is applied with a callable + to handle when Users are deleted from the system, preventing the Organisation + objects related to them being deleted also. + """ + return Organisation.objects.get_or_create(name="DELETED ORGANISATION")[0] + quality_grading = models.ForeignKey( Grading, on_delete=models.CASCADE, @@ -129,10 +121,14 @@ class CAF(models.Model): DocumentFile, on_delete=models.CASCADE, blank=True, null=True ) version = models.CharField(max_length=10, blank=True, null=True) + organisation = models.ForeignKey( + Organisation, on_delete=models.SET(get_sentinel_org) + ) triage_review_date = models.DateField(blank=True, null=True) triage_review_inspector = models.ForeignKey( Person, on_delete=models.CASCADE, blank=True, null=True ) + systems = models.ManyToManyField(ApplicableSystem) comments = models.TextField(max_length=1000) class Meta: @@ -147,14 +143,24 @@ class CAF(models.Model): """ return ApplicableSystem.objects.filter(caf=self) - def organisation(self): - first_ass = ApplicableSystem.objects.filter(caf=self).first() - return first_ass.organisation + # FIXME remove once we know we don't need it + # def organisation(self): + # first_ass = ApplicableSystem.objects.filter(caf=self).first() + # return first_ass.organisation def sub_mode(self): - return self.organisation().submode + return self.organisation.submode def __str__(self): # Get the organisation and applicable system - ass = ApplicableSystem.objects.filter(caf=self).first() - return f"CAF | {ass.organisation.name}_v{self.version}" + return f"CAF | {self.organisation.name}_v{self.version}" + + +class EssentialService(models.Model): + name = models.CharField(max_length=256) + description = models.CharField(max_length=512) + organisation = models.ForeignKey(Organisation, on_delete=models.CASCADE) + systems = models.ManyToManyField(ApplicableSystem) + + def __str__(self): + return self.name diff --git a/ctrack/caf/templates/caf/applicablesystem_detail.html b/ctrack/caf/templates/caf/applicablesystem_detail.html index 9e12043..b8467b0 100644 --- a/ctrack/caf/templates/caf/applicablesystem_detail.html +++ b/ctrack/caf/templates/caf/applicablesystem_detail.html @@ -5,24 +5,31 @@ {% block title %}{{ object.name }}{% endblock %} {% block content %} - <div class="container mt-3"> - <div class="row"> - <div class="col-sm-12 pl-0 my-2"> + <div class="container mt-3"> <div class="row"> - <h4>{{ object.name }}</h4> + <div class="col-sm-12 pl-0 my-2"> + <div class="row"> + <h4>{{ object.name }}</h4> + </div> + <div class="row"> + <h5>{{ object.caf.organisation }}</h5> + </div> + <div class="row"> + <h5>Function</h5> + </div> + <div class="row"> + <p>{{ object.function }}</p> + <p>Supports Essential Services:</p> + </div> + <div class="row"> + <ul> + {% for s in object.essentialservice_set.all %} + <li>{{ s.name }}</li> + {% endfor %} + </ul> + </div> + </div> </div> - <div class="row"> - <h5>{{ object.caf.organisation }}</h5> - </div> - <div class="row"> - <h6>Function</h6> - </div> - <div class="row"> - <p>{{ object.function }}</p> - <p>Contained within CAF: <a href="{% url "caf:detail" object.caf.pk %}">{{ object.caf }}</a></p> - </div> - </div> </div> - </div> {% endblock %} diff --git a/ctrack/caf/templates/caf/caf_detail.html b/ctrack/caf/templates/caf/caf_detail.html index fc2fcfd..fdd939e 100644 --- a/ctrack/caf/templates/caf/caf_detail.html +++ b/ctrack/caf/templates/caf/caf_detail.html @@ -72,7 +72,7 @@ <tr> <td><a href="{% url "caf:ass_detail" system.id %}">{{ system.name }}</a></td> <td>{{ system.function }}<br> - <a href="{% url "caf:detail" system.caf.pk %}" class="small"> + <a href="{% url "caf:detail" system.pk %}" class="small"> {{ system.caf }} </a> <span class="text-muted"> | <a href="#" class="small">Edit System</a></span> </td> diff --git a/ctrack/caf/tests/factories.py b/ctrack/caf/tests/factories.py index 3c62307..7aaf716 100644 --- a/ctrack/caf/tests/factories.py +++ b/ctrack/caf/tests/factories.py @@ -10,6 +10,7 @@ from ctrack.organisations.tests.factories import OrganisationFactory, PersonFact class CAFFactory(factory.DjangoModelFactory): quality_grading = factory.SubFactory("ctrack.caf.tests.factories.GradingFactory") confidence_grading = factory.SubFactory("ctrack.caf.tests.factories.GradingFactory") + organisation = factory.SubFactory("ctrack.organisations.tests.OrganisationFactory") file = None version = Faker("bothify", text="??##", letters="ABCD") triage_review_date = Faker("date_object") @@ -29,8 +30,6 @@ class ApplicableSystemFactory(factory.DjangoModelFactory): function = Faker( "paragraph", nb_sentences=4, variable_nb_sentences=True, ext_word_list=None ) - organisation = factory.SubFactory(OrganisationFactory) - caf = factory.SubFactory("ctrack.caf.tests.factories.CAFFactory") dft_categorisation = "CR" class Meta: diff --git a/ctrack/caf/views.py b/ctrack/caf/views.py index 258774e..480e305 100644 --- a/ctrack/caf/views.py +++ b/ctrack/caf/views.py @@ -36,8 +36,8 @@ def caf_detail_view(request, pk): context = { "object": caf, "assessments_and_scores": _scrs, - "organisation": ApplicableSystem.objects.filter(caf=caf).first().organisation, - "systems": caf.applicable_systems.all(), + "organisation": caf.organisation, + "systems": caf.systems.all(), } return render(request, "caf/caf_detail.html", context) @@ -107,20 +107,22 @@ class ApplicableSystemCreateFromOrg( ass = ApplicableSystem.objects.create( name=form.cleaned_data["name"], function=form.cleaned_data["function"], - organisation=form.cleaned_data["organisation"], - caf=form.cleaned_data["caf"], + # organisation=form.cleaned_data["organisation"], + # caf=form.cleaned_data["caf"], ) + es = form.cleaned_data["essential_service"] + es.systems.add(ass) return super().form_valid(form) def get_form_kwargs(self): kwargs = super().get_form_kwargs() org = Organisation.objects.get(slug=self.kwargs["slug"]) - asses = org.applicablesystem_set.all() - org_cafs = {ass.caf for ass in asses} + asses = org.applicable_systems() + # org_cafs = org.caf_set.all() kwargs["org_id"] = org.id kwargs["slug"] = org.slug kwargs["org_name"] = org.name - kwargs["org_cafs"] = list(org_cafs) + # kwargs["org_cafs"] = list(org_cafs) return kwargs def get_success_url(self): diff --git a/ctrack/core/utils.py b/ctrack/core/utils.py index 0612c52..af01549 100644 --- a/ctrack/core/utils.py +++ b/ctrack/core/utils.py @@ -15,7 +15,7 @@ from ctrack.assessments.models import ( CAFObjective, CAFPrinciple, ) -from ctrack.caf.models import CAF +from ctrack.caf.models import CAF, EssentialService from ctrack.caf.tests.factories import ( ApplicableSystemFactory, CAFFactory, @@ -52,17 +52,25 @@ fnames = [ def _create_caf_app_service(c_descriptors, org, q_descriptors): + # Get the essential services and systems belonging to the org + + es = EssentialService.objects.create( + name="".join(["Essential Service for ", org.name]), + description="Random description", + organisation=org, + ) + as1 = ApplicableSystemFactory.create(name=random.choice(fnames)) + as2 = ApplicableSystemFactory.create(name=random.choice(fnames)) + es.systems.add(as1, as2) + caf = CAFFactory.create( quality_grading__descriptor=random.choice(q_descriptors), confidence_grading__descriptor=random.choice(c_descriptors), + organisation=org, triage_review_date=None, triage_review_inspector=None, ) - # Each CAF can have up to three systems associated with it - for _ in range(random.randint(1, 3)): - ApplicableSystemFactory.create( - name=random.choice(fnames), organisation=org, caf=caf, - ) + caf.systems.add(as1, as2) def populate_db(**kwargs): @@ -219,9 +227,9 @@ def populate_db(**kwargs): # File store FileStoreFactory.create(physical_location_organisation=orgs[1]) - # Every org gets on CAF for now + # Every org gets on CAF and Essential Service for now for org in orgs: - # create a CAF and ApplicableService for it + # create a CAF _create_caf_app_service(c_descriptors, org, q_descriptors) # CAF submissions - they create EngagementEvents diff --git a/ctrack/core/views.py b/ctrack/core/views.py index 5f5627c..d7efe34 100644 --- a/ctrack/core/views.py +++ b/ctrack/core/views.py @@ -12,7 +12,7 @@ def home_page(request): name=request.user.stakeholder.person.get_organisation_name() ) irs = IncidentReport.objects.filter(organisation__name=org) - systems = org.applicablesystem_set.all() + systems = org.applicable_systems() peoples = org.person_set.all() engagement_events = EngagementEvent.objects.filter(participants__in=peoples) return render( diff --git a/ctrack/organisations/admin.py b/ctrack/organisations/admin.py index 203baa9..d3e6284 100644 --- a/ctrack/organisations/admin.py +++ b/ctrack/organisations/admin.py @@ -1,5 +1,7 @@ from django.contrib import admin +from ctrack.caf.models import CAF + from .models import ( Address, AddressType, @@ -18,6 +20,10 @@ def get_organisation_name(person): return Organisation.objects.filter(person__id=person.id).first().name +def get_first_caf(org): + return CAF.objects.filter(organisation__id=org.id).first().version + + # We need this to ensure the column header in the admin does't read the func name get_organisation_name.short_description = "Organisation" diff --git a/ctrack/organisations/migrations/0001_initial.py b/ctrack/organisations/migrations/0001_initial.py index a420fa1..c5ab495 100644 --- a/ctrack/organisations/migrations/0001_initial.py +++ b/ctrack/organisations/migrations/0001_initial.py @@ -1,5 +1,6 @@ -# Generated by Django 2.2.9 on 2020-04-03 14:07 +# Generated by Django 2.2.12 on 2020-08-27 09:40 +import django.contrib.auth from django.db import migrations, models import django.db.models.deletion import django_extensions.db.fields @@ -14,23 +15,6 @@ class Migration(migrations.Migration): operations = [ migrations.CreateModel( - name='Address', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('line1', models.CharField(max_length=255)), - ('line2', models.CharField(blank=True, max_length=255)), - ('line3', models.CharField(blank=True, max_length=255)), - ('city', models.CharField(max_length=100)), - ('county', models.CharField(blank=True, max_length=100)), - ('postcode', models.CharField(max_length=10)), - ('country', models.CharField(max_length=100)), - ('other_details', models.CharField(blank=True, max_length=255)), - ], - options={ - 'verbose_name_plural': 'Addresses', - }, - ), - migrations.CreateModel( name='AddressType', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), @@ -50,31 +34,16 @@ class Migration(migrations.Migration): ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('name', models.CharField(max_length=255)), ('slug', django_extensions.db.fields.AutoSlugField(blank=True, editable=False, populate_from=['name'])), - ('oes', models.BooleanField()), + ('oes', models.BooleanField(default=True)), ('designation_type', models.IntegerField(choices=[(1, 'Automatic'), (2, 'Reserve Power'), (3, 'NA')], default=1)), ('registered_company_name', models.CharField(blank=True, max_length=255)), ('registered_company_number', models.CharField(blank=True, max_length=100)), ('date_updated', models.DateField(auto_now=True)), - ('comments', models.TextField(max_length=500)), + ('comments', models.TextField(blank=True, max_length=500, null=True)), ('active', models.BooleanField(default=True)), ], ), migrations.CreateModel( - name='Role', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(max_length=100)), - ], - ), - migrations.CreateModel( - name='Submode', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('descriptor', models.CharField(max_length=100)), - ('mode', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='organisations.Mode')), - ], - ), - migrations.CreateModel( name='Person', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), @@ -100,10 +69,83 @@ class Migration(migrations.Migration): ('comments', models.TextField(blank=True, max_length=1000)), ('organisation', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='organisations.Organisation')), ('predecessor', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='previous_person', to='organisations.Person')), - ('role', models.ManyToManyField(to='organisations.Role')), ], options={ 'verbose_name_plural': 'People', }, ), + migrations.CreateModel( + name='Role', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=100)), + ], + ), + migrations.CreateModel( + name='Submode', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('descriptor', models.CharField(max_length=100)), + ('mode', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='organisations.Mode')), + ], + ), + migrations.CreateModel( + name='Stakeholder', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('person', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='organisations.Person')), + ], + ), + migrations.AddField( + model_name='person', + name='role', + field=models.ManyToManyField(to='organisations.Role'), + ), + migrations.AddField( + model_name='organisation', + name='submode', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='organisations.Submode'), + ), + migrations.CreateModel( + name='IncidentReport', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('person_involved', models.CharField(blank=True, max_length=100, verbose_name='Name of person reporting/detecting incident')), + ('role', models.CharField(blank=True, help_text='Role of person reporting/detecting incident', 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(verbose_name='Date/Time incident detected')), + ('date_time_incident_reported', models.DateTimeField(auto_now=True, verbose_name='Date/Time incident reported')), + ('incident_type', models.CharField(choices=[('Cyber', 'Cyber'), ('Non-Cyber', 'Non-Cyber'), ('Both', 'Both'), ('Power Outage', 'Power Outage')], help_text='This can be appoximate', max_length=20)), + ('incident_status', models.CharField(choices=[('Detected', 'Detected'), ('Suspected', 'Suspected'), ('Resolved', 'Resolved')], max_length=20)), + ('incident_stage', models.CharField(choices=[('Ongoing', 'Ongoing'), ('Ended', 'Ended'), ('Ongoing but managed', 'Ongoing but managed')], max_length=20)), + ('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')), + ('dft_handle_status', models.CharField(choices=[('QUEUED', 'QUEUED'), ('REVIEWING', 'REVIEWING'), ('WAITING', 'WAITING'), ('COMPLETED', 'COMPLETED')], default='QUEUED', max_length=20)), + ('organisation', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='organisations.Organisation')), + ('reporting_person', models.ForeignKey(on_delete=models.SET(django.contrib.auth.get_user_model), to='organisations.Person', verbose_name='Person reporting the incident')), + ], + ), + migrations.CreateModel( + name='Address', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('line1', models.CharField(max_length=255)), + ('line2', models.CharField(blank=True, max_length=255)), + ('line3', models.CharField(blank=True, max_length=255)), + ('city', models.CharField(max_length=100)), + ('county', models.CharField(blank=True, max_length=100)), + ('postcode', models.CharField(max_length=10)), + ('country', models.CharField(max_length=100)), + ('other_details', models.CharField(blank=True, max_length=255)), + ('organisation', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='addresses', to='organisations.Organisation')), + ('type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='organisations.AddressType', verbose_name='Address Type')), + ], + options={ + 'verbose_name_plural': 'Addresses', + }, + ), ] diff --git a/ctrack/organisations/migrations/0002_auto_20200403_1407.py b/ctrack/organisations/migrations/0002_auto_20200403_1407.py deleted file mode 100644 index d9f3aed..0000000 --- a/ctrack/organisations/migrations/0002_auto_20200403_1407.py +++ /dev/null @@ -1,44 +0,0 @@ -# Generated by Django 2.2.9 on 2020-04-03 14:07 - -import ctrack.organisations.models -from django.conf import settings -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - initial = True - - dependencies = [ - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('organisations', '0001_initial'), - ] - - operations = [ - migrations.AddField( - model_name='person', - name='updated_by', - field=models.ForeignKey(on_delete=models.SET(ctrack.organisations.models.Person.get_sentinel_user), to=settings.AUTH_USER_MODEL), - ), - migrations.AddField( - model_name='organisation', - name='submode', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='organisations.Submode'), - ), - migrations.AddField( - model_name='organisation', - name='updated_by', - field=models.ForeignKey(on_delete=models.SET(ctrack.organisations.models.Organisation.get_sentinel_user), to=settings.AUTH_USER_MODEL), - ), - migrations.AddField( - model_name='address', - name='organisation', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='addresses', to='organisations.Organisation'), - ), - migrations.AddField( - model_name='address', - name='type', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='organisations.AddressType', verbose_name='Address Type'), - ), - ] diff --git a/ctrack/organisations/migrations/0003_auto_20200424_1607.py b/ctrack/organisations/migrations/0003_auto_20200424_1607.py deleted file mode 100644 index 75268a0..0000000 --- a/ctrack/organisations/migrations/0003_auto_20200424_1607.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 2.2.9 on 2020-04-24 16:07 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('organisations', '0002_auto_20200403_1407'), - ] - - operations = [ - migrations.AlterField( - model_name='organisation', - name='comments', - field=models.TextField(blank=True, max_length=500, null=True), - ), - ] diff --git a/ctrack/organisations/migrations/0004_auto_20200513_1441.py b/ctrack/organisations/migrations/0004_auto_20200513_1441.py deleted file mode 100644 index 180211a..0000000 --- a/ctrack/organisations/migrations/0004_auto_20200513_1441.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 3.0.5 on 2020-05-13 14:41 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('organisations', '0003_auto_20200424_1607'), - ] - - operations = [ - migrations.AlterField( - model_name='organisation', - name='oes', - field=models.BooleanField(default=True), - ), - ] diff --git a/ctrack/organisations/migrations/0005_auto_20200525_1502.py b/ctrack/organisations/migrations/0005_auto_20200525_1502.py deleted file mode 100644 index 921fdbb..0000000 --- a/ctrack/organisations/migrations/0005_auto_20200525_1502.py +++ /dev/null @@ -1,29 +0,0 @@ -# Generated by Django 3.0.5 on 2020-05-25 15:02 - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('organisations', '0004_auto_20200513_1441'), - ] - - operations = [ - migrations.RemoveField( - model_name='organisation', - name='updated_by', - ), - migrations.RemoveField( - model_name='person', - name='updated_by', - ), - migrations.CreateModel( - name='Stakeholder', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('person', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='organisations.Person')), - ], - ), - ] diff --git a/ctrack/organisations/migrations/0006_incidentreport.py b/ctrack/organisations/migrations/0006_incidentreport.py deleted file mode 100644 index 0528aaa..0000000 --- a/ctrack/organisations/migrations/0006_incidentreport.py +++ /dev/null @@ -1,35 +0,0 @@ -# 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/migrations/0007_auto_20200529_1520.py b/ctrack/organisations/migrations/0007_auto_20200529_1520.py deleted file mode 100644 index 59a5089..0000000 --- a/ctrack/organisations/migrations/0007_auto_20200529_1520.py +++ /dev/null @@ -1,23 +0,0 @@ -# Generated by Django 2.2.12 on 2020-05-29 15:20 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('organisations', '0006_incidentreport'), - ] - - operations = [ - migrations.AddField( - model_name='incidentreport', - name='dft_handle_status', - field=models.CharField(choices=[('queued', 'QUEUED'), ('reviewing', 'REVIEWING'), ('waiting', 'WAITING'), ('completed', 'COMPLETE')], default='queued', max_length=20), - ), - migrations.AlterField( - model_name='incidentreport', - name='date_time_incident_reported', - field=models.DateTimeField(auto_now=True, verbose_name='Date/Time incident reported'), - ), - ] diff --git a/ctrack/organisations/migrations/0008_auto_20200529_1545.py b/ctrack/organisations/migrations/0008_auto_20200529_1545.py deleted file mode 100644 index 1c4cec6..0000000 --- a/ctrack/organisations/migrations/0008_auto_20200529_1545.py +++ /dev/null @@ -1,38 +0,0 @@ -# Generated by Django 2.2.12 on 2020-05-29 15:45 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('organisations', '0007_auto_20200529_1520'), - ] - - operations = [ - migrations.AlterField( - model_name='incidentreport', - name='date_time_incident_detected', - field=models.DateTimeField(verbose_name='Date/Time incident detected'), - ), - migrations.AlterField( - model_name='incidentreport', - name='dft_handle_status', - field=models.CharField(choices=[('QUEUED', 'QUEUED'), ('REVIEWING', 'REVIEWING'), ('WAITING', 'WAITING'), ('COMPLETED', 'COMPLETED')], default='QUEUED', max_length=20), - ), - migrations.AlterField( - model_name='incidentreport', - name='incident_stage', - field=models.CharField(choices=[('Ongoing', 'Ongoing'), ('Ended', 'Ended'), ('Ongoing but managed', 'Ongoing but managed')], max_length=20), - ), - migrations.AlterField( - model_name='incidentreport', - name='incident_status', - field=models.CharField(choices=[('Detected', 'Detected'), ('Suspected', 'Suspected'), ('Resolved', 'Resolved')], max_length=20), - ), - migrations.AlterField( - model_name='incidentreport', - name='incident_type', - field=models.CharField(choices=[('Cyber', 'Cyber'), ('Non-Cyber', 'Non-Cyber'), ('Both', 'Both'), ('Power Outage', 'Power Outage')], help_text='This can be appoximate', max_length=20), - ), - ] diff --git a/ctrack/organisations/migrations/0009_incidentreport_person_involved.py b/ctrack/organisations/migrations/0009_incidentreport_person_involved.py deleted file mode 100644 index ea4799c..0000000 --- a/ctrack/organisations/migrations/0009_incidentreport_person_involved.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 2.2.12 on 2020-05-29 16:01 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('organisations', '0008_auto_20200529_1545'), - ] - - operations = [ - migrations.AddField( - model_name='incidentreport', - name='person_involved', - field=models.CharField(blank=True, max_length=100, verbose_name='Name of person reporting/detecting incident'), - ), - ] diff --git a/ctrack/organisations/migrations/0010_auto_20200529_1602.py b/ctrack/organisations/migrations/0010_auto_20200529_1602.py deleted file mode 100644 index 2986d36..0000000 --- a/ctrack/organisations/migrations/0010_auto_20200529_1602.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 2.2.12 on 2020-05-29 16:02 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('organisations', '0009_incidentreport_person_involved'), - ] - - operations = [ - migrations.AlterField( - model_name='incidentreport', - name='role', - field=models.CharField(blank=True, help_text='Role of person reporting/detecting incident', max_length=100), - ), - ] diff --git a/ctrack/organisations/migrations/0011_auto_20200531_1441.py b/ctrack/organisations/migrations/0011_auto_20200531_1441.py deleted file mode 100644 index 3dced7d..0000000 --- a/ctrack/organisations/migrations/0011_auto_20200531_1441.py +++ /dev/null @@ -1,19 +0,0 @@ -# Generated by Django 2.2.12 on 2020-05-31 14:41 - -import django.contrib.auth -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('organisations', '0010_auto_20200529_1602'), - ] - - operations = [ - migrations.AlterField( - model_name='incidentreport', - name='reporting_person', - field=models.ForeignKey(on_delete=models.SET(django.contrib.auth.get_user_model), to='organisations.Person', verbose_name='Person reporting the incident'), - ), - ] diff --git a/ctrack/organisations/models.py b/ctrack/organisations/models.py index 9eb0a23..f91dabf 100644 --- a/ctrack/organisations/models.py +++ b/ctrack/organisations/models.py @@ -153,7 +153,19 @@ class Organisation(models.Model): return self.person_set.filter(primary_nis_contact=True) def applicable_systems(self): - return self.applicablesystem_set.all() + # return self.applicablesystem_set.all() + ess = self.essentialservice_set.all() + out = [] + for es in ess: + out.extend(es.systems.all()) + return out + + def systems(self): + ess = self.essentialservice_set.all() + out = [] + for es in ess: + out.extend(list(es.systems.all())) + return out class Address(models.Model): diff --git a/ctrack/organisations/templates/organisations/organisation_detail.html b/ctrack/organisations/templates/organisations/organisation_detail.html index 8c9ac3b..a86adf1 100644 --- a/ctrack/organisations/templates/organisations/organisation_detail.html +++ b/ctrack/organisations/templates/organisations/organisation_detail.html @@ -65,6 +65,33 @@ </div> </div> </div> + + <div class="row"> + <div class="col-md-12 my-2"> + <div class="card bg-light"> + <div class="card-body"> + <div class="card-title">Essential Services</div> + <div class="table table-responsive"> + <table class="table"> + {% if applicable_systems|length > 0 %} + {% for es in essential_services %} + <tr> + <td style="width: 18%">{{ es.name }}</td> + <td>{{ es.description }}<br> + <span class="text-muted"> | <a href="#" class="small">Edit System</a></span> + </td> + </tr> + {% endfor %} + {% else %} + <p>button to create new one here</p> + {% endif %} + </table> + </div> + </div> + </div> + </div> + </div> + <div class="row"> <div class="col-md-12 my-2"> <div class="card bg-light"> @@ -79,7 +106,7 @@ <tr> <td><a href="{% url "caf:ass_detail" ass.id %}">{{ ass.name }}</a></td> <td>{{ ass.function }}<br> - <a href="{% url "caf:detail" ass.caf.pk %}" class="small"> + <a href="{% url "caf:detail" ass.pk %}" class="small"> {{ ass.caf }} </a> <span class="text-muted"> | <a href="#" class="small">Edit System</a></span> </td> @@ -95,21 +122,13 @@ </div> </div> </div> + + <div class="row"> <div class="col-md-12 my-2"> <div class="card bg-light"> <div class="card-body"> - <div class="card-title">OES Engagements<a href="{% url "register:create" %}" class="btn btn-primary btn-sm float-right">Add - new...</a></div> - <div class="table table-responsive"> - <table class="table"> - {% if engagement_events|length > 0 %} - {% for event in engagement_events %} - <tr> - <td><a href="#">{{ event.type}}</a></td> - <td>{{ event.short_description }}</td> - </tr> - {% endfor %} + <div class="card-title">OES Engagements<a href="{% url "register:create" %}" class="btn btn-primary btn-sm float-right">Add new...</a></div> <div class="table table-responsive"> <table class="table"> {% if engagement_events|length > 0 %} {% for event in engagement_events %} <tr> <td><a href="#">{{ event.type}}</a></td> <td>{{ event.short_description }}</td> </tr> {% endfor %} {% else %} <a class="btn btn-primary" href="{% url "register:create" %}" role="button">Add new...</a> diff --git a/ctrack/organisations/tests/test_models.py b/ctrack/organisations/tests/test_models.py index c6314af..108d0ce 100644 --- a/ctrack/organisations/tests/test_models.py +++ b/ctrack/organisations/tests/test_models.py @@ -1,7 +1,14 @@ +import random + import pytest + from slugify import slugify from ctrack.organisations.models import IncidentReport, Organisation +from ctrack.caf.models import CAF, Grading +from ctrack.caf.tests.factories import ApplicableSystemFactory +from ctrack.caf.models import EssentialService +from ctrack.core.utils import fnames pytestmark = pytest.mark.django_db @@ -21,3 +28,27 @@ def test_update_organisation(org_with_people): def test_new_address(addr): # The address "has" an organisation assert addr.organisation.name + + +def test_essential_service(org): + q1 = Grading.objects.create(descriptor="Q1", description="baws", type="QUALITY") + c1 = Grading.objects.create( + descriptor="C1", description="baws_c", type="CONFIDENCE" + ) + caf = CAF.objects.create( + quality_grading=q1, + organisation=org, + confidence_grading=c1, + triage_review_date=None, + triage_review_inspector=None, + ) + ass = ApplicableSystemFactory.create(name=random.choice(fnames), caf=caf,) + ass2 = ApplicableSystemFactory.create(name=random.choice(fnames), caf=caf,) + es = EssentialService.objects.create( + name="Test ES", description="Test ES Description", organisation=org + ) + es.systems.add(ass, ass2) + assert es.organisation.name == org.name + assert es.name == "Test ES" + assert es.systems.count() == 2 + assert ass.name in [s.name for s in org.systems()] diff --git a/ctrack/organisations/views.py b/ctrack/organisations/views.py index 394966a..7652783 100644 --- a/ctrack/organisations/views.py +++ b/ctrack/organisations/views.py @@ -10,6 +10,7 @@ from ctrack.register.models import EngagementEvent from .forms import AddressInlineFormSet, IncidentReportForm, OrganisationCreateForm from .models import IncidentReport, Organisation, Person +from ctrack.caf.models import EssentialService class PersonListView(LoginRequiredMixin, PermissionRequiredMixin, ListView): @@ -66,6 +67,7 @@ class OrganisationDetailView(LoginRequiredMixin, PermissionRequiredMixin, Detail org = kwargs["object"] peoples = org.person_set.all() engagement_events = EngagementEvent.objects.filter(participants__in=peoples) + essential_services = EssentialService.objects.filter(organisation=org) no_addr = org.addresses.count() if no_addr > 1: context["no_addr"] = no_addr @@ -77,9 +79,10 @@ class OrganisationDetailView(LoginRequiredMixin, PermissionRequiredMixin, Detail context["addr"] = addr people = org.person_set.all() context["people"] = people - applicable_systems = org.applicablesystem_set.all() + applicable_systems = org.applicable_systems() context["applicable_systems"] = applicable_systems context["engagement_events"] = engagement_events + context["essential_services"] = essential_services return context diff --git a/ctrack/register/migrations/0001_initial.py b/ctrack/register/migrations/0001_initial.py index 7db1237..30a69ba 100644 --- a/ctrack/register/migrations/0001_initial.py +++ b/ctrack/register/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 2.2.9 on 2020-04-03 14:07 +# Generated by Django 2.2.12 on 2020-08-27 12:44 from django.db import migrations, models import django.db.models.deletion @@ -9,8 +9,8 @@ class Migration(migrations.Migration): initial = True dependencies = [ - ('caf', '0002_auto_20200403_1407'), ('organisations', '0001_initial'), + ('caf', '0002_caf_systems'), ] operations = [ diff --git a/ctrack/register/migrations/0002_engagementevent_user.py b/ctrack/register/migrations/0002_engagementevent_user.py index 59c93d6..56866dc 100644 --- a/ctrack/register/migrations/0002_engagementevent_user.py +++ b/ctrack/register/migrations/0002_engagementevent_user.py @@ -1,4 +1,4 @@ -# Generated by Django 2.2.9 on 2020-04-03 14:07 +# Generated by Django 2.2.12 on 2020-08-27 12:44 import ctrack.register.models from django.conf import settings @@ -10,8 +10,8 @@ class Migration(migrations.Migration): initial = True dependencies = [ - migrations.swappable_dependency(settings.AUTH_USER_MODEL), ('register', '0001_initial'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), ] operations = [ diff --git a/ctrack/users/migrations/0001_initial.py b/ctrack/users/migrations/0001_initial.py index 2f760c2..df3cd12 100644 --- a/ctrack/users/migrations/0001_initial.py +++ b/ctrack/users/migrations/0001_initial.py @@ -1,8 +1,9 @@ -# Generated by Django 2.2.9 on 2020-04-03 14:07 +# Generated by Django 2.2.12 on 2020-08-27 12:44 import django.contrib.auth.models import django.contrib.auth.validators from django.db import migrations, models +import django.db.models.deletion import django.utils.timezone @@ -11,6 +12,7 @@ class Migration(migrations.Migration): initial = True dependencies = [ + ('organisations', '0001_initial'), ('auth', '0011_update_proxy_permissions'), ] @@ -31,6 +33,7 @@ class Migration(migrations.Migration): ('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')), ('name', models.CharField(blank=True, max_length=255, verbose_name='Name of User')), ('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.Group', verbose_name='groups')), + ('stakeholder', models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='organisations.Stakeholder')), ('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.Permission', verbose_name='user permissions')), ], options={ diff --git a/ctrack/users/migrations/0002_user_is_person.py b/ctrack/users/migrations/0002_user_is_person.py deleted file mode 100644 index 2223429..0000000 --- a/ctrack/users/migrations/0002_user_is_person.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 3.0.5 on 2020-05-22 15:19 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('users', '0001_initial'), - ] - - operations = [ - migrations.AddField( - model_name='user', - name='is_person', - field=models.BooleanField(default=False), - ), - ] diff --git a/ctrack/users/migrations/0003_auto_20200522_1527.py b/ctrack/users/migrations/0003_auto_20200522_1527.py deleted file mode 100644 index dc4f7c1..0000000 --- a/ctrack/users/migrations/0003_auto_20200522_1527.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 3.0.5 on 2020-05-22 15:27 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('users', '0002_user_is_person'), - ] - - operations = [ - migrations.RenameField( - model_name='user', - old_name='is_person', - new_name='oes_user', - ), - ] diff --git a/ctrack/users/migrations/0004_auto_20200524_1945.py b/ctrack/users/migrations/0004_auto_20200524_1945.py deleted file mode 100644 index 8839fd2..0000000 --- a/ctrack/users/migrations/0004_auto_20200524_1945.py +++ /dev/null @@ -1,23 +0,0 @@ -# Generated by Django 3.0.5 on 2020-05-24 19:45 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('users', '0003_auto_20200522_1527'), - ] - - operations = [ - migrations.CreateModel( - name='UserProfile', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ], - ), - migrations.RemoveField( - model_name='user', - name='oes_user', - ), - ] diff --git a/ctrack/users/migrations/0005_delete_userprofile.py b/ctrack/users/migrations/0005_delete_userprofile.py deleted file mode 100644 index 42f62bb..0000000 --- a/ctrack/users/migrations/0005_delete_userprofile.py +++ /dev/null @@ -1,16 +0,0 @@ -# Generated by Django 3.0.5 on 2020-05-25 14:41 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('users', '0004_auto_20200524_1945'), - ] - - operations = [ - migrations.DeleteModel( - name='UserProfile', - ), - ] diff --git a/ctrack/users/migrations/0006_user_stakeholder.py b/ctrack/users/migrations/0006_user_stakeholder.py deleted file mode 100644 index d8a3089..0000000 --- a/ctrack/users/migrations/0006_user_stakeholder.py +++ /dev/null @@ -1,20 +0,0 @@ -# Generated by Django 3.0.5 on 2020-05-25 15:02 - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('organisations', '0005_auto_20200525_1502'), - ('users', '0005_delete_userprofile'), - ] - - operations = [ - migrations.AddField( - model_name='user', - name='stakeholder', - field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='organisations.Stakeholder'), - ), - ] @@ -1,6 +1,7 @@ [pytest] addopts = - --ds=config.settings.test --disable-warnings --nomigrations --reuse-db +# --ds=config.settings.test --disable-warnings --nomigrations --reuse-db + --ds=config.settings.test --disable-warnings --nomigrations filterwarnings = ignore::DeprecationWarning python_files = tests.py test_*.py diff --git a/utility/drop_and_recreate.sh b/utility/drop_and_recreate.sh new file mode 100755 index 0000000..89734f4 --- /dev/null +++ b/utility/drop_and_recreate.sh @@ -0,0 +1,7 @@ +#!/usr/bin/env bash + +echo "Ensure this is run from project root." +echo "Dropping database..." +sudo -u postgres psql -f utility/drop_and_recreate.sql +echo "Done." + diff --git a/utility/drop_and_recreate.sql b/utility/drop_and_recreate.sql new file mode 100644 index 0000000..4e03e74 --- /dev/null +++ b/utility/drop_and_recreate.sql @@ -0,0 +1,2 @@ +DROP DATABASE ctrack; +CREATE DATABASE ctrack; diff --git a/utility/remove_migrations.sh b/utility/remove_migrations.sh new file mode 100755 index 0000000..8e3d6d1 --- /dev/null +++ b/utility/remove_migrations.sh @@ -0,0 +1,7 @@ +#!/usr/bin/env bash + +echo "Removing migrations from all migrations directories, except in .venv and contrib..." + +find . -path "*/migrations/*.py" -not -name "__init__.py" -not -path "./contrib/*" -not -path "./.venv/*" -delete +find . -path "*/migrations/*.pyc" -not -path "./contrib/*" -not -path "./.venv/*" -delete +find . -path "*/migrations/__pycache__" -not -path "./contrib/*" -not -path "./.venv/*" -delete |