Skip to content

Commit 2229012

Browse files
authored
Merge pull request #335 from AvaCodeSolutions/feat/334/terms-of-service-link
feat: #334 add tos config
2 parents 1e516ed + 97ab523 commit 2229012

File tree

5 files changed

+81
-2
lines changed

5 files changed

+81
-2
lines changed

django_email_learning/public/views.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@
1717
)
1818

1919

20+
def get_terms_of_service_url() -> str | None:
21+
return settings.DJANGO_EMAIL_LEARNING.get("TERMS_OF_SERVICE_URL") # type: ignore[return-value]
22+
23+
2024
def get_organization_json_ld_links(organization: Organization) -> dict[str, object]:
2125
json_ld_links: dict[str, object] = {"url": organization.public_url}
2226
same_as: list[str] = []
@@ -165,6 +169,7 @@ def get_context_data(self, **kwargs) -> dict: # type: ignore[no-untyped-def]
165169
"organization": organization_data.model_dump(),
166170
"enrollApiUrl": f"{settings.DJANGO_EMAIL_LEARNING['SITE_BASE_URL']}{enroll_api_path}",
167171
"direction": "rtl" if lang_info["bidi"] else "ltr",
172+
"termsOfServiceUrl": get_terms_of_service_url(),
168173
"localeMessages": {
169174
"courses": _("Courses"),
170175
"enroll_now": _("Enroll Now"),
@@ -188,6 +193,9 @@ def get_context_data(self, **kwargs) -> dict: # type: ignore[no-untyped-def]
188193
"linkedin_page": _("LinkedIn Page"),
189194
"youtube_channel": _("YouTube Channel"),
190195
"website": _("Website"),
196+
"terms_of_service_confirmation": _(
197+
"By enrolling, you agree to our <a href='TERMS_OF_SERVICE_URL' target='_blank'>Terms of Service</a>."
198+
),
191199
},
192200
}
193201
context["organization_name"] = organization.name
@@ -270,6 +278,7 @@ def get_context_data(self, **kwargs) -> dict: # type: ignore[no-untyped-def]
270278
"organization": organization_data.model_dump(),
271279
"enrollApiUrl": f"{settings.DJANGO_EMAIL_LEARNING['SITE_BASE_URL']}{enroll_api_path}",
272280
"direction": "rtl" if lang_info["bidi"] else "ltr",
281+
"termsOfServiceUrl": get_terms_of_service_url(),
273282
"localeMessages": {
274283
"enroll_now": _("Enroll Now"),
275284
"enrol_for_course": _("Enroll for COURSE_NAME"),
@@ -294,6 +303,9 @@ def get_context_data(self, **kwargs) -> dict: # type: ignore[no-untyped-def]
294303
"continue": _("Continue"),
295304
"target_audience_title": _("Who is this course for?"),
296305
"external_references_title": _("External References"),
306+
"terms_of_service_confirmation": _(
307+
"By enrolling, you agree to our <a href='TERMS_OF_SERVICE_URL' target='_blank'>Terms of Service</a>."
308+
),
297309
},
298310
}
299311
context["course_title"] = course.title

django_service/settings.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@
107107
"SITE_BASE_URL": "http://localhost:8000",
108108
"ENCRYPTION_SECRET_KEY": "your-very-secure-and-random-key",
109109
"FROM_EMAIL": os.environ.get("FROM_EMAIL", "webmaster@localhost"),
110+
"TERMS_OF_SERVICE_URL": "https://www.example.com/terms",
110111
"AI": {
111112
"OPENAI_API_KEY": os.environ.get("OPENAI_API_KEY"),
112113
"TEXT_EDITING_MODEL": LanguageModel.GPT_4O_MINI.model_name,

docs/source/installation.rst

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,18 @@ The default email address for outgoing course emails. If not specified, falls ba
138138
'FROM_EMAIL': 'courses@yourdomain.com',
139139
}
140140
141+
**TERMS_OF_SERVICE_URL**
142+
143+
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.
144+
145+
.. code-block:: python
146+
147+
DJANGO_EMAIL_LEARNING = {
148+
'SITE_BASE_URL': 'https://yourdomain.com',
149+
'ENCRYPTION_SECRET_KEY': 'your-very-long-random-string',
150+
'TERMS_OF_SERVICE_URL': 'https://yourdomain.com/terms',
151+
}
152+
141153
**SIDEBAR.CUSTOM_COMPONENT**
142154

143155
Optional configuration for injecting a custom component in the platform sidebar.

frontend/public/components/EnrollmentForm.jsx

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ const EnrollmentForm = ({course_title, course_slug, organization_id, endpoint, o
1212
const [isProcessing, setIsProcessing] = React.useState(false);
1313
const [csrfToken, setCsrfToken] = React.useState(getCookie('csrftoken'));
1414
const [showReloadDialog, setShowReloadDialog] = React.useState(false);
15-
const { localeMessages } = useAppContext();
15+
const { localeMessages, termsOfServiceUrl } = useAppContext();
1616

1717
useEffect(() => {
1818
if (!csrfToken) {
@@ -95,8 +95,27 @@ const EnrollmentForm = ({course_title, course_slug, organization_id, endpoint, o
9595
enroll();
9696
}
9797
}} />
98+
{termsOfServiceUrl && (
99+
<Typography
100+
variant='caption'
101+
sx={{
102+
display: 'block',
103+
mt: 2.5,
104+
mb: 1.5,
105+
color: 'text.secondary',
106+
lineHeight: 1.6,
107+
'& a': {
108+
color: 'primary.main',
109+
textDecoration: 'underline',
110+
fontWeight: 500,
111+
},
112+
}}
113+
>
114+
<span dangerouslySetInnerHTML={{ __html: localeMessages['terms_of_service_confirmation'].replace('TERMS_OF_SERVICE_URL', termsOfServiceUrl) }} />
115+
</Typography>
116+
)}
98117
<input type="hidden" name="course_slug" value={course_slug} />
99-
<Box sx={{ mt: 2, textAlign: 'right' }}>
118+
<Box sx={{ mt: 1.5, textAlign: 'right' }}>
100119
<Button variant="outlined" sx={{ mx: 1 }} onClick={onCancle}>
101120
{localeMessages['cancel']}
102121
</Button>

tests/public/test_views/test_public_course_view.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,22 @@
11
import json
22

3+
from django.conf import settings
4+
from django.test import override_settings
35
from django.urls import reverse
46

57
from django_email_learning.models import ExternalReference
8+
import pytest
9+
10+
11+
@pytest.fixture
12+
def tos_settings(request):
13+
with override_settings(
14+
DJANGO_EMAIL_LEARNING={
15+
**settings.DJANGO_EMAIL_LEARNING,
16+
"TERMS_OF_SERVICE_URL": request.param,
17+
}
18+
):
19+
yield settings.DJANGO_EMAIL_LEARNING["TERMS_OF_SERVICE_URL"]
620

721

822
def test_course_view_anonymous_client(
@@ -65,3 +79,24 @@ def test_course_view_non_existent_course(db, anonymous_client):
6579
)
6680
response = anonymous_client.get(url)
6781
assert response.status_code == 404
82+
83+
84+
@pytest.mark.parametrize(
85+
"tos_settings",
86+
["https://example.com/terms", None],
87+
indirect=True,
88+
)
89+
def test_course_view_includes_terms_of_service_url_when_configured(
90+
db, anonymous_client, course, tos_settings
91+
):
92+
course.enabled = True
93+
course.save()
94+
95+
url = reverse(
96+
"django_email_learning:public:course_view",
97+
kwargs={"organization_id": 1, "course_slug": course.slug},
98+
)
99+
response = anonymous_client.get(url)
100+
101+
assert response.status_code == 200
102+
assert response.context["appContext"]["termsOfServiceUrl"] == tos_settings

0 commit comments

Comments
 (0)