Promene u dizajnu dokumenata, controla, residual graph, residual tabele...
This commit is contained in:
@@ -36,7 +36,7 @@ class RiskAdmin(admin.ModelAdmin):
|
||||
list_display = ['risk_id','risk_name','category']
|
||||
|
||||
class ControlAdmin(admin.ModelAdmin):
|
||||
list_display = ('id', 'name')
|
||||
list_display = ('id','safeguard_id','name', 'description')
|
||||
|
||||
class DocumentRiskControlAdmin(admin.ModelAdmin):
|
||||
list_display = ('document', 'risk', 'control', 'weight','likelihood')
|
||||
|
||||
@@ -15,10 +15,13 @@ class Command(BaseCommand):
|
||||
reader = csv.DictReader(csv_file)
|
||||
|
||||
for row in reader:
|
||||
safeguard = row["CIS v8.1 Safeguards (Sub-Controls)"].strip()
|
||||
|
||||
safeguard_id = row["Safeguard ID"].strip()
|
||||
safeguard = row["Name"].strip()
|
||||
description = row["Description"].strip()
|
||||
Control.objects.update_or_create(
|
||||
name=safeguard,
|
||||
name=safeguard,
|
||||
safeguard_id = safeguard_id,
|
||||
description=description,
|
||||
defaults={"name": safeguard},
|
||||
)
|
||||
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
# Generated by Django 5.1.3 on 2025-06-13 14:01
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('core', '0014_alter_organization_customer_base'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='control',
|
||||
name='description',
|
||||
field=models.TextField(default=' ', help_text='Description of the control'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='control',
|
||||
name='safeguard_id',
|
||||
field=models.CharField(help_text="Unique identifier for the safeguard (e.g. '1.1', '4.6')", max_length=10, null=True, unique=True),
|
||||
),
|
||||
]
|
||||
@@ -157,8 +157,9 @@ class Risk(models.Model):
|
||||
|
||||
class Control(models.Model):
|
||||
id = models.AutoField(primary_key=True)
|
||||
safeguard_id = models.CharField(max_length=10, unique=True, null=True, help_text="Unique identifier for the safeguard (e.g. '1.1', '4.6')")
|
||||
name = models.CharField(max_length=255)
|
||||
|
||||
description = models.TextField(default=" ", help_text="Description of the control")
|
||||
def __str__(self):
|
||||
return f"{self.id} ({self.name})"
|
||||
|
||||
|
||||
@@ -18,6 +18,50 @@ def render_universal_segment(segment, context_data):
|
||||
rendered = []
|
||||
context = Context(context_data)
|
||||
|
||||
if segment_type == 'organization':
|
||||
rendered.append(
|
||||
f'<div class="front-page">'
|
||||
f'<img src="path/to/your/logo.png" alt="Risklet Logo" class="logo">'
|
||||
f'<h1>Cyber Risk Assessment Report</h1>'
|
||||
f'<p>Comprehensive Evaluation and Strategic Recommendations for Enhanced Cybersecurity Posture</p>'
|
||||
f'<div class="prepared-by">'
|
||||
f'<p>Prepared for</p>')
|
||||
for item in content:
|
||||
name = Template(item.get('name', '')).render(context)
|
||||
date = Template(item.get('date', '')).render(context)
|
||||
if name:
|
||||
rendered.append(f'<p><strong>{name}</strong></p>')
|
||||
rendered.append(f'<p>Prepared by</p>'
|
||||
f'<p><strong>Risklet</strong></p>'
|
||||
f'</div>')
|
||||
if date:
|
||||
rendered.append(f'<p style="margin-top: 40px;">{date}</p>')
|
||||
rendered.append(f'</div>')
|
||||
return '\n'.join(rendered)
|
||||
|
||||
elif segment_type == 'disclaimer':
|
||||
rendered.append(
|
||||
f'<div class="disclaimer-page">'
|
||||
f'<img src="path/to/your/logo.png" alt="Risklet Logo" class="logo">'
|
||||
)
|
||||
for item in content:
|
||||
subtitle = Template(item.get('subtitle', '')).render(context)
|
||||
description = Template(item.get('description', '')).render(context)
|
||||
if subtitle:
|
||||
rendered.append(f'<h3>{subtitle}</h3>')
|
||||
if description:
|
||||
processed_desc = []
|
||||
for line in description.split('\n'):
|
||||
line = line.strip()
|
||||
if line:
|
||||
processed_desc.append(f'<p>{line}</p>')
|
||||
rendered.append('\n'.join(processed_desc))
|
||||
rendered.append(f'</div>')
|
||||
return '\n'.join(rendered)
|
||||
|
||||
else:
|
||||
rendered.append(f'<div class="section">')
|
||||
|
||||
for item in content:
|
||||
if not isinstance(item, dict):
|
||||
continue
|
||||
@@ -27,10 +71,10 @@ def render_universal_segment(segment, context_data):
|
||||
description = Template(item.get('description', '')).render(context)
|
||||
|
||||
if title:
|
||||
rendered.append(f'<h2 style="color: #2c3e50; margin-top: 30px;">{title}</h2>')
|
||||
rendered.append(f'<h2>{title}</h2>')
|
||||
|
||||
if subtitle:
|
||||
rendered.append(f'<h3 style="color: #34495e; margin-top: 20px;">{subtitle}</h3>')
|
||||
rendered.append(f'<h3>{subtitle}</h3>')
|
||||
|
||||
if description:
|
||||
processed_desc = []
|
||||
@@ -47,7 +91,7 @@ def render_universal_segment(segment, context_data):
|
||||
processed_desc.append('</ul>')
|
||||
in_list = False
|
||||
if line:
|
||||
processed_desc.append(f'<p style="margin: 10px 0; line-height: 1.6;">{line}</p>')
|
||||
processed_desc.append(f'<p>{line}</p>')
|
||||
if in_list:
|
||||
processed_desc.append('</ul>')
|
||||
rendered.append('\n'.join(processed_desc))
|
||||
@@ -68,20 +112,53 @@ def render_universal_segment(segment, context_data):
|
||||
table_html.append('</tbody></table>')
|
||||
rendered.append('\n'.join(table_html))
|
||||
|
||||
if 'html' in item:
|
||||
html_template = Template(item['html'])
|
||||
rendered_html = html_template.render(context)
|
||||
rendered.append(rendered_html)
|
||||
|
||||
if 'image' in item:
|
||||
image_url = Template(item['image']).render(context)
|
||||
rendered.append(f'<img src="{image_url}" alt="{title}" style="max-width: 100%; height: auto; margin: 20px 0;">')
|
||||
|
||||
if 'warning' in item:
|
||||
warning_text = Template(item['warning']).render(context)
|
||||
rendered.append(f'<p style="color: #e74c3c; font-weight: bold;">{warning_text}</p>')
|
||||
|
||||
if 'note' in item:
|
||||
note_text = Template(item['note']).render(context)
|
||||
rendered.append(f'<p><em>{note_text}</p></em>')
|
||||
|
||||
if 'html' in segment:
|
||||
html_template = Template(segment['html'])
|
||||
rendered_html = html_template.render(context)
|
||||
rendered.append(rendered_html)
|
||||
|
||||
rendered.append('</div>')
|
||||
return '\n'.join(rendered)
|
||||
|
||||
def render_template(template_segments, context_data):
|
||||
final_output = []
|
||||
container_segments = []
|
||||
disclaimer_segment = None
|
||||
|
||||
for segment in template_segments:
|
||||
segment_html = render_universal_segment(segment, context_data)
|
||||
final_output.append(f'<div class="segment {segment.get("segment_type", "")}">{segment_html}</div>')
|
||||
segment_type = segment.get('segment_type', 'unknown')
|
||||
if segment_type == 'organization':
|
||||
final_output.append(render_universal_segment(segment, context_data))
|
||||
elif segment_type == 'disclaimer':
|
||||
disclaimer_segment = segment
|
||||
else:
|
||||
container_segments.append(segment)
|
||||
|
||||
if container_segments:
|
||||
container_html = ['<div class="container">']
|
||||
for segment in container_segments:
|
||||
container_html.append(render_universal_segment(segment, context_data))
|
||||
container_html.append('</div>')
|
||||
final_output.append('\n'.join(container_html))
|
||||
|
||||
if disclaimer_segment:
|
||||
final_output.append(render_universal_segment(disclaimer_segment, context_data))
|
||||
|
||||
return '\n'.join(final_output)
|
||||
@@ -3,14 +3,6 @@ from backend.core.utils import calculate_aggregate_likelihood, calculate_aggrega
|
||||
|
||||
|
||||
def risk_matrix_table():
|
||||
likelihood_labels = [
|
||||
"Almost Certain (90-100%) (5)",
|
||||
"Probable (51-89%) (4)",
|
||||
"Possible (25-50%) (3)",
|
||||
"Unlikely (11-24%) (2)",
|
||||
"Rare (0-10%) (1)"
|
||||
]
|
||||
|
||||
impact_labels = [
|
||||
"Insignificant (1)",
|
||||
"Significant (2)",
|
||||
@@ -18,39 +10,28 @@ def risk_matrix_table():
|
||||
"Material (4)",
|
||||
"Major (5)"
|
||||
]
|
||||
header = ["Likelihood ↓ / Impact →"] + impact_labels
|
||||
|
||||
color_mapping = {
|
||||
"Very Low": "lightgreen",
|
||||
"Low": "green",
|
||||
"Medium": "yellow",
|
||||
"High": "orange",
|
||||
"Critical": "red"
|
||||
}
|
||||
matrix = [
|
||||
["Almost Certain (5)",
|
||||
(5, "bg-medium"), (10, "bg-high"), (15, "bg-critical"), (20, "bg-critical"), (25, "bg-critical")
|
||||
],
|
||||
["Likely (4)",
|
||||
(4, "bg-low"), (8, "bg-medium"), (12, "bg-high"), (16, "bg-high"), (20, "bg-critical")
|
||||
],
|
||||
["Probable (3)",
|
||||
(3, "bg-low"), (6, "bg-low"), (9, "bg-medium"), (12, "bg-high"), (15, "bg-high")
|
||||
],
|
||||
["Unlikely (2)",
|
||||
(2, "bg-very-low"), (4, "bg-low"), (6, "bg-medium"), (8, "bg-medium"), (10, "bg-medium")
|
||||
],
|
||||
["Rare (1)",
|
||||
(1, "bg-very-low"), (2, "bg-very-low"), (3, "bg-low"), (4, "bg-low"), (5, "bg-medium")
|
||||
],
|
||||
]
|
||||
|
||||
def get_label(score):
|
||||
if score <= 2:
|
||||
return "Very Low"
|
||||
elif score <= 4:
|
||||
return "Low"
|
||||
elif score <= 10:
|
||||
return "Medium"
|
||||
elif score <= 16:
|
||||
return "High"
|
||||
else:
|
||||
return "Critical"
|
||||
|
||||
table_matrix_risk = [["Likelihood ↓ / Impact →"] + impact_labels]
|
||||
|
||||
for likelihood in range(5, 0, -1):
|
||||
row = [likelihood_labels[5 - likelihood]]
|
||||
for impact in range(1, 6):
|
||||
score = likelihood * impact
|
||||
label = get_label(score)
|
||||
color_class = color_mapping[label]
|
||||
row.append((score, label, color_class))
|
||||
table_matrix_risk.append(row)
|
||||
|
||||
return table_matrix_risk
|
||||
table = [header] + matrix
|
||||
return table
|
||||
|
||||
def get_risk_table(document):
|
||||
risks = (
|
||||
@@ -98,3 +79,34 @@ def get_risk_table(document):
|
||||
risks_with_controls.sort(key=lambda x: x['risk_score'], reverse=True)
|
||||
|
||||
return risks_with_controls
|
||||
|
||||
def get_safeguard_summary_table(risks_with_controls):
|
||||
from collections import Counter
|
||||
from backend.core.models import Control
|
||||
|
||||
safeguard_counter = Counter()
|
||||
safeguard_names = {}
|
||||
|
||||
for risk in risks_with_controls:
|
||||
for control in risk.get('controls', []):
|
||||
control_id = control.get('control')
|
||||
control_name = control.get('control__name')
|
||||
if control_id:
|
||||
safeguard_counter[control_id] += 1
|
||||
safeguard_names[control_id] = control_name
|
||||
|
||||
summary = []
|
||||
controls = Control.objects.filter(id__in=safeguard_counter.keys())
|
||||
controls_map = {c.id: c for c in controls}
|
||||
|
||||
for control_id, count in safeguard_counter.items():
|
||||
control = controls_map.get(control_id)
|
||||
summary.append({
|
||||
'id': control_id,
|
||||
'safeguard_id': control.safeguard_id if control else '',
|
||||
'name': safeguard_names.get(control_id, ''),
|
||||
'description': control.description if control else '',
|
||||
'count': count,
|
||||
})
|
||||
summary.sort(key=lambda x: x['count'], reverse=True)
|
||||
return summary
|
||||
File diff suppressed because it is too large
Load Diff
@@ -3,157 +3,14 @@
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Document PDF</title>
|
||||
<style>
|
||||
@page {
|
||||
size: A4;
|
||||
margin: 2cm;
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
@media print {
|
||||
table {
|
||||
page-break-inside: avoid; /* Prevent table from breaking across pages */
|
||||
}
|
||||
|
||||
tr {
|
||||
page-break-inside: avoid; /* Prevent table rows from breaking across pages */
|
||||
page-break-after: auto;
|
||||
}
|
||||
|
||||
thead {
|
||||
display: table-header-group; /* Ensure table headers repeat on each page */
|
||||
}
|
||||
|
||||
tfoot {
|
||||
display: table-footer-group; /* Ensure table footers repeat on each page */
|
||||
}
|
||||
|
||||
.page-break {
|
||||
page-break-before: always; /* Force a page break before this element */
|
||||
}
|
||||
}
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-family: sans-serif;
|
||||
font-size: 12pt;
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
|
||||
.document-container {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.document-header {
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.document-meta {
|
||||
color: #666;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.document-content {
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.document-title {
|
||||
font-size: 2.5rem;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.document-subtitle {
|
||||
font-size: 2rem;
|
||||
margin-bottom: 1.25rem;
|
||||
}
|
||||
|
||||
.document-h1 {
|
||||
font-size: 1.75rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.document-h2 {
|
||||
font-size: 1.5rem;
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
.document-h3 {
|
||||
font-size: 1.25rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.document-quote {
|
||||
border-left: 4px solid #ccc;
|
||||
margin: 1.5rem 0;
|
||||
padding-left: 1rem;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.document-body {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.green { background-color: green; color: white; }
|
||||
.lightgreen { background-color: lightgreen; }
|
||||
.yellow { background-color: yellow; }
|
||||
.orange { background-color: orange; }
|
||||
.red { background-color: red; color: white; }
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
table-layout: fixed;
|
||||
word-wrap: break-word;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
th, td {
|
||||
padding: 4px 6px;
|
||||
font-size: 10pt;
|
||||
text-align: center;
|
||||
border: 1px solid #ddd;
|
||||
overflow-wrap: break-word;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
th {
|
||||
background-color: #f2f2f2;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
caption {
|
||||
font-weight: bold;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.cmmi thead th {
|
||||
border-bottom: 1px solid black;
|
||||
}
|
||||
|
||||
img {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.page-break {
|
||||
page-break-after: always;
|
||||
}
|
||||
</style>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Cyber Risk Assessment Report - {{document.organization.name}}</title>
|
||||
<link rel="stylesheet" href="{% static 'css/document.css' %}">
|
||||
</head>
|
||||
<body>
|
||||
<div class="document-container">
|
||||
{% if error %}
|
||||
<p style="color: red;">{{ error }}</p>
|
||||
{% endif %}
|
||||
|
||||
<div>
|
||||
{{ rendered_html|safe }}
|
||||
</div>
|
||||
</div>
|
||||
{{ rendered_html|safe }}
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -32,14 +32,14 @@ class TestProcessors(unittest.TestCase):
|
||||
def test_render_universal_segment(self):
|
||||
segment = self.template_segments[0]
|
||||
result = render_universal_segment(segment, self.context_data)
|
||||
self.assertIn("<h2 style=", result)
|
||||
self.assertIn("<h3 style=", result)
|
||||
self.assertIn("<h2>", result)
|
||||
self.assertIn("<h3>", result)
|
||||
self.assertIn("<ul style=", result)
|
||||
self.assertIn("<div><p>Custom HTML content with Example Corp</p></div>", result)
|
||||
|
||||
def test_render_template(self):
|
||||
result = render_template(self.template_segments, self.context_data)
|
||||
self.assertIn('<div class="segment example_segment">', result)
|
||||
self.assertIn('<div class="section">', result)
|
||||
self.assertIn("Main Title", result)
|
||||
self.assertIn("Subtitle 1", result)
|
||||
self.assertIn("Custom HTML content with Example Corp", result)
|
||||
@@ -47,7 +47,7 @@ class TestProcessors(unittest.TestCase):
|
||||
def test_empty_segment(self):
|
||||
segment = {"segment_type": "empty_segment", "content": []}
|
||||
result = render_universal_segment(segment, self.context_data)
|
||||
self.assertEqual(result, "")
|
||||
self.assertEqual(result, '<div class="section">\n</div>')
|
||||
|
||||
def test_missing_html(self):
|
||||
segment = {
|
||||
|
||||
@@ -128,4 +128,45 @@ class UtilsTests(TestCase):
|
||||
|
||||
graph_data = generate_risk_graph(risks_with_controls)
|
||||
self.assertIsInstance(graph_data, str)
|
||||
self.assertTrue(len(graph_data) > 1000)
|
||||
self.assertTrue(len(graph_data) > 1000)
|
||||
|
||||
def test_generate_residual_risk_graph_base64(self):
|
||||
risks_with_controls = [
|
||||
{
|
||||
'risk': {'id': 1, 'name': 'Risk 1'},
|
||||
'residual_impact': 3,
|
||||
'residual_likelihood': 4,
|
||||
},
|
||||
{
|
||||
'risk': {'id': 2, 'name': 'Risk 2'},
|
||||
'residual_impact': 2,
|
||||
'residual_likelihood': 2,
|
||||
}
|
||||
]
|
||||
|
||||
graph_data = generate_residual_risk_graph(risks_with_controls)
|
||||
self.assertIsInstance(graph_data, str)
|
||||
self.assertTrue(len(graph_data) > 1000)
|
||||
|
||||
def test_get_safeguard_summary_table_basic(self):
|
||||
from backend.core.tables import get_safeguard_summary_table
|
||||
risks_with_controls = [
|
||||
{
|
||||
'risk': {'id': 1, 'name': 'Risk 1'},
|
||||
'controls': [
|
||||
{'control': 101, 'control__name': 'Control A'},
|
||||
{'control': 102, 'control__name': 'Control B'},
|
||||
]
|
||||
},
|
||||
{
|
||||
'risk': {'id': 2, 'name': 'Risk 2'},
|
||||
'controls': [
|
||||
{'control': 101, 'control__name': 'Control A'},
|
||||
]
|
||||
}
|
||||
]
|
||||
summary = get_safeguard_summary_table(risks_with_controls)
|
||||
self.assertEqual(summary, [
|
||||
{'id': 101, 'name': 'Control A', 'count': 2},
|
||||
{'id': 102, 'name': 'Control B', 'count': 1},
|
||||
])
|
||||
@@ -294,4 +294,51 @@ def generate_risk_graph(risks_with_controls):
|
||||
buffer.close()
|
||||
plt.close()
|
||||
|
||||
return base64.b64encode(image_png).decode("utf-8")
|
||||
return base64.b64encode(image_png).decode("utf-8")
|
||||
|
||||
def generate_residual_risk_graph(risks_with_controls):
|
||||
impacts = [risk.get('residual_impact', 0) for risk in risks_with_controls]
|
||||
likelihoods = [risk.get('residual_likelihood', 0) for risk in risks_with_controls]
|
||||
risk_ids = [risk['risk']['id'] for risk in risks_with_controls]
|
||||
|
||||
bg_img_path = find('img/graph_matrix.png')
|
||||
bg_img = mpimg.imread(bg_img_path)
|
||||
|
||||
fig, ax = plt.subplots(figsize=(10, 8))
|
||||
|
||||
ax.imshow(bg_img, extent=[0.0, 5.4, 0.0, 5.4], aspect='auto')
|
||||
|
||||
scatter = ax.scatter(
|
||||
likelihoods, impacts,
|
||||
c="blue", edgecolors="white", s=500, alpha=0.9
|
||||
)
|
||||
|
||||
for i, risk_id in enumerate(risk_ids):
|
||||
ax.annotate(
|
||||
str(risk_id),
|
||||
(likelihoods[i], impacts[i]),
|
||||
color="white",
|
||||
fontsize=12,
|
||||
ha="center",
|
||||
va="center",
|
||||
weight="bold",
|
||||
)
|
||||
|
||||
ax.set_xticks([])
|
||||
ax.set_yticks([])
|
||||
ax.set_xticklabels([])
|
||||
ax.set_yticklabels([])
|
||||
|
||||
ax.spines['top'].set_visible(False)
|
||||
ax.spines['right'].set_visible(False)
|
||||
ax.spines['left'].set_visible(False)
|
||||
ax.spines['bottom'].set_visible(False)
|
||||
|
||||
buffer = io.BytesIO()
|
||||
plt.savefig(buffer, format="png", transparent=True, bbox_inches='tight', pad_inches=0)
|
||||
buffer.seek(0)
|
||||
image_png = buffer.getvalue()
|
||||
buffer.close()
|
||||
plt.close()
|
||||
|
||||
return base64.b64encode(image_png).decode("utf-8")
|
||||
|
||||
@@ -6,8 +6,8 @@ from .forms import OrganizationForm
|
||||
from .models import Organization,Document, DocumentTemplate
|
||||
from backend.accounts.utils import send_confirmation_email, send_document_email
|
||||
from django.contrib.admin.views.decorators import staff_member_required
|
||||
from .utils import generate_pdf, generate_risk_graph
|
||||
from .tables import risk_matrix_table ,get_risk_table
|
||||
from .utils import generate_pdf, generate_risk_graph, generate_residual_risk_graph
|
||||
from .tables import risk_matrix_table ,get_risk_table, get_safeguard_summary_table
|
||||
from django.conf import settings
|
||||
site_domain = settings.SITE_DOMAIN
|
||||
from .processors import render_template
|
||||
@@ -74,8 +74,9 @@ def document(request, document_id):
|
||||
document = get_object_or_404(Document, id=document_id)
|
||||
risks_with_controls = get_risk_table(document)
|
||||
table_risk_matrix = risk_matrix_table()
|
||||
safeguard_summary_table = get_safeguard_summary_table(risks_with_controls)
|
||||
graph_base64 = generate_risk_graph(risks_with_controls)
|
||||
|
||||
residual_graph_base64 = generate_residual_risk_graph(risks_with_controls)
|
||||
template_obj = get_object_or_404(DocumentTemplate, name="Default Template")
|
||||
template_content = template_obj.content
|
||||
|
||||
@@ -88,9 +89,15 @@ def document(request, document_id):
|
||||
'risks_with_controls': risks_with_controls,
|
||||
'graph': graph_base64,
|
||||
'table_risk_matrix': table_risk_matrix,
|
||||
'residual_graph': residual_graph_base64,
|
||||
'safeguard_summary_table': safeguard_summary_table,
|
||||
'table_risk_matrix_header' : table_risk_matrix[0],
|
||||
'table_risk_matrix_rows': table_risk_matrix[1:],
|
||||
}
|
||||
rendered_content = render_template(template_segments, context)
|
||||
return render(request, 'document.html', {'rendered_html': rendered_content})
|
||||
return render(request, 'document.html',
|
||||
{'rendered_html': rendered_content,
|
||||
'document': document,})
|
||||
|
||||
|
||||
@staff_member_required
|
||||
|
||||
Reference in New Issue
Block a user