diff --git a/apps/citation/__init__.py b/apps/citation/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/citation/admin.py b/apps/citation/admin.py new file mode 100644 index 0000000..ea2f85b --- /dev/null +++ b/apps/citation/admin.py @@ -0,0 +1,7 @@ +from django.conf import settings +from django.contrib import admin + +# Set the admin site title, display the current release version. +admin.site.site_header = admin.site.site_title = ( + f"ai-helpdesk versie: {settings.RELEASE_VERSION}" +) diff --git a/apps/citation/apps.py b/apps/citation/apps.py new file mode 100644 index 0000000..d3d4afe --- /dev/null +++ b/apps/citation/apps.py @@ -0,0 +1,10 @@ +from django.apps import AppConfig + +from apps.core.utils import check_for_debug_settings_in_production + + +class CoreConfig(AppConfig): + name = "apps.citation" + + def ready(self): + check_for_debug_settings_in_production() diff --git a/apps/citation/migrations/0001_initial.py b/apps/citation/migrations/0001_initial.py new file mode 100644 index 0000000..3f1c9e6 --- /dev/null +++ b/apps/citation/migrations/0001_initial.py @@ -0,0 +1,37 @@ +# Generated by Django 4.2.11 on 2024-04-30 12:08 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ("cms", "0003_alter_answertag_tag"), + ] + + operations = [ + migrations.CreateModel( + name="AIAnswer", + fields=[ + ( + "answer_ptr", + models.OneToOneField( + auto_created=True, + on_delete=django.db.models.deletion.CASCADE, + parent_link=True, + primary_key=True, + serialize=False, + to="cms.answer", + ), + ), + ("citation", models.CharField(blank=True, max_length=255, null=True)), + ], + options={ + "abstract": False, + }, + bases=("cms.answer",), + ), + ] diff --git a/apps/citation/migrations/0002_alter_aianswer_options.py b/apps/citation/migrations/0002_alter_aianswer_options.py new file mode 100644 index 0000000..ec4575e --- /dev/null +++ b/apps/citation/migrations/0002_alter_aianswer_options.py @@ -0,0 +1,17 @@ +# Generated by Django 4.2.11 on 2024-04-30 12:20 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ("citation", "0001_initial"), + ] + + operations = [ + migrations.AlterModelOptions( + name="aianswer", + options={"verbose_name": "AI-answer", "verbose_name_plural": "AI-answer"}, + ), + ] diff --git a/apps/citation/migrations/__init__.py b/apps/citation/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/citation/models.py b/apps/citation/models.py new file mode 100644 index 0000000..3ed7bd9 --- /dev/null +++ b/apps/citation/models.py @@ -0,0 +1,20 @@ +from django.db import models +from wagtail.admin.panels import FieldPanel +from wagtail.models import Page +from wagtail_helpdesk.cms.models import Answer, AnswerIndexPage + +Answer.parent_page_types = [] +AnswerIndexPage.subpage_types = ["citation.AIAnswer"] + + +class AIAnswer(Answer): + parent_page_types = ["cms.AnswerIndexPage"] + citation = models.CharField( + max_length=255, blank=True, null=True, help_text="Please follow the APA format." + ) + + content_panels = Answer.content_panels + [FieldPanel("citation")] + + class Meta: + verbose_name = "AI-answer" + verbose_name_plural = "AI-answer" diff --git a/apps/citation/tests/__init__.py b/apps/citation/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/citation/tests/conftest.py b/apps/citation/tests/conftest.py new file mode 100644 index 0000000..cbdccd9 --- /dev/null +++ b/apps/citation/tests/conftest.py @@ -0,0 +1,13 @@ +import pytest +from wagtail.models import Site + +from .factories import HomePageFactory + + +@pytest.fixture() +def home_page(): + site = Site.objects.get() + home_page = HomePageFactory() + site.root_page = home_page + site.save() + return home_page diff --git a/apps/citation/tests/factories.py b/apps/citation/tests/factories.py new file mode 100644 index 0000000..3698651 --- /dev/null +++ b/apps/citation/tests/factories.py @@ -0,0 +1,7 @@ +from wagtail_factories import PageFactory +from wagtail_helpdesk.cms.models import HomePage + + +class HomePageFactory(PageFactory): + class Meta: + model = HomePage diff --git a/apps/citation/tests/test_admin.py b/apps/citation/tests/test_admin.py new file mode 100644 index 0000000..666e4d8 --- /dev/null +++ b/apps/citation/tests/test_admin.py @@ -0,0 +1,12 @@ +from http import HTTPStatus + +import pytest +from django.urls import reverse + + +@pytest.mark.django_db +@pytest.mark.usefixtures("home_page") +def test_django_admin_login_shows_release_version_from_setting(django_app, settings): + response = django_app.get(reverse("admin:login")) + assert response.status_code == HTTPStatus.OK + assert f"ai-helpdesk versie {settings.RELEASE_VERSION} in response" diff --git a/apps/citation/tests/test_utils.py b/apps/citation/tests/test_utils.py new file mode 100644 index 0000000..8c20197 --- /dev/null +++ b/apps/citation/tests/test_utils.py @@ -0,0 +1,34 @@ +import os +from unittest.mock import patch + +import pytest +from django.core.exceptions import ImproperlyConfigured + +from apps.core.utils import check_for_debug_settings_in_production + + +def test_check_for_debug_settings_in_production(settings): + settings.DEBUG = False + with patch.dict(os.environ, {"DJANGO_SETTINGS_MODULE": "settings.production"}): + check_for_debug_settings_in_production(), ( + "No error should be raised when DEBUG is set to False while " + "production settings are active" + ) + settings.DEBUG = True + with pytest.raises( + ImproperlyConfigured, match="Running production settings with DEBUG = True" + ): + check_for_debug_settings_in_production(), ( + "An error should be raised when DEBUG is set to True while " + "production settings are active" + ) + with patch.dict(os.environ, {"DJANGO_SETTINGS_MODULE": "settings.development"}): + check_for_debug_settings_in_production(), ( + "No error should be raised when DEBUG is set to False while " + "development settings are active" + ) + settings.DEBUG = True + check_for_debug_settings_in_production(), ( + "No error should be raised when DEBUG is set to True while " + "development settings are active" + ) diff --git a/apps/citation/utils.py b/apps/citation/utils.py new file mode 100644 index 0000000..ae330b0 --- /dev/null +++ b/apps/citation/utils.py @@ -0,0 +1,10 @@ +import os + +from django.conf import ENVIRONMENT_VARIABLE, settings +from django.core.exceptions import ImproperlyConfigured + + +def check_for_debug_settings_in_production(): + settings_module = os.environ.get(ENVIRONMENT_VARIABLE) + if settings_module.endswith("production") and settings.DEBUG: + raise ImproperlyConfigured("Running production settings with DEBUG = True") diff --git a/apps/frontend/static_src/components/blocks/_citation.scss b/apps/frontend/static_src/components/blocks/_citation.scss new file mode 100644 index 0000000..c642907 --- /dev/null +++ b/apps/frontend/static_src/components/blocks/_citation.scss @@ -0,0 +1,61 @@ +.citation { + letter-spacing: 0; + letter-spacing: var(--button-letter-spacing); + text-transform: var(--button-text-transform); + word-spacing: var(--button-word-spacing); + max-width: calc(100% + 5px * 2 + 0 * 2); + max-width: calc(var(--grid-max-width) + var(--grid-gutter) * 2 + var(--grid-margin) * 2); + margin-left: auto; + margin-right: auto; + padding-left: 0; + padding-left: var(--grid-margin); + padding-right: 0; + padding-right: var(--grid-margin); + position: relative; + margin-bottom: 5px; + + .format { + position: relative; + display: inline-block; + } + + .cite { + .tooltip { + position: relative; + display: inline-block; + } + + .tooltip .tooltiptext { + visibility: hidden; + width: 140px; + background-color: #555; + color: #fff; + text-align: center; + border-radius: 6px; + padding: 5px; + position: absolute; + z-index: 1; + bottom: 150%; + left: 50%; + margin-left: -75px; + opacity: 0; + transition: opacity 0.3s; + } + + .tooltip .tooltiptext::after { + content: ""; + position: absolute; + top: 100%; + left: 50%; + margin-left: -5px; + border-width: 5px; + border-style: solid; + border-color: #555 transparent transparent transparent; + } + + .tooltip:hover .tooltiptext { + visibility: visible; + opacity: 1; + } + } +} diff --git a/apps/frontend/static_src/components/blocks/_content.scss b/apps/frontend/static_src/components/blocks/_content.scss new file mode 100644 index 0000000..cedd4a2 --- /dev/null +++ b/apps/frontend/static_src/components/blocks/_content.scss @@ -0,0 +1,5 @@ +.content { + &::before { + height: calc(100% + 300px); + } +} diff --git a/apps/frontend/static_src/js/components/tooltip.js b/apps/frontend/static_src/js/components/tooltip.js new file mode 100644 index 0000000..59518fb --- /dev/null +++ b/apps/frontend/static_src/js/components/tooltip.js @@ -0,0 +1,26 @@ +export function copyCitation() { + const myInput = document.getElementById("citation-field"); + if (!myInput) { + return; + } + + myInput.addEventListener("click", () => { + const text = myInput.textContent || myInput.innerText; + navigator.clipboard.writeText(text); + + const tooltip = document.getElementById("citation-tooltip"); + tooltip.innerHTML = "Copied: " + text; + }); +} + +export function resetToolTip() { + const myInput = document.getElementById("citation-field"); + if (!myInput) { + return; + } + + myInput.addEventListener("mouseleave", () => { + const tooltip = document.getElementById("citation-tooltip"); + tooltip.innerHTML = "Copy to clipboard"; + }); +} diff --git a/apps/frontend/static_src/js/main.js b/apps/frontend/static_src/js/main.js index e69de29..24cc418 100644 --- a/apps/frontend/static_src/js/main.js +++ b/apps/frontend/static_src/js/main.js @@ -0,0 +1,4 @@ +import { copyCitation, resetToolTip } from "./components/tooltip"; + +copyCitation(); +resetToolTip(); diff --git a/apps/frontend/static_src/scss/main.scss b/apps/frontend/static_src/scss/main.scss new file mode 100644 index 0000000..d07027a --- /dev/null +++ b/apps/frontend/static_src/scss/main.scss @@ -0,0 +1,2 @@ +@import "../components/blocks/citation"; +@import "../components/blocks/content"; diff --git a/apps/frontend/templates/citation/ai_answer.html b/apps/frontend/templates/citation/ai_answer.html new file mode 100644 index 0000000..277288b --- /dev/null +++ b/apps/frontend/templates/citation/ai_answer.html @@ -0,0 +1,25 @@ +{% extends "wagtail_helpdesk/cms/answer_detail.html" %} +{% load i18n static wagtailcore_tags wagtailimages_tags %} +{% wagtail_site as current_site %} + + + +{% block content %} + {% for block in self.page_content %} + {% include_block block %} + {% endfor %} + {% include "wagtail_helpdesk/includes/social_share_buttons.html" %} + {% include "includes/cite.html" with page=page %} + + {% for block in self.answer_origin %} + {% include_block block %} + {% endfor %} + + {% for block in self.related_items %} + {% include_block block %} + {% endfor %} + +{% endblock %} + + + diff --git a/apps/frontend/templates/includes/cite.html b/apps/frontend/templates/includes/cite.html new file mode 100644 index 0000000..1a4d750 --- /dev/null +++ b/apps/frontend/templates/includes/cite.html @@ -0,0 +1,9 @@ +
+

APA

+
+
+
{{ page.citation }}
+ Copy to clipboard +
+
+
diff --git a/apps/frontend/templates/wagtail_helpdesk/base.html b/apps/frontend/templates/wagtail_helpdesk/base.html index 2644152..fe71a63 100644 --- a/apps/frontend/templates/wagtail_helpdesk/base.html +++ b/apps/frontend/templates/wagtail_helpdesk/base.html @@ -6,3 +6,7 @@ {% endblock %} + +{% block js %} + +{% endblock %}