├── conformity
├── __init__.py
├── tests
│ ├── __init__.py
│ └── test_auth.py
├── migrations
│ ├── __init__.py
│ ├── 0054_remove_finding_status.py
│ ├── 0049_remove_action_attachment.py
│ ├── 0003_rename_mesure_measure.py
│ ├── 0048_remove_finding_attachment.py
│ ├── 0009_rename_auditors_audit_auditor.py
│ ├── 0010_rename_type_finding_severity.py
│ ├── 0056_audit_name.py
│ ├── 0057_alter_audit_name.py
│ ├── 0002_rename_mesure_conformity_measure.py
│ ├── 0055_finding_archived.py
│ ├── 0022_action_status_comment.py
│ ├── 0047_alter_attachment_create_date.py
│ ├── 0039_control_control.py
│ ├── 0018_rename_plan_end_sate_action_plan_end_date.py
│ ├── 0037_alter_controlpoint_control_date.py
│ ├── 0040_alter_control_level.py
│ ├── 0034_alter_controlpoint_control_date.py
│ ├── 0046_alter_attachment_mime_type.py
│ ├── 0033_alter_controlpoint_control_date.py
│ ├── 0025_alter_conformity_comment.py
│ ├── 0035_action_associated_controlpoints.py
│ ├── 0065_alter_control_conformity.py
│ ├── 0027_alter_organization_description.py
│ ├── 0031_alter_control_frequency.py
│ ├── 0050_conformity_status_origin.py
│ ├── 0023_alter_action_organization.py
│ ├── 0006_alter_measure_parent.py
│ ├── 0021_alter_action_status.py
│ ├── 0060_pre_MPTT_migration.py
│ ├── 0036_alter_controlpoint_status.py
│ ├── 0020_alter_action_status.py
│ ├── 0019_alter_conformity_status.py
│ ├── 0053_finding_status.py
│ ├── 0004_conformity_comment_alter_measure_description.py
│ ├── 0038_action_reference_control_level.py
│ ├── 0014_alter_finding_description_alter_finding_reference.py
│ ├── 0045_alter_attachment_create_date_and_more.py
│ ├── 0017_alter_action_create_date_alter_action_update_date.py
│ ├── 0024_action_active_alter_action_status.py
│ ├── 0028_alter_conformity_measure_and_more.py
│ ├── 0016_alter_action_create_date_alter_action_update_date.py
│ ├── 0012_alter_audit_end_date_alter_audit_report_date_and_more.py
│ ├── 0013_alter_audit_end_date_alter_audit_report_date_and_more.py
│ ├── 0043_attachment.py
│ ├── 0063_conformity_status_justification_and_more.py
│ ├── 0032_remove_control_owner_control_organization_and_more.py
│ ├── 0041_rename_policy_framework_alter_framework_options_and_more.py
│ ├── 0042_rename_measure_requirement_alter_conformity_options_and_more.py
│ ├── 0005_alter_measure_name_alter_organization_name_and_more.py
│ ├── 0059_alter_finding_cvss_alter_finding_cvss_descriptor_and_more.py
│ ├── 0007_alter_conformity_options_alter_measure_options_and_more.py
│ ├── 0058_finding_cvss_finding_cvss_descriptor_finding_name_and_more.py
│ ├── 0051_alter_action_options_alter_attachment_options_and_more.py
│ ├── 0062_alter_conformity_options_alter_requirement_options_and_more.py
│ ├── 0011_alter_audit_conclusion_alter_audit_description_and_more.py
│ ├── 0044_action_attachment_audit_attachment_and_more.py
│ ├── 0026_alter_action_control_comment_and_more.py
│ ├── 0030_control_controlpoint.py
│ ├── 0008_audit_finding.py
│ ├── 0061_remove_requirement_legacy_is_parent_and_more.py
│ ├── 0029_alter_action_control_comment_and_more.py
│ ├── 0064_indicator_indicatorpoint.py
│ ├── 0001_squashed_0014_mesure_is_parent.py
│ ├── 0015_alter_finding_severity_alter_policy_type_action.py
│ └── 0052_framework_language.py
├── static
│ ├── favicon.ico
│ ├── conformity
│ │ ├── bootstrap-icons-1.8.3
│ │ │ └── fonts
│ │ │ │ ├── bootstrap-icons.woff
│ │ │ │ └── bootstrap-icons.woff2
│ │ ├── main.css
│ │ └── conformity.js
│ └── registration
│ │ └── login.css
├── apps.py
├── templates
│ ├── conformity
│ │ ├── control_form.html
│ │ ├── indicator_form.html
│ │ ├── finding_form.html
│ │ ├── indicatorpoint_form.html
│ │ ├── audit_form.html
│ │ ├── organization_form.html
│ │ ├── organization_detail.html
│ │ ├── framework_list.html
│ │ ├── conformity_list.html
│ │ ├── framework_detail.html
│ │ ├── controlpoint_form.html
│ │ ├── organization_list.html
│ │ ├── conformity_detail_list.html
│ │ ├── attachment_list.html
│ │ ├── indicator_detail.html
│ │ ├── control_detail_list.html
│ │ ├── finding_detail.html
│ │ ├── audit_list.html
│ │ ├── conformity_detail_list_item.html
│ │ ├── action_list.html
│ │ ├── controlpoint_list.html
│ │ ├── finding_list.html
│ │ ├── indicator_list.html
│ │ └── action_form.html
│ ├── includes
│ │ ├── filter_row.html
│ │ └── pagination.html
│ ├── registration
│ │ └── login.html
│ └── auditlog
│ │ └── logentry_list.html
├── fixture
│ └── Template-Policy-Test.yaml
├── middleware.py
├── filterset.py
├── admin.py
├── urls.py
├── signals.py
└── forms.py
├── oxomium
├── __init__.py
├── asgi.py
├── wsgi.py
├── urls.py
└── settings.py
├── .sonar-project.properties.swp
├── .github
├── workflows
│ ├── .SonarCloud.yml.swp
│ ├── django.yml
│ ├── pylint.yml
│ ├── dependency-review.yml
│ └── SonarCloud.yml
└── dependabot.yml
├── SECURITY.md
├── misc
├── oxomium.socket
├── generate_django_secret_key.py
├── nginx.conf
└── oxomium.service
├── urls.py
├── requirements.txt
├── sonar-project.properties
├── env-exemple
├── manage.py
├── .gitignore
└── README.md
/conformity/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/oxomium/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/conformity/tests/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/conformity/migrations/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.sonar-project.properties.swp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pep-un/Oxomium/HEAD/.sonar-project.properties.swp
--------------------------------------------------------------------------------
/conformity/static/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pep-un/Oxomium/HEAD/conformity/static/favicon.ico
--------------------------------------------------------------------------------
/.github/workflows/.SonarCloud.yml.swp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pep-un/Oxomium/HEAD/.github/workflows/.SonarCloud.yml.swp
--------------------------------------------------------------------------------
/conformity/static/conformity/bootstrap-icons-1.8.3/fonts/bootstrap-icons.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pep-un/Oxomium/HEAD/conformity/static/conformity/bootstrap-icons-1.8.3/fonts/bootstrap-icons.woff
--------------------------------------------------------------------------------
/conformity/static/conformity/bootstrap-icons-1.8.3/fonts/bootstrap-icons.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pep-un/Oxomium/HEAD/conformity/static/conformity/bootstrap-icons-1.8.3/fonts/bootstrap-icons.woff2
--------------------------------------------------------------------------------
/SECURITY.md:
--------------------------------------------------------------------------------
1 | # Security Policy
2 |
3 | ## Supported Versions
4 |
5 | Only the last release is suported.
6 |
7 | ## Reporting a Vulnerability
8 |
9 | Please report vulnerability by opening an issues.
10 |
--------------------------------------------------------------------------------
/misc/oxomium.socket:
--------------------------------------------------------------------------------
1 | [Unit]
2 | Description=oxomium socket
3 |
4 | [Socket]
5 | ListenStream=/run/oxomium.sock
6 | SocketUser=www-data
7 | SocketMode=600
8 |
9 | [Install]
10 | WantedBy=sockets.target
11 |
--------------------------------------------------------------------------------
/urls.py:
--------------------------------------------------------------------------------
1 | """
2 | Global django instance URL router
3 | """
4 |
5 | from django.contrib import admin
6 | from django.urls import include, path
7 |
8 | urlpatterns = [
9 | path('admin/', admin.site.urls),
10 | ]
11 |
--------------------------------------------------------------------------------
/conformity/apps.py:
--------------------------------------------------------------------------------
1 | from django.apps import AppConfig
2 |
3 | class ConformityConfig(AppConfig):
4 | default_auto_field = "django.db.models.BigAutoField"
5 | name = "conformity"
6 |
7 | def ready(self):
8 | from . import signals # noqa: F401
9 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | - package-ecosystem: "pip"
4 | directory: "/"
5 | schedule:
6 | interval: "daily"
7 | - package-ecosystem: "github-actions"
8 | directory: "/"
9 | schedule:
10 | interval: "weekly"
11 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | Django>=5.2,<5.3
2 | django-bootstrap5
3 | django-import-export
4 | django-auditlog
5 | django-tinymce
6 | django-filter
7 | django-tables2
8 | python-magic
9 | python-decouple
10 | python-dateutil
11 | django-mptt
12 | django-constance
13 | pycountry
14 | tablib[xlsx,ods]
--------------------------------------------------------------------------------
/misc/generate_django_secret_key.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # This script will generate a random 50-character string suitable for use as a SECRET_KEY.
3 | import secrets
4 |
5 | charset = 'abcdefghijkmnopqrstuvwxyzABCDEFGHIJKLMNPQRSTUVWXYZ0123456789!@#$%^&*(-_=+)'
6 | print(''.join(secrets.choice(charset) for _ in range(50)))
7 |
--------------------------------------------------------------------------------
/conformity/migrations/0054_remove_finding_status.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 4.2.16 on 2025-07-27 10:54
2 |
3 | from django.db import migrations
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ('conformity', '0053_finding_status'),
10 | ]
11 |
12 | operations = [
13 | migrations.RemoveField(
14 | model_name='finding',
15 | name='status',
16 | ),
17 | ]
18 |
--------------------------------------------------------------------------------
/conformity/migrations/0049_remove_action_attachment.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 4.2.9 on 2024-10-13 01:16
2 |
3 | from django.db import migrations
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ('conformity', '0048_remove_finding_attachment'),
10 | ]
11 |
12 | operations = [
13 | migrations.RemoveField(
14 | model_name='action',
15 | name='attachment',
16 | ),
17 | ]
18 |
--------------------------------------------------------------------------------
/conformity/migrations/0003_rename_mesure_measure.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 4.0.4 on 2022-05-26 12:39
2 |
3 | from django.db import migrations
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ('conformity', '0002_rename_mesure_conformity_measure'),
10 | ]
11 |
12 | operations = [
13 | migrations.RenameModel(
14 | old_name='Mesure',
15 | new_name='Measure',
16 | ),
17 | ]
18 |
--------------------------------------------------------------------------------
/conformity/migrations/0048_remove_finding_attachment.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 4.2.9 on 2024-10-13 01:15
2 |
3 | from django.db import migrations
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ('conformity', '0047_alter_attachment_create_date'),
10 | ]
11 |
12 | operations = [
13 | migrations.RemoveField(
14 | model_name='finding',
15 | name='attachment',
16 | ),
17 | ]
18 |
--------------------------------------------------------------------------------
/oxomium/asgi.py:
--------------------------------------------------------------------------------
1 | """
2 | ASGI config for oxomium project.
3 |
4 | It exposes the ASGI callable as a module-level variable named ``application``.
5 |
6 | For more information on this file, see
7 | https://docs.djangoproject.com/en/4.0/howto/deployment/asgi/
8 | """
9 |
10 | import os
11 |
12 | from django.core.asgi import get_asgi_application
13 |
14 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'oxomium.settings')
15 |
16 | application = get_asgi_application()
17 |
--------------------------------------------------------------------------------
/oxomium/wsgi.py:
--------------------------------------------------------------------------------
1 | """
2 | WSGI config for oxomium project.
3 |
4 | It exposes the WSGI callable as a module-level variable named ``application``.
5 |
6 | For more information on this file, see
7 | https://docs.djangoproject.com/en/4.0/howto/deployment/wsgi/
8 | """
9 |
10 | import os
11 |
12 | from django.core.wsgi import get_wsgi_application
13 |
14 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'oxomium.settings')
15 |
16 | application = get_wsgi_application()
17 |
--------------------------------------------------------------------------------
/conformity/migrations/0009_rename_auditors_audit_auditor.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 4.0.4 on 2022-07-09 16:13
2 |
3 | from django.db import migrations
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ('conformity', '0008_audit_finding'),
10 | ]
11 |
12 | operations = [
13 | migrations.RenameField(
14 | model_name='audit',
15 | old_name='auditors',
16 | new_name='auditor',
17 | ),
18 | ]
19 |
--------------------------------------------------------------------------------
/conformity/migrations/0010_rename_type_finding_severity.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 4.0.4 on 2022-07-09 16:40
2 |
3 | from django.db import migrations
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ('conformity', '0009_rename_auditors_audit_auditor'),
10 | ]
11 |
12 | operations = [
13 | migrations.RenameField(
14 | model_name='finding',
15 | old_name='type',
16 | new_name='severity',
17 | ),
18 | ]
19 |
--------------------------------------------------------------------------------
/conformity/migrations/0056_audit_name.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 4.2.16 on 2025-08-01 21:13
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ('conformity', '0055_finding_archived'),
10 | ]
11 |
12 | operations = [
13 | migrations.AddField(
14 | model_name='audit',
15 | name='name',
16 | field=models.TextField(blank=True, max_length=256),
17 | ),
18 | ]
19 |
--------------------------------------------------------------------------------
/sonar-project.properties:
--------------------------------------------------------------------------------
1 | sonar.projectKey=pep-un_Oxomium
2 | sonar.organization=pep-un
3 | sonar.sourceEncoding=UTF-8
4 | sonar.python.version=3.10,3.11,3.12
5 | sonar.sources=.
6 | sonar.tests=conformity/tests
7 | sonar.test.inclusions=conformity/tests/**/*.py
8 | sonar.exclusions=**/migrations/**,**/__pycache__/**,**/settings/*.py,**/wsgi.py,**/asgi.py,**/manage.py
9 | sonar.python.coverage.reportPaths=coverage.xml
10 | sonar.coverage.exclusions=**/migrations/**,**/settings/*.py,**/wsgi.py,**/asgi.py,**/manage.py
--------------------------------------------------------------------------------
/conformity/migrations/0057_alter_audit_name.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 4.2.16 on 2025-08-01 21:16
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ('conformity', '0056_audit_name'),
10 | ]
11 |
12 | operations = [
13 | migrations.AlterField(
14 | model_name='audit',
15 | name='name',
16 | field=models.CharField(blank=True, max_length=256),
17 | ),
18 | ]
19 |
--------------------------------------------------------------------------------
/conformity/migrations/0002_rename_mesure_conformity_measure.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 4.0.4 on 2022-05-26 12:31
2 |
3 | from django.db import migrations
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ('conformity', '0001_squashed_0014_mesure_is_parent'),
10 | ]
11 |
12 | operations = [
13 | migrations.RenameField(
14 | model_name='conformity',
15 | old_name='mesure',
16 | new_name='measure',
17 | ),
18 | ]
19 |
--------------------------------------------------------------------------------
/conformity/migrations/0055_finding_archived.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 4.2.16 on 2025-07-27 10:58
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ('conformity', '0054_remove_finding_status'),
10 | ]
11 |
12 | operations = [
13 | migrations.AddField(
14 | model_name='finding',
15 | name='archived',
16 | field=models.BooleanField(default=False),
17 | ),
18 | ]
19 |
--------------------------------------------------------------------------------
/conformity/migrations/0022_action_status_comment.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 4.1.5 on 2023-02-07 04:42
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ('conformity', '0021_alter_action_status'),
10 | ]
11 |
12 | operations = [
13 | migrations.AddField(
14 | model_name='action',
15 | name='status_comment',
16 | field=models.CharField(blank=True, max_length=4096),
17 | ),
18 | ]
19 |
--------------------------------------------------------------------------------
/conformity/migrations/0047_alter_attachment_create_date.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 4.2.9 on 2024-10-12 04:42
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ('conformity', '0046_alter_attachment_mime_type'),
10 | ]
11 |
12 | operations = [
13 | migrations.AlterField(
14 | model_name='attachment',
15 | name='create_date',
16 | field=models.DateTimeField(auto_now_add=True),
17 | ),
18 | ]
19 |
--------------------------------------------------------------------------------
/conformity/migrations/0039_control_control.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 4.1.6 on 2023-03-14 01:56
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ('conformity', '0038_action_reference_control_level'),
10 | ]
11 |
12 | operations = [
13 | migrations.AddField(
14 | model_name='control',
15 | name='control',
16 | field=models.ManyToManyField(blank=True, to='conformity.control'),
17 | ),
18 | ]
19 |
--------------------------------------------------------------------------------
/conformity/migrations/0018_rename_plan_end_sate_action_plan_end_date.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 4.1.5 on 2023-02-03 23:35
2 |
3 | from django.db import migrations
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ('conformity', '0017_alter_action_create_date_alter_action_update_date'),
10 | ]
11 |
12 | operations = [
13 | migrations.RenameField(
14 | model_name='action',
15 | old_name='plan_end_sate',
16 | new_name='plan_end_date',
17 | ),
18 | ]
19 |
--------------------------------------------------------------------------------
/conformity/migrations/0037_alter_controlpoint_control_date.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 4.1.6 on 2023-02-25 23:48
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ('conformity', '0036_alter_controlpoint_status'),
10 | ]
11 |
12 | operations = [
13 | migrations.AlterField(
14 | model_name='controlpoint',
15 | name='control_date',
16 | field=models.DateTimeField(blank=True, null=True),
17 | ),
18 | ]
19 |
--------------------------------------------------------------------------------
/conformity/migrations/0040_alter_control_level.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 4.1.6 on 2023-03-18 05:28
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ('conformity', '0039_control_control'),
10 | ]
11 |
12 | operations = [
13 | migrations.AlterField(
14 | model_name='control',
15 | name='level',
16 | field=models.IntegerField(choices=[(1, '1st level'), (2, '2nd level')], default=1),
17 | ),
18 | ]
19 |
--------------------------------------------------------------------------------
/conformity/migrations/0034_alter_controlpoint_control_date.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 4.1.6 on 2023-02-25 21:48
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ('conformity', '0033_alter_controlpoint_control_date'),
10 | ]
11 |
12 | operations = [
13 | migrations.AlterField(
14 | model_name='controlpoint',
15 | name='control_date',
16 | field=models.DateField(blank=True, null=True),
17 | ),
18 | ]
19 |
--------------------------------------------------------------------------------
/conformity/migrations/0046_alter_attachment_mime_type.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 4.2.9 on 2024-10-12 04:31
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ('conformity', '0045_alter_attachment_create_date_and_more'),
10 | ]
11 |
12 | operations = [
13 | migrations.AlterField(
14 | model_name='attachment',
15 | name='mime_type',
16 | field=models.CharField(blank=True, max_length=255),
17 | ),
18 | ]
19 |
--------------------------------------------------------------------------------
/conformity/migrations/0033_alter_controlpoint_control_date.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 4.1.6 on 2023-02-25 21:47
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ('conformity', '0032_remove_control_owner_control_organization_and_more'),
10 | ]
11 |
12 | operations = [
13 | migrations.AlterField(
14 | model_name='controlpoint',
15 | name='control_date',
16 | field=models.DateField(blank=True),
17 | ),
18 | ]
19 |
--------------------------------------------------------------------------------
/conformity/migrations/0025_alter_conformity_comment.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 4.1.6 on 2023-02-12 23:15
2 |
3 | from django.db import migrations
4 | import tinymce.models
5 |
6 |
7 | class Migration(migrations.Migration):
8 |
9 | dependencies = [
10 | ('conformity', '0024_action_active_alter_action_status'),
11 | ]
12 |
13 | operations = [
14 | migrations.AlterField(
15 | model_name='conformity',
16 | name='comment',
17 | field=tinymce.models.HTMLField(blank=True, max_length=4096),
18 | ),
19 | ]
20 |
--------------------------------------------------------------------------------
/conformity/migrations/0035_action_associated_controlpoints.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 4.1.6 on 2023-02-25 21:52
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ('conformity', '0034_alter_controlpoint_control_date'),
10 | ]
11 |
12 | operations = [
13 | migrations.AddField(
14 | model_name='action',
15 | name='associated_controlPoints',
16 | field=models.ManyToManyField(blank=True, to='conformity.controlpoint'),
17 | ),
18 | ]
19 |
--------------------------------------------------------------------------------
/conformity/migrations/0065_alter_control_conformity.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 5.2.7 on 2025-10-12 08:26
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ('conformity', '0064_indicator_indicatorpoint'),
10 | ]
11 |
12 | operations = [
13 | migrations.AlterField(
14 | model_name='control',
15 | name='conformity',
16 | field=models.ManyToManyField(blank=True, related_name='controls', to='conformity.conformity'),
17 | ),
18 | ]
19 |
--------------------------------------------------------------------------------
/conformity/migrations/0027_alter_organization_description.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 4.1.6 on 2023-02-13 01:08
2 |
3 | from django.db import migrations
4 | import tinymce.models
5 |
6 |
7 | class Migration(migrations.Migration):
8 |
9 | dependencies = [
10 | ('conformity', '0026_alter_action_control_comment_and_more'),
11 | ]
12 |
13 | operations = [
14 | migrations.AlterField(
15 | model_name='organization',
16 | name='description',
17 | field=tinymce.models.HTMLField(blank=True, max_length=4096),
18 | ),
19 | ]
20 |
--------------------------------------------------------------------------------
/conformity/migrations/0031_alter_control_frequency.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 4.1.6 on 2023-02-25 05:51
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ('conformity', '0030_control_controlpoint'),
10 | ]
11 |
12 | operations = [
13 | migrations.AlterField(
14 | model_name='control',
15 | name='frequency',
16 | field=models.IntegerField(choices=[(1, 'Yearly'), (2, 'Half-Yearly'), (4, 'Quarterly'), (6, 'Bimonthly'), (12, 'Monthly')], default=1),
17 | ),
18 | ]
19 |
--------------------------------------------------------------------------------
/conformity/templates/conformity/control_form.html:
--------------------------------------------------------------------------------
1 | {% extends "conformity/main.html" %}
2 | {% load django_bootstrap5 %}
3 |
4 | {% block header %}
5 |
Edit of a control
6 | {% endblock %}
7 |
8 | {% block content %}
9 |
18 | {% endblock %}
19 |
--------------------------------------------------------------------------------
/conformity/migrations/0050_conformity_status_origin.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 4.2.16 on 2025-01-07 10:10
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ('conformity', '0049_remove_action_attachment'),
10 | ]
11 |
12 | operations = [
13 | migrations.AddField(
14 | model_name='conformity',
15 | name='status_origin',
16 | field=models.CharField(choices=[('MAN', 'From expert statement'), ('CTRL', 'From a control'), ('IND', 'From indicator')], default='MAN', max_length=5),
17 | ),
18 | ]
19 |
--------------------------------------------------------------------------------
/conformity/templates/conformity/indicator_form.html:
--------------------------------------------------------------------------------
1 | {% extends "conformity/main.html" %}
2 | {% load django_bootstrap5 %}
3 |
4 | {% block header %}
5 | Edit of a indicator
6 | {% endblock %}
7 |
8 | {% block content %}
9 |
18 | {% endblock %}
19 |
--------------------------------------------------------------------------------
/conformity/migrations/0023_alter_action_organization.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 4.1.5 on 2023-02-07 04:55
2 |
3 | from django.db import migrations, models
4 | import django.db.models.deletion
5 |
6 |
7 | class Migration(migrations.Migration):
8 |
9 | dependencies = [
10 | ('conformity', '0022_action_status_comment'),
11 | ]
12 |
13 | operations = [
14 | migrations.AlterField(
15 | model_name='action',
16 | name='organization',
17 | field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='conformity.organization'),
18 | ),
19 | ]
20 |
--------------------------------------------------------------------------------
/conformity/templates/conformity/finding_form.html:
--------------------------------------------------------------------------------
1 | {% extends "conformity/main.html" %}
2 | {% load django_bootstrap5 %}
3 |
4 | {% block header %}
5 | Edit of a finding record
6 | {% endblock %}
7 |
8 | {% block content %}
9 |
18 | {% endblock %}
19 |
--------------------------------------------------------------------------------
/conformity/templates/conformity/indicatorpoint_form.html:
--------------------------------------------------------------------------------
1 | {% extends "conformity/main.html" %}
2 | {% load django_bootstrap5 %}
3 |
4 | {% block header %}
5 | Edit of a indicator point
6 | {% endblock %}
7 |
8 | {% block content %}
9 |
18 | {% endblock %}
19 |
--------------------------------------------------------------------------------
/conformity/migrations/0006_alter_measure_parent.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 4.0.4 on 2022-05-26 21:48
2 |
3 | from django.db import migrations, models
4 | import django.db.models.deletion
5 |
6 |
7 | class Migration(migrations.Migration):
8 |
9 | dependencies = [
10 | ('conformity', '0005_alter_measure_name_alter_organization_name_and_more'),
11 | ]
12 |
13 | operations = [
14 | migrations.AlterField(
15 | model_name='measure',
16 | name='parent',
17 | field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='conformity.measure'),
18 | ),
19 | ]
20 |
--------------------------------------------------------------------------------
/env-exemple:
--------------------------------------------------------------------------------
1 | DEBUG = True
2 | SECRET_KEY='django-insecure-oy-h!94w($b%mb2l-jv&g!1-7iqa9(jc!c=guv=%31_z%yyghr'
3 | LANGUAGE_CODE = 'en-us'
4 | TIME_ZONE = 'UTC'
5 | USE_I18N = True
6 | USE_TZ = True
7 |
8 | STATIC_URL = 'static/'
9 | STATIC_ROOT = 'static'
10 | DB_NAME = 'db.sqlite3'
11 |
12 | SESSION_COOKIE_SECURE = True
13 | SESSION_COOKIE_HTTPONLY = True
14 | SESSION_COOKIE_SAMESITE = 'Strict'
15 | SESSION_COOKIE_NAME = '__Host-sessionid'
16 |
17 | CSRF_USE_SESSIONS = True
18 |
19 | SECURE_SSL_REDIRECT = True
20 | SECURE_HSTS_SECONDS = '15768000'
21 | SECURE_HSTS_INCLUDE_SUBDOMAINS = True
22 | SECURE_HSTS_PRELOAD = True
23 |
24 | ALLOWED_HOSTS = '127.0.0.1'
25 |
--------------------------------------------------------------------------------
/conformity/migrations/0021_alter_action_status.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 4.1.5 on 2023-02-06 22:39
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ('conformity', '0020_alter_action_status'),
10 | ]
11 |
12 | operations = [
13 | migrations.AlterField(
14 | model_name='action',
15 | name='status',
16 | field=models.CharField(choices=[('1', 'Analysing'), ('2', 'Planning'), ('3', 'Implementing'), ('4', 'Controlling'), ('7', 'Frozen'), ('8', 'Closed'), ('9', 'Canceled')], default='1', max_length=5),
17 | ),
18 | ]
19 |
--------------------------------------------------------------------------------
/conformity/migrations/0060_pre_MPTT_migration.py:
--------------------------------------------------------------------------------
1 | # migration 0060_pre_mptt.py
2 | from django.db import migrations
3 |
4 | class Migration(migrations.Migration):
5 | dependencies = [
6 | ('conformity', '0059_alter_finding_cvss_alter_finding_cvss_descriptor_and_more'),
7 | ]
8 | operations = [
9 | migrations.RenameField(
10 | model_name='requirement',
11 | old_name='level',
12 | new_name='legacy_level',
13 | ),
14 | migrations.RenameField(
15 | model_name='requirement',
16 | old_name='is_parent',
17 | new_name='legacy_is_parent',
18 | ),
19 | ]
20 |
--------------------------------------------------------------------------------
/conformity/migrations/0036_alter_controlpoint_status.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 4.1.6 on 2023-02-25 22:39
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ('conformity', '0035_action_associated_controlpoints'),
10 | ]
11 |
12 | operations = [
13 | migrations.AlterField(
14 | model_name='controlpoint',
15 | name='status',
16 | field=models.CharField(choices=[('SCHD', 'Scheduled'), ('TOBE', 'To evaluate'), ('OK', 'Compliant'), ('NOK', 'Non-Compliant'), ('MISS', 'Missed')], default='SCHD', max_length=4),
17 | ),
18 | ]
19 |
--------------------------------------------------------------------------------
/conformity/migrations/0020_alter_action_status.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 4.1.5 on 2023-02-05 23:16
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ('conformity', '0019_alter_conformity_status'),
10 | ]
11 |
12 | operations = [
13 | migrations.AlterField(
14 | model_name='action',
15 | name='status',
16 | field=models.CharField(choices=[('A', 'Analysing'), ('P', 'Planning'), ('I', 'Implementing'), ('C', 'Controlling'), ('E', 'Close successfully'), ('F', 'Frozen'), ('X', 'Canceled')], default='A', max_length=5),
17 | ),
18 | ]
19 |
--------------------------------------------------------------------------------
/conformity/templates/conformity/audit_form.html:
--------------------------------------------------------------------------------
1 | {% extends "conformity/main.html" %}
2 | {% load django_bootstrap5 %}
3 |
4 | {% block header %}
5 | Edit of an audit record
6 | {% endblock %}
7 |
8 | {% block content %}
9 |
18 | {% endblock %}
19 |
--------------------------------------------------------------------------------
/conformity/templates/conformity/organization_form.html:
--------------------------------------------------------------------------------
1 | {% extends "conformity/main.html" %}
2 | {% load django_bootstrap5 %}
3 |
4 | {% block header %}
5 | Organization update
6 | {% endblock %}
7 |
8 | {% block content %}
9 |
18 | {% endblock %}
19 |
--------------------------------------------------------------------------------
/conformity/migrations/0019_alter_conformity_status.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 4.1.5 on 2023-02-05 21:48
2 |
3 | import django.core.validators
4 | from django.db import migrations, models
5 |
6 |
7 | class Migration(migrations.Migration):
8 |
9 | dependencies = [
10 | ('conformity', '0018_rename_plan_end_sate_action_plan_end_date'),
11 | ]
12 |
13 | operations = [
14 | migrations.AlterField(
15 | model_name='conformity',
16 | name='status',
17 | field=models.IntegerField(blank=True, default=0, null=True, validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(100)]),
18 | ),
19 | ]
20 |
--------------------------------------------------------------------------------
/conformity/migrations/0053_finding_status.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 4.2.16 on 2025-07-26 23:29
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ('conformity', '0052_framework_language'),
10 | ]
11 |
12 | operations = [
13 | migrations.AddField(
14 | model_name='finding',
15 | name='status',
16 | field=models.CharField(choices=[('DFT', 'Draft, not validated yet'), ('NEW', 'New and under review'), ('ACT', 'Active, actions identified and planned'), ('REJ', 'Incorrect or inadequate finding'), ('ARC', 'Archived, actions closes')], default='NEW', max_length=3),
17 | ),
18 | ]
19 |
--------------------------------------------------------------------------------
/conformity/migrations/0004_conformity_comment_alter_measure_description.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 4.0.4 on 2022-05-26 12:55
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ('conformity', '0003_rename_mesure_measure'),
10 | ]
11 |
12 | operations = [
13 | migrations.AddField(
14 | model_name='conformity',
15 | name='comment',
16 | field=models.TextField(blank=True, max_length=4096),
17 | ),
18 | migrations.AlterField(
19 | model_name='measure',
20 | name='description',
21 | field=models.TextField(blank=True),
22 | ),
23 | ]
24 |
--------------------------------------------------------------------------------
/manage.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | """Django's command-line utility for administrative tasks."""
3 | import os
4 | import sys
5 |
6 |
7 | def main():
8 | """Run administrative tasks."""
9 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'oxomium.settings')
10 | try:
11 | from django.core.management import execute_from_command_line
12 | except ImportError as exc:
13 | raise ImportError(
14 | "Couldn't import Django. Are you sure it's installed and "
15 | "available on your PYTHONPATH environment variable? Did you "
16 | "forget to activate a virtual environment?"
17 | ) from exc
18 | execute_from_command_line(sys.argv)
19 |
20 |
21 | if __name__ == '__main__':
22 | main()
23 |
--------------------------------------------------------------------------------
/conformity/migrations/0038_action_reference_control_level.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 4.1.6 on 2023-03-11 22:58
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ('conformity', '0037_alter_controlpoint_control_date'),
10 | ]
11 |
12 | operations = [
13 | migrations.AddField(
14 | model_name='action',
15 | name='reference',
16 | field=models.URLField(blank=True),
17 | ),
18 | migrations.AddField(
19 | model_name='control',
20 | name='level',
21 | field=models.IntegerField(choices=[(1, '1st level control'), (2, '2nd level control')], default=1),
22 | ),
23 | ]
24 |
--------------------------------------------------------------------------------
/conformity/migrations/0014_alter_finding_description_alter_finding_reference.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 4.1.5 on 2023-02-01 02:14
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ('conformity', '0013_alter_audit_end_date_alter_audit_report_date_and_more'),
10 | ]
11 |
12 | operations = [
13 | migrations.AlterField(
14 | model_name='finding',
15 | name='description',
16 | field=models.CharField(blank=True, max_length=4096),
17 | ),
18 | migrations.AlterField(
19 | model_name='finding',
20 | name='reference',
21 | field=models.CharField(blank=True, max_length=4096),
22 | ),
23 | ]
24 |
--------------------------------------------------------------------------------
/conformity/migrations/0045_alter_attachment_create_date_and_more.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 4.2.9 on 2024-10-12 04:28
2 |
3 | from django.db import migrations, models
4 | import django.utils.timezone
5 |
6 |
7 | class Migration(migrations.Migration):
8 |
9 | dependencies = [
10 | ('conformity', '0044_action_attachment_audit_attachment_and_more'),
11 | ]
12 |
13 | operations = [
14 | migrations.AlterField(
15 | model_name='attachment',
16 | name='create_date',
17 | field=models.DateTimeField(default=django.utils.timezone.now),
18 | ),
19 | migrations.AlterField(
20 | model_name='attachment',
21 | name='mime_type',
22 | field=models.CharField(max_length=255),
23 | ),
24 | ]
25 |
--------------------------------------------------------------------------------
/.github/workflows/django.yml:
--------------------------------------------------------------------------------
1 | name: Django CI
2 | permissions:
3 | contents: read
4 |
5 | on:
6 | push:
7 | branches: [ main ]
8 | pull_request:
9 | branches: [ main ]
10 |
11 | jobs:
12 | build:
13 |
14 | runs-on: ubuntu-latest
15 | strategy:
16 | max-parallel: 4
17 | matrix:
18 | python-version: ["3.10", "3.11", "3.12"]
19 |
20 | steps:
21 | - uses: actions/checkout@v5
22 | - name: Set up Python ${{ matrix.python-version }}
23 | uses: actions/setup-python@v6
24 | with:
25 | python-version: ${{ matrix.python-version }}
26 | - name: Install Dependencies
27 | run: |
28 | python -m pip install --upgrade pip
29 | pip install -r requirements.txt
30 | - name: Run Tests
31 | run: |
32 | python manage.py test
33 |
--------------------------------------------------------------------------------
/conformity/migrations/0017_alter_action_create_date_alter_action_update_date.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 4.1.5 on 2023-02-03 23:34
2 |
3 | from django.db import migrations, models
4 | import django.utils.timezone
5 |
6 |
7 | class Migration(migrations.Migration):
8 |
9 | dependencies = [
10 | ('conformity', '0016_alter_action_create_date_alter_action_update_date'),
11 | ]
12 |
13 | operations = [
14 | migrations.AlterField(
15 | model_name='action',
16 | name='create_date',
17 | field=models.DateField(default=django.utils.timezone.now),
18 | ),
19 | migrations.AlterField(
20 | model_name='action',
21 | name='update_date',
22 | field=models.DateField(default=django.utils.timezone.now),
23 | ),
24 | ]
25 |
--------------------------------------------------------------------------------
/conformity/migrations/0024_action_active_alter_action_status.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 4.1.5 on 2023-02-07 05:31
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ('conformity', '0023_alter_action_organization'),
10 | ]
11 |
12 | operations = [
13 | migrations.AddField(
14 | model_name='action',
15 | name='active',
16 | field=models.BooleanField(default=True),
17 | ),
18 | migrations.AlterField(
19 | model_name='action',
20 | name='status',
21 | field=models.CharField(choices=[('1', 'Analysing'), ('2', 'Planning'), ('3', 'Implementing'), ('4', 'Controlling'), ('5', 'Closed'), ('7', 'Frozen'), ('9', 'Canceled')], default='1', max_length=5),
22 | ),
23 | ]
24 |
--------------------------------------------------------------------------------
/conformity/migrations/0028_alter_conformity_measure_and_more.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 4.1.6 on 2023-02-14 03:08
2 |
3 | from django.db import migrations, models
4 | import django.db.models.deletion
5 |
6 |
7 | class Migration(migrations.Migration):
8 |
9 | dependencies = [
10 | ('conformity', '0027_alter_organization_description'),
11 | ]
12 |
13 | operations = [
14 | migrations.AlterField(
15 | model_name='conformity',
16 | name='measure',
17 | field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='conformity.measure'),
18 | ),
19 | migrations.AlterField(
20 | model_name='conformity',
21 | name='organization',
22 | field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='conformity.organization'),
23 | ),
24 | ]
25 |
--------------------------------------------------------------------------------
/conformity/migrations/0016_alter_action_create_date_alter_action_update_date.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 4.1.5 on 2023-02-03 23:33
2 |
3 | import datetime
4 | from django.db import migrations, models
5 |
6 |
7 | class Migration(migrations.Migration):
8 |
9 | dependencies = [
10 | ('conformity', '0015_alter_finding_severity_alter_policy_type_action'),
11 | ]
12 |
13 | operations = [
14 | migrations.AlterField(
15 | model_name='action',
16 | name='create_date',
17 | field=models.DateField(default=datetime.datetime(2023, 2, 3, 23, 33, 22, 666918, tzinfo=datetime.timezone.utc)),
18 | ),
19 | migrations.AlterField(
20 | model_name='action',
21 | name='update_date',
22 | field=models.DateField(default=datetime.datetime(2023, 2, 3, 23, 33, 22, 666936, tzinfo=datetime.timezone.utc)),
23 | ),
24 | ]
25 |
--------------------------------------------------------------------------------
/conformity/migrations/0012_alter_audit_end_date_alter_audit_report_date_and_more.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 4.1.5 on 2023-02-01 00:56
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ('conformity', '0011_alter_audit_conclusion_alter_audit_description_and_more'),
10 | ]
11 |
12 | operations = [
13 | migrations.AlterField(
14 | model_name='audit',
15 | name='end_date',
16 | field=models.DateField(null=True),
17 | ),
18 | migrations.AlterField(
19 | model_name='audit',
20 | name='report_date',
21 | field=models.DateField(null=True),
22 | ),
23 | migrations.AlterField(
24 | model_name='audit',
25 | name='start_date',
26 | field=models.DateField(null=True),
27 | ),
28 | ]
29 |
--------------------------------------------------------------------------------
/.github/workflows/pylint.yml:
--------------------------------------------------------------------------------
1 | name: Pylint
2 | permissions:
3 | contents: read
4 |
5 | on: [push]
6 |
7 | jobs:
8 | build:
9 | runs-on: ubuntu-latest
10 | strategy:
11 | matrix:
12 | python-version: ["3.10", "3.11", "3.12"]
13 | steps:
14 | - uses: actions/checkout@v5
15 | - name: Set up Python ${{ matrix.python-version }}
16 | uses: actions/setup-python@v6
17 | with:
18 | python-version: ${{ matrix.python-version }}
19 | - name: Install project dependencies
20 | run: |
21 | pip install -r requirements.txt
22 | - name: Install pylint
23 | run: |
24 | python -m pip install --upgrade pip
25 | pip install pylint-django
26 | - name: Analysing the code with pylint
27 | run: |
28 | pylint -E --load-plugins pylint_django --django-settings-module=oxomium --ignore-paths='.*/migrations/' --ignore=__init__.py,manage.py $(git ls-files '*.py')
29 |
--------------------------------------------------------------------------------
/conformity/migrations/0013_alter_audit_end_date_alter_audit_report_date_and_more.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 4.1.5 on 2023-02-01 00:56
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ('conformity', '0012_alter_audit_end_date_alter_audit_report_date_and_more'),
10 | ]
11 |
12 | operations = [
13 | migrations.AlterField(
14 | model_name='audit',
15 | name='end_date',
16 | field=models.DateField(blank=True, null=True),
17 | ),
18 | migrations.AlterField(
19 | model_name='audit',
20 | name='report_date',
21 | field=models.DateField(blank=True, null=True),
22 | ),
23 | migrations.AlterField(
24 | model_name='audit',
25 | name='start_date',
26 | field=models.DateField(blank=True, null=True),
27 | ),
28 | ]
29 |
--------------------------------------------------------------------------------
/oxomium/urls.py:
--------------------------------------------------------------------------------
1 | """oxomium URL Configuration
2 |
3 | The `urlpatterns` list routes URLs to views. For more information please see:
4 | https://docs.djangoproject.com/en/4.0/topics/http/urls/
5 | Examples:
6 | Function views
7 | 1. Add an import: from my_app import views
8 | 2. Add a URL to urlpatterns: path('', views.home, name='home')
9 | Class-based views
10 | 1. Add an import: from other_app.views import Home
11 | 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
12 | Including another URLconf
13 | 1. Import the include() function: from django.urls import include, path
14 | 2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
15 | """
16 | from django.contrib import admin
17 | from django.urls import include, path
18 |
19 | urlpatterns = [
20 | path('', include('conformity.urls')),
21 | path('accounts/', include('django.contrib.auth.urls')),
22 | path('django-backend/', admin.site.urls),
23 | ]
24 |
--------------------------------------------------------------------------------
/conformity/migrations/0043_attachment.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 4.2.9 on 2024-10-12 02:57
2 |
3 | from django.db import migrations, models
4 | import django.utils.timezone
5 |
6 |
7 | class Migration(migrations.Migration):
8 |
9 | dependencies = [
10 | ('conformity', '0042_rename_measure_requirement_alter_conformity_options_and_more'),
11 | ]
12 |
13 | operations = [
14 | migrations.CreateModel(
15 | name='Attachment',
16 | fields=[
17 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
18 | ('file', models.FileField(upload_to='attachments/')),
19 | ('comment', models.TextField(blank=True, max_length=4096)),
20 | ('mime_type', models.CharField(blank=True, max_length=255)),
21 | ('create_date', models.DateField(default=django.utils.timezone.now)),
22 | ],
23 | ),
24 | ]
25 |
--------------------------------------------------------------------------------
/conformity/templates/includes/filter_row.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 |
17 |
18 |
19 |
20 |
21 |
{{ object_list | length }} items
22 |
23 |
--------------------------------------------------------------------------------
/.github/workflows/dependency-review.yml:
--------------------------------------------------------------------------------
1 | # Dependency Review Action
2 | #
3 | # This Action will scan dependency manifest files that change as part of a Pull Reqest, surfacing known-vulnerable versions of the packages declared or updated in the PR. Once installed, if the workflow run is marked as required, PRs introducing known-vulnerable packages will be blocked from merging.
4 | #
5 | # Source repository: https://github.com/actions/dependency-review-action
6 | # Public documentation: https://docs.github.com/en/code-security/supply-chain-security/understanding-your-software-supply-chain/about-dependency-review#dependency-review-enforcement
7 | name: 'Dependency Review'
8 | on: [pull_request]
9 |
10 | permissions:
11 | contents: read
12 |
13 | jobs:
14 | dependency-review:
15 | runs-on: ubuntu-latest
16 | steps:
17 | - name: 'Checkout Repository'
18 | uses: actions/checkout@v5
19 | - name: 'Dependency Review'
20 | uses: actions/dependency-review-action@v4
21 |
--------------------------------------------------------------------------------
/conformity/migrations/0063_conformity_status_justification_and_more.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 5.2.4 on 2025-08-24 06:00
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ('conformity', '0062_alter_conformity_options_alter_requirement_options_and_more'),
10 | ]
11 |
12 | operations = [
13 | migrations.AddField(
14 | model_name='conformity',
15 | name='status_justification',
16 | field=models.CharField(blank=True, choices=[('EXPT', 'From expert statement'), ('CTRL', 'From successful control'), ('ACT', 'From completed action'), ('FIN', 'From an audit finding'), ('CONF', 'From conformity aggregation')], default='EXPT', max_length=4),
17 | ),
18 | migrations.AddField(
19 | model_name='conformity',
20 | name='status_last_update',
21 | field=models.DateTimeField(blank=True, null=True),
22 | ),
23 | ]
24 |
--------------------------------------------------------------------------------
/conformity/migrations/0032_remove_control_owner_control_organization_and_more.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 4.1.6 on 2023-02-25 20:18
2 |
3 | import datetime
4 | from django.db import migrations, models
5 | import django.db.models.deletion
6 |
7 |
8 | class Migration(migrations.Migration):
9 |
10 | dependencies = [
11 | ('conformity', '0031_alter_control_frequency'),
12 | ]
13 |
14 | operations = [
15 | migrations.RemoveField(
16 | model_name='control',
17 | name='owner',
18 | ),
19 | migrations.AddField(
20 | model_name='control',
21 | name='organization',
22 | field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='conformity.organization'),
23 | ),
24 | migrations.AlterField(
25 | model_name='controlpoint',
26 | name='control_date',
27 | field=models.DateField(default=datetime.date(2023, 2, 25)),
28 | ),
29 | ]
30 |
--------------------------------------------------------------------------------
/conformity/migrations/0041_rename_policy_framework_alter_framework_options_and_more.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 4.2.9 on 2024-08-25 02:39
2 |
3 | from django.db import migrations
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ('conformity', '0040_alter_control_level'),
10 | ]
11 |
12 | operations = [
13 | migrations.RenameModel(
14 | old_name='Policy',
15 | new_name='Framework',
16 | ),
17 | migrations.AlterModelOptions(
18 | name='framework',
19 | options={'ordering': ['name'], 'verbose_name': 'Framework', 'verbose_name_plural': 'Frameworks'},
20 | ),
21 | migrations.RenameField(
22 | model_name='measure',
23 | old_name='policy',
24 | new_name='framework',
25 | ),
26 | migrations.RenameField(
27 | model_name='audit',
28 | old_name='audited_policies',
29 | new_name='audited_frameworks',
30 | ),
31 | ]
32 |
--------------------------------------------------------------------------------
/.github/workflows/SonarCloud.yml:
--------------------------------------------------------------------------------
1 | name: SonarCloud
2 | permissions:
3 | contents: read
4 | on:
5 | push:
6 | branches:
7 | - main
8 | pull_request:
9 | types: [opened, synchronize, reopened]
10 | jobs:
11 | sonarcloud:
12 | name: SonarCloud
13 | runs-on: ubuntu-latest
14 | steps:
15 | - uses: actions/checkout@v5
16 | with:
17 | fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis
18 | - name: Install Dependencies
19 | run: |
20 | python -m pip install --upgrade pip
21 | pip install -r requirements.txt
22 | pip install coverage
23 | - name: Run coverage test
24 | run: |
25 | coverage run manage.py test
26 | coverage xml
27 | - name: SonarCloud Scan
28 | uses: SonarSource/sonarcloud-github-action@master
29 | env:
30 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any
31 | SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
32 |
--------------------------------------------------------------------------------
/conformity/migrations/0042_rename_measure_requirement_alter_conformity_options_and_more.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 4.2.9 on 2024-08-25 04:38
2 |
3 | from django.db import migrations
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ('conformity', '0041_rename_policy_framework_alter_framework_options_and_more'),
10 | ]
11 |
12 | operations = [
13 | migrations.RenameModel(
14 | old_name='Measure',
15 | new_name='Requirement',
16 | ),
17 | migrations.AlterModelOptions(
18 | name='conformity',
19 | options={'ordering': ['organization', 'requirement'], 'verbose_name': 'Conformity', 'verbose_name_plural': 'Conformities'},
20 | ),
21 | migrations.RenameField(
22 | model_name='conformity',
23 | old_name='measure',
24 | new_name='requirement',
25 | ),
26 | migrations.AlterUniqueTogether(
27 | name='conformity',
28 | unique_together={('organization', 'requirement')},
29 | ),
30 | ]
31 |
--------------------------------------------------------------------------------
/conformity/migrations/0005_alter_measure_name_alter_organization_name_and_more.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 4.0.4 on 2022-05-26 21:32
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ('conformity', '0004_conformity_comment_alter_measure_description'),
10 | ]
11 |
12 | operations = [
13 | migrations.AlterField(
14 | model_name='measure',
15 | name='name',
16 | field=models.CharField(blank=True, max_length=50, unique=True),
17 | ),
18 | migrations.AlterField(
19 | model_name='organization',
20 | name='name',
21 | field=models.CharField(max_length=256, unique=True),
22 | ),
23 | migrations.AlterField(
24 | model_name='policy',
25 | name='name',
26 | field=models.CharField(max_length=256, unique=True),
27 | ),
28 | migrations.AlterUniqueTogether(
29 | name='conformity',
30 | unique_together={('organization', 'measure')},
31 | ),
32 | ]
33 |
--------------------------------------------------------------------------------
/conformity/migrations/0059_alter_finding_cvss_alter_finding_cvss_descriptor_and_more.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 5.2.4 on 2025-08-02 14:05
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ('conformity', '0058_finding_cvss_finding_cvss_descriptor_finding_name_and_more'),
10 | ]
11 |
12 | operations = [
13 | migrations.AlterField(
14 | model_name='finding',
15 | name='cvss',
16 | field=models.FloatField(blank=True, default=None, null=True, verbose_name='CVSS'),
17 | ),
18 | migrations.AlterField(
19 | model_name='finding',
20 | name='cvss_descriptor',
21 | field=models.CharField(blank=True, max_length=256, verbose_name='CVSS Vector'),
22 | ),
23 | migrations.AlterField(
24 | model_name='finding',
25 | name='severity',
26 | field=models.CharField(choices=[('CRT', 'Critical non-conformity'), ('MAJ', 'Major non-conformity'), ('MIN', 'Minor non-conformity'), ('OBS', 'Opportunity For Improvement'), ('POS', 'Positive finding'), ('OTHER', 'Other comment')], default='OBS', max_length=5),
27 | ),
28 | ]
29 |
--------------------------------------------------------------------------------
/conformity/migrations/0007_alter_conformity_options_alter_measure_options_and_more.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 4.0.4 on 2022-06-05 17:28
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ('conformity', '0006_alter_measure_parent'),
10 | ]
11 |
12 | operations = [
13 | migrations.AlterModelOptions(
14 | name='conformity',
15 | options={'ordering': ['organization', 'measure'], 'verbose_name': 'Conformity', 'verbose_name_plural': 'Conformities'},
16 | ),
17 | migrations.AlterModelOptions(
18 | name='measure',
19 | options={'ordering': ['name']},
20 | ),
21 | migrations.AlterModelOptions(
22 | name='organization',
23 | options={'ordering': ['name']},
24 | ),
25 | migrations.AlterModelOptions(
26 | name='policy',
27 | options={'ordering': ['name'], 'verbose_name': 'Policy', 'verbose_name_plural': 'Policies'},
28 | ),
29 | migrations.AddField(
30 | model_name='conformity',
31 | name='applicable',
32 | field=models.BooleanField(default=True),
33 | ),
34 | ]
35 |
--------------------------------------------------------------------------------
/conformity/templates/conformity/organization_detail.html:
--------------------------------------------------------------------------------
1 | {% extends "conformity/main.html" %}
2 |
3 | {% block header %}
4 | Organizations {{ organization }}
5 | {{ organization.administrative_id | default:'ø' }}
6 | {% endblock %}
7 |
8 | {% block content %}
9 | {{ organization.description | linebreaksbr }}
10 |
11 | Applicable policy :
12 | {% for item in organization.get_frameworks %}
13 |
14 |
15 | {{ item }}
16 |
17 |
18 |
19 | {% endfor %}
20 |
21 |
22 |
23 |
24 |
Attachments
25 |
26 | {% for attachment in organization.attachment.all %}
27 | - {{ attachment }}
28 | {% empty %}
29 | No attachment
30 | {% endfor %}
31 |
32 |
33 |
34 | {% endblock %}
35 |
--------------------------------------------------------------------------------
/misc/nginx.conf:
--------------------------------------------------------------------------------
1 | server {
2 | listen 443 ssl http2;
3 | server_name demo.oxomium.org;
4 |
5 | root /var/www/Oxomium;
6 | index index.php;
7 |
8 | error_log /var/log/nginx/oxomium-error.log;
9 | access_log /var/log/nginx/oxomium-access.log;
10 |
11 | ssl_certificate /etc/letsencrypt/live/demo.oxomium.org/fullchain.pem;
12 | ssl_certificate_key /etc/letsencrypt/live/demo.oxomium.org/privkey.pem;
13 | ssl_trusted_certificate /etc/letsencrypt/live/demo.oxomium.org/chain.pem;
14 |
15 | add_header Content-Security-Policy "default-src 'self';script-src 'self';style-src 'self' 'unsafe-inline';object-src 'none';img-src 'self' data:;";
16 |
17 | location /static/ {
18 | autoindex on;
19 | alias /var/www/Oxomium/static/;
20 | }
21 |
22 | location / {
23 | proxy_pass http://unix:/run/oxomium.sock;
24 | proxy_set_header Host $http_host;
25 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
26 | proxy_redirect off;
27 | }
28 | }
29 |
30 | server {
31 | # Redirect HTTP traffic to HTTPS
32 | listen 80 ;
33 | server_name demo.oxomium.org;
34 | return 301 https://$host$request_uri;
35 |
36 | if ($host = demo.oxomium.org) {
37 | return 301 https://$host$request_uri;
38 | }
39 | }
--------------------------------------------------------------------------------
/conformity/migrations/0058_finding_cvss_finding_cvss_descriptor_finding_name_and_more.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 4.2.16 on 2025-08-01 21:36
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ('conformity', '0057_alter_audit_name'),
10 | ]
11 |
12 | operations = [
13 | migrations.AddField(
14 | model_name='finding',
15 | name='cvss',
16 | field=models.FloatField(blank=True, default=None, null=True),
17 | ),
18 | migrations.AddField(
19 | model_name='finding',
20 | name='cvss_descriptor',
21 | field=models.CharField(blank=True, max_length=256),
22 | ),
23 | migrations.AddField(
24 | model_name='finding',
25 | name='name',
26 | field=models.CharField(blank=True, max_length=256),
27 | ),
28 | migrations.AddField(
29 | model_name='finding',
30 | name='observation',
31 | field=models.TextField(blank=True, max_length=4096),
32 | ),
33 | migrations.AddField(
34 | model_name='finding',
35 | name='recommendation',
36 | field=models.TextField(blank=True, max_length=4096),
37 | ),
38 | ]
39 |
--------------------------------------------------------------------------------
/conformity/migrations/0051_alter_action_options_alter_attachment_options_and_more.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 4.2.16 on 2025-07-06 07:04
2 |
3 | from django.db import migrations
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ('conformity', '0050_conformity_status_origin'),
10 | ]
11 |
12 | operations = [
13 | migrations.AlterModelOptions(
14 | name='action',
15 | options={'ordering': ['status', '-update_date']},
16 | ),
17 | migrations.AlterModelOptions(
18 | name='attachment',
19 | options={'ordering': ['-create_date', 'file']},
20 | ),
21 | migrations.AlterModelOptions(
22 | name='audit',
23 | options={'ordering': ['-report_date', '-start_date']},
24 | ),
25 | migrations.AlterModelOptions(
26 | name='control',
27 | options={'ordering': ['level', 'frequency', 'title']},
28 | ),
29 | migrations.AlterModelOptions(
30 | name='controlpoint',
31 | options={'ordering': ['period_end_date']},
32 | ),
33 | migrations.AlterModelOptions(
34 | name='finding',
35 | options={'ordering': ['severity']},
36 | ),
37 | migrations.RemoveField(
38 | model_name='conformity',
39 | name='status_origin',
40 | ),
41 | ]
42 |
--------------------------------------------------------------------------------
/conformity/static/conformity/main.css:
--------------------------------------------------------------------------------
1 | .sidebar {
2 | position: fixed;
3 | top: 0;
4 | bottom: 0;
5 | left: 0;
6 | z-index: 100;
7 | padding: 48px 0 0;
8 | box-shadow: inset -1px 0 0 rgba(0, 0, 0, .1);
9 | }
10 |
11 |
12 | .sidebar-sticky {
13 | position: relative;
14 | top: 0;
15 | height: calc(100vh - 48px);
16 | padding-top: .5rem;
17 | overflow-x: hidden;
18 | overflow-y: auto;
19 | }
20 |
21 | .sidebar .nav-link {
22 | font-weight: 500;
23 | color: #333;
24 | }
25 |
26 | .sidebar .nav-link.active {
27 | color: #2470dc;
28 | }
29 |
30 | .sidebar-heading {
31 | font-size: .75rem;
32 | text-transform: uppercase;
33 | }
34 |
35 | .navbar-brand {
36 | padding-top: .75rem;
37 | padding-bottom: .75rem;
38 | font-size: 1rem;
39 | background-color: rgba(0, 0, 0, .25);
40 | box-shadow: inset -1px 0 0 rgba(0, 0, 0, .25);
41 | }
42 |
43 | .navbar .navbar-toggler {
44 | top: .25rem;
45 | right: 1rem;
46 | }
47 |
48 | .navbar .form-control {
49 | padding: .75rem 1rem;
50 | border-width: 0;
51 | border-radius: 0;
52 | }
53 |
54 | .form-control-dark {
55 | color: #fff;
56 | background-color: rgba(255, 255, 255, .1);
57 | border-color: rgba(255, 255, 255, .1);
58 | }
59 |
60 | .form-control-dark:focus {
61 | border-color: transparent;
62 | box-shadow: 0 0 0 3px rgba(255, 255, 255, .25);
63 | }
--------------------------------------------------------------------------------
/conformity/migrations/0062_alter_conformity_options_alter_requirement_options_and_more.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 5.2.4 on 2025-08-23 19:03
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ('conformity', '0061_remove_requirement_legacy_is_parent_and_more'),
10 | ]
11 |
12 | operations = [
13 | migrations.AlterModelOptions(
14 | name='conformity',
15 | options={'ordering': ['organization', 'requirement__framework', 'requirement__tree_id', 'requirement__lft'], 'verbose_name': 'Conformity', 'verbose_name_plural': 'Conformities'},
16 | ),
17 | migrations.AlterModelOptions(
18 | name='requirement',
19 | options={},
20 | ),
21 | migrations.AlterField(
22 | model_name='action',
23 | name='associated_conformity',
24 | field=models.ManyToManyField(blank=True, related_name='actions', to='conformity.conformity'),
25 | ),
26 | migrations.AlterField(
27 | model_name='action',
28 | name='associated_controlPoints',
29 | field=models.ManyToManyField(blank=True, related_name='actions', to='conformity.controlpoint'),
30 | ),
31 | migrations.AlterField(
32 | model_name='action',
33 | name='associated_findings',
34 | field=models.ManyToManyField(blank=True, related_name='actions', to='conformity.finding'),
35 | ),
36 | ]
37 |
--------------------------------------------------------------------------------
/conformity/migrations/0011_alter_audit_conclusion_alter_audit_description_and_more.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 4.1.5 on 2023-02-01 00:40
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ('conformity', '0010_rename_type_finding_severity'),
10 | ]
11 |
12 | operations = [
13 | migrations.AlterField(
14 | model_name='audit',
15 | name='conclusion',
16 | field=models.CharField(blank=True, max_length=4096),
17 | ),
18 | migrations.AlterField(
19 | model_name='audit',
20 | name='description',
21 | field=models.CharField(blank=True, max_length=4096),
22 | ),
23 | migrations.AlterField(
24 | model_name='audit',
25 | name='end_date',
26 | field=models.DateField(blank=True),
27 | ),
28 | migrations.AlterField(
29 | model_name='audit',
30 | name='report_date',
31 | field=models.DateField(blank=True),
32 | ),
33 | migrations.AlterField(
34 | model_name='audit',
35 | name='start_date',
36 | field=models.DateField(blank=True),
37 | ),
38 | migrations.AlterField(
39 | model_name='finding',
40 | name='severity',
41 | field=models.CharField(choices=[('CRIT', 'Critical non-conformity'), ('MAJ', 'Major non-conformity'), ('MIN', 'Minor non-conformity'), ('OBS', 'Observation or Opportunity For Improvement'), ('POS', 'Positive element'), ('OTHER', 'Other remark')], default='OBS', max_length=5),
42 | ),
43 | ]
44 |
--------------------------------------------------------------------------------
/conformity/migrations/0044_action_attachment_audit_attachment_and_more.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 4.2.9 on 2024-10-12 03:03
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ('conformity', '0043_attachment'),
10 | ]
11 |
12 | operations = [
13 | migrations.AddField(
14 | model_name='action',
15 | name='attachment',
16 | field=models.ManyToManyField(blank=True, related_name='actions', to='conformity.attachment'),
17 | ),
18 | migrations.AddField(
19 | model_name='audit',
20 | name='attachment',
21 | field=models.ManyToManyField(blank=True, related_name='audits', to='conformity.attachment'),
22 | ),
23 | migrations.AddField(
24 | model_name='controlpoint',
25 | name='attachment',
26 | field=models.ManyToManyField(blank=True, related_name='ControlPoint', to='conformity.attachment'),
27 | ),
28 | migrations.AddField(
29 | model_name='finding',
30 | name='attachment',
31 | field=models.ManyToManyField(blank=True, related_name='findings', to='conformity.attachment'),
32 | ),
33 | migrations.AddField(
34 | model_name='framework',
35 | name='attachment',
36 | field=models.ManyToManyField(blank=True, related_name='frameworks', to='conformity.attachment'),
37 | ),
38 | migrations.AddField(
39 | model_name='organization',
40 | name='attachment',
41 | field=models.ManyToManyField(blank=True, related_name='organizations', to='conformity.attachment'),
42 | ),
43 | ]
44 |
--------------------------------------------------------------------------------
/conformity/static/registration/login.css:
--------------------------------------------------------------------------------
1 | html, body {
2 | height: 100%;
3 | }
4 |
5 | body {
6 | display: -ms-flexbox;
7 | display: -webkit-box;
8 | display: flex;
9 | -ms-flex-align: center;
10 | -ms-flex-pack: center;
11 | -webkit-box-align: center;
12 | align-items: center;
13 | -webkit-box-pack: center;
14 | justify-content: center;
15 | padding-top: 40px;
16 | padding-bottom: 40px;
17 | background-color: #f5f5f5;
18 | }
19 |
20 | .form-signin {
21 | width: 100%;
22 | max-width: 330px;
23 | padding: 15px;
24 | margin: 0 auto;
25 | }
26 | .form-signin .form-control {
27 | position: relative;
28 | box-sizing: border-box;
29 | height: auto;
30 | padding: 10px;
31 | font-size: 16px;
32 | }
33 | .form-signin .form-control:focus {
34 | z-index: 2;
35 | }
36 | .form-signin input[type="email"] {
37 | margin-bottom: -1px;
38 | border-bottom-right-radius: 0;
39 | border-bottom-left-radius: 0;
40 | }
41 | .form-signin input[type="password"] {
42 | margin-bottom: 10px;
43 | border-top-left-radius: 0;
44 | border-top-right-radius: 0;
45 | }
46 |
47 | .animated {
48 | animation-duration: 2.2s;
49 | animation-fill-mode: both;
50 | }
51 |
52 | @keyframes fadeIn1 {
53 | 0% {opacity: 0;}
54 | 33% {opacity: 1;}
55 | 100% {opacity: 1;}
56 | }
57 |
58 | @keyframes fadeIn2 {
59 | 0% {opacity: 0;}
60 | 33% {opacity: 0;}
61 | 66% {opacity: 1;}
62 | 100% {opacity: 1;}
63 | }
64 |
65 | @keyframes fadeIn3 {
66 | 0% {opacity: 0;}
67 | 66% {opacity: 0;}
68 | 100% {opacity: 1;}
69 | }
70 |
71 | .fadeIn1 {
72 | animation-name: fadeIn1;
73 | }
74 | .fadeIn2 {
75 | animation-name: fadeIn2;
76 | }
77 | .fadeIn3 {
78 | animation-name: fadeIn3;
79 | }
--------------------------------------------------------------------------------
/misc/oxomium.service:
--------------------------------------------------------------------------------
1 | [Unit]
2 | Description=Oxomium WSGI Service
3 | Documentation=https://www.oxomium.org
4 | After=network-online.target
5 | Requires=oxomium.socket
6 |
7 | [Service]
8 | Type=simple
9 |
10 | User=www-data
11 | Group=www-data
12 | DynamicUser=yes
13 |
14 | PrivateDevices=yes
15 | PrivateUsers=yes
16 | PrivateDevices=yes
17 | PrivateTmp=yes
18 | PrivateTmp=true
19 | PrivateNetwork=yes
20 |
21 | ProtectProc=invisible
22 | ProtectControlGroups=yes
23 | ProtectHome=yes
24 | ProtectHostname=yes
25 | ProtectKernelLogs=yes
26 | ProtectKernelModules=yes
27 | ProtectKernelTunables=yes
28 | ProtectSystem=strict
29 | ProtectClock=yes
30 |
31 | RestrictAddressFamilies=AF_UNIX
32 | RestrictNamespaces=yes
33 | RestrictRealtime=yes
34 | RestrictSUIDSGID=yes
35 |
36 | CapabilityBoundingSet=~CAP_SETUID CAP_SETGID CAP_SETPCAP CAP_SYS_ADMIN CAP_SYS_PTRACE CAP_CHOWN CAP_FSETID CAP_SETFCAP CAP_FOWNER CAP_IPC_OWNER CAP_CHECKPOINT_RESTORECAP_NET_ADMIN CAP_SYS_MODULE CAP_SYS_RAWIO CAP_SYS_TIME CAP_KILL CAP_MKNOD CAP_SYSLOG CAP_SYS_NICE CAP_SYS_RESOURCE CAP_NET_BIND_SERVICE CAP_NET_BROADCAST CAP_NET_RAW CAP_SYS_BOOT CAP_LINUX_IMMUTABLE CAP_SYS_CHROOT CAP_BLOCK_SUSPEND CAP_LEASE CAP_SYS_PACCT CAP_SYS_TTY_CONFIG CAP_WAKE_ALARM CAP_MAC_OVERRIDE CAP_MAC_ADMIN CAP_DAC_OVERRIDE CAP_DAC_READ_SEARCH
37 | RestrictNamespaces=~CLONE_NEWUSER
38 | SystemCallFilter=~@clock @debug @module @mount @reboot @swap @resources @raw-io @cpu-emulation @obsolete
39 |
40 | LockPersonality=yes
41 | MemoryDenyWriteExecute=yes
42 | NoNewPrivileges=yes
43 | ProcSubset=pid
44 |
45 | Restart=always
46 | RestartSec=0
47 | RuntimeDirectory=oxomium
48 | WorkingDirectory=/srv/web/Oxomium
49 | ReadWritePaths=/srv/web/Oxomium
50 | ExecStart=/srv/web/Oxomium/.venv/bin/gunicorn oxomium.wsgi:application
51 | ExecReload=/bin/kill -s HUP $MAINPID
52 | KillMode=mixed
53 | TimeoutStopSec=5
54 | UMask=550
55 |
56 | [Install]
57 | WantedBy=multi-user.target
--------------------------------------------------------------------------------
/conformity/fixture/Template-Policy-Test.yaml:
--------------------------------------------------------------------------------
1 | - model: conformity.policy
2 | fields:
3 | name: Test policy
4 | version: 1
5 | publish_by: Oxomium
6 | type: OTHER
7 | - model: conformity.measure
8 | fields:
9 | code: TEST
10 | name: TEST
11 | level: 0
12 | order: 1
13 | policy:
14 | - Test policy
15 | parent: null
16 | title: Test policy
17 | description: ''
18 | is_parent: true
19 | - model: conformity.measure
20 | fields:
21 | code: A
22 | name: NIST-A
23 | level: 1
24 | order: 1
25 | policy:
26 | - Test policy
27 | parent:
28 | - TEST
29 | title: Titre A
30 | description: ''
31 | is_parent: true
32 | - model: conformity.measure
33 | fields:
34 | code: 1
35 | name: NIST-A-1
36 | level: 2
37 | order: 1
38 | policy:
39 | - Test policy
40 | parent:
41 | - TEST-A
42 | title: Measure A-1
43 | description: This is the measure 1 of section A of Test policy.
44 | is_parent: false
45 | - model: conformity.measure
46 | fields:
47 | code: 2
48 | name: NIST-A-2
49 | level: 2
50 | order: 2
51 | policy:
52 | - Test policy
53 | parent:
54 | - TEST-A
55 | title: Measure A-2
56 | description: This is the measure 2 of section A of Test policy.
57 | is_parent: false
58 | - model: conformity.measure
59 | fields:
60 | code: B
61 | name: NIST-B
62 | level: 1
63 | order: 2
64 | policy:
65 | - Test policy
66 | parent:
67 | - TEST
68 | title: Measure B
69 | description: This is the measure B of Test policy.
70 | is_parent: false
71 | - model: conformity.measure
72 | fields:
73 | code: C
74 | name: NIST-C
75 | level: 1
76 | order: 3
77 | policy:
78 | - Test policy
79 | parent:
80 | - TEST
81 | title: Measure C
82 | description: This is the measure C of Test policy.
83 | is_parent: false
--------------------------------------------------------------------------------
/conformity/templates/conformity/framework_list.html:
--------------------------------------------------------------------------------
1 | {% extends "conformity/main.html" %}
2 |
3 | {% block header %}
4 | Frameworks
5 | {% endblock %}
6 |
7 | {% block content %}
8 |
9 | {% include "includes/filter_row.html" %}
10 |
11 | {% if framework_list %}
12 |
13 | List of frameworks
14 |
15 |
16 | | Framework |
17 | Version |
18 | Language |
19 | Published by |
20 | Type |
21 | Requirements |
22 |
23 |
24 |
25 | {% for framework in framework_list %}
26 |
27 | | {{ framework.name }} |
28 | {{ framework.version | default:'-'}} |
29 | {{ framework.get_language_display }} |
30 | {{ framework.publish_by }} |
31 | {{ framework.get_type }} |
32 |
33 |
34 |
35 |
36 | |
37 |
38 | {% endfor %}
39 |
40 |
41 | {% else %}
42 |
43 | No framework registered.
44 |
45 | {% endif %}
46 | {% endblock %}
47 |
--------------------------------------------------------------------------------
/conformity/tests/test_auth.py:
--------------------------------------------------------------------------------
1 | import inspect
2 | import importlib
3 | from django.test import TestCase
4 | from django.contrib.auth.mixins import LoginRequiredMixin, PermissionRequiredMixin, UserPassesTestMixin
5 |
6 |
7 | class SecurityCoverageTests(TestCase):
8 | """
9 | Security coverage tests:
10 | - All CBV defined in `conformity.views` must be protected by an auth-related mixin.
11 | """
12 |
13 | ALLOW_PUBLIC_VIEWS = set()
14 |
15 | def test_all_cbv_have_auth_mixin(self):
16 | """
17 | Every class-based view (CBV) declared in `conformity.views` must inherit from
18 | one of: LoginRequiredMixin, PermissionRequiredMixin, UserPassesTestMixin.
19 | This catches accidental public exposure of internal views.
20 | """
21 | try:
22 | views_mod = importlib.import_module("conformity.views")
23 | except ModuleNotFoundError:
24 | self.skipTest("Module 'conformity.views' not found; adjust module path if your app is named differently.")
25 |
26 | from django.views import View
27 |
28 | protected_mixins = (LoginRequiredMixin, PermissionRequiredMixin, UserPassesTestMixin)
29 | missing = []
30 |
31 | for name, obj in inspect.getmembers(views_mod, inspect.isclass):
32 | # Only classes defined in this module (ignore imports)
33 | if obj.__module__ != views_mod.__name__:
34 | continue
35 | if not issubclass(obj, View):
36 | continue
37 | if name in self.ALLOW_PUBLIC_VIEWS:
38 | continue
39 | if not any(issubclass(obj, mixin) for mixin in protected_mixins):
40 | missing.append(name)
41 |
42 | self.assertFalse(
43 | missing,
44 | msg=(
45 | "The following CBV lack an auth/permission mixin "
46 | f"(LoginRequiredMixin/PermissionRequiredMixin/UserPassesTestMixin): {missing}"
47 | ),
48 | )
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Django #
2 | *.log
3 | *.pot
4 | *.pyc
5 | __pycache__
6 | *.sqlite3
7 | media
8 | attachments/
9 |
10 | # Backup files #
11 | *.bak
12 |
13 | # If you are using PyCharm #
14 | .idea
15 |
16 | # File-based project format
17 | *.iws
18 |
19 | # IntelliJ
20 | out/
21 |
22 | # JIRA plugin
23 | atlassian-ide-plugin.xml
24 |
25 | # Python #
26 | *.py[cod]
27 | *$py.class
28 |
29 | # Distribution / packaging
30 | .Python build/
31 | develop-eggs/
32 | dist/
33 | downloads/
34 | eggs/
35 | .eggs/
36 | lib/
37 | lib64/
38 | parts/
39 | sdist/
40 | var/
41 | wheels/
42 | static/
43 | *.egg-info/
44 | .installed.cfg
45 | *.egg
46 | *.manifest
47 | *.spec
48 |
49 | # Installer logs
50 | pip-log.txt
51 | pip-delete-this-directory.txt
52 |
53 | # Unit test / coverage reports
54 | htmlcov/
55 | .tox/
56 | .coverage
57 | .coverage.*
58 | .cache
59 | .pytest_cache/
60 | nosetests.xml
61 | coverage.xml
62 | *.cover
63 | .hypothesis/
64 |
65 | # Jupyter Notebook
66 | .ipynb_checkpoints
67 |
68 | # pyenv
69 | .python-version
70 |
71 | # celery
72 | celerybeat-schedule.*
73 |
74 | # SageMath parsed files
75 | *.sage.py
76 |
77 | # Environments
78 | .env
79 | .venv
80 | env/
81 | venv/
82 | ENV/
83 | env.bak/
84 | venv.bak/
85 |
86 | # mkdocs documentation
87 | /site
88 |
89 | # mypy
90 | .mypy_cache/
91 |
92 | # Sublime Text #
93 | *.tmlanguage.cache
94 | *.tmPreferences.cache
95 | *.stTheme.cache
96 | *.sublime-workspace
97 | *.sublime-project
98 |
99 | # sftp configuration file
100 | sftp-config.json
101 |
102 | # Package control specific files Package
103 | Control.last-run
104 | Control.ca-list
105 | Control.ca-bundle
106 | Control.system-ca-bundle
107 | GitHub.sublime-settings
108 |
109 | # Visual Studio Code #
110 | .vscode/*
111 | !.vscode/settings.json
112 | !.vscode/tasks.json
113 | !.vscode/launch.json
114 | !.vscode/extensions.json
115 | .history
116 | .sonarlint/connectedMode.json
117 |
--------------------------------------------------------------------------------
/conformity/templates/conformity/conformity_list.html:
--------------------------------------------------------------------------------
1 | {% extends "conformity/main.html" %}
2 |
3 | {% block header %}
4 | Conformities
5 | {% endblock %}
6 |
7 | {% block content %}
8 |
9 | {% include "includes/filter_row.html" %}
10 |
11 |
12 | Overview of the organizations conformity to there Frameworks
13 |
14 |
15 | | Organization |
16 | Framework |
17 | Requirement |
18 | Completeness |
19 | Conformity |
20 | Actions |
21 |
22 |
23 |
24 | {% for con in conformity_list %}
25 |
26 | |
27 | {{ con.organization }}
28 | |
29 |
30 | {{ con.requirement.framework.name }}
31 | |
32 |
33 | {{ con.get_leaf | length }}
34 | |
35 |
36 | {{ con.get_completeness }} %
37 | |
38 |
39 |
40 | {{ con.status }}%
41 |
42 | |
43 |
44 |
46 | |
47 |
48 | {% empty %}
49 |
50 | No conformity review available.
51 |
52 | | No data to display |
53 | {% endfor %}
54 |
55 | {% endblock %}
56 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 | [](https://github.com/pep-un/Oxomium/actions/workflows/pylint.yml)
3 | [](https://github.com/pep-un/Oxomium/actions/workflows/django.yml)
4 | [](https://github.com/pep-un/Oxomium/actions/workflows/dependency-review.yml)
5 |
6 | [](https://sonarcloud.io/summary/new_code?id=pep-un_Oxomium)
7 | [](https://sonarcloud.io/summary/new_code?id=pep-un_Oxomium)
8 | [](https://sonarcloud.io/summary/new_code?id=pep-un_Oxomium)
9 | [](https://sonarcloud.io/summary/new_code?id=pep-un_Oxomium)
10 | [](https://sonarcloud.io/summary/new_code?id=pep-un_Oxomium)
11 | [](https://sonarcloud.io/summary/new_code?id=pep-un_Oxomium)
12 | [](https://sonarcloud.io/summary/new_code?id=pep-un_Oxomium)
13 |
14 |
15 | # Oxomium Project
16 |
17 | Oxomium is an opensource project build to help company to manage the cybersecurity compliance of organisations.
18 |
19 | It provides help to CISO or other security people to follow conformity to a Framework.
20 |
21 | More information on [Oxomium Website](https://www.oxomium.org).
22 |
23 | An online demonstration in available with user `demo` and password `6NLYm6F4PBBQBjc`: [Oxomium Demo](https://demo.oxomium.org)
24 |
25 | A wiki page detail the process of [installation](https://github.com/pep-un/Oxomium/wiki/Instalation).
26 |
--------------------------------------------------------------------------------
/conformity/templates/conformity/framework_detail.html:
--------------------------------------------------------------------------------
1 | {% extends "conformity/main.html" %}
2 |
3 | {% block header %}
4 | Framework: {{ framework.name }}
5 |
6 | Version : {{ framework.version | default:'-' }}
7 | Language : {{ framework.get_language_display | default:'-' }}
8 | Published by : {{ framework.publish_by | default:'-' }}
9 | Type : {{ framework.get_type | default:'-' }}
10 |
11 | {% endblock %}
12 |
13 | {% block content %}
14 | {% for requirement in framework.get_requirements %}
15 | {% if requirement.level == 1 %}
16 | {{ requirement.title }}
17 | {{ requirement.description | linebreaksbr }}
18 | {% elif requirement.level == 2 %}
19 | {{ requirement.title }}
20 | {{ requirement.description | linebreaksbr }}
21 | {% else %}
22 | {{ requirement.title }}
23 | {{ requirement.description | linebreaksbr }}
24 | {% endif %}
25 |
26 | {% empty %}
27 |
28 | No requirement defined for this framework.
29 |
30 | {% endfor %}
31 |
32 |
33 |
34 |
35 |
Attachments
36 |
37 | {% for attachment in framework.attachment.all %}
38 | - {{ attachment }}
39 | {% empty %}
40 | No attachment
41 | {% endfor %}
42 |
43 |
44 |
45 | {% endblock %}
--------------------------------------------------------------------------------
/conformity/migrations/0026_alter_action_control_comment_and_more.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 4.1.6 on 2023-02-13 00:52
2 |
3 | from django.db import migrations
4 | import tinymce.models
5 |
6 |
7 | class Migration(migrations.Migration):
8 |
9 | dependencies = [
10 | ('conformity', '0025_alter_conformity_comment'),
11 | ]
12 |
13 | operations = [
14 | migrations.AlterField(
15 | model_name='action',
16 | name='control_comment',
17 | field=tinymce.models.HTMLField(blank=True, max_length=4096),
18 | ),
19 | migrations.AlterField(
20 | model_name='action',
21 | name='description',
22 | field=tinymce.models.HTMLField(blank=True, max_length=4096),
23 | ),
24 | migrations.AlterField(
25 | model_name='action',
26 | name='implement_comment',
27 | field=tinymce.models.HTMLField(blank=True, max_length=4096),
28 | ),
29 | migrations.AlterField(
30 | model_name='action',
31 | name='plan_comment',
32 | field=tinymce.models.HTMLField(blank=True, max_length=4096),
33 | ),
34 | migrations.AlterField(
35 | model_name='action',
36 | name='status_comment',
37 | field=tinymce.models.HTMLField(blank=True, max_length=4096),
38 | ),
39 | migrations.AlterField(
40 | model_name='audit',
41 | name='conclusion',
42 | field=tinymce.models.HTMLField(blank=True, max_length=4096),
43 | ),
44 | migrations.AlterField(
45 | model_name='audit',
46 | name='description',
47 | field=tinymce.models.HTMLField(blank=True, max_length=4096),
48 | ),
49 | migrations.AlterField(
50 | model_name='finding',
51 | name='description',
52 | field=tinymce.models.HTMLField(blank=True, max_length=4096),
53 | ),
54 | migrations.AlterField(
55 | model_name='finding',
56 | name='reference',
57 | field=tinymce.models.HTMLField(blank=True, max_length=4096),
58 | ),
59 | migrations.AlterField(
60 | model_name='measure',
61 | name='description',
62 | field=tinymce.models.HTMLField(blank=True),
63 | ),
64 | ]
65 |
--------------------------------------------------------------------------------
/conformity/migrations/0030_control_controlpoint.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 4.1.6 on 2023-02-24 23:22
2 |
3 | from django.conf import settings
4 | from django.db import migrations, models
5 | import django.db.models.deletion
6 | import django.utils.timezone
7 |
8 |
9 | class Migration(migrations.Migration):
10 |
11 | dependencies = [
12 | migrations.swappable_dependency(settings.AUTH_USER_MODEL),
13 | ('conformity', '0001_squashed_0029_alter_action_control_comment_and_more'),
14 | ]
15 |
16 | operations = [
17 | migrations.CreateModel(
18 | name='Control',
19 | fields=[
20 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
21 | ('title', models.CharField(max_length=256)),
22 | ('description', models.TextField(blank=True, max_length=4096)),
23 | ('frequency', models.IntegerField(choices=[('1', 'Yearly'), ('2', 'Half-Yearly'), ('4', 'Quarterly'), ('6', 'Bimonthly'), ('12', 'Monthly')], default='1')),
24 | ('conformity', models.ManyToManyField(blank=True, to='conformity.conformity')),
25 | ('owner', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
26 | ],
27 | ),
28 | migrations.CreateModel(
29 | name='ControlPoint',
30 | fields=[
31 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
32 | ('control_date', models.DateField(default=django.utils.timezone.now)),
33 | ('period_start_date', models.DateField()),
34 | ('period_end_date', models.DateField()),
35 | ('status', models.CharField(choices=[('SCHD', 'Scheduled'), ('TOBE', 'To be evaluated'), ('OK', 'Compliant'), ('NOK', 'Non-Compliant'), ('MISS', 'Missed')], default='SCHD', max_length=4)),
36 | ('comment', models.TextField(blank=True, max_length=4096)),
37 | ('control', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='conformity.control')),
38 | ('control_user', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
39 | ],
40 | ),
41 | ]
42 |
--------------------------------------------------------------------------------
/conformity/migrations/0008_audit_finding.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 4.0.4 on 2022-07-09 15:35
2 |
3 | from django.db import migrations, models
4 | import django.db.models.deletion
5 |
6 |
7 | class Migration(migrations.Migration):
8 |
9 | dependencies = [
10 | ('conformity', '0007_alter_conformity_options_alter_measure_options_and_more'),
11 | ]
12 |
13 | operations = [
14 | migrations.CreateModel(
15 | name='Audit',
16 | fields=[
17 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
18 | ('description', models.CharField(max_length=4096)),
19 | ('conclusion', models.CharField(max_length=4096)),
20 | ('auditors', models.CharField(max_length=256)),
21 | ('start_date', models.DateField()),
22 | ('end_date', models.DateField()),
23 | ('report_date', models.DateField()),
24 | ('type', models.CharField(choices=[('INT', 'Internal Audit'), ('CUS', 'Customer Audit'), ('NAT', 'National Authority'), ('AUD', '3rd party auditor'), ('OTHER', 'Other')], default='OTHER', max_length=5)),
25 | ('audited_policies', models.ManyToManyField(blank=True, to='conformity.policy')),
26 | ('organization', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='conformity.organization')),
27 | ],
28 | options={
29 | 'ordering': ['report_date'],
30 | },
31 | ),
32 | migrations.CreateModel(
33 | name='Finding',
34 | fields=[
35 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
36 | ('short_description', models.CharField(max_length=256)),
37 | ('description', models.CharField(max_length=4096)),
38 | ('reference', models.CharField(max_length=4096)),
39 | ('type', models.CharField(choices=[('POS', 'Positive element'), ('OBS', 'Observation or Opportunity For Improvement'), ('MIN', 'Minor non-conformity'), ('MAJ', 'Major non-conformity'), ('CRIT', 'Critical non-conformity'), ('OTHER', 'Other remark')], default='OBS', max_length=5)),
40 | ('audit', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='conformity.audit')),
41 | ],
42 | ),
43 | ]
44 |
--------------------------------------------------------------------------------
/conformity/migrations/0061_remove_requirement_legacy_is_parent_and_more.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 5.2.4 on 2025-08-22 21:25
2 |
3 | import django.core.validators
4 | import django.db.models.deletion
5 | import mptt.fields
6 | from django.db import migrations, models
7 |
8 | def rebuild_mptt(apps, schema_editor):
9 | Requirement = apps.get_model('conformity', 'Requirement')
10 | from mptt import register
11 | try:
12 | register(Requirement)
13 | except Exception:
14 | pass
15 | Requirement._tree_manager.rebuild()
16 |
17 | class Migration(migrations.Migration):
18 |
19 | dependencies = [
20 | ('conformity', '0060_pre_MPTT_migration'),
21 | ]
22 |
23 | operations = [
24 | migrations.RemoveField(
25 | model_name='requirement',
26 | name='legacy_is_parent',
27 | ),
28 | migrations.RemoveField(
29 | model_name='requirement',
30 | name='legacy_level',
31 | ),
32 | migrations.AddField(
33 | model_name='requirement',
34 | name='level',
35 | field=models.PositiveIntegerField(default=0, editable=False),
36 | preserve_default=False,
37 | ),
38 | migrations.AddField(
39 | model_name='requirement',
40 | name='lft',
41 | field=models.PositiveIntegerField(default=0, editable=False),
42 | preserve_default=False,
43 | ),
44 | migrations.AddField(
45 | model_name='requirement',
46 | name='rght',
47 | field=models.PositiveIntegerField(default=0, editable=False),
48 | preserve_default=False,
49 | ),
50 | migrations.AddField(
51 | model_name='requirement',
52 | name='tree_id',
53 | field=models.PositiveIntegerField(db_index=True, default=0, editable=False),
54 | preserve_default=False,
55 | ),
56 | migrations.AlterField(
57 | model_name='conformity',
58 | name='status',
59 | field=models.IntegerField(blank=True, default=None, null=True, validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(100)]),
60 | ),
61 | migrations.AlterField(
62 | model_name='requirement',
63 | name='parent',
64 | field=mptt.fields.TreeForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='children', to='conformity.requirement'),
65 | ),
66 | migrations.RunPython(rebuild_mptt, migrations.RunPython.noop),
67 | ]
68 |
--------------------------------------------------------------------------------
/conformity/templates/includes/pagination.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/conformity/migrations/0029_alter_action_control_comment_and_more.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 4.1.6 on 2023-02-17 09:22
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ('conformity', '0028_alter_conformity_measure_and_more'),
10 | ]
11 |
12 | operations = [
13 | migrations.AlterField(
14 | model_name='action',
15 | name='control_comment',
16 | field=models.TextField(blank=True, max_length=4096),
17 | ),
18 | migrations.AlterField(
19 | model_name='action',
20 | name='description',
21 | field=models.TextField(blank=True, max_length=4096),
22 | ),
23 | migrations.AlterField(
24 | model_name='action',
25 | name='implement_comment',
26 | field=models.TextField(blank=True, max_length=4096),
27 | ),
28 | migrations.AlterField(
29 | model_name='action',
30 | name='plan_comment',
31 | field=models.TextField(blank=True, max_length=4096),
32 | ),
33 | migrations.AlterField(
34 | model_name='action',
35 | name='status_comment',
36 | field=models.TextField(blank=True, max_length=4096),
37 | ),
38 | migrations.AlterField(
39 | model_name='audit',
40 | name='conclusion',
41 | field=models.TextField(blank=True, max_length=4096),
42 | ),
43 | migrations.AlterField(
44 | model_name='audit',
45 | name='description',
46 | field=models.TextField(blank=True, max_length=4096),
47 | ),
48 | migrations.AlterField(
49 | model_name='conformity',
50 | name='comment',
51 | field=models.TextField(blank=True, max_length=4096),
52 | ),
53 | migrations.AlterField(
54 | model_name='finding',
55 | name='description',
56 | field=models.TextField(blank=True, max_length=4096),
57 | ),
58 | migrations.AlterField(
59 | model_name='finding',
60 | name='reference',
61 | field=models.TextField(blank=True, max_length=4096),
62 | ),
63 | migrations.AlterField(
64 | model_name='measure',
65 | name='description',
66 | field=models.TextField(blank=True),
67 | ),
68 | migrations.AlterField(
69 | model_name='organization',
70 | name='description',
71 | field=models.TextField(blank=True, max_length=4096),
72 | ),
73 | ]
74 |
--------------------------------------------------------------------------------
/conformity/templates/conformity/controlpoint_form.html:
--------------------------------------------------------------------------------
1 | {% extends "conformity/main.html" %}
2 | {% load django_bootstrap5 %}
3 |
4 | {% block header %}
5 | Edit of a control point
6 | {% endblock %}
7 |
8 | {% block content %}
9 |
52 | {% endblock %}
53 |
--------------------------------------------------------------------------------
/conformity/middleware.py:
--------------------------------------------------------------------------------
1 | from django.contrib.auth.signals import user_logged_in
2 | from django.dispatch import receiver
3 | from datetime import datetime
4 | from .models import ControlPoint, IndicatorPoint
5 |
6 |
7 | class SanityCheckMiddleware:
8 | last_checked = datetime.today() # Class variable to store the last check date
9 |
10 | def __init__(self, get_response):
11 | self.get_response = get_response
12 |
13 | def __call__(self, request):
14 | response = self.get_response(request)
15 | self.run_daily_checks()
16 | return response
17 |
18 | def run_daily_checks(self):
19 | """Performs the daily integrity checks."""
20 | today = datetime.today().date()
21 |
22 | if SanityCheckMiddleware.last_checked != today:
23 | self.check_control_points(today)
24 | self.check_indicator_points(today)
25 | SanityCheckMiddleware.last_checked = today
26 |
27 | @staticmethod
28 | def check_control_points(today):
29 | """Checks and updates the status of ControlPoint."""
30 |
31 | """Update SCHD to TOBE when period start"""
32 | scheduled_controls = ControlPoint.objects.filter(period_start_date__lte=today,
33 | period_end_date__gte=today,
34 | status="SCHD")
35 | scheduled_controls.update(status='TOBE')
36 |
37 | """Update expired TOBE to MISS """
38 | missed_controls = ControlPoint.objects.filter(period_start_date__lt=today,
39 | period_end_date__lt=today,
40 | status__in=["TOBE","SCHD"])
41 | missed_controls.update(status='MISS')
42 |
43 | @staticmethod
44 | def check_indicator_points(today):
45 | """Checks and updates the status of IndicatorPoint."""
46 |
47 | """Update SCHD to TOBE when period start"""
48 | scheduled_indicators = IndicatorPoint.objects.filter(period_start_date__lte=today,
49 | period_end_date__gte=today,
50 | status="SCHD")
51 | scheduled_indicators.update(status='TOBE')
52 |
53 | """Update expired TOBE to MISS """
54 | missed_indicators = IndicatorPoint.objects.filter(period_start_date__lt=today,
55 | period_end_date__lt=today,
56 | status__in=["TOBE","SCHD"])
57 | missed_indicators.update(status='MISS')
58 |
59 | # Connect the user login signal
60 | @receiver(user_logged_in)
61 | def update_on_login(sender, user, request, **kwargs):
62 | middleware = SanityCheckMiddleware(None)
63 | middleware.run_daily_checks()
--------------------------------------------------------------------------------
/conformity/static/conformity/conformity.js:
--------------------------------------------------------------------------------
1 | if (document.getElementById('accordion')){
2 | // Dirty fix for Date form
3 | // https://github.com/zostera/django-bootstrap5/issues/445
4 | // TODO: contribute upstream to django-bootstrap
5 | document.getElementById('id_control_date').type = 'Date'
6 | document.getElementById('id_implement_start_date').type = 'Date'
7 | document.getElementById('id_implement_end_date').type = 'Date'
8 | document.getElementById('id_plan_start_date').type = 'Date'
9 | document.getElementById('id_plan_end_date').type = 'Date'
10 |
11 | // Open the good accordion at page load
12 | if (document.getElementById('id_status').value == '1') {
13 | document.getElementById('id_status_comment').parentNode.classList.add("d-none");
14 | document.getElementById('buttonAnalyse').classList.remove("collapsed");
15 | document.getElementById('collapseAnalyse').classList.add("show");
16 | } else if (document.getElementById('id_status').value == '2') {
17 | document.getElementById('id_status_comment').parentNode.classList.add("d-none");
18 | document.getElementById('buttonPlan').classList.remove("collapsed");
19 | document.getElementById('collapsePlan').classList.add("show");
20 | } else if (document.getElementById('id_status').value == '3') {
21 | document.getElementById('id_status_comment').parentNode.classList.add("d-none");
22 | document.getElementById('buttonImplement').classList.remove("collapsed");
23 | document.getElementById('collapseImplement').classList.add("show");
24 | } else if (document.getElementById('id_status').value == '4') {
25 | document.getElementById('id_status_comment').parentNode.classList.add("d-none");
26 | document.getElementById('buttonControl').classList.remove("collapsed");
27 | document.getElementById('collapseControl').classList.add("show");
28 | }
29 |
30 | // Display the field id_status_comment for MISC status
31 | document.getElementById('id_status').onchange = function(){
32 | if (document.getElementById('id_status').value > '5') {
33 | document.getElementById('id_status_comment').parentNode.classList.remove("d-none");
34 | document.getElementById('id_status_comment').required = true;
35 | } else {
36 | document.getElementById('id_status_comment').parentNode.classList.add("d-none");
37 | document.getElementById('id_status_comment').required = false;
38 | }
39 | };
40 | }
41 |
42 | if (document.getElementById('id_start_date')){
43 | // Dirty fix for Date form
44 | // https://github.com/zostera/django-bootstrap5/issues/445
45 | // TODO: contribute upstream to django-bootstrap
46 | document.getElementById('id_start_date').type = 'Date'
47 | document.getElementById('id_end_date').type = 'Date'
48 | document.getElementById('id_report_date').type = 'Date'
49 | }
50 |
--------------------------------------------------------------------------------
/conformity/templates/conformity/organization_list.html:
--------------------------------------------------------------------------------
1 | {% extends "conformity/main.html" %}
2 |
3 | {% block header %}
4 | Organizations
5 | {% endblock %}
6 |
7 | {% block content %}
8 |
9 | {% include "includes/filter_row.html" %}
10 |
11 |
12 | List of the organizations
13 |
14 |
15 | | Organization |
16 | Description |
17 | Applicable frameworks |
18 | Actions |
19 |
20 |
21 |
22 | {% for org in organization_list %}
23 |
24 | |
25 | {{ org.name }}
26 | |
27 |
28 | {{ org.description | linebreaksbr }}
29 | {% if org.administrative_id %}
30 | Company identifier : {{ org.administrative_id }}
31 | {% else %}
32 | No identifier
33 | {%endif %}
34 | |
35 |
36 | {% for item in org.get_frameworks %}
37 |
38 |
39 | {{ item }}
40 |
41 |
42 |
43 | {% endfor %}
44 | |
45 |
46 |
52 | |
53 |
54 | {% empty %}
55 |
56 | No organization defined.
57 |
58 | {% endfor %}
59 |
60 |
61 |
62 |
63 |
64 | |
65 |
66 |
67 | {% endblock %}
68 |
--------------------------------------------------------------------------------
/conformity/templates/conformity/conformity_detail_list.html:
--------------------------------------------------------------------------------
1 | {% extends "conformity/main.html" %}
2 |
3 | {% block header %}
4 |
5 | {{ conformity_list.0.organization }} conformity to {{ conformity_list.0.requirement.framework }}:
6 |
7 | {% endblock %}
8 |
9 | {% block content %}
10 |
11 |
12 |
{{ conformity_list.0.get_leaf | length }} Requirements
13 |
Completeness: {{ conformity_list.0.get_completeness }} %
14 |
Conformity: {{ conformity_list.0.status }} %
15 |
16 |
28 |
29 |
30 |
31 | List and status of framework requirements for the organisation.
32 |
33 |
34 | | Requirement |
35 | Status |
36 | Owner |
37 | Control |
38 | Action |
39 | Comment |
40 | Edit |
41 |
42 |
43 |
44 | {% for con in conformity_list.0.get_children %}
45 | {% include 'conformity/conformity_detail_list_item.html' with con=con style="primary" %}
46 | {% empty %}
47 |
48 | No conformity review for this organization and this framework.
49 |
50 | | No data to display |
51 | {% endfor %}
52 |
53 | {% endblock %}
54 |
--------------------------------------------------------------------------------
/conformity/templates/conformity/attachment_list.html:
--------------------------------------------------------------------------------
1 | {% extends "conformity/main.html" %}
2 |
3 | {% block header %}
4 | Attachment library
5 | {{ object_list | length }} Attachments
6 | {% endblock %}
7 |
8 | {% block content %}
9 |
10 | Attachment library
11 |
12 |
13 | | Attachment |
14 | MIME type |
15 | Creation date |
16 | References |
17 |
18 |
19 |
20 | {% for attachment in attachment_list %}
21 |
22 | |
23 |
24 |
25 |
27 | {{ attachment | truncatechars:50 }}
28 |
29 |
30 | |
31 |
32 | {{ attachment.mime_type }}
33 | |
34 |
35 | {{ attachment.create_date }}
36 | |
37 |
38 | {% for org in attachment.organizations.all %}
39 | {{ org }}
40 | {% endfor %}
41 | {% for framework in attachment.frameworks.all %}
42 | {{ framework }}
43 | {% endfor %}
44 | {% for cp in attachment.ControlPoint.all %}
45 | {{ cp }}
46 | {% endfor %}
47 | {% for audit in attachment.audits.all %}
48 | {{ audit }}
49 | {% endfor %}
50 | |
51 |
52 | {% empty %}
53 |
54 | No attachment in the library.
55 |
56 | {% endfor %}
57 |
58 |
59 | {% endblock %}
--------------------------------------------------------------------------------
/conformity/migrations/0064_indicator_indicatorpoint.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 5.2.4 on 2025-09-03 07:55
2 |
3 | import django.db.models.deletion
4 | from django.conf import settings
5 | from django.db import migrations, models
6 |
7 |
8 | class Migration(migrations.Migration):
9 |
10 | dependencies = [
11 | ('conformity', '0063_conformity_status_justification_and_more'),
12 | migrations.swappable_dependency(settings.AUTH_USER_MODEL),
13 | ]
14 |
15 | operations = [
16 | migrations.CreateModel(
17 | name='Indicator',
18 | fields=[
19 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
20 | ('name', models.CharField(max_length=256)),
21 | ('goal', models.TextField(blank=True, max_length=4096)),
22 | ('source', models.TextField(blank=True, max_length=4096)),
23 | ('formula', models.TextField(blank=True, max_length=4096)),
24 | ('worst', models.IntegerField(default=0)),
25 | ('best', models.IntegerField(default=100)),
26 | ('warning', models.IntegerField(default=80)),
27 | ('critical', models.IntegerField(default=90)),
28 | ('frequency', models.IntegerField(choices=[(1, 'Yearly'), (2, 'Half-Yearly'), (4, 'Quarterly'), (6, 'Bimonthly'), (12, 'Monthly')], default=4)),
29 | ('conformity', models.ManyToManyField(blank=True, to='conformity.conformity')),
30 | ('organization', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='conformity.organization')),
31 | ('responsible', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
32 | ],
33 | ),
34 | migrations.CreateModel(
35 | name='IndicatorPoint',
36 | fields=[
37 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
38 | ('control_date', models.DateTimeField(blank=True, null=True)),
39 | ('period_start_date', models.DateField()),
40 | ('period_end_date', models.DateField()),
41 | ('status', models.CharField(choices=[('SCHD', 'Scheduled'), ('TOBE', 'To evaluate'), ('OK', 'Compliant'), ('WARN', 'Warning'), ('CRIT', 'Critical'), ('MISS', 'Missed')], default='SCHD', max_length=4)),
42 | ('comment', models.TextField(blank=True, max_length=4096)),
43 | ('value', models.IntegerField(null=True)),
44 | ('attachment', models.ManyToManyField(blank=True, related_name='IndicatorPoint', to='conformity.attachment')),
45 | ('control_user', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
46 | ('indicator', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='conformity.indicator')),
47 | ],
48 | ),
49 | ]
50 |
--------------------------------------------------------------------------------
/conformity/templates/registration/login.html:
--------------------------------------------------------------------------------
1 | {% load django_bootstrap5 %}
2 | {% load static %}
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 | Oxomium - Log in
12 |
13 |
16 |
19 |
21 |
23 |
24 |
25 |
53 |
54 |
55 |
--------------------------------------------------------------------------------
/conformity/templates/conformity/indicator_detail.html:
--------------------------------------------------------------------------------
1 | {% extends "conformity/main.html" %}
2 |
3 | {% block header %}
4 |
5 | Indicator "{{ indicator.name }}"
6 |
7 | {% endblock %}
8 |
9 | {% block content %}
10 | Indicator description
11 |
12 |
13 |
14 | - Organization: {{ indicator.organization }}
15 | - Responsible: {{ indicator.responsible }}
16 | - Frequency: {{ indicator.get_frequency_display }}
17 | - Goal: {{ indicator.goal }}
18 | - Source of data: {{ indicator.source }}
19 | - Formula or calculation: {{ indicator.formula }}
20 | - Last updated: {{ indicator.get_current_point.control_date }} by {{ indicator.get_current_point.control_user }}
21 |
22 |
23 |
24 |
25 |
26 |
29 |
30 | - Critical: from {{ indicator.worst }} to {{ indicator.critical }}
31 | - Warning: from {{ indicator.critical }} to {{ indicator.warning }}
32 | - Compliant: from {{ indicator.warning }} to {{ indicator.best }}
33 |
34 |
35 |
36 |
37 |
38 | Indicator values
39 |
40 | List of the indicator point
41 |
42 |
43 | | Period start |
44 | Period end |
45 | Value |
46 | Status |
47 | Comment |
48 |
49 |
50 |
51 | {% for point in indicator_point_list %}
52 |
53 | | {{ point.period_start_date }} |
54 | {{ point.period_end_date }} |
55 | {{ point.value|default:"–" }} |
56 |
57 | {% if point.status == "TOBE" %}
58 |
60 | {{ point.get_status_display }}
61 |
62 | {% else %}
63 | {{ point.get_status_display }}
64 | {% endif %}
65 | |
66 | {{ point.comment}} |
67 |
68 | {% empty %}
69 |
70 | No indicator point associated to this indicator.
71 |
72 | {% endfor %}
73 |
74 |
75 |
76 | {% endblock %}
77 |
--------------------------------------------------------------------------------
/conformity/migrations/0001_squashed_0014_mesure_is_parent.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 4.0.4 on 2022-05-19 21:18
2 |
3 | from django.conf import settings
4 | import django.core.validators
5 | from django.db import migrations, models
6 | import django.db.models.deletion
7 |
8 |
9 | class Migration(migrations.Migration):
10 |
11 | initial = True
12 |
13 | dependencies = [
14 | migrations.swappable_dependency(settings.AUTH_USER_MODEL),
15 | ]
16 |
17 | operations = [
18 | migrations.CreateModel(
19 | name='Policy',
20 | fields=[
21 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
22 | ('name', models.CharField(max_length=256)),
23 | ('version', models.IntegerField(default=0)),
24 | ('publish_by', models.CharField(max_length=256)),
25 | ('type', models.CharField(choices=[('INT', 'International Standard'), ('NAT', 'National Standard'), ('TECH', 'Technical Standard'), ('RECO', 'Technical Recomandation'), ('POL', 'Internal Policy'), ('OTHER', 'Other')], default='OTHER', max_length=5)),
26 | ],
27 | ),
28 | migrations.CreateModel(
29 | name='Mesure',
30 | fields=[
31 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
32 | ('code', models.CharField(blank=True, max_length=5)),
33 | ('title', models.CharField(blank=True, max_length=256)),
34 | ('description', models.CharField(blank=True, max_length=4096)),
35 | ('parent', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='conformity.mesure')),
36 | ('policy', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='conformity.policy')),
37 | ('name', models.CharField(blank=True, max_length=50)),
38 | ('level', models.IntegerField(default=0)),
39 | ('order', models.IntegerField(default=1)),
40 | ('is_parent', models.BooleanField(default=False)),
41 | ],
42 | ),
43 | migrations.CreateModel(
44 | name='Organization',
45 | fields=[
46 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
47 | ('name', models.CharField(max_length=256)),
48 | ('administrative_id', models.CharField(blank=True, max_length=256)),
49 | ('description', models.CharField(blank=True, max_length=256)),
50 | ('applicable_frameworks', models.ManyToManyField(blank=True, to='conformity.policy')),
51 | ],
52 | ),
53 | migrations.CreateModel(
54 | name='Conformity',
55 | fields=[
56 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
57 | ('status', models.IntegerField(default=0, validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(100)])),
58 | ('mesure', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='conformity.mesure')),
59 | ('organization', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='conformity.organization')),
60 | ('responsible', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
61 | ],
62 | ),
63 | ]
64 |
--------------------------------------------------------------------------------
/conformity/migrations/0015_alter_finding_severity_alter_policy_type_action.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 4.1.5 on 2023-02-03 03:43
2 |
3 | from django.conf import settings
4 | import django.core.validators
5 | from django.db import migrations, models
6 | import django.db.models.deletion
7 |
8 |
9 | class Migration(migrations.Migration):
10 |
11 | dependencies = [
12 | migrations.swappable_dependency(settings.AUTH_USER_MODEL),
13 | ('conformity', '0014_alter_finding_description_alter_finding_reference'),
14 | ]
15 |
16 | operations = [
17 | migrations.AlterField(
18 | model_name='finding',
19 | name='severity',
20 | field=models.CharField(choices=[('CRT', 'Critical non-conformity'), ('MAJ', 'Major non-conformity'), ('MIN', 'Minor non-conformity'), ('OBS', 'Opportunity For Improvement'), ('POS', 'Positive finding'), ('OTHER', 'Other remark')], default='OBS', max_length=5),
21 | ),
22 | migrations.AlterField(
23 | model_name='policy',
24 | name='type',
25 | field=models.CharField(choices=[('INT', 'International Standard'), ('NAT', 'National Standard'), ('TECH', 'Technical Standard'), ('RECO', 'Technical Recommendation'), ('POL', 'Internal Policy'), ('OTHER', 'Other')], default='OTHER', max_length=5),
26 | ),
27 | migrations.CreateModel(
28 | name='Action',
29 | fields=[
30 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
31 | ('title', models.CharField(max_length=256)),
32 | ('description', models.CharField(blank=True, max_length=4096)),
33 | ('create_date', models.DateField(auto_now_add=True)),
34 | ('update_date', models.DateField(auto_now=True)),
35 | ('status', models.CharField(choices=[('A', 'Action under analyse'), ('P', 'Action analysed, waiting to be plan'), ('Q', 'Action planned, waiting for implementation'), ('I', 'Implementation in progress'), ('C', 'Implemented, to bo controlled'), ('S', 'Control successfully'), ('U', 'control unsuccessfully'), ('F', 'Frozen'), ('X', 'Canceled')], default='A', max_length=5)),
36 | ('plan_start_date', models.DateField(blank=True, null=True)),
37 | ('plan_end_sate', models.DateField(blank=True, null=True)),
38 | ('plan_comment', models.CharField(blank=True, max_length=4096)),
39 | ('implement_start_date', models.DateField(blank=True, null=True)),
40 | ('implement_end_date', models.DateField(blank=True, null=True)),
41 | ('implement_status', models.IntegerField(default=0, validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(100)])),
42 | ('implement_comment', models.CharField(blank=True, max_length=4096)),
43 | ('control_date', models.DateField(blank=True, null=True)),
44 | ('control_comment', models.CharField(blank=True, max_length=4096)),
45 | ('associated_conformity', models.ManyToManyField(blank=True, to='conformity.conformity')),
46 | ('associated_findings', models.ManyToManyField(blank=True, to='conformity.finding')),
47 | ('control_user', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='controller', to=settings.AUTH_USER_MODEL)),
48 | ('organization', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='conformity.organization')),
49 | ('owner', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
50 | ],
51 | ),
52 | ]
53 |
--------------------------------------------------------------------------------
/conformity/templates/conformity/control_detail_list.html:
--------------------------------------------------------------------------------
1 | {% extends "conformity/main.html" %}
2 | {% load render_table from django_tables2 %}
3 |
4 | {% block header %}
5 |
6 | Control detail
7 |
8 |
9 | {% if control.level == 1 %}
10 | {% elif control.level == 2 %}
11 | {%endif %} {{ control.get_level_display }}
12 | {{ control.get_frequency_display }}
13 | {{ control.organization }}
14 |
15 | {% endblock %}
16 |
17 | {% block content %}
18 | Titre : {{ control.title }}
19 | Description : {{ control.description }}
20 | Control history :
21 |
22 |
23 |
24 |
25 |
26 | List of all controls
27 |
28 |
29 | | Start Date |
30 | End Date |
31 | Owner |
32 | Status |
33 | Edit |
34 |
35 |
36 |
37 | {% for cp in control.controlpoint %}
38 |
39 | |
40 | {{ cp.period_start_date | date:'d-M-Y'}}
41 | |
42 |
43 | {{ cp.period_end_date | date:'d-M-Y'}}
44 | |
45 |
46 | {{ cp.control_user| default_if_none:"" }}
47 | |
48 |
49 | {% if cp.status == "SCHD" %}
50 |
51 | {% endif %}
52 | {% if cp.status == "TOBE" %}
53 |
54 | {% endif %}
55 | {% if cp.status == "NOK" %}
56 |
57 | {% endif %}
58 | {% if cp.status == "OK" %}
59 |
60 | {% endif %}
61 | {% if cp.status == "MISS" %}
62 |
63 | {% endif %}
64 | {{ cp.get_status_display }}
65 | |
66 |
67 |
68 | {% if cp.status == "TOBE" %}
69 |
70 | {% else %}
71 |
72 | {% endif %}
73 |
74 | |
75 |
76 | {% empty %}
77 |
78 | No control recorded.
79 |
80 | | No data to display |
81 | {% endfor %}
82 |
83 |
84 |
85 |
86 |
87 | |
88 |
89 |
90 |
91 |
92 | {% endblock %}
93 |
--------------------------------------------------------------------------------
/conformity/filterset.py:
--------------------------------------------------------------------------------
1 | from cProfile import label
2 | from random import choices
3 |
4 | from django_filters import FilterSet, CharFilter, DateFromToRangeFilter, ModelChoiceFilter, ChoiceFilter, NumberFilter
5 | from .models import Action, Control, ControlPoint, Conformity, Finding, Requirement, Framework, Organization, Audit, \
6 | Indicator
7 |
8 |
9 | class ActionFilter(FilterSet):
10 | title = CharFilter(lookup_expr='icontains', label="Title")
11 | associated_conformity = ModelChoiceFilter(queryset=Conformity.objects.all(), label='Conformity')
12 | associated_findings = ModelChoiceFilter(queryset=Finding.objects.all(), label='Finding')
13 | associated_controlPoints = ModelChoiceFilter(queryset=ControlPoint.objects.all(), label='Control Point')
14 |
15 | class Meta:
16 | model = Action
17 | fields = ['title', 'owner', 'status', 'organization',
18 | 'associated_conformity', 'associated_findings', 'associated_controlPoints']
19 |
20 |
21 | class ControlFilter(FilterSet):
22 | title = CharFilter(lookup_expr='icontains', label="Title")
23 | conformity = ModelChoiceFilter(queryset=Conformity.objects.all(), label='Conformity')
24 |
25 | class Meta:
26 | model = Control
27 | fields = ['title', 'level', 'frequency', 'organization',
28 | 'conformity']
29 |
30 |
31 | class ControlPointFilter(FilterSet):
32 | control = ModelChoiceFilter(queryset=Control.objects.all(), label='Control')
33 | class Meta:
34 | model = ControlPoint
35 | fields = [ 'status',
36 | 'control', 'control__frequency']
37 |
38 | class FrameworkFilter(FilterSet):
39 | name = CharFilter(lookup_expr='icontains', label="Name")
40 | publish_by = CharFilter(lookup_expr='icontains', label="Publish by")
41 | type = ChoiceFilter(choices=Framework.Type.choices, label='Type')
42 | language = ChoiceFilter(choices=Framework.Language.choices, label='Language')
43 |
44 | class Meta:
45 | model = Framework
46 | fields = [ 'name', 'version', 'language', 'publish_by', 'type' ]
47 |
48 | class OrganizationFilter(FilterSet):
49 | name = CharFilter(lookup_expr='icontains', label="Name")
50 | description = CharFilter(lookup_expr='icontains', label="Description")
51 | administrative_id = CharFilter(lookup_expr='icontains', label="Administrative identifier")
52 |
53 | class Meta:
54 | model = Organization
55 | fields = [ 'name', 'description', 'administrative_id' ]
56 |
57 | class ConformityFilter(FilterSet):
58 | organization = ModelChoiceFilter(queryset=Organization.objects.all(), label="Organization")
59 | requirement__framework = ModelChoiceFilter(queryset=Framework.objects.all(), label="Framework")
60 |
61 | class Meta:
62 | model = Conformity
63 | fields = [ 'organization', 'requirement__framework' ]
64 |
65 | class AuditFilter(FilterSet):
66 | name = CharFilter(lookup_expr='icontains', label='Name')
67 | auditor = CharFilter(lookup_expr='icontains', label='Auditor')
68 | type = ChoiceFilter(choices=Audit.Type.choices, label='Type')
69 |
70 | class Meta:
71 | model = Audit
72 | fields = [ 'name', 'auditor', 'type' ]
73 |
74 | class FindingFilter(FilterSet):
75 | name = CharFilter(lookup_expr='icontains', label='Name')
76 | short_description = CharFilter(lookup_expr='icontains', label='Short Description')
77 | cvss = CharFilter(lookup_expr='icontains', label='CVSS')
78 |
79 | class Meta:
80 | model = Finding
81 | fields = [ 'name', 'short_description', 'cvss']
82 |
83 | class IndicatorFilter(FilterSet):
84 | name = CharFilter(lookup_expr='icontains', label='Name')
85 | goal = CharFilter(lookup_expr='icontains', label='Goal')
86 |
87 | class Meta:
88 | model = Indicator
89 | fields = [ 'name', 'goal' ]
--------------------------------------------------------------------------------
/conformity/templates/auditlog/logentry_list.html:
--------------------------------------------------------------------------------
1 | {% extends "conformity/main.html" %}
2 |
3 | {% block header %}
4 | Audit Log
5 | {% endblock %}
6 |
7 | {% block content %}
8 |
9 | {% include "includes/pagination.html" with page_obj=page_obj %}
10 |
11 |
12 | Audit trail of the actions on Oxomium
13 |
14 |
15 | | Date |
16 | User |
17 | Action |
18 | Ressource |
19 | Change |
20 |
21 |
22 |
23 | {% for logentry in logentry_list %}
24 | {% if logentry.object_id is not Null %}
25 |
26 | |
27 | {{ logentry.timestamp | date:'d-M-Y H:i T' }}
28 | |
29 |
30 | {{ logentry.actor | default_if_none:"Oxomium" }}
31 | {{ logentry.remote_addr | default_if_none:"" }}
32 | |
33 |
34 | {% if logentry.action is 0 %}
35 | CREATE
36 | {% endif %}
37 | {% if logentry.action is 1 %}
38 | UPDATE
39 | {% endif %}
40 | {% if logentry.action is 2 %}
41 | DELETE
42 | {% endif %}
43 | {% if logentry.action is 3 %}
44 | ACCESS
45 | {% endif %}
46 | |
47 |
48 | {{ logentry.object_repr | truncatechars:30 }}
49 | {{ logentry.content_type}}
50 | |
51 |
52 |
53 | {% for key, value in logentry.changes_display_dict.items %}
54 | {% if logentry.action is 0 %}
55 | - {{ key }}: {{ value.1 |truncatechars:40 }}
56 | {% endif %}
57 | {% if logentry.action is 1 %}
58 | - {{ key }}: {{ value.0 | truncatechars:20 }} ⇒ {{ value.1 |truncatechars:20 }}
59 | {% endif %}
60 | {% if logentry.action is 2 %}
61 | - {{ key }}: {{ value.0 | truncatechars:40 }}
62 | {% endif %}
63 | {% endfor %}
64 |
65 | |
66 |
67 | {% endif %}
68 | {% empty %}
69 |
70 | No action recorded.
71 |
72 | | No data to display |
73 | {% endfor %}
74 |
75 |
76 |
77 | {% include "includes/pagination.html" with page_obj=page_obj %}
78 |
79 | {% endblock %}
80 |
--------------------------------------------------------------------------------
/conformity/templates/conformity/finding_detail.html:
--------------------------------------------------------------------------------
1 | {% extends "conformity/main.html" %}
2 |
3 | {% block header %}
4 |
5 | Finding "{{ finding.short_description }}"
6 | {% if finding.severity == "CRT" %}
7 | Critical ({{ finding.cvss }})
8 | {% endif %}
9 | {% if finding.severity == "MAJ" %}
10 | Major ({{ finding.cvss }})
11 | {% endif %}
12 | {% if finding.severity == "MIN" %}
13 | Minor ({{ finding.cvss }})
14 | {% endif %}
15 | {% if finding.severity == "OBS" %}
16 | Opportunity ({{ finding.cvss }})
17 | {% endif %}
18 | {% if finding.severity == "OTHER" %}
19 | Remark ({{ finding.cvss }})
20 | {% endif %}
21 | {% if finding.severity == "POS" %}
22 | Positive ({{ finding.cvss }})
23 | {% endif %}
24 |
25 |
26 | {% endblock %}
27 |
28 | {% block content %}
29 | {% if finding.archived %}
30 |
31 | This finding is archived.
32 |
33 | {% endif %}
34 |
35 | Audit of {{ audit.organization }} realised by {{ audit.auditor }} ({{ audit.get_type }}).
36 | See audit
37 |
38 |
39 | Description
40 | {{ finding.description | default:"No detail provided" | linebreaksbr }}
41 |
42 | Observation
43 | {{ finding.observation | default:"No detail provided" | linebreaksbr }}
44 |
45 | Scoring
46 |
47 | CVSS Score: {{finding.cvss}}
48 |
49 | CVSS Vector:
50 | {% if finding.cvss_descriptor %}
51 |
52 |
53 | {{ finding.cvss_descriptor }}
54 |
55 |
56 | {%endif%}
57 |
58 |
59 | Recommendation
60 | {{ finding.recommendation | linebreaksbr }}
61 |
62 | Associated action
63 |
77 |
78 | {% if finding.reference %}
79 | External Reference
80 | {{ finding.reference }}
81 | {% endif %}
82 |
83 | {% if not finding.archived %}
84 |
85 |
86 |
87 | {% endif %}
88 | {% endblock %}
89 |
--------------------------------------------------------------------------------
/conformity/migrations/0052_framework_language.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 4.2.16 on 2025-07-20 00:05
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ('conformity', '0051_alter_action_options_alter_attachment_options_and_more'),
10 | ]
11 |
12 | operations = [
13 | migrations.AddField(
14 | model_name='framework',
15 | name='language',
16 | field=models.CharField(choices=[('aa', 'Afar'), ('ab', 'Abkhazian'), ('af', 'Afrikaans'), ('ak', 'Akan'), ('am', 'Amharic'), ('ar', 'Arabic'), ('an', 'Aragonese'), ('as', 'Assamese'), ('av', 'Avaric'), ('ae', 'Avestan'), ('ay', 'Aymara'), ('az', 'Azerbaijani'), ('ba', 'Bashkir'), ('bm', 'Bambara'), ('be', 'Belarusian'), ('bn', 'Bengali'), ('bi', 'Bislama'), ('bo', 'Tibetan'), ('bs', 'Bosnian'), ('br', 'Breton'), ('bg', 'Bulgarian'), ('ca', 'Catalan'), ('cs', 'Czech'), ('ch', 'Chamorro'), ('ce', 'Chechen'), ('cu', 'Church Slavic'), ('cv', 'Chuvash'), ('kw', 'Cornish'), ('co', 'Corsican'), ('cr', 'Cree'), ('cy', 'Welsh'), ('da', 'Danish'), ('de', 'German'), ('dv', 'Dhivehi'), ('dz', 'Dzongkha'), ('el', 'Modern Greek (1453-)'), ('en', 'English'), ('eo', 'Esperanto'), ('et', 'Estonian'), ('eu', 'Basque'), ('ee', 'Ewe'), ('fo', 'Faroese'), ('fa', 'Persian'), ('fj', 'Fijian'), ('fi', 'Finnish'), ('fr', 'French'), ('fy', 'Western Frisian'), ('ff', 'Fulah'), ('gd', 'Scottish Gaelic'), ('ga', 'Irish'), ('gl', 'Galician'), ('gv', 'Manx'), ('gn', 'Guarani'), ('gu', 'Gujarati'), ('ht', 'Haitian'), ('ha', 'Hausa'), ('sh', 'Serbo-Croatian'), ('he', 'Hebrew'), ('hz', 'Herero'), ('hi', 'Hindi'), ('ho', 'Hiri Motu'), ('hr', 'Croatian'), ('hu', 'Hungarian'), ('hy', 'Armenian'), ('ig', 'Igbo'), ('io', 'Ido'), ('ii', 'Sichuan Yi'), ('iu', 'Inuktitut'), ('ie', 'Interlingue'), ('ia', 'Interlingua (International Auxiliary Language Association)'), ('id', 'Indonesian'), ('ik', 'Inupiaq'), ('is', 'Icelandic'), ('it', 'Italian'), ('jv', 'Javanese'), ('ja', 'Japanese'), ('kl', 'Kalaallisut'), ('kn', 'Kannada'), ('ks', 'Kashmiri'), ('ka', 'Georgian'), ('kr', 'Kanuri'), ('kk', 'Kazakh'), ('km', 'Khmer'), ('ki', 'Kikuyu'), ('rw', 'Kinyarwanda'), ('ky', 'Kirghiz'), ('kv', 'Komi'), ('kg', 'Kongo'), ('ko', 'Korean'), ('kj', 'Kuanyama'), ('ku', 'Kurdish'), ('lo', 'Lao'), ('la', 'Latin'), ('lv', 'Latvian'), ('li', 'Limburgan'), ('ln', 'Lingala'), ('lt', 'Lithuanian'), ('lb', 'Luxembourgish'), ('lu', 'Luba-Katanga'), ('lg', 'Ganda'), ('mh', 'Marshallese'), ('ml', 'Malayalam'), ('mr', 'Marathi'), ('mk', 'Macedonian'), ('mg', 'Malagasy'), ('mt', 'Maltese'), ('mn', 'Mongolian'), ('mi', 'Maori'), ('ms', 'Malay (macrolanguage)'), ('my', 'Burmese'), ('na', 'Nauru'), ('nv', 'Navajo'), ('nr', 'South Ndebele'), ('nd', 'North Ndebele'), ('ng', 'Ndonga'), ('ne', 'Nepali (macrolanguage)'), ('nl', 'Dutch'), ('nn', 'Norwegian Nynorsk'), ('nb', 'Norwegian Bokmål'), ('no', 'Norwegian'), ('ny', 'Nyanja'), ('oc', 'Occitan (post 1500)'), ('oj', 'Ojibwa'), ('or', 'Oriya (macrolanguage)'), ('om', 'Oromo'), ('os', 'Ossetian'), ('pa', 'Panjabi'), ('pi', 'Pali'), ('pl', 'Polish'), ('pt', 'Portuguese'), ('ps', 'Pushto'), ('qu', 'Quechua'), ('rm', 'Romansh'), ('ro', 'Romanian'), ('rn', 'Rundi'), ('ru', 'Russian'), ('sg', 'Sango'), ('sa', 'Sanskrit'), ('si', 'Sinhala'), ('sk', 'Slovak'), ('sl', 'Slovenian'), ('se', 'Northern Sami'), ('sm', 'Samoan'), ('sn', 'Shona'), ('sd', 'Sindhi'), ('so', 'Somali'), ('st', 'Southern Sotho'), ('es', 'Spanish'), ('sq', 'Albanian'), ('sc', 'Sardinian'), ('sr', 'Serbian'), ('ss', 'Swati'), ('su', 'Sundanese'), ('sw', 'Swahili (macrolanguage)'), ('sv', 'Swedish'), ('ty', 'Tahitian'), ('ta', 'Tamil'), ('tt', 'Tatar'), ('te', 'Telugu'), ('tg', 'Tajik'), ('tl', 'Tagalog'), ('th', 'Thai'), ('ti', 'Tigrinya'), ('to', 'Tonga (Tonga Islands)'), ('tn', 'Tswana'), ('ts', 'Tsonga'), ('tk', 'Turkmen'), ('tr', 'Turkish'), ('tw', 'Twi'), ('ug', 'Uighur'), ('uk', 'Ukrainian'), ('ur', 'Urdu'), ('uz', 'Uzbek'), ('ve', 'Venda'), ('vi', 'Vietnamese'), ('vo', 'Volapük'), ('wa', 'Walloon'), ('wo', 'Wolof'), ('xh', 'Xhosa'), ('yi', 'Yiddish'), ('yo', 'Yoruba'), ('za', 'Zhuang'), ('zh', 'Chinese'), ('zu', 'Zulu')], default='en', max_length=2),
17 | ),
18 | ]
19 |
--------------------------------------------------------------------------------
/conformity/admin.py:
--------------------------------------------------------------------------------
1 | """
2 | Customize Django Admin Site to manage my Models instances
3 | """
4 |
5 | from django.contrib import admin
6 | from import_export import resources
7 | from import_export.admin import ImportExportModelAdmin
8 | from .models import Organization, Framework, Requirement, Conformity, Audit, Finding, Action, Control, ControlPoint, \
9 | Attachment, Indicator, IndicatorPoint
10 |
11 |
12 | class OrganizationResources(resources.ModelResource):
13 | class Meta:
14 | model = Organization
15 |
16 |
17 | class OrganizationAdmin(ImportExportModelAdmin):
18 | ressource_class = Organization
19 | list_select_related = ['applicable_frameworks']
20 |
21 |
22 | class FrameworkResources(resources.ModelResource):
23 | class Meta:
24 | model = Framework
25 |
26 |
27 | class FrameworkAdmin(ImportExportModelAdmin):
28 | ressource_class = Framework
29 | list_select_related = ['attachment']
30 |
31 |
32 | class RequirementResources(resources.ModelResource):
33 | class Meta:
34 | model = Requirement
35 |
36 |
37 | class RequirementAdmin(ImportExportModelAdmin):
38 | ressource_class = Requirement
39 | list_select_related = ['framework', 'parent']
40 |
41 |
42 | class ConformityResources(resources.ModelResource):
43 | class Meta:
44 | model = Conformity
45 |
46 |
47 | class ConformityAdmin(ImportExportModelAdmin):
48 | ressource_class = Conformity
49 | list_select_related = ['organization', 'requirement']
50 |
51 |
52 | class ActionResources(resources.ModelResource):
53 | class Meta:
54 | model = Action
55 |
56 |
57 | class ActionAdmin(ImportExportModelAdmin):
58 | ressource_class = Action
59 | list_select_related = ['organization', 'responsible']
60 |
61 |
62 | class ControlResources(resources.ModelResource):
63 | class Meta:
64 | model = Control
65 |
66 |
67 | class ControlAdmin(ImportExportModelAdmin):
68 | ressource_class = Control
69 | list_select_related = ['organization']
70 |
71 |
72 | class ControlPointResources(resources.ModelResource):
73 | class Meta:
74 | model = ControlPoint
75 |
76 |
77 | class ControlPointAdmin(ImportExportModelAdmin):
78 | ressource_class = ControlPoint
79 | list_select_related = ['control', 'control_user']
80 |
81 |
82 | class FindingResources(resources.ModelResource):
83 | class Meta:
84 | model = Finding
85 |
86 |
87 | class FindingAdmin(ImportExportModelAdmin):
88 | ressource_class = Finding
89 | list_select_related = ['audit']
90 |
91 |
92 | class AuditResources(resources.ModelResource):
93 | class Meta:
94 | model = Audit
95 |
96 |
97 | class AuditAdmin(ImportExportModelAdmin):
98 | ressource_class = Audit
99 | list_select_related = ['organization', 'auditor']
100 |
101 |
102 | class AttachmentResources(resources.ModelResource):
103 | class Meta:
104 | model = Attachment
105 |
106 | class AttachmentAdmin(ImportExportModelAdmin):
107 | ressource_class = Attachment
108 |
109 | class IndicatorResources(resources.ModelResource):
110 | class Meta:
111 | model = Indicator
112 |
113 | class IndicatorAdmin(ImportExportModelAdmin):
114 | ressource_class = Indicator
115 |
116 | class IndicatorPointResources(resources.ModelResource):
117 | class Meta:
118 | model = IndicatorPoint
119 |
120 | class IndicatorPointAdmin(ImportExportModelAdmin):
121 | ressource_class = IndicatorPoint
122 |
123 |
124 | # Registration
125 | admin.site.register(Action, ActionAdmin)
126 | admin.site.register(Attachment, AttachmentAdmin)
127 | admin.site.register(Audit, AuditAdmin)
128 | admin.site.register(Conformity, ConformityAdmin)
129 | admin.site.register(Control, ControlAdmin)
130 | admin.site.register(ControlPoint, ControlPointAdmin)
131 | admin.site.register(Finding, FindingAdmin)
132 | admin.site.register(Framework, FrameworkAdmin)
133 | admin.site.register(Organization, OrganizationAdmin)
134 | admin.site.register(Requirement, RequirementAdmin)
135 | admin.site.register(Indicator, IndicatorAdmin)
136 | admin.site.register(IndicatorPoint, IndicatorPointAdmin)
137 |
--------------------------------------------------------------------------------
/conformity/templates/conformity/audit_list.html:
--------------------------------------------------------------------------------
1 | {% extends "conformity/main.html" %}
2 |
3 | {% block header %}
4 | Audits
5 | {% endblock %}
6 |
7 | {% block content %}
8 |
9 |
10 |
11 |
12 |
13 |
16 |
26 |
27 |
28 |
40 |
41 |
42 |
43 |
{{ object_list | length }} items
44 |
45 |
46 |
47 |
48 | List of all Audits
49 |
50 |
51 | | Name |
52 | Auditor |
53 | Type |
54 | Start |
55 | End |
56 | Finding |
57 | Actions |
58 |
59 |
60 |
61 | {% for audit in audit_list %}
62 |
63 | |
64 | {{ audit.name }}
65 | |
66 |
67 | {{ audit.auditor }}
68 | |
69 |
70 | {{ audit.get_type }}
71 | |
72 |
73 | {{ audit.start_date }}
74 | |
75 |
76 | {{ audit.end_date }}
77 | |
78 |
79 | {{ audit.get_findings_number }}
80 | |
81 |
82 |
88 | |
89 |
90 | {% empty %}
91 |
92 | No audit recorded.
93 |
94 | | No data to display |
95 | {% endfor %}
96 |
97 |
98 |
99 |
100 |
101 | |
102 |
103 | {% endblock %}
104 |
--------------------------------------------------------------------------------
/conformity/urls.py:
--------------------------------------------------------------------------------
1 | """
2 | Conformity module URL router
3 | """
4 | from django.urls import path
5 | from django.views.generic import TemplateView
6 |
7 | from . import views
8 |
9 | app_name = 'conformity'
10 | urlpatterns = [
11 | path('', views.HomeView.as_view(), name='home'),
12 |
13 | path('audit/', views.AuditIndexView.as_view(), name='audit_index'),
14 | path('audit/', views.AuditDetailView.as_view(), name='audit_detail'),
15 | path('audit/create', views.AuditCreateView.as_view(), name='audit_create'),
16 | path('audit/update/', views.AuditUpdateView.as_view(), name='audit_form'),
17 | path('audit/export/', views.AuditExportView.as_view(), name='audit_export'),
18 |
19 | path('conformity/', views.ConformityIndexView.as_view(), name='conformity_index'),
20 | path('conformity/update/', views.ConformityUpdateView.as_view(), name='conformity_form'),
21 | path('conformity/organization//framework//', views.ConformityDetailIndexView.as_view(),
22 | name='conformity_detail_index'),
23 | path('conformity/organization//framework//export/', views.ConformityExportView.as_view(),
24 | name='conformity_export'),
25 |
26 | path('finding/', views.FindingIndexView.as_view(), name='finding_index'),
27 | path('finding/', views.FindingDetailView.as_view(), name='finding_detail'),
28 | path('finding/create', views.FindingCreateView.as_view(), name='finding_create'),
29 | path('finding/update/', views.FindingUpdateView.as_view(), name='finding_form'),
30 | path('finding/export', views.FindingExportView.as_view(), name='finding_export'),
31 |
32 | path('organization/', views.OrganizationIndexView.as_view(), name='organization_index'),
33 | path('organization/', views.OrganizationDetailView.as_view(), name='organization_detail'),
34 | path('organization/create', views.OrganizationCreateView.as_view(), name='organization_create'),
35 | path('organization/update/', views.OrganizationUpdateView.as_view(), name='organization_form'),
36 |
37 | path('framework/', views.FrameworkIndexView.as_view(), name='framework_index'),
38 | path('framework//', views.FrameworkDetailView.as_view(), name='framework_detail'),
39 |
40 | path('action/', views.ActionIndexView.as_view(), name='action_index'),
41 | path('action/create', views.ActionCreateView.as_view(), name='action_create'),
42 | path('action/update/', views.ActionUpdateView.as_view(), name='action_form'),
43 | path('action/export', views.ActionExportView.as_view(), name='action_export'),
44 |
45 | path('control/', views.ControlIndexView.as_view(), name='control_index'),
46 | path('control/create', views.ControlCreateView.as_view(), name='control_create'),
47 | path('control/update/', views.ControlUpdateView.as_view(), name='control_form'),
48 | path('control/', views.ControlDetailView.as_view(), name='control_detail'),
49 | path('control/export', views.ControlExportView.as_view(), name='control_export'),
50 |
51 | path('controlpoint/', views.ControlPointIndexView.as_view(), name='controlpoint_index'),
52 | path('controlpoint/update/', views.ControlPointUpdateView.as_view(), name='controlpoint_form'),
53 |
54 | path('indicator/', views.IndicatorIndexView.as_view(), name='indicator_index'),
55 | path('indicator/create', views.IndicatorCreateView.as_view(), name='indicator_create'),
56 | path('indicator/update//', views.IndicatorUpdateView.as_view(), name='indicator_form'),
57 | path('indicator//', views.IndicatorDetailView.as_view(), name='indicator_detail'),
58 | path('indicator/export', views.IndicatorExportView.as_view(), name='indicator_export'),
59 | path('indicatorpoint//', views.IndicatorPointUpdateView.as_view(), name='indicatorpoint_form'),
60 |
61 | path('attachment/', views.AttachmentIndexView.as_view(), name='attachment_index'),
62 | path('attachment//', views.AttachmentDownloadView.as_view(), name='attachment_download'),
63 |
64 | path('help/', TemplateView.as_view(template_name='help.html'), name='help'),
65 | path('auditlog/', views.AuditLogDetailView.as_view(), name='auditlog_index'),
66 | ]
67 |
--------------------------------------------------------------------------------
/conformity/signals.py:
--------------------------------------------------------------------------------
1 | from django.db.models.signals import m2m_changed, pre_save, post_save
2 | from django.dispatch import receiver
3 | from .models import Organization, Requirement, Control, ControlPoint, Attachment, Action, Finding, Conformity, \
4 | Indicator, IndicatorPoint
5 |
6 |
7 | @receiver(post_save, sender=Control)
8 | def control_post_save_bootstrap(instance: Control, **kwargs):
9 | Control.controlpoint_bootstrap(instance)
10 |
11 | @receiver(pre_save, sender=ControlPoint)
12 | def controlpoint_pre_save_status(sender, instance: ControlPoint, **kwargs):
13 | ControlPoint.update_status(instance)
14 |
15 | @receiver(pre_save, sender=Attachment)
16 | def attachment_pre_autoset_mimetype(sender, instance: Attachment, **kwargs):
17 | Attachment.autoset_mimetype(instance)
18 |
19 | @receiver(pre_save, sender=Requirement)
20 | def requirement_pre_save_naming(instance, **kwargs):
21 | """This function keep hierarchy of the Requirement working on each Requirement instantiation"""
22 | if instance.parent:
23 | instance.name = f"{instance.parent.name}-{instance.code}"
24 | else:
25 | instance.name = instance.code
26 |
27 | @receiver(m2m_changed, sender=Organization.applicable_frameworks.through)
28 | def change_framework(instance, action, pk_set, **kwargs):
29 | if action == "post_add":
30 | for pk in pk_set:
31 | instance.add_conformity(pk)
32 |
33 | if action == "post_remove":
34 | for pk in pk_set:
35 | instance.remove_conformity(pk)
36 |
37 | @receiver(post_save, sender=Action)
38 | def action_post_save_sync_findings(instance: Action, **kwargs):
39 | """
40 | When an Action is saved (status/active may have changed),
41 | re-evaluate the archive state of all linked Findings.
42 | """
43 | for f in instance.associated_findings.all():
44 | f.update_archived()
45 |
46 | @receiver(m2m_changed, sender=Action.associated_findings.through)
47 | def action_finding_sync_on_m2m(instance, action, reverse, pk_set, **kwargs):
48 | """
49 | Keep Finding.archived consistent when Action<->Finding links change.
50 |
51 | - reverse=False: `instance` is an Action; `pk_set` are Finding IDs added/removed.
52 | - reverse=True: `instance` is a Finding; re-evaluate that single Finding.
53 | """
54 | if action not in {'post_add', 'post_remove', 'post_clear'}:
55 | return
56 |
57 | if reverse:
58 | findings = [instance]
59 | else:
60 | findings = Finding.objects.filter(pk__in=pk_set) if pk_set else instance.associated_findings.all()
61 |
62 | for f in findings:
63 | f.update_archived()
64 |
65 | @receiver(post_save, sender=ControlPoint)
66 | def controlpoint_post_save_sync(instance: ControlPoint, **kwargs):
67 | """
68 | Transactional rule:
69 | - NONCOMPLIANT (current period) -> conformity = 0 (CTRL)
70 | - COMPLIANT (current period) -> try conformity = 100 (CTRL) if no negatives remain
71 | """
72 | if instance.is_current_period() and instance.is_final_status():
73 | for conf in instance.control.conformity.all():
74 | if instance.status == ControlPoint.Status.NONCOMPLIANT:
75 | conf.set_status_from(0, Conformity.StatusJustification.CONTROL)
76 | elif instance.status == ControlPoint.Status.COMPLIANT:
77 | conf.set_status_from(100, Conformity.StatusJustification.CONTROL)
78 |
79 | @receiver(post_save, sender=Action)
80 | def action_post_save_sync(instance: Action, **kwargs):
81 | """
82 | Transactional rule:
83 | - in progress -> conformity = 0 (ACT)
84 | - ended -> try conformity = 100 (ACT) if no negatives remain
85 | """
86 | for conf in instance.associated_conformity.all():
87 | if instance.is_in_progress():
88 | conf.set_status_from(0, Conformity.StatusJustification.ACTION)
89 | elif instance.is_completed():
90 | conf.set_status_from(100, Conformity.StatusJustification.ACTION)
91 |
92 | @receiver(post_save, sender=Indicator)
93 | def indicator_post_save_bootstrap(instance: Indicator, **kwargs):
94 | instance.indicator_point_init()
95 |
96 | @receiver(pre_save, sender=IndicatorPoint)
97 | def indicatorpoint_pre_save_ctrl(instance: IndicatorPoint, **kwargs):
98 | instance.status_update()
--------------------------------------------------------------------------------
/conformity/templates/conformity/conformity_detail_list_item.html:
--------------------------------------------------------------------------------
1 | {% if con.requirement.is_parent and con.requirement.level < 3 %}
2 |
3 | {% else %}
4 |
5 | {% endif %}
6 |
7 | {% if con.requirement.is_leaf_node %}
8 | |
9 | {% if con.requirement.level == 1 %}
10 | {{ con.requirement.name }}
11 | {% elif con.requirement.level == 2 %}
12 | {{ con.requirement.name }}
13 | {% else %}
14 | {{ con.requirement.name }}
15 | {% endif %}
16 | {{ con.requirement.title }}
17 | {% else %}
18 | |
19 | {% if con.requirement.level == 1 %}
20 | {{ con.requirement.name }}
21 | {% elif con.requirement.level == 2 %}
22 | {{ con.requirement.name }}
23 | {% else %}
24 | {{ con.requirement.name }}
25 | {% endif %}
26 | {{ con.requirement.title }}
27 | {% endif %}
28 | |
29 |
30 |
31 |
32 | {% if not con.applicable %}
33 | Not applicable
35 | {% else %}
36 | {% if con.status is Null %}
37 | Not Evaluated
39 | {% elif con.status > 79 %}
40 | {{ con.status }} %
42 | {% elif con.status > 49 %}
43 | {{ con.status }} %
45 | {% else %}
46 | {{ con.status }} %
48 | {% endif %}
49 | {% endif %}
50 |
51 | |
52 |
53 |
54 | {% if con.applicable and con.responsible%}
55 | {{ con.responsible|capfirst }}
56 | {% endif %}
57 | |
58 |
59 |
60 | {% if con.get_control %}
61 |
62 |
66 |
67 | {% endif %}
68 | |
69 |
70 |
71 | {% if con.get_action %}
72 |
73 |
77 |
78 | {% endif %}
79 | |
80 |
81 |
82 | {% if con.comment %}
83 |
84 | {% endif %}
85 | |
86 |
87 |
88 |
92 | |
93 |
94 |
95 | {% for con in con.get_children %}
96 | {% if con.requirement.level == 1 %}
97 | {% include 'conformity/conformity_detail_list_item.html' with con=con style="primary" %}
98 | {% elif con.requirement.level == 2 %}
99 | {% include 'conformity/conformity_detail_list_item.html' with con=con style="info" %}
100 | {% else %}
101 | {% include 'conformity/conformity_detail_list_item.html' with con=con style="secondary" %}
102 | {% endif %}
103 | {% endfor %}
104 |
--------------------------------------------------------------------------------
/conformity/templates/conformity/action_list.html:
--------------------------------------------------------------------------------
1 | {% extends "conformity/main.html" %}
2 |
3 | {% block header %}
4 | Actions
5 | {% endblock %}
6 |
7 | {% block content %}
8 |
9 |
10 |
11 |
12 |
15 |
25 |
26 |
27 |
39 |
40 |
41 |
42 |
{{ object_list | length }} items
43 |
44 |
45 |
46 |
47 | List of all actions
48 |
49 |
50 | | Titre |
51 | Owner |
52 | Status |
53 | Last Update |
54 | Association |
55 | Actions |
56 |
57 |
58 |
59 | {% for action in object_list %}
60 |
61 | |
62 | {{ action.title }}
63 | |
64 |
65 | {{ action.owner | default_if_none:"" }}
66 | |
67 |
68 | {% if action.status == "1" %}
69 |
70 | {% endif %}
71 | {% if action.status == "2" %}
72 |
73 | {% endif %}
74 | {% if action.status == "3" %}
75 |
76 | {% endif %}
77 | {% if action.status == "4" %}
78 |
79 | {% endif %}
80 | {% if action.status == "5" %}
81 |
82 | {% endif %}
83 | {% if action.status == "7" %}
84 |
85 | {% endif %}
86 | {% if action.status == "9" %}
87 |
88 | {% endif %}
89 | {{ action.get_status_display }}
90 | |
91 |
92 | {{ action.update_date | date:'d-M-Y'}}
93 | |
94 |
95 | {% if action.get_associated %}
96 |
99 | {% endif %}
100 | |
101 |
102 |
108 | |
109 |
110 | {% empty %}
111 |
112 | No action recorded.
113 |
114 | | No data to display |
115 | {% endfor %}
116 |
117 |
118 |
120 | Register a new action
121 |
122 | |
123 |
124 | {% endblock %}
125 |
--------------------------------------------------------------------------------
/conformity/templates/conformity/controlpoint_list.html:
--------------------------------------------------------------------------------
1 | {% extends "conformity/main.html" %}
2 | {% load render_table from django_tables2 %}
3 |
4 | {% block header %}
5 |
6 | Control Point
7 |
8 | {% endblock %}
9 |
10 | {% block content %}
11 |
12 | {% if filter.form.data.control %}
13 |
14 |
15 |
16 |
20 |
21 |
22 |
{{ controlpoint_list.0.control.description | linebreaksbr }}
23 |
24 |
25 | - Level: {{ controlpoint_list.0.control.get_level_display }}
26 | - Frequency: {{ controlpoint_list.0.control.get_frequency_display }}
27 | - Organization: {{ controlpoint_list.0.control.organization }}
28 |
29 |
30 |
Associated conformity:
31 |
32 | {% for conformity in controlpoint_list.0.control.conformity.all %}
33 | - {{ conformity.requirement.name }}: {{ conformity.requirement.title }}
34 | {% endfor %}
35 |
36 |
37 |
38 |
39 | {% endif %}
40 |
41 |
42 | {% if filter.form.data.control %}
43 |
Control Point List
44 | {% else %}
45 | {% include "includes/filter_row.html" %}
46 | {% endif %}
47 |
48 |
49 | List of all control points
50 |
51 |
52 | | Start Date |
53 | End Date |
54 | Owner |
55 | Status |
56 | Edit |
57 |
58 |
59 |
60 | {% for cp in controlpoint_list %}
61 |
62 | |
63 | {{ cp.period_start_date | date:'d-M-Y'}}
64 | |
65 |
66 | {{ cp.period_end_date | date:'d-M-Y'}}
67 | |
68 |
69 | {{ cp.control_user| default_if_none:"" }}
70 | |
71 |
72 | {% if cp.status == "SCHD" %}
73 |
74 | {% endif %}
75 | {% if cp.status == "TOBE" %}
76 |
77 | {% endif %}
78 | {% if cp.status == "NOK" %}
79 |
80 | {% endif %}
81 | {% if cp.status == "OK" %}
82 |
83 | {% endif %}
84 | {% if cp.status == "MISS" %}
85 |
86 | {% endif %}
87 | {{ cp.get_status_display }}
88 | |
89 |
90 |
91 | {% if cp.status == "TOBE" %}
92 |
93 | {% else %}
94 |
95 | {% endif %}
96 |
97 | |
98 |
99 | {% empty %}
100 |
101 | No control recorded.
102 |
103 | | No data to display |
104 | {% endfor %}
105 |
106 |
107 | |
108 |
109 |
110 |
111 |
112 |
113 |
121 | {% endblock %}
122 |
--------------------------------------------------------------------------------
/conformity/templates/conformity/finding_list.html:
--------------------------------------------------------------------------------
1 | {% extends "conformity/main.html" %}
2 |
3 | {% block header %}
4 | Active findings
5 | {% endblock %}
6 |
7 | {% block content %}
8 |
9 |
10 |
11 |
12 |
15 |
25 |
26 |
27 |
39 |
40 |
41 |
42 |
{{ object_list | length }} items
43 |
44 |
45 |
46 |
47 | List of Critical, Major, Minor finding and Opportunity for improvement.
48 |
49 |
50 | | Finding |
51 | Description |
52 | CVSS |
53 | Audit campaign |
54 | Associated actions |
55 | Action |
56 |
57 |
58 |
59 | {% for finding in finding_list %}
60 |
61 | |
62 | {{ finding.name | default:"Unnamed finding" }}
63 | |
64 |
65 | {{ finding.short_description }}
66 | |
67 |
68 | {% if finding.severity == "CRT" %}
69 |
70 | {% endif %}
71 | {% if finding.severity == "MAJ" %}
72 |
73 | {% endif %}
74 | {% if finding.severity == "MIN" %}
75 |
76 | {% endif %}
77 | {% if finding.severity == "OBS" %}
78 |
79 | {% endif %}
80 | {% if finding.severity == "OTHER" %}
81 |
82 | {% endif %}
83 | {% if finding.severity == "POS" %}
84 |
85 | {% endif %}
86 | {{ finding.cvss | default:'-'}}
87 |
88 | |
89 |
90 |
91 |
94 |
95 | |
96 |
97 | {% if finding.get_action %}
98 |
99 |
102 |
103 | {% endif %}
104 | |
105 |
106 |
112 | |
113 |
114 | {% empty %}
115 |
116 | No finding recorded.
117 |
118 | | No data to display |
119 | {% endfor %}
120 |
121 | {% endblock %}
122 |
--------------------------------------------------------------------------------
/conformity/templates/conformity/indicator_list.html:
--------------------------------------------------------------------------------
1 | {% extends "conformity/main.html" %}
2 | {% load render_table from django_tables2 %}
3 |
4 | {% block header %}
5 | Indicators
6 | {% endblock %}
7 |
8 | {% block content %}
9 |
10 |
11 |
12 |
13 |
16 |
26 |
27 |
28 |
40 |
41 |
42 |
43 |
{{ object_list | length }} items
44 |
45 |
46 |
47 |
48 |
49 | {% for indicator in indicator_list %}
50 |
51 |
52 |
61 |
62 | {% if indicator.get_current_point.status == "CRIT" %}
63 |
64 | {{ indicator.get_current_point.value }}
65 | {% elif indicator.get_current_point.status == "WARN" %}
66 |
67 | {{ indicator.get_current_point.value }}
68 | {% elif indicator.get_current_point.status == "OK" %}
69 |
70 | {{ indicator.get_current_point.value }}
71 | {% else %}
72 |
73 |
75 | Add a value
76 |
77 | {% endif %}
78 |
79 | {% if indicator.get_current_point.value %}
80 | {% if indicator.best == 100 and indicator.worst == 0 %} %
81 | {% elif indicator.best == 0 and indicator.worst == 100 %} %
82 | {% else %} / {{indicator.best}} {% endif %}
83 | {% endif %}
84 |
85 |
86 |
87 |
88 | {{indicator.goal }}
89 |
90 |
91 |
92 | - Critical: from {{ indicator.worst }} to {{ indicator.critical }}
93 | - Warning: from {{ indicator.critical }} to {{ indicator.warning }}
94 | - Compliant: from {{ indicator.warning }} to {{ indicator.best }}
95 |
96 |
102 |
103 |
104 |
105 | {% empty %}
106 |
107 | No indicator recorded.
108 |
109 |
110 | {% endfor %}
111 |
112 |
113 |
118 |
119 | {% endblock %}
--------------------------------------------------------------------------------
/conformity/forms.py:
--------------------------------------------------------------------------------
1 | """
2 | Forms for front-end editing of Models instance
3 | """
4 |
5 | from django.forms import ModelForm, FileField, ClearableFileInput
6 | from django.utils import timezone
7 | from .models import Conformity, Organization, Audit, Finding, Action, Control, ControlPoint, Indicator, IndicatorPoint
8 |
9 |
10 | class ConformityForm(ModelForm):
11 | class Meta:
12 | model = Conformity
13 | fields = ['applicable', 'responsible', 'status', 'comment']
14 |
15 | def __init__(self, *args, **kwargs):
16 | super(ConformityForm, self).__init__(*args, **kwargs)
17 | if self.instance.get_descendants().exists():
18 | self.fields['status'].disabled = True
19 |
20 |
21 | class OrganizationForm(ModelForm):
22 | attachments = FileField(required=False, widget=ClearableFileInput())
23 | class Meta:
24 | model = Organization
25 | fields = ['name', 'administrative_id', 'description', 'applicable_frameworks']
26 |
27 |
28 | class AuditForm(ModelForm):
29 | attachments = FileField(required=False, widget=ClearableFileInput())
30 | class Meta:
31 | model = Audit
32 | fields = ['name', 'organization', 'description', 'conclusion', 'auditor', 'audited_frameworks', 'start_date',
33 | 'end_date', 'report_date', 'type', 'attachments']
34 |
35 |
36 | class FindingForm(ModelForm):
37 | class Meta:
38 | model = Finding
39 | fields = ['name', 'audit', 'severity', 'short_description', 'description', 'observation', 'recommendation', 'reference', 'cvss', 'cvss_descriptor', 'archived']
40 | # TODO add a preselection and a disable selector for 'audit' field when the form is open from an audit.
41 |
42 | def __init__(self, *args, **kwargs):
43 | super(FindingForm, self).__init__(*args, **kwargs)
44 |
45 | if self.get_initial_for_field(self.fields['archived'], 'archived') :
46 | for key, value in self.fields.items():
47 | self.fields[key].disabled = True
48 |
49 |
50 | class ActionForm(ModelForm):
51 | class Meta:
52 | model = Action
53 | fields = '__all__'
54 |
55 | def __init__(self, *args, **kwargs):
56 | super(ActionForm, self).__init__(*args, **kwargs)
57 | self.fields['create_date'].disabled = True
58 | self.fields['update_date'].disabled = True
59 |
60 | generic_fields = ['title', 'owner', 'status', 'status_comment', 'reference']
61 | analyse_fields = ['organization', 'associated_conformity', 'associated_findings', 'associated_controlPoints', 'description']
62 | plan_fields = ['plan_start_date', 'plan_end_date', 'plan_comment']
63 | implement_fields = ['implement_start_date', 'implement_end_date', 'implement_status', 'implement_comment']
64 | control_fields = ['control_date', 'control_comment', 'control_user']
65 | fields_by_status = {
66 | Action.Status.ANALYSING.value: generic_fields + analyse_fields,
67 | Action.Status.PLANNING.value: generic_fields + plan_fields,
68 | Action.Status.IMPLEMENTING.value: generic_fields + implement_fields,
69 | Action.Status.CONTROLLING.value: generic_fields + control_fields,
70 | Action.Status.FROZEN.value: generic_fields,
71 | Action.Status.CANCELED.value: generic_fields,
72 | Action.Status.ENDED.value: generic_fields,
73 | }
74 |
75 | for key, value in self.fields.items():
76 | if key not in fields_by_status[self.get_initial_for_field(self.fields['status'], 'status')]:
77 | self.fields[key].disabled = True
78 |
79 |
80 | class ControlForm(ModelForm):
81 | class Meta:
82 | model = Control
83 | fields = ['title', 'description', 'organization', 'conformity', 'control', 'frequency', 'level']
84 |
85 |
86 | class ControlPointForm(ModelForm):
87 | attachments = FileField(required=False, widget=ClearableFileInput())
88 | class Meta:
89 | model = ControlPoint
90 | fields = ['control_date', 'control_user', 'status', 'comment', 'attachments']
91 |
92 | def __init__(self, *args, **kwargs):
93 | self.user = kwargs.pop('user', None)
94 | super(ControlPointForm, self).__init__(*args, **kwargs)
95 |
96 | # Set some value for all situation
97 | self.fields['control_date'].disabled = True
98 | self.fields['control_user'].disabled = True
99 |
100 | # Set some value if the ControlPoint has to be evaluated
101 | if self.get_initial_for_field(self.fields['status'], 'status') == ControlPoint.Status.TOBEEVALUATED.value:
102 | self.initial['control_date'] = timezone.now()
103 | self.initial['control_user'] = self.user
104 | self.fields['status'].widget.choices = [
105 | (ControlPoint.Status.COMPLIANT, ControlPoint.Status.COMPLIANT.label),
106 | (ControlPoint.Status.NONCOMPLIANT, ControlPoint.Status.NONCOMPLIANT.label),
107 | ]
108 | # Switch to display mode if ControlPoint is not to be evaluated
109 | else:
110 | del self.fields['attachments']
111 | for field in self.fields:
112 | self.fields[field].disabled = True
113 |
114 |
115 | class IndicatorForm(ModelForm):
116 | class Meta:
117 | model = Indicator
118 | fields = '__all__'
119 |
120 |
121 | class IndicatorPointForm(ModelForm):
122 | class Meta:
123 | model = IndicatorPoint
124 | fields = ['value', 'comment', 'attachment']
--------------------------------------------------------------------------------
/conformity/templates/conformity/action_form.html:
--------------------------------------------------------------------------------
1 | {% extends "conformity/main.html" %}
2 | {% load django_bootstrap5 %}
3 |
4 | {% block header %}
5 | Edit of an action record
6 | {% endblock %}
7 |
8 | {% block content %}
9 |
93 | {% endblock %}
94 |
--------------------------------------------------------------------------------
/oxomium/settings.py:
--------------------------------------------------------------------------------
1 | """
2 | Django settings for oxomium project.
3 |
4 | Generated by 'django-admin startproject' using Django 4.0.4.
5 |
6 | For more information on this file, see
7 | https://docs.djangoproject.com/en/4.0/topics/settings/
8 |
9 | For the full list of settings and their values, see
10 | https://docs.djangoproject.com/en/4.0/ref/settings/
11 | """
12 | from pathlib import Path
13 | from decouple import config, Csv
14 | from django.conf.locale.az.formats import DATETIME_FORMAT, SHORT_DATETIME_FORMAT
15 |
16 | # Build paths inside the project like this: BASE_DIR / 'subdir'.
17 | BASE_DIR = Path(__file__).resolve().parent.parent
18 |
19 |
20 | # Quick-start development settings - unsuitable for production
21 | # See https://docs.djangoproject.com/en/4.0/howto/deployment/checklist/
22 |
23 | # SECURITY WARNING: keep the secret key used in production secret!
24 | SECRET_KEY = config('SECRET_KEY', default='django-insecure-oy-h!94w($b%mb2l-jv&g!1-7iqa9(jc!c=guv=%31_z%yyghr')
25 |
26 | # SECURITY WARNING: don't run with debug turned on in production!
27 | DEBUG = config('DEBUG', default='False', cast=bool)
28 |
29 | # SECURITY WARNING: to be configured before production setup
30 | ALLOWED_HOSTS = config('ALLOWED_HOSTS', default='[localhost,127.0.0.1]', cast=Csv())
31 |
32 | # SECURITY WARNING: to be enabled of production usage
33 | SESSION_COOKIE_SECURE = config('SESSION_COOKIE_SECURE', default=False, cast=bool)
34 | SESSION_COOKIE_HTTPONLY = config('SESSION_COOKIE_HTTPONLY', default=False, cast=bool)
35 | SESSION_COOKIE_SAMESITE = config('SESSION_COOKIE_SAMESITE', default='Lax')
36 | SESSION_COOKIE_NAME = config('SESSION_COOKIE_NAME', default='sessionid')
37 | CSRF_USE_SESSIONS = config('CSRF_USE_SESSIONS', default=False, cast=bool)
38 | SECURE_SSL_REDIRECT = config('SECURE_SSL_REDIRECT', default=False, cast=bool)
39 | SECURE_HSTS_SECONDS = config('SECURE_HSTS_SECONDS', default=0, cast=int)
40 | SECURE_HSTS_INCLUDE_SUBDOMAINS = config('SECURE_HSTS_INCLUDE_SUBDOMAINS', default=False, cast=bool)
41 | SECURE_HSTS_PRELOAD = config('SECURE_HSTS_PRELOAD', default=False, cast=bool)
42 |
43 | # Application definition
44 |
45 | INSTALLED_APPS = [
46 | 'django_filters',
47 | 'auditlog',
48 | 'conformity.apps.ConformityConfig',
49 | 'import_export',
50 | 'django_bootstrap5',
51 | 'django_tables2',
52 | 'django.contrib.admin',
53 | 'django.contrib.auth',
54 | 'django.contrib.contenttypes',
55 | 'django.contrib.sessions',
56 | 'django.contrib.messages',
57 | 'django.contrib.staticfiles',
58 | 'mptt',
59 | 'constance',
60 | ]
61 |
62 | MIDDLEWARE = [
63 | 'django.middleware.security.SecurityMiddleware',
64 | 'django.contrib.sessions.middleware.SessionMiddleware',
65 | 'django.middleware.common.CommonMiddleware',
66 | 'django.middleware.csrf.CsrfViewMiddleware',
67 | 'django.contrib.auth.middleware.AuthenticationMiddleware',
68 | 'django.contrib.messages.middleware.MessageMiddleware',
69 | 'django.middleware.clickjacking.XFrameOptionsMiddleware',
70 | 'auditlog.middleware.AuditlogMiddleware',
71 | 'conformity.middleware.SanityCheckMiddleware',
72 | ]
73 |
74 | ROOT_URLCONF = 'oxomium.urls'
75 |
76 | TEMPLATES = [
77 | {
78 | 'BACKEND': 'django.template.backends.django.DjangoTemplates',
79 | 'DIRS': [],
80 | 'APP_DIRS': True,
81 | 'OPTIONS': {
82 | 'context_processors': [
83 | 'constance.context_processors.config',
84 | 'django.template.context_processors.debug',
85 | 'django.template.context_processors.request',
86 | 'django.contrib.auth.context_processors.auth',
87 | 'django.contrib.messages.context_processors.messages',
88 | ],
89 | },
90 | },
91 | ]
92 |
93 | WSGI_APPLICATION = 'oxomium.wsgi.application'
94 |
95 |
96 | # Database
97 | # https://docs.djangoproject.com/en/4.0/ref/settings/#databases
98 |
99 | DATABASES = {
100 | 'default': {
101 | 'ENGINE': 'django.db.backends.sqlite3',
102 | 'NAME': BASE_DIR / config('DB_NAME', default='db.sqlite3'),
103 | }
104 | }
105 |
106 |
107 | # Password validation
108 | # https://docs.djangoproject.com/en/4.0/ref/settings/#auth-password-validators
109 |
110 | AUTH_PASSWORD_VALIDATORS = [
111 | {
112 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
113 | },
114 | {
115 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
116 | },
117 | {
118 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
119 | },
120 | {
121 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
122 | },
123 | ]
124 |
125 |
126 | # Internationalization
127 | # https://docs.djangoproject.com/en/4.0/topics/i18n/
128 |
129 | LANGUAGE_CODE = config('LANGUAGE_CODE', default='en-us')
130 | TIME_ZONE = config('TIME_ZONE', default='UTC')
131 | USE_I18N = config('USE_I18N', default='True', cast=bool)
132 | USE_TZ = config('USE_TZ', default='True', cast=bool)
133 |
134 |
135 | # Static files (CSS, JavaScript, Images)
136 | # https://docs.djangoproject.com/en/4.0/howto/static-files/
137 |
138 | STATIC_URL = config('STATIC_URL', default='static/')
139 | STATIC_ROOT = config('STATIC_ROOT', default='static')
140 |
141 | # Default primary key field type
142 | # https://docs.djangoproject.com/en/4.0/ref/settings/#default-auto-field
143 |
144 | DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
145 |
146 | # Login / Logout configuration
147 | LOGIN_REDIRECT_URL = config('STATIC_ROOT', default='/')
148 | LOGOUT_REDIRECT_URL = config('STATIC_ROOT', default='/')
149 |
150 | # AuditLog configuration
151 | AUDITLOG_INCLUDE_ALL_MODELS = True
152 | AUDITLOG_INCLUDE_TRACKING_MODELS = (
153 | "conformity.Organization", {"model": "conformity.Organization", "m2m_fields": ["applicable_frameworks"], },
154 | "conformity.Action", {"model": "conformity.Action", "m2m_fields": ["associated_conformity", "associated_findings"], },
155 | "conformity.Audit", {"model": "conformity.Audit", "m2m_fields": ["audited_frameworks"], }
156 | )
157 | AUDITLOG_EXCLUDE_TRACKING_FIELDS = (
158 | "created",
159 | "modified",
160 | "password",
161 | "last_login",
162 | "id",
163 | "ID",
164 | "create_date",
165 | "update_date"
166 | )
167 |
168 | CONSTANCE_BACKEND = 'constance.backends.database.DatabaseBackend'
169 | CONSTANCE_CONFIG = {
170 | 'WELCOME_HEADER': (
171 | "Bonjour !",
172 | "This text will be printed on the login page",
173 | str),
174 | }
175 |
--------------------------------------------------------------------------------