Skip to content

Commit 027b88b

Browse files
Merge issue-125-legal: add legal footer and preferences (#125)
# Conflicts: # core/accessibility_registry.py
2 parents d41398d + 20c1de2 commit 027b88b

19 files changed

Lines changed: 1740 additions & 43 deletions

File tree

_docs/adoption/course-platform/integration-patched-files.tsv

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ scripts/load_rds_export.py 23700 f08edf55c064f061646a2f1e5a03693356c3b044db2bfd7
111111
scripts/score_project_dev.py 5535 28b9c8b7e02b5d5a2fe680ec1bd8e368bb36b1b54f2616550f85c6112a351131 Run copied project scoring against the unified local SQLite settings without reading database credentials or directing privileged SQL writes.
112112
course_platform_templates/account/logout.html 1406 6faa3b82a9ebc9346d99dafbdcc465b9d34dc3507634ca0c9738eb3b0f9b5c88 Use the unified DataTalks.Club name in the copied sign-out page title without changing logout behavior.
113113
course_platform_templates/accounts/account_settings.html 14795 396d3c64c726efd62bddd9d9b241cfb0c497345507033e1889ced7178e8b61bb Use the unified DataTalks.Club title and shared accessible label, help, error, status, and toggle relationships without changing account-settings behavior.
114-
course_platform_templates/base.html 18818 fb5337edbc27ff8b5fc447371216a921157aff88625d35675038db03b58dc744 Use the unified canonical policy and accessible shared shell with skip-target focus, current navigation, resilient account links, public-safe announcements, local interaction helpers, and preserved local Tailwind and Font Awesome assets.
114+
course_platform_templates/base.html 17636 c0a7519654e82b13057c52f152e928f7cb7646cab3f0ad76e7525051a6ce6497 Use the unified canonical policy and accessible shared shell with skip-target focus, current navigation, resilient account links, public-safe announcements, the issue #125 single-source legal footer and analytics preferences, local interaction helpers, and preserved local Tailwind and Font Awesome assets.
115115
course_platform_templates/socialaccount/authentication_error.html 1240 4a70aaf5e51dbb57eeef156d37fa56144bc60fd55f2f4af8ba4f2abe93870d49 Use the unified DataTalks.Club name in the copied authentication-error title without changing recovery behavior.
116116
course_platform_templates/socialaccount/connections.html 3818 a1e0756bfe763150fddefc5018949eb90f7d90e7f5bd9b9eab890e6ae00c62f5 Use the unified DataTalks.Club name in the copied account-connections title without changing connection behavior.
117117
course_platform_templates/socialaccount/login_cancelled.html 1210 607d2d1d5c5c43aa69632030e144817416732482e04ecb0ac54fdd252de8fd71 Use the unified DataTalks.Club name in the copied cancelled-login title without changing recovery behavior.

_docs/adoption/course-platform/integration-patches.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,10 @@ overlays:
3535
`_docs/provenance/course-platform-vendor-assets.json`; the Tailwind configuration remains before
3636
the runtime script and the browser no longer requires either production CDN. Issue #114 adds the
3737
escaped public-safe announcement after the shared header and before main content without changing
38-
copied account, navigation, preference, or session behavior;
38+
copied account, navigation, preference, or session behavior. Issue #125 replaces only the copied
39+
footer markup with the target-owned shared legal-footer partial used by both target shells and
40+
loads its provider-free analytics-preferences behavior; course, account, navigation, preference,
41+
and session behavior remains unchanged;
3942
- `courses/templates/projects/project.html`: issue #75 replaces the external commit-ID screenshot
4043
with concise accessible text explaining where to copy the first seven commit characters. Project
4144
submission fields, validation, persistence, and operator controls remain unchanged;

_docs/legal/data-flow-inventory.md

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
# Legal and analytics data-flow inventory
2+
3+
Issue: `#125`
4+
Candidate base: `8c0239254ab690918e6930c2368614dcfc709cf5`
5+
Inventory date: 11 August 2026
6+
7+
This inventory bounds the source-reviewed legal pages and analytics preferences. The architecture
8+
and privacy authority remains `_docs/specs/07-security-privacy-operations.md`; the other cited specs
9+
define the product flows summarized here. This document does not authorize a new processor,
10+
production setting, message purpose, data import, or external mutation.
11+
12+
## Current website flows
13+
14+
| Surface | Data and purpose | Current boundary | Authority |
15+
|---|---|---|---|
16+
| Public content | GitHub-authored articles, podcasts, books, docs, FAQ, wiki, courses, events, and public editorial people | Validated versioned read models; public requests perform no GitHub fetch or render | Specs 01, 02, and 03 |
17+
| Account and private member profile | Email identity, sign-in links, session/security state, preferences, and member-entered onboarding fields | One account-owned private profile, never inferred to or from a public editorial person | Specs 01 and 07 |
18+
| Slack onboarding | Profile completion, eligibility, grant/delivery state, and reveal of the approved shared join URL | No Slack invitation API, public directory, or membership synchronization | Specs 01, 06, and 07 |
19+
| Course and cohort participation | Registration/enrollment, confirmed minimal profile snapshot, target, comment, privacy evidence, separate optional marketing consent, work, reviews, scores, complaints, leaderboard, and certificates | Course-owned records and shared application services; profile edits affect future snapshots | Specs 01, 04, and 07 |
20+
| Event registration | Event, identity/contact fields requested by the flow, privacy evidence, separate optional marketing consent, verification/management state, and attendance/status | Accountless or account-associated event services with protected hashed tokens | Specs 01, 05, and 07 |
21+
| Transactional email | Versioned website intent and redacted delivery/reconciliation status | Relay is the only website delivery boundary; provider infrastructure stays downstream | Specs 05, 07, and issue #124 |
22+
| Country suggestion | CloudFront country signal on explicit zero-cache onboarding/profile consumers | Editable suggestion only; raw header is not separately retained, logged, or used as a public cache key | Specs 01, 07, and 08 |
23+
| Security and operations | Bounded request/correlation IDs, safe route/status metadata, audit events, durable job state, and redacted diagnostics | No secrets, credentials, complete query, cookies, raw IP, profile values, registration answers, or message bodies in logs/metrics | Specs 07 and 08 |
24+
25+
## Cookie and optional analytics boundary
26+
27+
- Django session and CSRF cookies are necessary security/application cookies.
28+
- `dtc_analytics_consent` is a necessary preference cookie. Version 1 stores only `allow` or
29+
`deny`, lasts 180 days, uses `Path=/`, `SameSite=Lax`, and adds `Secure` on HTTPS.
30+
- This candidate adds no analytics provider loader, measurement ID, endpoint, event taxonomy,
31+
account join key, attribution store, tracking pixel, tag-manager editor, or behavioral profile.
32+
- Before a choice and after rejection or withdrawal, no optional analytics script, endpoint,
33+
event, or cookie exists. Rejection/withdrawal also expires recognized optional first-party
34+
analytics cookie prefixes without deleting the necessary preference cookie.
35+
- Allowing records the preference only. It cannot send data because the repository contains no
36+
enabled provider path. A later provider/configuration change requires its own groomed scope,
37+
typed Studio/admin API parity, CSP reconciliation, privacy review, and production authorization.
38+
- Local, CI, development, and preview environments therefore remain incapable of sending
39+
production analytics under this candidate. The existing executable source guard remains intact.
40+
41+
## Provisional retention represented in the policy
42+
43+
- unverified registration records: 14 days;
44+
- event registration personal data: 90 days after the event;
45+
- educational records: while active, then the published deletion/anonymization schedule;
46+
- website Relay intent and redacted delivery metadata: 180 days;
47+
- security audit evidence: one year; and
48+
- development logs: 30 days.
49+
50+
The educational-record schedule and any production exception remain subject to owner/privacy
51+
review, as required by specification 07 and open decision 12.
52+
53+
## Explicit human acceptance gate
54+
55+
The authoritative repository does not contain confirmed DataTalks.Club operator identity, public
56+
address, authorized representative, legal/privacy contact, VAT or tax identifiers, final minors
57+
policy, or current German statutory/dispute-resolution wording. The candidate deliberately does not
58+
infer those values or copy them from AI Shipping Labs. An authorized owner/legal reviewer must:
59+
60+
1. supply and approve the exact Impressum identity, address, representative, contact, and tax data;
61+
2. approve current statutory and dispute-resolution wording;
62+
3. approve the Terms governing-law/contact wording and the Privacy controller/contact, transfer,
63+
retention, rights, and minors wording; and
64+
4. confirm the displayed last-updated date before production legal acceptance.
65+
66+
Until that review is recorded, `/impressum` remains visibly marked as requiring verification and
67+
issue #125 cannot receive production acceptance.

content/legal_views.py

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
"""Source-reviewed public legal pages for DataTalks.Club."""
2+
3+
from __future__ import annotations
4+
5+
from django.conf import settings
6+
from django.http import HttpRequest, HttpResponse
7+
from django.shortcuts import render
8+
from django.views.decorators.http import require_safe
9+
10+
LEGAL_PAGE_CONTEXT = {
11+
"terms": {
12+
"title": "Terms of Service",
13+
"description": (
14+
"Terms for using the free DataTalks.Club community, website, events, and courses."
15+
),
16+
"path": "/terms",
17+
},
18+
"privacy": {
19+
"title": "Privacy Policy",
20+
"description": (
21+
"How DataTalks.Club handles account, community, course, event, and website data."
22+
),
23+
"path": "/privacy",
24+
},
25+
"impressum": {
26+
"title": "Impressum",
27+
"description": "Legal notice and operator information for DataTalks.Club.",
28+
"path": "/impressum",
29+
},
30+
}
31+
32+
33+
def _legal_page(request: HttpRequest, *, page: str) -> HttpResponse:
34+
definition = LEGAL_PAGE_CONTEXT[page]
35+
path = definition["path"]
36+
return render(
37+
request,
38+
f"public/legal/{page}.html",
39+
{
40+
"canonical_url": f"https://datatalks.club{path}",
41+
"seo_title": f"{definition['title']} · DataTalks.Club",
42+
"seo_description": definition["description"],
43+
"legal_robots": "noindex,nofollow" if settings.NOINDEX else "index,follow",
44+
},
45+
)
46+
47+
48+
@require_safe
49+
def terms(request: HttpRequest) -> HttpResponse:
50+
return _legal_page(request, page="terms")
51+
52+
53+
@require_safe
54+
def privacy(request: HttpRequest) -> HttpResponse:
55+
return _legal_page(request, page="privacy")
56+
57+
58+
@require_safe
59+
def impressum(request: HttpRequest) -> HttpResponse:
60+
return _legal_page(request, page="impressum")

content/public_urls.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,18 @@
11
from django.urls import path
22

3-
from . import public_views
3+
from . import legal_views, public_views
44

55
urlpatterns = [
6+
path("terms", legal_views.terms, name="terms"),
7+
path("terms/", public_views.permanent_public_redirect, {"target": "/terms"}),
8+
path("privacy", legal_views.privacy, name="privacy"),
9+
path("privacy/", public_views.permanent_public_redirect, {"target": "/privacy"}),
10+
path("impressum", legal_views.impressum, name="impressum"),
11+
path(
12+
"impressum/",
13+
public_views.permanent_public_redirect,
14+
{"target": "/impressum"},
15+
),
616
path("blog", public_views.collection_hub, {"collection": "articles"}, name="articles"),
717
path("articles.html", public_views.permanent_public_redirect, {"target": "/blog"}),
818
path("blog/", public_views.permanent_public_redirect, {"target": "/blog"}),

content/public_views.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -625,7 +625,13 @@ def media(request: HttpRequest, media_path: str) -> FileResponse:
625625
def _section_records(section: str) -> tuple[tuple[str, str], ...]:
626626
projection = public_projection()
627627
static_sections = {
628-
"main": (("/", ""), ("/slack.html", "")),
628+
"main": (
629+
("/", ""),
630+
("/terms", ""),
631+
("/privacy", ""),
632+
("/impressum", ""),
633+
("/slack.html", ""),
634+
),
629635
"docs": (
630636
("/docs/", ""),
631637
("/docs/courses/ai-dev-tools-zoomcamp/getting-started/", ""),

0 commit comments

Comments
 (0)