Merge branch '7-napraviti-template-za-dokument' into 'master'

Resolve "Napraviti template za dokument"

Closes #7

See merge request kbr4/riskletpy!7
This commit was merged in pull request #56.
This commit is contained in:
2025-02-14 05:09:38 +00:00
12 changed files with 229 additions and 6 deletions

View File

@@ -1,5 +1,8 @@
from django.contrib import admin from django.contrib import admin
from .models import Document, DocumentSegment, Organization, Risk, Control from .models import Document, DocumentSegment, Organization, Risk, Control, DocumentTemplate
from django.urls import reverse
from django.utils.html import format_html
class DocumentSegmentInline(admin.StackedInline): class DocumentSegmentInline(admin.StackedInline):
model = DocumentSegment model = DocumentSegment
@@ -13,6 +16,17 @@ class DocumentAdmin(admin.ModelAdmin):
search_fields = ['organization__name'] search_fields = ['organization__name']
readonly_fields = ('created_at', 'modified_at') readonly_fields = ('created_at', 'modified_at')
class DocumentTemplateAdmin(admin.ModelAdmin):
list_display = ['name', 'created_at', 'updated_at', 'preview_button']
def preview_button(self, obj):
url = reverse('core:template_preview', args=[obj.name])
return format_html('<a href="{}" target="_blank">Preview</a>', url)
preview_button.short_description = 'Preview'
preview_button.allow_tags = True
class OrganizationAdmin(admin.ModelAdmin): class OrganizationAdmin(admin.ModelAdmin):
list_display = ('name', 'email', 'industry_sector') list_display = ('name', 'email', 'industry_sector')
search_fields = ['name', 'email'] search_fields = ['name', 'email']
@@ -31,3 +45,4 @@ admin.site.register(Document, DocumentAdmin)
admin.site.register(Organization, OrganizationAdmin) admin.site.register(Organization, OrganizationAdmin)
admin.site.register(Risk ,RiskAdmin) admin.site.register(Risk ,RiskAdmin)
admin.site.register(Control, ControlAdmin) admin.site.register(Control, ControlAdmin)
admin.site.register(DocumentTemplate, DocumentTemplateAdmin)

View File

@@ -0,0 +1,22 @@
import yaml
from django.core.management.base import BaseCommand
from backend.core.models import DocumentTemplate
class Command(BaseCommand):
help = "Export document template to a YAML file"
def add_arguments(self, parser):
parser.add_argument('yaml_file', type=str, help="The YAML file to export template to")
def handle(self, *args, **options):
yaml_file_path = options['yaml_file']
template = DocumentTemplate.objects.first()
if template:
with open(yaml_file_path, 'w') as yaml_file:
yaml_file.write(template.content)
self.stdout.write(self.style.SUCCESS('Template exported successfully'))
else:
self.stdout.write(self.style.ERROR('No template found in the database'))

View File

@@ -0,0 +1,23 @@
import yaml
from django.core.management.base import BaseCommand
from backend.core.models import DocumentTemplate
class Command(BaseCommand):
help = "Import document template from a YAML file"
def add_arguments(self, parser):
parser.add_argument('yaml_file', type=str, help="YAML file to import template from")
def handle(self, *args, **options):
yaml_file_path = options['yaml_file']
with open(yaml_file_path, 'r') as file:
content = file.read()
yaml_data = yaml.safe_load(content)
DocumentTemplate.objects.update_or_create(
name="Default Template",
defaults={"content": content}
)
self.stdout.write(self.style.SUCCESS("Template imported successfully."))

View File

@@ -0,0 +1,34 @@
import os
from io import StringIO
from django.core.management import call_command
from django.test import TestCase
from backend.core.models import DocumentTemplate
class DocumentTemplateExportCommandTest(TestCase):
def setUp(self):
self.template_content = """
- segment_type: "title"
content: "Document Title"
- segment_type: "subtitle"
content: "Document Subtitle"
"""
self.template = DocumentTemplate.objects.create(
name="Default Template",
content=self.template_content
)
self.export_file = 'exported_template.yaml'
def tearDown(self):
if os.path.exists(self.export_file):
os.remove(self.export_file)
def test_export_template(self):
out = StringIO()
call_command('export_template', self.export_file, stdout=out)
self.assertIn("Template exported successfully", out.getvalue())
with open(self.export_file, 'r') as f:
content = f.read()
self.assertEqual(content.strip(), self.template_content.strip())

View File

@@ -0,0 +1,31 @@
import os
from io import StringIO
from django.core.management import call_command
from django.test import TestCase
from backend.core.models import DocumentTemplate
class DocumentTemplateImportCommandTest(TestCase):
def setUp(self):
self.yaml_content = """
- segment_type: "title"
content: "Document Title"
- segment_type: "subtitle"
content: "Document Subtitle"
"""
self.yaml_file = 'test_template.yaml'
with open(self.yaml_file, 'w') as f:
f.write(self.yaml_content)
def tearDown(self):
if os.path.exists(self.yaml_file):
os.remove(self.yaml_file)
def test_import_template(self):
out = StringIO()
call_command('import_template', self.yaml_file, stdout=out)
self.assertIn("Template imported successfully", out.getvalue())
template = DocumentTemplate.objects.get(name="Default Template")
self.assertEqual(template.content.strip(), self.yaml_content.strip())

View File

@@ -0,0 +1,23 @@
# Generated by Django 5.1.3 on 2025-02-13 16:19
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0005_control'),
]
operations = [
migrations.CreateModel(
name='DocumentTemplate',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=255, unique=True)),
('content', models.TextField(help_text='YAML format content')),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
],
),
]

View File

@@ -4,6 +4,7 @@ from django.contrib.auth.models import User
from django.db import models from django.db import models
from localflavor.br.br_states import STATE_CHOICES from localflavor.br.br_states import STATE_CHOICES
from django.contrib import admin from django.contrib import admin
import yaml
class UuidModel(models.Model): class UuidModel(models.Model):
@@ -126,6 +127,16 @@ class Document(models.Model):
) )
class DocumentTemplate(models.Model):
name = models.CharField(max_length=255, unique=True)
content = models.TextField(help_text="YAML format content")
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
def to_dict(self):
return yaml.safe_load(self.content)
class Risk(models.Model): class Risk(models.Model):
risk_id = models.IntegerField(unique=True) risk_id = models.IntegerField(unique=True)
category = models.CharField(max_length=255) category = models.CharField(max_length=255)

View File

@@ -0,0 +1,22 @@
{% extends "base.html" %}
{% block content %}
<h1>Template Preview</h1>
{% for segment in template %}
{% if segment.segment_type == "title" %}
<h1>{{ segment.content }}</h1>
{% elif segment.segment_type == "subtitle" %}
<h2>{{ segment.content }}</h2>
{% elif segment.segment_type == "h1" %}
<h1>{{ segment.content }}</h1>
{% elif segment.segment_type == "h2" %}
<h2>{{ segment.content }}</h2>
{% elif segment.segment_type == "h3" %}
<h3>{{ segment.content }}</h3>
{% elif segment.segment_type == "body" %}
<p>{{ segment.content|safe }}</p>
{% elif segment.segment_type == "quote" %}
<blockquote>{{ segment.content }}</blockquote>
{% endif %}
{% endfor %}
{% endblock %}

View File

@@ -10,4 +10,5 @@ urlpatterns = [
path('thankyou/', v.thankyou, name='thankyou'), path('thankyou/', v.thankyou, name='thankyou'),
# url document/ recieves a parameter named 'uuid' and passes it to the view # url document/ recieves a parameter named 'uuid' and passes it to the view
path('document/<uuid:document_id>/', v.document, name='document'), path('document/<uuid:document_id>/', v.document, name='document'),
path('preview/<str:name>/', v.template_preview, name='template_preview'),
] ]

View File

@@ -2,10 +2,11 @@ import logging
from django.shortcuts import render, redirect , get_object_or_404 from django.shortcuts import render, redirect , get_object_or_404
from .forms import OrganizationForm from .forms import OrganizationForm
from .models import Organization,Document,Risk from .models import Organization,Document,Risk, DocumentTemplate
from backend.core.utils import get_top_risk from backend.core.utils import get_top_risk
from django.urls import reverse from django.urls import reverse
from backend.accounts.utils import send_confirmation_email from backend.accounts.utils import send_confirmation_email
from django.contrib.admin.views.decorators import staff_member_required
# @login_required # @login_required
# def index(request): # def index(request):
@@ -73,3 +74,9 @@ def document(request, document_id):
'organization': doc.organization, 'organization': doc.organization,
'segments': doc.segments.all(), 'segments': doc.segments.all(),
}) })
@staff_member_required
def template_preview(request, name):
template = get_object_or_404(DocumentTemplate, name=name)
parsed_template = template.to_dict()
return render(request, 'template_preview.html', {'template': parsed_template})

View File

@@ -140,9 +140,6 @@ EMAIL_USE_TLS = True
EMAIL_HOST_USER = os.getenv("CONF_MAIL") EMAIL_HOST_USER = os.getenv("CONF_MAIL")
EMAIL_HOST_PASSWORD = os.getenv("CONF_MAIL_PASSWORD") EMAIL_HOST_PASSWORD = os.getenv("CONF_MAIL_PASSWORD")
print(f"Email: {EMAIL_HOST_USER}")
print(f"Password: {EMAIL_HOST_PASSWORD}")
# Static files (CSS, JavaScript, Images) # Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/5.1/howto/static-files/ # https://docs.djangoproject.com/en/5.1/howto/static-files/

37
document_template.yml Normal file
View File

@@ -0,0 +1,37 @@
- segment_type: "title"
content: "Document Title"
- segment_type: "subtitle"
content: "Document Subtitle"
- segment_type: "h1"
content: "Introduction"
- segment_type: "body"
content: "{{ dynamic_intro }}"
- segment_type: "h2"
content: "Section 1: Details"
- segment_type: "body"
content: |
<p>This is a static section with an embedded HTML table:</p>
<table>
<tr>
<th>Header 1</th>
<th>Header 2</th>
</tr>
<tr>
<td>Data 1</td>
<td>Data 2</td>
</tr>
</table>
- segment_type: "quote"
content: "{{ dynamic_quote }}"
- segment_type: "h3"
content: "Subsection 1.1"
- segment_type: "body"
content: "{{ dynamic_subsection }}"