Compare commits
2
Commits
master
...
d6bed439d7
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d6bed439d7 | ||
|
|
351994673a |
@@ -156,6 +156,6 @@ EMAIL_USE_TLS = True
|
|||||||
LOGIN_REDIRECT_URL = '/'
|
LOGIN_REDIRECT_URL = '/'
|
||||||
LOGOUT_REDIRECT_URL = '/'
|
LOGOUT_REDIRECT_URL = '/'
|
||||||
|
|
||||||
# Tianji analytics (loaded only after user consent)
|
# Tianji analytics (loaded on page load; users acknowledge via notice banner)
|
||||||
TIANJI_TRACKER_URL = 'https://tianji.aimloperations.com/tracker.js'
|
TIANJI_TRACKER_URL = 'https://tianji.aimloperations.com/tracker.js'
|
||||||
TIANJI_WEBSITE_ID = 'cm7w80pyy020oddswy2evl957'
|
TIANJI_WEBSITE_ID = 'cm7w80pyy020oddswy2evl957'
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
(function () {
|
(function () {
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
var CONSENT_KEY = 'aiml_analytics_consent';
|
var NOTICE_KEY = 'aiml_analytics_notice';
|
||||||
|
var LEGACY_CONSENT_KEY = 'aiml_analytics_consent';
|
||||||
|
var LEGACY_DISABLED_KEY = 'tianji.disabled';
|
||||||
var configEl = document.getElementById('tianji-config');
|
var configEl = document.getElementById('tianji-config');
|
||||||
if (!configEl) {
|
if (!configEl) {
|
||||||
return;
|
return;
|
||||||
@@ -10,19 +12,26 @@
|
|||||||
var trackerUrl = configEl.dataset.trackerUrl;
|
var trackerUrl = configEl.dataset.trackerUrl;
|
||||||
var websiteId = configEl.dataset.websiteId;
|
var websiteId = configEl.dataset.websiteId;
|
||||||
var userId = configEl.dataset.userId || '';
|
var userId = configEl.dataset.userId || '';
|
||||||
var pendingConsentEvent = false;
|
|
||||||
|
|
||||||
function getConsent() {
|
function getStorageItem(key) {
|
||||||
try {
|
try {
|
||||||
return localStorage.getItem(CONSENT_KEY);
|
return localStorage.getItem(key);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function setConsent(value) {
|
function setStorageItem(key, value) {
|
||||||
try {
|
try {
|
||||||
localStorage.setItem(CONSENT_KEY, value);
|
localStorage.setItem(key, value);
|
||||||
|
} catch (e) {
|
||||||
|
/* ignore storage errors */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeStorageItem(key) {
|
||||||
|
try {
|
||||||
|
localStorage.removeItem(key);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
/* ignore storage errors */
|
/* ignore storage errors */
|
||||||
}
|
}
|
||||||
@@ -71,51 +80,44 @@
|
|||||||
|
|
||||||
script.onload = function () {
|
script.onload = function () {
|
||||||
identifyUser();
|
identifyUser();
|
||||||
if (pendingConsentEvent) {
|
|
||||||
trackEvent('consent_accepted');
|
|
||||||
pendingConsentEvent = false;
|
|
||||||
}
|
|
||||||
document.dispatchEvent(new CustomEvent('tianji:ready'));
|
document.dispatchEvent(new CustomEvent('tianji:ready'));
|
||||||
};
|
};
|
||||||
|
|
||||||
document.head.appendChild(script);
|
document.head.appendChild(script);
|
||||||
}
|
}
|
||||||
|
|
||||||
function acceptTracking() {
|
function migrateLegacyConsent() {
|
||||||
setConsent('accepted');
|
var legacyConsent = getStorageItem(LEGACY_CONSENT_KEY);
|
||||||
hideBanner();
|
var legacyDisabled = getStorageItem(LEGACY_DISABLED_KEY);
|
||||||
try {
|
|
||||||
localStorage.removeItem('tianji.disabled');
|
if (legacyConsent === 'accepted') {
|
||||||
} catch (e) {
|
setStorageItem(NOTICE_KEY, 'acknowledged');
|
||||||
/* ignore storage errors */
|
removeStorageItem(LEGACY_CONSENT_KEY);
|
||||||
}
|
removeStorageItem(LEGACY_DISABLED_KEY);
|
||||||
pendingConsentEvent = true;
|
return 'migrated_acknowledged';
|
||||||
loadTracker();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function declineTracking() {
|
if (legacyConsent === 'declined' || legacyDisabled === '1') {
|
||||||
setConsent('declined');
|
removeStorageItem(LEGACY_CONSENT_KEY);
|
||||||
|
removeStorageItem(LEGACY_DISABLED_KEY);
|
||||||
|
return 'migrated_declined';
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function acknowledgeNotice() {
|
||||||
|
setStorageItem(NOTICE_KEY, 'acknowledged');
|
||||||
hideBanner();
|
hideBanner();
|
||||||
try {
|
window.aimlTrackWhenReady('notice_acknowledged');
|
||||||
localStorage.setItem('tianji.disabled', '1');
|
|
||||||
} catch (e) {
|
|
||||||
/* ignore storage errors */
|
|
||||||
}
|
|
||||||
if (window.aimlTrack) {
|
|
||||||
window.aimlTrack('consent_declined');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function bindBannerControls() {
|
function bindBannerControls() {
|
||||||
var acceptBtn = document.getElementById('cookie-consent-accept');
|
var acknowledgeBtn = document.getElementById('cookie-consent-acknowledge');
|
||||||
var declineBtn = document.getElementById('cookie-consent-decline');
|
|
||||||
var manageLinks = document.querySelectorAll('[data-open-cookie-preferences]');
|
var manageLinks = document.querySelectorAll('[data-open-cookie-preferences]');
|
||||||
|
|
||||||
if (acceptBtn) {
|
if (acknowledgeBtn) {
|
||||||
acceptBtn.addEventListener('click', acceptTracking);
|
acknowledgeBtn.addEventListener('click', acknowledgeNotice);
|
||||||
}
|
|
||||||
if (declineBtn) {
|
|
||||||
declineBtn.addEventListener('click', declineTracking);
|
|
||||||
}
|
}
|
||||||
manageLinks.forEach(function (link) {
|
manageLinks.forEach(function (link) {
|
||||||
link.addEventListener('click', function (event) {
|
link.addEventListener('click', function (event) {
|
||||||
@@ -140,16 +142,14 @@
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
function initConsent() {
|
function initNotice() {
|
||||||
bindBannerControls();
|
bindBannerControls();
|
||||||
|
|
||||||
var consent = getConsent();
|
|
||||||
if (consent === 'accepted') {
|
|
||||||
hideBanner();
|
|
||||||
loadTracker();
|
loadTracker();
|
||||||
return;
|
|
||||||
}
|
var migration = migrateLegacyConsent();
|
||||||
if (consent === 'declined') {
|
var notice = getStorageItem(NOTICE_KEY);
|
||||||
|
|
||||||
|
if (notice === 'acknowledged' || migration === 'migrated_acknowledged') {
|
||||||
hideBanner();
|
hideBanner();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -157,5 +157,5 @@
|
|||||||
showBanner();
|
showBanner();
|
||||||
}
|
}
|
||||||
|
|
||||||
initConsent();
|
initNotice();
|
||||||
})();
|
})();
|
||||||
|
|||||||
@@ -137,15 +137,14 @@
|
|||||||
<div id="cookie-consent-banner" class="cookie-consent-banner" hidden role="dialog" aria-modal="true"
|
<div id="cookie-consent-banner" class="cookie-consent-banner" hidden role="dialog" aria-modal="true"
|
||||||
aria-labelledby="cookie-consent-title" aria-describedby="cookie-consent-description">
|
aria-labelledby="cookie-consent-title" aria-describedby="cookie-consent-description">
|
||||||
<div class="cookie-consent-content">
|
<div class="cookie-consent-content">
|
||||||
<h2 id="cookie-consent-title" class="visually-hidden">Cookie consent</h2>
|
<h2 id="cookie-consent-title" class="visually-hidden">Analytics notice</h2>
|
||||||
<p class="cookie-consent-text" id="cookie-consent-description">
|
<p class="cookie-consent-text" id="cookie-consent-description">
|
||||||
<span class="cookie-consent-text-full">We use analytics tracking to understand how visitors use our site. Tracking runs only if you accept.
|
<span class="cookie-consent-text-full">We use analytics to understand how visitors use our site. This helps us improve performance and content.
|
||||||
See our <a href="{% url 'terms_of_service' %}">Terms of Service & Privacy Policy</a> for details.</span>
|
See our <a href="{% url 'terms_of_service' %}">Terms of Service & Privacy Policy</a> for details.</span>
|
||||||
<span class="cookie-consent-text-short">We use analytics if you accept. <a href="{% url 'terms_of_service' %}">Privacy Policy</a></span>
|
<span class="cookie-consent-text-short">We use analytics on this site. <a href="{% url 'terms_of_service' %}">Privacy Policy</a></span>
|
||||||
</p>
|
</p>
|
||||||
<div class="cookie-consent-actions">
|
<div class="cookie-consent-actions">
|
||||||
<button type="button" id="cookie-consent-decline" class="btn btn-outline">Decline</button>
|
<button type="button" id="cookie-consent-acknowledge" class="btn">Acknowledge</button>
|
||||||
<button type="button" id="cookie-consent-accept" class="btn">Accept Analytics</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -52,7 +52,7 @@
|
|||||||
<ul>
|
<ul>
|
||||||
<li><strong>Information you provide:</strong> name, email address, message content, and other details submitted through contact forms or account-related requests.</li>
|
<li><strong>Information you provide:</strong> name, email address, message content, and other details submitted through contact forms or account-related requests.</li>
|
||||||
<li><strong>Account information:</strong> username and authentication data for authorized users of internal tools.</li>
|
<li><strong>Account information:</strong> username and authentication data for authorized users of internal tools.</li>
|
||||||
<li><strong>Usage and analytics data:</strong> if you accept analytics in our cookie banner, we collect information about how you interact with the site, as described in Section 4.2.</li>
|
<li><strong>Usage and analytics data:</strong> information about how you interact with the site, as described in Section 4.2.</li>
|
||||||
<li><strong>Technical information:</strong> browser type, device type, operating system, language, referring URLs, and similar data collected automatically when you use the site.</li>
|
<li><strong>Technical information:</strong> browser type, device type, operating system, language, referring URLs, and similar data collected automatically when you use the site.</li>
|
||||||
</ul>
|
</ul>
|
||||||
<p>
|
<p>
|
||||||
@@ -63,9 +63,9 @@
|
|||||||
<h3>4.2 Analytics and Usage Tracking</h3>
|
<h3>4.2 Analytics and Usage Tracking</h3>
|
||||||
<p>
|
<p>
|
||||||
We use Tianji, a self-hosted analytics platform, to understand how visitors use our website and internal
|
We use Tianji, a self-hosted analytics platform, to understand how visitors use our website and internal
|
||||||
tools. Tracking is enabled only after you provide consent through our cookie banner.
|
tools. Analytics run when you use the site so we can measure usage and improve the experience.
|
||||||
</p>
|
</p>
|
||||||
<p>When you accept analytics, we may collect information such as:</p>
|
<p>We may collect information such as:</p>
|
||||||
<ul>
|
<ul>
|
||||||
<li>Pages viewed and navigation paths</li>
|
<li>Pages viewed and navigation paths</li>
|
||||||
<li>Approximate geographic location derived from truncated IP addresses</li>
|
<li>Approximate geographic location derived from truncated IP addresses</li>
|
||||||
@@ -76,16 +76,16 @@
|
|||||||
</ul>
|
</ul>
|
||||||
<p>
|
<p>
|
||||||
We do not use Tianji to sell your personal information. Analytics data helps us improve site performance,
|
We do not use Tianji to sell your personal information. Analytics data helps us improve site performance,
|
||||||
content, and product usability. You may decline analytics at any time using the cookie preferences link
|
content, and product usability. We show an analytics notice when you first visit the site. You can review it
|
||||||
in the site footer. If you decline, the analytics script will not load.
|
again at any time using the cookie preferences link in the site footer.
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
We store your consent choice locally in your browser so we can remember your preference on future visits.
|
We store your acknowledgement locally in your browser so we do not show the notice on every visit.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<h3>4.3 Cookies and Local Storage</h3>
|
<h3>4.3 Cookies and Local Storage</h3>
|
||||||
<p>
|
<p>
|
||||||
In addition to analytics consent storage, our site uses essential cookies and session storage required for
|
In addition to analytics notice storage, our site uses essential cookies and session storage required for
|
||||||
authentication, security (including CSRF protection), and basic site functionality. These essential
|
authentication, security (including CSRF protection), and basic site functionality. These essential
|
||||||
technologies are necessary for the site to operate and are not used for marketing analytics.
|
technologies are necessary for the site to operate and are not used for marketing analytics.
|
||||||
</p>
|
</p>
|
||||||
@@ -126,7 +126,7 @@
|
|||||||
<h3>4.7 Your Choices and Rights</h3>
|
<h3>4.7 Your Choices and Rights</h3>
|
||||||
<p>You can:</p>
|
<p>You can:</p>
|
||||||
<ul>
|
<ul>
|
||||||
<li>Accept or decline analytics tracking through our cookie banner or the cookie preferences link in the footer</li>
|
<li>Acknowledge our analytics notice through the banner or review it again using the cookie preferences link in the footer</li>
|
||||||
<li>Contact us to request access to, correction of, or deletion of personal information you have provided, subject to applicable law</li>
|
<li>Contact us to request access to, correction of, or deletion of personal information you have provided, subject to applicable law</li>
|
||||||
<li>Disable non-essential browser storage or cookies through your browser settings, though essential site features may not function properly</li>
|
<li>Disable non-essential browser storage or cookies through your browser settings, though essential site features may not function properly</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|||||||
@@ -115,16 +115,20 @@ class ContactViewTests(TestCase):
|
|||||||
|
|
||||||
@override_settings(DEBUG=True, TIANJI_ENABLED=True)
|
@override_settings(DEBUG=True, TIANJI_ENABLED=True)
|
||||||
class TianjiTrackingTests(TestCase):
|
class TianjiTrackingTests(TestCase):
|
||||||
def test_homepage_includes_consent_banner_when_enabled(self):
|
def test_homepage_includes_analytics_notice_when_enabled(self):
|
||||||
response = self.client.get(reverse("public_index"))
|
response = self.client.get(reverse("public_index"))
|
||||||
|
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
self.assertContains(response, "cookie-consent-banner")
|
self.assertContains(response, "cookie-consent-banner")
|
||||||
|
self.assertContains(response, "cookie-consent-acknowledge")
|
||||||
|
self.assertContains(response, "Acknowledge")
|
||||||
|
self.assertNotContains(response, "cookie-consent-decline")
|
||||||
|
self.assertNotContains(response, "Accept Analytics")
|
||||||
self.assertContains(response, "tianji-config")
|
self.assertContains(response, "tianji-config")
|
||||||
self.assertContains(response, "tianji-consent.js")
|
self.assertContains(response, "tianji-consent.js")
|
||||||
|
|
||||||
@override_settings(TIANJI_ENABLED=False)
|
@override_settings(TIANJI_ENABLED=False)
|
||||||
def test_homepage_omits_consent_banner_when_disabled(self):
|
def test_homepage_omits_analytics_notice_when_disabled(self):
|
||||||
response = self.client.get(reverse("public_index"))
|
response = self.client.get(reverse("public_index"))
|
||||||
|
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|||||||
Reference in New Issue
Block a user