diff --git a/django_email_learning/public/views.py b/django_email_learning/public/views.py
index 9f3b030..641e473 100644
--- a/django_email_learning/public/views.py
+++ b/django_email_learning/public/views.py
@@ -17,6 +17,10 @@
)
+def get_terms_of_service_url() -> str | None:
+ return settings.DJANGO_EMAIL_LEARNING.get("TERMS_OF_SERVICE_URL") # type: ignore[return-value]
+
+
def get_organization_json_ld_links(organization: Organization) -> dict[str, object]:
json_ld_links: dict[str, object] = {"url": organization.public_url}
same_as: list[str] = []
@@ -165,6 +169,7 @@ def get_context_data(self, **kwargs) -> dict: # type: ignore[no-untyped-def]
"organization": organization_data.model_dump(),
"enrollApiUrl": f"{settings.DJANGO_EMAIL_LEARNING['SITE_BASE_URL']}{enroll_api_path}",
"direction": "rtl" if lang_info["bidi"] else "ltr",
+ "termsOfServiceUrl": get_terms_of_service_url(),
"localeMessages": {
"courses": _("Courses"),
"enroll_now": _("Enroll Now"),
@@ -188,6 +193,9 @@ def get_context_data(self, **kwargs) -> dict: # type: ignore[no-untyped-def]
"linkedin_page": _("LinkedIn Page"),
"youtube_channel": _("YouTube Channel"),
"website": _("Website"),
+ "terms_of_service_confirmation": _(
+ "By enrolling, you agree to our Terms of Service."
+ ),
},
}
context["organization_name"] = organization.name
@@ -270,6 +278,7 @@ def get_context_data(self, **kwargs) -> dict: # type: ignore[no-untyped-def]
"organization": organization_data.model_dump(),
"enrollApiUrl": f"{settings.DJANGO_EMAIL_LEARNING['SITE_BASE_URL']}{enroll_api_path}",
"direction": "rtl" if lang_info["bidi"] else "ltr",
+ "termsOfServiceUrl": get_terms_of_service_url(),
"localeMessages": {
"enroll_now": _("Enroll Now"),
"enrol_for_course": _("Enroll for COURSE_NAME"),
@@ -294,6 +303,9 @@ def get_context_data(self, **kwargs) -> dict: # type: ignore[no-untyped-def]
"continue": _("Continue"),
"target_audience_title": _("Who is this course for?"),
"external_references_title": _("External References"),
+ "terms_of_service_confirmation": _(
+ "By enrolling, you agree to our Terms of Service."
+ ),
},
}
context["course_title"] = course.title
diff --git a/django_service/settings.py b/django_service/settings.py
index cbc3773..a064bea 100644
--- a/django_service/settings.py
+++ b/django_service/settings.py
@@ -107,6 +107,7 @@
"SITE_BASE_URL": "http://localhost:8000",
"ENCRYPTION_SECRET_KEY": "your-very-secure-and-random-key",
"FROM_EMAIL": os.environ.get("FROM_EMAIL", "webmaster@localhost"),
+ "TERMS_OF_SERVICE_URL": "https://www.example.com/terms",
"AI": {
"OPENAI_API_KEY": os.environ.get("OPENAI_API_KEY"),
"TEXT_EDITING_MODEL": LanguageModel.GPT_4O_MINI.model_name,
diff --git a/docs/source/installation.rst b/docs/source/installation.rst
index e53964e..c2c04a0 100644
--- a/docs/source/installation.rst
+++ b/docs/source/installation.rst
@@ -138,6 +138,18 @@ The default email address for outgoing course emails. If not specified, falls ba
'FROM_EMAIL': 'courses@yourdomain.com',
}
+**TERMS_OF_SERVICE_URL**
+
+Optional link to your terms of service. When provided, this link is displayed in the public enrollment dialog so learners can review your terms before submitting their email address.
+
+.. code-block:: python
+
+ DJANGO_EMAIL_LEARNING = {
+ 'SITE_BASE_URL': 'https://yourdomain.com',
+ 'ENCRYPTION_SECRET_KEY': 'your-very-long-random-string',
+ 'TERMS_OF_SERVICE_URL': 'https://yourdomain.com/terms',
+ }
+
**SIDEBAR.CUSTOM_COMPONENT**
Optional configuration for injecting a custom component in the platform sidebar.
diff --git a/frontend/public/components/EnrollmentForm.jsx b/frontend/public/components/EnrollmentForm.jsx
index b3885cc..f6d9734 100644
--- a/frontend/public/components/EnrollmentForm.jsx
+++ b/frontend/public/components/EnrollmentForm.jsx
@@ -12,7 +12,7 @@ const EnrollmentForm = ({course_title, course_slug, organization_id, endpoint, o
const [isProcessing, setIsProcessing] = React.useState(false);
const [csrfToken, setCsrfToken] = React.useState(getCookie('csrftoken'));
const [showReloadDialog, setShowReloadDialog] = React.useState(false);
- const { localeMessages } = useAppContext();
+ const { localeMessages, termsOfServiceUrl } = useAppContext();
useEffect(() => {
if (!csrfToken) {
@@ -95,8 +95,27 @@ const EnrollmentForm = ({course_title, course_slug, organization_id, endpoint, o
enroll();
}
}} />
+ {termsOfServiceUrl && (
+
+
+
+ )}
-
+
diff --git a/tests/public/test_views/test_public_course_view.py b/tests/public/test_views/test_public_course_view.py
index fcb341a..500bd41 100644
--- a/tests/public/test_views/test_public_course_view.py
+++ b/tests/public/test_views/test_public_course_view.py
@@ -1,8 +1,22 @@
import json
+from django.conf import settings
+from django.test import override_settings
from django.urls import reverse
from django_email_learning.models import ExternalReference
+import pytest
+
+
+@pytest.fixture
+def tos_settings(request):
+ with override_settings(
+ DJANGO_EMAIL_LEARNING={
+ **settings.DJANGO_EMAIL_LEARNING,
+ "TERMS_OF_SERVICE_URL": request.param,
+ }
+ ):
+ yield settings.DJANGO_EMAIL_LEARNING["TERMS_OF_SERVICE_URL"]
def test_course_view_anonymous_client(
@@ -65,3 +79,24 @@ def test_course_view_non_existent_course(db, anonymous_client):
)
response = anonymous_client.get(url)
assert response.status_code == 404
+
+
+@pytest.mark.parametrize(
+ "tos_settings",
+ ["https://example.com/terms", None],
+ indirect=True,
+)
+def test_course_view_includes_terms_of_service_url_when_configured(
+ db, anonymous_client, course, tos_settings
+):
+ course.enabled = True
+ course.save()
+
+ url = reverse(
+ "django_email_learning:public:course_view",
+ kwargs={"organization_id": 1, "course_slug": course.slug},
+ )
+ response = anonymous_client.get(url)
+
+ assert response.status_code == 200
+ assert response.context["appContext"]["termsOfServiceUrl"] == tos_settings