From f41997fd594d733b6e9b941542a8798288200476 Mon Sep 17 00:00:00 2001 From: Amir Date: Thu, 13 Feb 2025 17:55:46 +0100 Subject: [PATCH] #7 Dodati su export/import za template, isto tako template za document --- backend/core/admin.py | 19 +++++++++- .../management/commands/export_template.py | 22 +++++++++++ .../management/commands/import_template.py | 23 ++++++++++++ .../commands/tests/test_export_template.py | 34 +++++++++++++++++ .../commands/tests/test_import_template.py | 31 ++++++++++++++++ .../core/migrations/0006_documenttemplate.py | 23 ++++++++++++ backend/core/models.py | 11 ++++++ backend/core/templates/template_preview.html | 22 +++++++++++ backend/core/urls.py | 1 + backend/core/views.py | 9 ++++- backend/settings.py | 3 -- document_template.yml | 37 +++++++++++++++++++ 12 files changed, 229 insertions(+), 6 deletions(-) create mode 100644 backend/core/management/commands/export_template.py create mode 100644 backend/core/management/commands/import_template.py create mode 100644 backend/core/management/commands/tests/test_export_template.py create mode 100644 backend/core/management/commands/tests/test_import_template.py create mode 100644 backend/core/migrations/0006_documenttemplate.py create mode 100644 backend/core/templates/template_preview.html create mode 100644 document_template.yml diff --git a/backend/core/admin.py b/backend/core/admin.py index d3b2467..0647ada 100644 --- a/backend/core/admin.py +++ b/backend/core/admin.py @@ -1,5 +1,8 @@ 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): model = DocumentSegment @@ -12,7 +15,18 @@ class DocumentAdmin(admin.ModelAdmin): list_display = ('organization', 'created_at', 'modified_at') search_fields = ['organization__name'] 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('Preview', url) + + preview_button.short_description = 'Preview' + preview_button.allow_tags = True + + class OrganizationAdmin(admin.ModelAdmin): list_display = ('name', 'email', 'industry_sector') search_fields = ['name', 'email'] @@ -31,3 +45,4 @@ admin.site.register(Document, DocumentAdmin) admin.site.register(Organization, OrganizationAdmin) admin.site.register(Risk ,RiskAdmin) admin.site.register(Control, ControlAdmin) +admin.site.register(DocumentTemplate, DocumentTemplateAdmin) diff --git a/backend/core/management/commands/export_template.py b/backend/core/management/commands/export_template.py new file mode 100644 index 0000000..6c9cef5 --- /dev/null +++ b/backend/core/management/commands/export_template.py @@ -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')) diff --git a/backend/core/management/commands/import_template.py b/backend/core/management/commands/import_template.py new file mode 100644 index 0000000..d055293 --- /dev/null +++ b/backend/core/management/commands/import_template.py @@ -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.")) diff --git a/backend/core/management/commands/tests/test_export_template.py b/backend/core/management/commands/tests/test_export_template.py new file mode 100644 index 0000000..4b05734 --- /dev/null +++ b/backend/core/management/commands/tests/test_export_template.py @@ -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()) diff --git a/backend/core/management/commands/tests/test_import_template.py b/backend/core/management/commands/tests/test_import_template.py new file mode 100644 index 0000000..12df27e --- /dev/null +++ b/backend/core/management/commands/tests/test_import_template.py @@ -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()) diff --git a/backend/core/migrations/0006_documenttemplate.py b/backend/core/migrations/0006_documenttemplate.py new file mode 100644 index 0000000..e7fe127 --- /dev/null +++ b/backend/core/migrations/0006_documenttemplate.py @@ -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)), + ], + ), + ] diff --git a/backend/core/models.py b/backend/core/models.py index bc25609..c1531a5 100644 --- a/backend/core/models.py +++ b/backend/core/models.py @@ -4,6 +4,7 @@ from django.contrib.auth.models import User from django.db import models from localflavor.br.br_states import STATE_CHOICES from django.contrib import admin +import yaml class UuidModel(models.Model): @@ -124,6 +125,16 @@ class Document(models.Model): content=content, order=new_order ) + + +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): diff --git a/backend/core/templates/template_preview.html b/backend/core/templates/template_preview.html new file mode 100644 index 0000000..209a36f --- /dev/null +++ b/backend/core/templates/template_preview.html @@ -0,0 +1,22 @@ +{% extends "base.html" %} + +{% block content %} +

Template Preview

+ {% for segment in template %} + {% if segment.segment_type == "title" %} +

{{ segment.content }}

+ {% elif segment.segment_type == "subtitle" %} +

{{ segment.content }}

+ {% elif segment.segment_type == "h1" %} +

{{ segment.content }}

+ {% elif segment.segment_type == "h2" %} +

{{ segment.content }}

+ {% elif segment.segment_type == "h3" %} +

{{ segment.content }}

+ {% elif segment.segment_type == "body" %} +

{{ segment.content|safe }}

+ {% elif segment.segment_type == "quote" %} +
{{ segment.content }}
+ {% endif %} + {% endfor %} +{% endblock %} diff --git a/backend/core/urls.py b/backend/core/urls.py index 0b6d5e0..1b67d6c 100644 --- a/backend/core/urls.py +++ b/backend/core/urls.py @@ -10,4 +10,5 @@ urlpatterns = [ path('thankyou/', v.thankyou, name='thankyou'), # url document/ recieves a parameter named 'uuid' and passes it to the view path('document//', v.document, name='document'), + path('preview//', v.template_preview, name='template_preview'), ] diff --git a/backend/core/views.py b/backend/core/views.py index f7e6c11..7b4ecc7 100644 --- a/backend/core/views.py +++ b/backend/core/views.py @@ -2,10 +2,11 @@ import logging from django.shortcuts import render, redirect , get_object_or_404 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 django.urls import reverse from backend.accounts.utils import send_confirmation_email +from django.contrib.admin.views.decorators import staff_member_required # @login_required # def index(request): @@ -73,3 +74,9 @@ def document(request, document_id): 'organization': doc.organization, '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}) \ No newline at end of file diff --git a/backend/settings.py b/backend/settings.py index 746e89d..f01cf3c 100644 --- a/backend/settings.py +++ b/backend/settings.py @@ -140,9 +140,6 @@ EMAIL_USE_TLS = True EMAIL_HOST_USER = os.getenv("CONF_MAIL") 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) # https://docs.djangoproject.com/en/5.1/howto/static-files/ diff --git a/document_template.yml b/document_template.yml new file mode 100644 index 0000000..a481e90 --- /dev/null +++ b/document_template.yml @@ -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: | +

This is a static section with an embedded HTML table:

+ + + + + + + + + +
Header 1Header 2
Data 1Data 2
+ +- segment_type: "quote" + content: "{{ dynamic_quote }}" + +- segment_type: "h3" + content: "Subsection 1.1" + +- segment_type: "body" + content: "{{ dynamic_subsection }}"