django-cookie-consent 0.9.0__py3-none-any.whl → 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- cookie_consent/__init__.py +1 -1
- cookie_consent/admin.py +5 -4
- cookie_consent/cache.py +13 -7
- cookie_consent/conf.py +20 -14
- cookie_consent/forms.py +50 -0
- cookie_consent/middleware.py +7 -3
- cookie_consent/models.py +24 -14
- cookie_consent/processor.py +77 -0
- cookie_consent/py.typed +0 -0
- cookie_consent/static/cookie_consent/cookiebar.module.js +8 -3
- cookie_consent/static/cookie_consent/cookiebar.module.js.map +2 -2
- cookie_consent/templates/cookie_consent/_cookie_group.html +4 -2
- cookie_consent/templatetags/cookie_consent_tags.py +25 -126
- cookie_consent/urls.py +3 -24
- cookie_consent/util.py +42 -113
- cookie_consent/views.py +45 -39
- {django_cookie_consent-0.9.0.dist-info → django_cookie_consent-1.0.0.dist-info}/METADATA +7 -6
- django_cookie_consent-1.0.0.dist-info/RECORD +31 -0
- {django_cookie_consent-0.9.0.dist-info → django_cookie_consent-1.0.0.dist-info}/WHEEL +1 -1
- cookie_consent/static/cookie_consent/cookiebar.js +0 -67
- django_cookie_consent-0.9.0.dist-info/RECORD +0 -29
- {django_cookie_consent-0.9.0.dist-info → django_cookie_consent-1.0.0.dist-info}/licenses/LICENSE +0 -0
- {django_cookie_consent-0.9.0.dist-info → django_cookie_consent-1.0.0.dist-info}/top_level.txt +0 -0
cookie_consent/__init__.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "0.
|
|
1
|
+
__version__ = "1.0.0"
|
cookie_consent/admin.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from django.contrib import admin
|
|
2
2
|
from django.db.models import Count
|
|
3
|
+
from django.http.request import HttpRequest
|
|
3
4
|
from django.templatetags.l10n import localize
|
|
4
5
|
from django.templatetags.static import static
|
|
5
6
|
from django.utils.html import format_html
|
|
@@ -9,6 +10,7 @@ from .conf import settings
|
|
|
9
10
|
from .models import Cookie, CookieGroup, LogItem
|
|
10
11
|
|
|
11
12
|
|
|
13
|
+
@admin.register(Cookie)
|
|
12
14
|
class CookieAdmin(admin.ModelAdmin):
|
|
13
15
|
list_display = ("varname", "name", "cookiegroup", "path", "domain", "get_version")
|
|
14
16
|
search_fields = ("name", "domain", "cookiegroup__varname", "cookiegroup__name")
|
|
@@ -16,6 +18,7 @@ class CookieAdmin(admin.ModelAdmin):
|
|
|
16
18
|
list_filter = ("cookiegroup",)
|
|
17
19
|
|
|
18
20
|
|
|
21
|
+
@admin.register(CookieGroup)
|
|
19
22
|
class CookieGroupAdmin(admin.ModelAdmin):
|
|
20
23
|
list_display = (
|
|
21
24
|
"varname",
|
|
@@ -34,12 +37,12 @@ class CookieGroupAdmin(admin.ModelAdmin):
|
|
|
34
37
|
"is_deletable",
|
|
35
38
|
)
|
|
36
39
|
|
|
37
|
-
def get_queryset(self, request):
|
|
40
|
+
def get_queryset(self, request: HttpRequest):
|
|
38
41
|
qs = super().get_queryset(request)
|
|
39
42
|
return qs.annotate(num_cookies=Count("cookie"))
|
|
40
43
|
|
|
41
44
|
@admin.display(ordering="num_cookies", description=_("# cookies"))
|
|
42
|
-
def num_cookies(self, obj):
|
|
45
|
+
def num_cookies(self, obj: CookieGroup):
|
|
43
46
|
if (count := obj.num_cookies) > 0:
|
|
44
47
|
return localize(count)
|
|
45
48
|
|
|
@@ -58,7 +61,5 @@ class LogItemAdmin(admin.ModelAdmin):
|
|
|
58
61
|
date_hierarchy = "created"
|
|
59
62
|
|
|
60
63
|
|
|
61
|
-
admin.site.register(Cookie, CookieAdmin)
|
|
62
|
-
admin.site.register(CookieGroup, CookieGroupAdmin)
|
|
63
64
|
if settings.COOKIE_CONSENT_LOG_ENABLED:
|
|
64
65
|
admin.site.register(LogItem, LogItemAdmin)
|
cookie_consent/cache.py
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
from collections.abc import Mapping
|
|
2
|
+
|
|
1
3
|
from django.core.cache import caches
|
|
2
4
|
|
|
3
5
|
from .conf import settings
|
|
4
|
-
from .models import CookieGroup
|
|
6
|
+
from .models import Cookie, CookieGroup
|
|
5
7
|
|
|
6
8
|
CACHE_KEY = "cookie_consent_cache"
|
|
7
9
|
CACHE_TIMEOUT = 60 * 60 # 60 minutes
|
|
@@ -18,17 +20,17 @@ def _get_cache():
|
|
|
18
20
|
return caches[settings.COOKIE_CONSENT_CACHE_BACKEND]
|
|
19
21
|
|
|
20
22
|
|
|
21
|
-
def delete_cache():
|
|
23
|
+
def delete_cache() -> None:
|
|
22
24
|
cache = _get_cache()
|
|
23
25
|
cache.delete(CACHE_KEY)
|
|
24
26
|
|
|
25
27
|
|
|
26
|
-
def _get_cookie_groups_from_db():
|
|
28
|
+
def _get_cookie_groups_from_db() -> Mapping[str, CookieGroup]:
|
|
27
29
|
qs = CookieGroup.objects.filter(is_required=False).prefetch_related("cookie_set")
|
|
28
30
|
return qs.in_bulk(field_name="varname")
|
|
29
31
|
|
|
30
32
|
|
|
31
|
-
def all_cookie_groups():
|
|
33
|
+
def all_cookie_groups() -> Mapping[str, CookieGroup]:
|
|
32
34
|
"""
|
|
33
35
|
Get all cookie groups that are optional.
|
|
34
36
|
|
|
@@ -36,16 +38,20 @@ def all_cookie_groups():
|
|
|
36
38
|
cache miss.
|
|
37
39
|
"""
|
|
38
40
|
cache = _get_cache()
|
|
39
|
-
|
|
41
|
+
result = cache.get_or_set(
|
|
40
42
|
CACHE_KEY, _get_cookie_groups_from_db, timeout=CACHE_TIMEOUT
|
|
41
43
|
)
|
|
44
|
+
assert result is not None
|
|
45
|
+
return result
|
|
42
46
|
|
|
43
47
|
|
|
44
|
-
def get_cookie_group(varname):
|
|
48
|
+
def get_cookie_group(varname: str) -> CookieGroup | None:
|
|
45
49
|
return all_cookie_groups().get(varname)
|
|
46
50
|
|
|
47
51
|
|
|
48
|
-
def get_cookie(cookie_group, name, domain):
|
|
52
|
+
def get_cookie(cookie_group: CookieGroup, name: str, domain: str) -> Cookie | None:
|
|
53
|
+
# loop over cookie set relation instead of doing a lookup query, as this should
|
|
54
|
+
# come from the cache and avoid hitting the database
|
|
49
55
|
for cookie in cookie_group.cookie_set.all():
|
|
50
56
|
if cookie.name == name and cookie.domain == domain:
|
|
51
57
|
return cookie
|
cookie_consent/conf.py
CHANGED
|
@@ -1,29 +1,35 @@
|
|
|
1
|
-
from
|
|
1
|
+
from typing import Literal
|
|
2
2
|
|
|
3
|
-
from
|
|
3
|
+
from django.conf import settings
|
|
4
4
|
from django.urls import reverse_lazy
|
|
5
|
+
from django.utils.functional import Promise
|
|
6
|
+
|
|
7
|
+
from appconf import AppConf
|
|
5
8
|
|
|
6
9
|
__all__ = ["settings"]
|
|
7
10
|
|
|
8
11
|
|
|
9
12
|
class CookieConsentConf(AppConf):
|
|
10
13
|
# django-cookie-consent cookie settings that store the configuration
|
|
11
|
-
NAME = "cookie_consent"
|
|
14
|
+
NAME: str = "cookie_consent"
|
|
12
15
|
# TODO: rename to AGE for parity with django settings
|
|
13
|
-
MAX_AGE = 60 * 60 * 24 * 365 * 1 # 1 year,
|
|
14
|
-
DOMAIN = None
|
|
15
|
-
SECURE = False
|
|
16
|
-
HTTPONLY = True
|
|
17
|
-
SAMESITE = "Lax"
|
|
16
|
+
MAX_AGE: int = 60 * 60 * 24 * 365 * 1 # 1 year,
|
|
17
|
+
DOMAIN: str | None = None
|
|
18
|
+
SECURE: bool = False
|
|
19
|
+
HTTPONLY: bool = True
|
|
20
|
+
SAMESITE: Literal["Strict", "Lax", "None", False] = "Lax"
|
|
18
21
|
|
|
19
|
-
DECLINE = "-1"
|
|
22
|
+
DECLINE: str = "-1"
|
|
20
23
|
|
|
21
|
-
ENABLED = True
|
|
24
|
+
ENABLED: bool = True
|
|
22
25
|
|
|
23
|
-
OPT_OUT = False
|
|
26
|
+
OPT_OUT: bool = False
|
|
24
27
|
|
|
25
|
-
CACHE_BACKEND = "default"
|
|
28
|
+
CACHE_BACKEND: str = "default"
|
|
26
29
|
|
|
27
|
-
LOG_ENABLED = True
|
|
30
|
+
LOG_ENABLED: bool = True
|
|
31
|
+
"""
|
|
32
|
+
DeprecationWarning: in future versions the default may switch to log disabled.
|
|
33
|
+
"""
|
|
28
34
|
|
|
29
|
-
SUCCESS_URL = reverse_lazy("cookie_consent_cookie_group_list")
|
|
35
|
+
SUCCESS_URL: str | Promise = reverse_lazy("cookie_consent_cookie_group_list")
|
cookie_consent/forms.py
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
from collections.abc import Collection, Iterator
|
|
2
|
+
|
|
3
|
+
from django import forms
|
|
4
|
+
from django.utils.translation import gettext_lazy as _
|
|
5
|
+
|
|
6
|
+
from .cache import all_cookie_groups
|
|
7
|
+
from .models import CookieGroup
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def iter_cookie_group_choices() -> Iterator[tuple[str, str]]:
|
|
11
|
+
"""
|
|
12
|
+
Use the cached cookie group instances to get a list of choices.
|
|
13
|
+
"""
|
|
14
|
+
for varname, cookie_group in all_cookie_groups().items():
|
|
15
|
+
yield varname, cookie_group.name
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class CookieGroupsChoiceField(forms.TypedMultipleChoiceField):
|
|
19
|
+
def __init__(self, **kwargs):
|
|
20
|
+
kwargs["coerce"] = self._coerce_choice
|
|
21
|
+
kwargs["choices"] = iter_cookie_group_choices
|
|
22
|
+
super().__init__(**kwargs)
|
|
23
|
+
|
|
24
|
+
def _coerce_choice(self, varname: str) -> CookieGroup:
|
|
25
|
+
all_groups = all_cookie_groups()
|
|
26
|
+
return all_groups[varname]
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class ProcessCookiesForm(forms.Form):
|
|
30
|
+
all_groups = forms.BooleanField(
|
|
31
|
+
label=_("Apply to all cookie groups"),
|
|
32
|
+
required=False,
|
|
33
|
+
)
|
|
34
|
+
cookie_groups = CookieGroupsChoiceField(
|
|
35
|
+
label=_("Cookie group varnames"),
|
|
36
|
+
choices=iter_cookie_group_choices,
|
|
37
|
+
required=False,
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
def get_cookie_groups(self) -> Collection[CookieGroup]:
|
|
41
|
+
"""
|
|
42
|
+
Build the collection of specified cookies.
|
|
43
|
+
"""
|
|
44
|
+
match self.cleaned_data:
|
|
45
|
+
case {"all_groups": True}:
|
|
46
|
+
return all_cookie_groups().values()
|
|
47
|
+
case {"cookie_groups": [*groups]}:
|
|
48
|
+
return groups
|
|
49
|
+
case _:
|
|
50
|
+
return []
|
cookie_consent/middleware.py
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
from collections.abc import Callable
|
|
2
|
+
|
|
3
|
+
from django.http import HttpRequest, HttpResponseBase
|
|
4
|
+
|
|
1
5
|
from .cache import all_cookie_groups
|
|
2
6
|
from .conf import settings
|
|
3
7
|
from .util import get_cookie_dict_from_request, is_cookie_consent_enabled
|
|
@@ -28,16 +32,16 @@ class CleanCookiesMiddleware:
|
|
|
28
32
|
Note that this only applies if COOKIE_CONSENT_OPT_OUT is not set.
|
|
29
33
|
"""
|
|
30
34
|
|
|
31
|
-
def __init__(self, get_response):
|
|
35
|
+
def __init__(self, get_response: Callable[[HttpRequest], HttpResponseBase]):
|
|
32
36
|
self.get_response = get_response
|
|
33
37
|
|
|
34
|
-
def __call__(self, request):
|
|
38
|
+
def __call__(self, request: HttpRequest):
|
|
35
39
|
response = self.get_response(request)
|
|
36
40
|
if is_cookie_consent_enabled(request):
|
|
37
41
|
self.process_response(request, response)
|
|
38
42
|
return response
|
|
39
43
|
|
|
40
|
-
def process_response(self, request, response):
|
|
44
|
+
def process_response(self, request: HttpRequest, response: HttpResponseBase):
|
|
41
45
|
cookie_dic = get_cookie_dict_from_request(request)
|
|
42
46
|
|
|
43
47
|
cookies_to_delete = []
|
cookie_consent/models.py
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
import re
|
|
2
|
-
from
|
|
4
|
+
from collections.abc import Callable
|
|
5
|
+
from typing import ClassVar, ParamSpec, TypedDict, TypeVar
|
|
3
6
|
|
|
4
7
|
from django.core.validators import RegexValidator
|
|
5
8
|
from django.db import models
|
|
@@ -15,9 +18,12 @@ validate_cookie_name = RegexValidator(
|
|
|
15
18
|
"invalid",
|
|
16
19
|
)
|
|
17
20
|
|
|
21
|
+
P = ParamSpec("P")
|
|
22
|
+
T = TypeVar("T")
|
|
23
|
+
|
|
18
24
|
|
|
19
|
-
def clear_cache_after(func):
|
|
20
|
-
def wrapper(*args, **kwargs):
|
|
25
|
+
def clear_cache_after(func: Callable[P, T]) -> Callable[P, T]:
|
|
26
|
+
def wrapper(*args: P.args, **kwargs: P.kwargs):
|
|
21
27
|
from .cache import delete_cache
|
|
22
28
|
|
|
23
29
|
return_value = func(*args, **kwargs)
|
|
@@ -32,9 +38,8 @@ class CookieGroupDict(TypedDict):
|
|
|
32
38
|
name: str
|
|
33
39
|
description: str
|
|
34
40
|
is_required: bool
|
|
35
|
-
#
|
|
36
|
-
#
|
|
37
|
-
# version: str
|
|
41
|
+
# The version is deliberately not included because it requires page/view cache
|
|
42
|
+
# busting if a new cookie gets added to the group, which we don't control.
|
|
38
43
|
|
|
39
44
|
|
|
40
45
|
class BaseQueryset(models.query.QuerySet):
|
|
@@ -48,7 +53,7 @@ class BaseQueryset(models.query.QuerySet):
|
|
|
48
53
|
|
|
49
54
|
|
|
50
55
|
class CookieGroupManager(models.Manager.from_queryset(BaseQueryset)):
|
|
51
|
-
def get_by_natural_key(self, varname):
|
|
56
|
+
def get_by_natural_key(self, varname: str) -> CookieGroup:
|
|
52
57
|
return self.get(varname=varname)
|
|
53
58
|
|
|
54
59
|
|
|
@@ -74,7 +79,8 @@ class CookieGroup(models.Model):
|
|
|
74
79
|
ordering = models.IntegerField(_("Ordering"), default=0)
|
|
75
80
|
created = models.DateTimeField(_("Created"), auto_now_add=True, blank=True)
|
|
76
81
|
|
|
77
|
-
objects = CookieGroupManager()
|
|
82
|
+
objects: ClassVar[CookieGroupManager] = CookieGroupManager() # pyright: ignore[reportIncompatibleVariableOverride]
|
|
83
|
+
cookie_set: ClassVar[CookieManager]
|
|
78
84
|
|
|
79
85
|
class Meta:
|
|
80
86
|
verbose_name = _("Cookie Group")
|
|
@@ -92,11 +98,15 @@ class CookieGroup(models.Model):
|
|
|
92
98
|
def delete(self, *args, **kwargs):
|
|
93
99
|
return super().delete(*args, **kwargs)
|
|
94
100
|
|
|
95
|
-
def natural_key(self):
|
|
101
|
+
def natural_key(self) -> tuple[str]:
|
|
96
102
|
return (self.varname,)
|
|
97
103
|
|
|
98
104
|
def get_version(self) -> str:
|
|
99
105
|
try:
|
|
106
|
+
# this relies on the cookie set being ordered by most-recently created
|
|
107
|
+
# first.
|
|
108
|
+
# Note that we don't use `.first()` as that's a new query and bypasses
|
|
109
|
+
# the cache.
|
|
100
110
|
return str(self.cookie_set.all()[0].get_version())
|
|
101
111
|
except IndexError:
|
|
102
112
|
return ""
|
|
@@ -112,7 +122,7 @@ class CookieGroup(models.Model):
|
|
|
112
122
|
|
|
113
123
|
|
|
114
124
|
class CookieManager(models.Manager.from_queryset(BaseQueryset)):
|
|
115
|
-
def get_by_natural_key(self, name, domain, cookiegroup):
|
|
125
|
+
def get_by_natural_key(self, name: str, domain: str, cookiegroup: str) -> Cookie:
|
|
116
126
|
group = CookieGroup.objects.get_by_natural_key(cookiegroup)
|
|
117
127
|
return self.get(cookiegroup=group, name=name, domain=domain)
|
|
118
128
|
|
|
@@ -153,17 +163,17 @@ class Cookie(models.Model):
|
|
|
153
163
|
def delete(self, *args, **kwargs):
|
|
154
164
|
return super().delete(*args, **kwargs)
|
|
155
165
|
|
|
156
|
-
def natural_key(self):
|
|
166
|
+
def natural_key(self) -> tuple[str, str, str]:
|
|
157
167
|
return (self.name, self.domain) + self.cookiegroup.natural_key()
|
|
158
168
|
|
|
159
|
-
natural_key.dependencies = ["cookie_consent.cookiegroup"]
|
|
169
|
+
natural_key.dependencies = ["cookie_consent.cookiegroup"] # pyright: ignore[reportFunctionMemberAccess]
|
|
160
170
|
|
|
161
171
|
@property
|
|
162
|
-
def varname(self):
|
|
172
|
+
def varname(self) -> str:
|
|
163
173
|
group_varname = self.cookiegroup.varname
|
|
164
174
|
return f"{group_varname}={self.name}:{self.domain}"
|
|
165
175
|
|
|
166
|
-
def get_version(self):
|
|
176
|
+
def get_version(self) -> str:
|
|
167
177
|
return self.created.isoformat()
|
|
168
178
|
|
|
169
179
|
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
from collections.abc import Collection
|
|
2
|
+
from typing import Literal
|
|
3
|
+
|
|
4
|
+
from django.http import HttpRequest, HttpResponseBase
|
|
5
|
+
|
|
6
|
+
from .conf import settings
|
|
7
|
+
from .models import ACTION_ACCEPTED, ACTION_DECLINED, CookieGroup, LogItem
|
|
8
|
+
from .util import get_cookie_dict_from_request, set_cookie_dict_to_response
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class CookiesProcessor:
|
|
12
|
+
"""
|
|
13
|
+
Process the accept/decline logic for cookie groups.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
def __init__(self, request: HttpRequest, response: HttpResponseBase):
|
|
17
|
+
self.request = request
|
|
18
|
+
self.response = response
|
|
19
|
+
|
|
20
|
+
def process(
|
|
21
|
+
self,
|
|
22
|
+
cookie_groups: Collection[CookieGroup],
|
|
23
|
+
action: Literal["accept", "decline"],
|
|
24
|
+
) -> None:
|
|
25
|
+
"""
|
|
26
|
+
Apply ``action`` to the specified ``cookie_groups``.
|
|
27
|
+
|
|
28
|
+
Mutates the response by updating the cookie tracking the cookie group status. If
|
|
29
|
+
there are no cookie groups provided, nothing happens.
|
|
30
|
+
"""
|
|
31
|
+
if not cookie_groups:
|
|
32
|
+
return
|
|
33
|
+
|
|
34
|
+
cookie_dic = get_cookie_dict_from_request(self.request)
|
|
35
|
+
|
|
36
|
+
match action:
|
|
37
|
+
case "accept":
|
|
38
|
+
for cookie_group in cookie_groups:
|
|
39
|
+
cookie_dic[cookie_group.varname] = cookie_group.get_version()
|
|
40
|
+
case "decline":
|
|
41
|
+
self._delete_cookies(cookie_groups)
|
|
42
|
+
for cookie_group in cookie_groups:
|
|
43
|
+
cookie_dic[cookie_group.varname] = settings.COOKIE_CONSENT_DECLINE
|
|
44
|
+
|
|
45
|
+
self._log_action(cookie_groups, action)
|
|
46
|
+
set_cookie_dict_to_response(self.response, cookie_dic)
|
|
47
|
+
|
|
48
|
+
def _log_action(
|
|
49
|
+
self,
|
|
50
|
+
cookie_groups: Collection[CookieGroup],
|
|
51
|
+
action: Literal["accept", "decline"],
|
|
52
|
+
) -> None:
|
|
53
|
+
if not settings.COOKIE_CONSENT_LOG_ENABLED:
|
|
54
|
+
return
|
|
55
|
+
# TODO: replace with stdlib logging call/helper instead of creating DB records
|
|
56
|
+
# directly.
|
|
57
|
+
|
|
58
|
+
action_map: dict[Literal["accept", "decline"], int] = {
|
|
59
|
+
"accept": ACTION_ACCEPTED,
|
|
60
|
+
"decline": ACTION_DECLINED,
|
|
61
|
+
}
|
|
62
|
+
log_items: list[LogItem] = [
|
|
63
|
+
LogItem(
|
|
64
|
+
action=action_map[action],
|
|
65
|
+
cookiegroup=cookie_group,
|
|
66
|
+
version=cookie_group.get_version(),
|
|
67
|
+
)
|
|
68
|
+
for cookie_group in cookie_groups
|
|
69
|
+
]
|
|
70
|
+
LogItem.objects.bulk_create(log_items)
|
|
71
|
+
|
|
72
|
+
def _delete_cookies(self, cookie_groups: Collection[CookieGroup]) -> None:
|
|
73
|
+
for cookie_group in cookie_groups:
|
|
74
|
+
if not cookie_group.is_deletable:
|
|
75
|
+
continue
|
|
76
|
+
for cookie in cookie_group.cookie_set.all():
|
|
77
|
+
self.response.delete_cookie(cookie.name, cookie.path, cookie.domain)
|
cookie_consent/py.typed
ADDED
|
File without changes
|
|
@@ -25,14 +25,19 @@ var FetchClient = class {
|
|
|
25
25
|
}
|
|
26
26
|
return this.cookieStatus;
|
|
27
27
|
}
|
|
28
|
-
async saveCookiesStatusBackend(urlProperty) {
|
|
28
|
+
async saveCookiesStatusBackend(urlProperty, cookieGroups) {
|
|
29
29
|
const cookieStatus = await this.getCookieStatus();
|
|
30
30
|
const url = cookieStatus[urlProperty];
|
|
31
31
|
if (!url) {
|
|
32
32
|
throw new Error(`Missing url for ${urlProperty} - was the cookie status not loaded properly?`);
|
|
33
33
|
}
|
|
34
|
+
const formData = new FormData();
|
|
35
|
+
for (const group of cookieGroups) {
|
|
36
|
+
formData.append("cookie_groups", group.varname);
|
|
37
|
+
}
|
|
34
38
|
await window.fetch(url, {
|
|
35
39
|
method: "POST",
|
|
40
|
+
body: formData,
|
|
36
41
|
credentials: "same-origin",
|
|
37
42
|
headers: {
|
|
38
43
|
...DEFAULT_FETCH_HEADERS,
|
|
@@ -71,7 +76,7 @@ var registerEvents = ({
|
|
|
71
76
|
event.preventDefault();
|
|
72
77
|
const acceptedGroups = filterCookieGroups(cookieGroups, accepted.concat(undecided));
|
|
73
78
|
onAccept == null ? void 0 : onAccept(acceptedGroups, event);
|
|
74
|
-
client.saveCookiesStatusBackend("acceptUrl");
|
|
79
|
+
client.saveCookiesStatusBackend("acceptUrl", acceptedGroups);
|
|
75
80
|
cookieBarNode.parentNode.removeChild(cookieBarNode);
|
|
76
81
|
});
|
|
77
82
|
}
|
|
@@ -81,7 +86,7 @@ var registerEvents = ({
|
|
|
81
86
|
event.preventDefault();
|
|
82
87
|
const declinedGroups = filterCookieGroups(cookieGroups, declined.concat(undecided));
|
|
83
88
|
onDecline == null ? void 0 : onDecline(declinedGroups, event);
|
|
84
|
-
client.saveCookiesStatusBackend("declineUrl");
|
|
89
|
+
client.saveCookiesStatusBackend("declineUrl", declinedGroups);
|
|
85
90
|
cookieBarNode.parentNode.removeChild(cookieBarNode);
|
|
86
91
|
});
|
|
87
92
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../js/src/cookiebar.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * Cookiebar functionality, as a TS/JS module.\n *\n * About modules: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules\n *\n * The code is organized here in a way to make the templates work with Django's page\n * cache. This means that anything user-specific (so different django session and even\n * cookie consent cookies) cannot be baked into the templates, as that breaks caches.\n *\n * The cookie bar operates on the following principles:\n *\n * - The developer using the library includes the desired template in their django\n * templates, using the HTML <template> element. This contains the content for the\n * cookie bar.\n * - The developer is responsible for loading some Javascript that loads this script.\n * - The main export of this script needs to be called (showCookieBar), with the\n * appropriate options.\n * - The options include the backend URLs where the retrieve data, which selectors/DOM\n * nodes to use for various functionality and the hooks to tap into the accept/decline\n * life-cycle.\n * - When a user accepts or declines (all) cookies, the call to the backend is made via\n * a fetch request, bypassing any page caches and preventing full-page reloads.\n */\n\n/**\n * A serialized cookie group.\n *\n * See the backend model method `CookieGroup.as_json()`.\n */\nexport interface CookieGroup {\n varname: string;\n name: string;\n description: string;\n is_required: boolean;\n}\n\nexport interface Options {\n statusUrl: string;\n // TODO: also accept element rather than selector?\n templateSelector: string;\n /**\n * DOM selector to the (script) tag holding the JSON-serialized cookie groups.\n *\n * This is typically rendered in a template with a template tag, e.g.\n *\n * ```django\n * {% all_cookie_groups 'cookie-consent__cookie-groups' %}\n * ```\n *\n * resulting in the selector: `'#cookie-consent__cookie-groups'`.\n */\n cookieGroupsSelector: string;\n acceptSelector: string;\n declineSelector: string;\n /**\n * Either a string (selector), DOMNode or null.\n *\n * If null, the bar is appended to the body. If provided, the node is used or looked\n * up.\n */\n insertBefore: string | HTMLElement | null;\n /**\n * Optional callback for when the cookie bar is being shown.\n *\n * You can use this to add a CSS class name to the body, for example.\n */\n onShow?: () => void;\n /**\n * Optional callback called when cookies are accepted.\n */\n onAccept?: (acceptedGroups: CookieGroup[], event?: MouseEvent) => void;\n /**\n * Optional callback called when cookies are accepted.\n */\n onDecline?: (declinedGroups: CookieGroup[], event?: MouseEvent) => void;\n /**\n * Name of the header to use for the CSRF token.\n *\n * If needed, this can be read/set via `settings.CSRF_HEADER_NAME` in the backend.\n */\n csrfHeaderName: string;\n};\n\nexport interface CookieStatus {\n csrftoken: string;\n /**\n * Backend endpoint to POST to to accept the cookie groups.\n */\n acceptUrl: string;\n /**\n * Backend endpoint to POST to to decline the cookie groups.\n */\n declineUrl: string;\n /**\n * Array of accepted cookie group varnames.\n */\n acceptedCookieGroups: string[];\n /**\n * Array of declined cookie group varnames.\n */\n declinedCookieGroups: string[];\n /**\n * Array of undecided cookie group varnames.\n */\n notAcceptedOrDeclinedCookieGroups: string[];\n}\n\nconst DEFAULT_FETCH_HEADERS: Record<string, string> = {\n 'X-Cookie-Consent-Fetch': '1'\n};\n\n/**\n * A simple wrapper around window.fetch that understands the django-cookie-consent\n * backend endpoints.\n *\n * @private - while exported, use at your own risk. This class is not part of the\n * public API covered by SemVer.\n */\nexport class FetchClient {\n protected statusUrl: string;\n protected csrfHeaderName: string;\n protected cookieStatus: CookieStatus | null;\n\n constructor(statusUrl: string, csrfHeaderName: string) {\n this.statusUrl = statusUrl;\n this.csrfHeaderName = csrfHeaderName;\n this.cookieStatus = null;\n }\n\n async getCookieStatus(): Promise<CookieStatus> {\n if (this.cookieStatus === null) {\n const response = await window.fetch(\n this.statusUrl,\n {\n method: 'GET',\n credentials: 'same-origin',\n headers: DEFAULT_FETCH_HEADERS,\n }\n );\n this.cookieStatus = await response.json();\n }\n\n // type checker sanity check\n if (this.cookieStatus === null) {\n throw new Error('Unexpectedly received null cookie status');\n }\n return this.cookieStatus;\n };\n\n async saveCookiesStatusBackend (urlProperty: 'acceptUrl' | 'declineUrl') {\n const cookieStatus = await this.getCookieStatus();\n const url = cookieStatus[urlProperty];\n if (!url) {\n throw new Error(`Missing url for ${urlProperty} - was the cookie status not loaded properly?`);\n }\n\n await window.fetch(url, {\n method: 'POST',\n credentials: 'same-origin',\n headers: {\n ...DEFAULT_FETCH_HEADERS,\n [this.csrfHeaderName]: cookieStatus.csrftoken\n }\n });\n }\n}\n\n/**\n * Read the JSON script node contents and parse the content as JSON.\n *\n * The result is the list of available/configured cookie groups.\n * Use the status URL to get the accepted/declined status for an individual user.\n */\nexport const loadCookieGroups = (selector: string): CookieGroup[] => {\n const node = document.querySelector<HTMLScriptElement>(selector);\n if (!node) {\n throw new Error(`No cookie groups (script) tag found, using selector: '${selector}'`);\n }\n return JSON.parse(node.innerText);\n};\n\nconst doInsertBefore = (beforeNode: HTMLElement, newNode: Node): void => {\n const parent = beforeNode.parentNode;\n if (parent === null) throw new Error('Reference node doesn\\'t have a parent.');\n parent.insertBefore(newNode, beforeNode);\n}\n\ntype RegisterEventsOptions = Pick<\n Options,\n 'acceptSelector' | 'onAccept' | 'declineSelector' | 'onDecline'\n> & Pick<\n CookieStatus,\n 'acceptedCookieGroups' | 'declinedCookieGroups' | 'notAcceptedOrDeclinedCookieGroups'\n> & {\n client: FetchClient,\n cookieBarNode: Element;\n cookieGroups: CookieGroup[];\n}\n\n/**\n * Register the accept/decline event handlers.\n *\n * Note that we can't just set the decline or accept cookie purely client-side, as the\n * cookie possibly has the httpOnly flag set.\n */\nconst registerEvents = ({\n client,\n cookieBarNode,\n cookieGroups,\n acceptSelector,\n onAccept,\n declineSelector,\n onDecline,\n acceptedCookieGroups: accepted,\n declinedCookieGroups: declined,\n notAcceptedOrDeclinedCookieGroups: undecided,\n}: RegisterEventsOptions): void => {\n\n const acceptNode = cookieBarNode.querySelector<HTMLElement>(acceptSelector);\n if (acceptNode) {\n acceptNode.addEventListener('click', event => {\n event.preventDefault();\n const acceptedGroups = filterCookieGroups(cookieGroups, accepted.concat(undecided));\n onAccept?.(acceptedGroups, event);\n // trigger async action, but don't wait for completion\n client.saveCookiesStatusBackend('acceptUrl');\n cookieBarNode.parentNode!.removeChild(cookieBarNode);\n });\n }\n\n const declineNode = cookieBarNode.querySelector<HTMLElement>(declineSelector);\n if (declineNode) {\n declineNode.addEventListener('click', event => {\n event.preventDefault();\n const declinedGroups = filterCookieGroups(cookieGroups, declined.concat(undecided));\n onDecline?.(declinedGroups, event);\n // trigger async action, but don't wait for completion\n client.saveCookiesStatusBackend('declineUrl');\n cookieBarNode.parentNode!.removeChild(cookieBarNode);\n });\n }\n};\n\n/**\n * Filter the cookie groups down to a subset of specified varnames.\n */\nconst filterCookieGroups = (cookieGroups: CookieGroup[], varNames: string[]) => {\n return cookieGroups.filter(group => varNames.includes(group.varname));\n};\n\n// See https://github.com/microsoft/TypeScript/issues/283\nfunction cloneNode<T extends Node>(node: T) {\n return <T>node.cloneNode(true);\n}\n\nexport const showCookieBar = async (options: Partial<Options> = {}): Promise<void> => {\n const {\n templateSelector = '#cookie-consent__cookie-bar',\n cookieGroupsSelector = '#cookie-consent__cookie-groups',\n acceptSelector = '.cookie-consent__accept',\n declineSelector = '.cookie-consent__decline',\n insertBefore = null,\n onShow,\n onAccept,\n onDecline,\n statusUrl = '',\n csrfHeaderName = 'X-CSRFToken', // Django's default, can be overridden with settings.CSRF_HEADER_NAME\n } = options;\n\n const cookieGroups = loadCookieGroups(cookieGroupsSelector);\n\n // no cookie groups -> abort, nothing to do\n if (!cookieGroups.length) return;\n\n const templateNode = document.querySelector<HTMLTemplateElement>(templateSelector);\n if (!templateNode) {\n throw new Error(`No (template) element found for selector '${templateSelector}'.`)\n }\n\n // insert before a given node, if specified, or append to the body as default behaviour\n const doInsert = insertBefore === null\n ? (cookieBarNode: Node) => document.querySelector('body')!.appendChild(cookieBarNode)\n : typeof insertBefore === 'string'\n ? (cookieBarNode: Node) => {\n const referenceNode = document.querySelector<HTMLElement>(insertBefore);\n if (referenceNode === null) throw new Error(`No element found for selector '${insertBefore}'.`)\n doInsertBefore(referenceNode, cookieBarNode);\n }\n : (cookieBarNode: Node) => doInsertBefore(insertBefore, cookieBarNode)\n ;\n\n if (!statusUrl) throw new Error('Missing status URL option, did you forget to pass the `statusUrl` option?');\n\n const client = new FetchClient(statusUrl, csrfHeaderName);\n const cookieStatus = await client.getCookieStatus();\n\n // calculate the cookie groups to invoke the callbacks. We deliberately fire those\n // without awaiting so that our cookie bar is shown/hidden as soon as possible.\n const {\n acceptedCookieGroups,\n declinedCookieGroups,\n notAcceptedOrDeclinedCookieGroups\n } = cookieStatus;\n\n const acceptedGroups = filterCookieGroups(cookieGroups, acceptedCookieGroups);\n if (acceptedGroups.length) onAccept?.(acceptedGroups);\n const declinedGroups = filterCookieGroups(cookieGroups, declinedCookieGroups);\n if (declinedGroups.length) onDecline?.(declinedGroups);\n\n // there are no (more) cookie groups to accept, don't show the bar\n if (!notAcceptedOrDeclinedCookieGroups.length) return;\n\n // grab the contents from the template node and add them to the DOM, optionally\n // calling the onShow callback\n const childToClone = templateNode.content.firstElementChild;\n if (childToClone === null) throw new Error('The cookie bar template element may not be empty.');\n const cookieBarNode = cloneNode(childToClone);\n registerEvents({\n client,\n cookieBarNode,\n cookieGroups,\n acceptSelector,\n onAccept,\n declineSelector,\n onDecline,\n acceptedCookieGroups,\n declinedCookieGroups,\n notAcceptedOrDeclinedCookieGroups,\n });\n doInsert(cookieBarNode);\n onShow?.();\n};\n"],
|
|
5
|
-
"mappings": ";AA2GA,IAAM,wBAAgD;AAAA,EACpD,0BAA0B;AAC5B;AASO,IAAM,cAAN,MAAkB;AAAA,EAKvB,YAAY,WAAmB,gBAAwB;AACrD,SAAK,YAAY;AACjB,SAAK,iBAAiB;AACtB,SAAK,eAAe;AAAA,EACtB;AAAA,EAEA,MAAM,kBAAyC;AAC7C,QAAI,KAAK,iBAAiB,MAAM;AAC9B,YAAM,WAAW,MAAM,OAAO;AAAA,QAC5B,KAAK;AAAA,QACL;AAAA,UACE,QAAQ;AAAA,UACR,aAAa;AAAA,UACb,SAAS;AAAA,QACX;AAAA,MACF;AACA,WAAK,eAAe,MAAM,SAAS,KAAK;AAAA,IAC1C;AAGA,QAAI,KAAK,iBAAiB,MAAM;AAC9B,YAAM,IAAI,MAAM,0CAA0C;AAAA,IAC5D;AACA,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,MAAM,
|
|
4
|
+
"sourcesContent": ["/**\n * Cookiebar functionality, as a TS/JS module.\n *\n * About modules: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules\n *\n * The code is organized here in a way to make the templates work with Django's page\n * cache. This means that anything user-specific (so different django session and even\n * cookie consent cookies) cannot be baked into the templates, as that breaks caches.\n *\n * The cookie bar operates on the following principles:\n *\n * - The developer using the library includes the desired template in their django\n * templates, using the HTML <template> element. This contains the content for the\n * cookie bar.\n * - The developer is responsible for loading some Javascript that loads this script.\n * - The main export of this script needs to be called (showCookieBar), with the\n * appropriate options.\n * - The options include the backend URLs where the retrieve data, which selectors/DOM\n * nodes to use for various functionality and the hooks to tap into the accept/decline\n * life-cycle.\n * - When a user accepts or declines (all) cookies, the call to the backend is made via\n * a fetch request, bypassing any page caches and preventing full-page reloads.\n */\n\n/**\n * A serialized cookie group.\n *\n * See the backend model method `CookieGroup.as_json()`.\n */\nexport interface CookieGroup {\n varname: string;\n name: string;\n description: string;\n is_required: boolean;\n}\n\nexport interface Options {\n statusUrl: string;\n // TODO: also accept element rather than selector?\n templateSelector: string;\n /**\n * DOM selector to the (script) tag holding the JSON-serialized cookie groups.\n *\n * This is typically rendered in a template with a template tag, e.g.\n *\n * ```django\n * {% all_cookie_groups 'cookie-consent__cookie-groups' %}\n * ```\n *\n * resulting in the selector: `'#cookie-consent__cookie-groups'`.\n */\n cookieGroupsSelector: string;\n acceptSelector: string;\n declineSelector: string;\n /**\n * Either a string (selector), DOMNode or null.\n *\n * If null, the bar is appended to the body. If provided, the node is used or looked\n * up.\n */\n insertBefore: string | HTMLElement | null;\n /**\n * Optional callback for when the cookie bar is being shown.\n *\n * You can use this to add a CSS class name to the body, for example.\n */\n onShow?: () => void;\n /**\n * Optional callback called when cookies are accepted.\n */\n onAccept?: (acceptedGroups: CookieGroup[], event?: MouseEvent) => void;\n /**\n * Optional callback called when cookies are accepted.\n */\n onDecline?: (declinedGroups: CookieGroup[], event?: MouseEvent) => void;\n /**\n * Name of the header to use for the CSRF token.\n *\n * If needed, this can be read/set via `settings.CSRF_HEADER_NAME` in the backend.\n */\n csrfHeaderName: string;\n};\n\nexport interface CookieStatus {\n csrftoken: string;\n /**\n * Backend endpoint to POST to to accept the cookie groups.\n */\n acceptUrl: string;\n /**\n * Backend endpoint to POST to to decline the cookie groups.\n */\n declineUrl: string;\n /**\n * Array of accepted cookie group varnames.\n */\n acceptedCookieGroups: string[];\n /**\n * Array of declined cookie group varnames.\n */\n declinedCookieGroups: string[];\n /**\n * Array of undecided cookie group varnames.\n */\n notAcceptedOrDeclinedCookieGroups: string[];\n}\n\nconst DEFAULT_FETCH_HEADERS: Record<string, string> = {\n 'X-Cookie-Consent-Fetch': '1'\n};\n\n/**\n * A simple wrapper around window.fetch that understands the django-cookie-consent\n * backend endpoints.\n *\n * @private - while exported, use at your own risk. This class is not part of the\n * public API covered by SemVer.\n */\nexport class FetchClient {\n protected statusUrl: string;\n protected csrfHeaderName: string;\n protected cookieStatus: CookieStatus | null;\n\n constructor(statusUrl: string, csrfHeaderName: string) {\n this.statusUrl = statusUrl;\n this.csrfHeaderName = csrfHeaderName;\n this.cookieStatus = null;\n }\n\n async getCookieStatus(): Promise<CookieStatus> {\n if (this.cookieStatus === null) {\n const response = await window.fetch(\n this.statusUrl,\n {\n method: 'GET',\n credentials: 'same-origin',\n headers: DEFAULT_FETCH_HEADERS,\n }\n );\n this.cookieStatus = await response.json();\n }\n\n // type checker sanity check\n if (this.cookieStatus === null) {\n throw new Error('Unexpectedly received null cookie status');\n }\n return this.cookieStatus;\n };\n\n async saveCookiesStatusBackend (\n urlProperty: 'acceptUrl' | 'declineUrl',\n cookieGroups: CookieGroup[],\n ) {\n const cookieStatus = await this.getCookieStatus();\n const url = cookieStatus[urlProperty];\n if (!url) {\n throw new Error(`Missing url for ${urlProperty} - was the cookie status not loaded properly?`);\n }\n\n const formData = new FormData();\n for (const group of cookieGroups) {\n formData.append('cookie_groups', group.varname);\n }\n\n await window.fetch(url, {\n method: 'POST',\n body: formData,\n credentials: 'same-origin',\n headers: {\n ...DEFAULT_FETCH_HEADERS,\n [this.csrfHeaderName]: cookieStatus.csrftoken\n }\n });\n }\n}\n\n/**\n * Read the JSON script node contents and parse the content as JSON.\n *\n * The result is the list of available/configured cookie groups.\n * Use the status URL to get the accepted/declined status for an individual user.\n */\nexport const loadCookieGroups = (selector: string): CookieGroup[] => {\n const node = document.querySelector<HTMLScriptElement>(selector);\n if (!node) {\n throw new Error(`No cookie groups (script) tag found, using selector: '${selector}'`);\n }\n return JSON.parse(node.innerText);\n};\n\nconst doInsertBefore = (beforeNode: HTMLElement, newNode: Node): void => {\n const parent = beforeNode.parentNode;\n if (parent === null) throw new Error('Reference node doesn\\'t have a parent.');\n parent.insertBefore(newNode, beforeNode);\n}\n\ntype RegisterEventsOptions = Pick<\n Options,\n 'acceptSelector' | 'onAccept' | 'declineSelector' | 'onDecline'\n> & Pick<\n CookieStatus,\n 'acceptedCookieGroups' | 'declinedCookieGroups' | 'notAcceptedOrDeclinedCookieGroups'\n> & {\n client: FetchClient,\n cookieBarNode: Element;\n cookieGroups: CookieGroup[];\n}\n\n/**\n * Register the accept/decline event handlers.\n *\n * Note that we can't just set the decline or accept cookie purely client-side, as the\n * cookie possibly has the httpOnly flag set.\n */\nconst registerEvents = ({\n client,\n cookieBarNode,\n cookieGroups,\n acceptSelector,\n onAccept,\n declineSelector,\n onDecline,\n acceptedCookieGroups: accepted,\n declinedCookieGroups: declined,\n notAcceptedOrDeclinedCookieGroups: undecided,\n}: RegisterEventsOptions): void => {\n\n const acceptNode = cookieBarNode.querySelector<HTMLElement>(acceptSelector);\n if (acceptNode) {\n acceptNode.addEventListener('click', event => {\n event.preventDefault();\n const acceptedGroups = filterCookieGroups(cookieGroups, accepted.concat(undecided));\n onAccept?.(acceptedGroups, event);\n // trigger async action, but don't wait for completion\n client.saveCookiesStatusBackend('acceptUrl', acceptedGroups);\n cookieBarNode.parentNode!.removeChild(cookieBarNode);\n });\n }\n\n const declineNode = cookieBarNode.querySelector<HTMLElement>(declineSelector);\n if (declineNode) {\n declineNode.addEventListener('click', event => {\n event.preventDefault();\n const declinedGroups = filterCookieGroups(cookieGroups, declined.concat(undecided));\n onDecline?.(declinedGroups, event);\n // trigger async action, but don't wait for completion\n client.saveCookiesStatusBackend('declineUrl', declinedGroups);\n cookieBarNode.parentNode!.removeChild(cookieBarNode);\n });\n }\n};\n\n/**\n * Filter the cookie groups down to a subset of specified varnames.\n */\nconst filterCookieGroups = (cookieGroups: CookieGroup[], varNames: string[]) => {\n return cookieGroups.filter(group => varNames.includes(group.varname));\n};\n\n// See https://github.com/microsoft/TypeScript/issues/283\nfunction cloneNode<T extends Node>(node: T) {\n return <T>node.cloneNode(true);\n}\n\nexport const showCookieBar = async (options: Partial<Options> = {}): Promise<void> => {\n const {\n templateSelector = '#cookie-consent__cookie-bar',\n cookieGroupsSelector = '#cookie-consent__cookie-groups',\n acceptSelector = '.cookie-consent__accept',\n declineSelector = '.cookie-consent__decline',\n insertBefore = null,\n onShow,\n onAccept,\n onDecline,\n statusUrl = '',\n csrfHeaderName = 'X-CSRFToken', // Django's default, can be overridden with settings.CSRF_HEADER_NAME\n } = options;\n\n const cookieGroups = loadCookieGroups(cookieGroupsSelector);\n\n // no cookie groups -> abort, nothing to do\n if (!cookieGroups.length) return;\n\n const templateNode = document.querySelector<HTMLTemplateElement>(templateSelector);\n if (!templateNode) {\n throw new Error(`No (template) element found for selector '${templateSelector}'.`)\n }\n\n // insert before a given node, if specified, or append to the body as default behaviour\n const doInsert = insertBefore === null\n ? (cookieBarNode: Node) => document.querySelector('body')!.appendChild(cookieBarNode)\n : typeof insertBefore === 'string'\n ? (cookieBarNode: Node) => {\n const referenceNode = document.querySelector<HTMLElement>(insertBefore);\n if (referenceNode === null) throw new Error(`No element found for selector '${insertBefore}'.`)\n doInsertBefore(referenceNode, cookieBarNode);\n }\n : (cookieBarNode: Node) => doInsertBefore(insertBefore, cookieBarNode)\n ;\n\n if (!statusUrl) throw new Error('Missing status URL option, did you forget to pass the `statusUrl` option?');\n\n const client = new FetchClient(statusUrl, csrfHeaderName);\n const cookieStatus = await client.getCookieStatus();\n\n // calculate the cookie groups to invoke the callbacks. We deliberately fire those\n // without awaiting so that our cookie bar is shown/hidden as soon as possible.\n const {\n acceptedCookieGroups,\n declinedCookieGroups,\n notAcceptedOrDeclinedCookieGroups\n } = cookieStatus;\n\n const acceptedGroups = filterCookieGroups(cookieGroups, acceptedCookieGroups);\n if (acceptedGroups.length) onAccept?.(acceptedGroups);\n const declinedGroups = filterCookieGroups(cookieGroups, declinedCookieGroups);\n if (declinedGroups.length) onDecline?.(declinedGroups);\n\n // there are no (more) cookie groups to accept, don't show the bar\n if (!notAcceptedOrDeclinedCookieGroups.length) return;\n\n // grab the contents from the template node and add them to the DOM, optionally\n // calling the onShow callback\n const childToClone = templateNode.content.firstElementChild;\n if (childToClone === null) throw new Error('The cookie bar template element may not be empty.');\n const cookieBarNode = cloneNode(childToClone);\n registerEvents({\n client,\n cookieBarNode,\n cookieGroups,\n acceptSelector,\n onAccept,\n declineSelector,\n onDecline,\n acceptedCookieGroups,\n declinedCookieGroups,\n notAcceptedOrDeclinedCookieGroups,\n });\n doInsert(cookieBarNode);\n onShow?.();\n};\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["cookieBarNode"]
|
|
7
7
|
}
|
|
@@ -11,8 +11,9 @@
|
|
|
11
11
|
{% if request|cookie_group_accepted:cookie_group.varname %}
|
|
12
12
|
<span class="cookie-consent-accepted">{% trans "Accepted" %}</span>
|
|
13
13
|
{% else %}
|
|
14
|
-
<form class="cookie-consent-accept" action="{% url "cookie_consent_accept"
|
|
14
|
+
<form class="cookie-consent-accept" action="{% url "cookie_consent_accept" %}" method="post">
|
|
15
15
|
{% csrf_token %}
|
|
16
|
+
<input type="hidden" name="cookie_groups" value="{{ cookie_group.varname }}">
|
|
16
17
|
<input type="submit" value="{% trans "Accept" %}">
|
|
17
18
|
</form>
|
|
18
19
|
{% endif %}
|
|
@@ -20,8 +21,9 @@
|
|
|
20
21
|
{% if request|cookie_group_declined:cookie_group.varname %}
|
|
21
22
|
<span class="cookie-consent-declined">{% trans "Declined" %}</span>
|
|
22
23
|
{% else %}
|
|
23
|
-
<form class="cookie-consent-decline" action="{% url "cookie_consent_decline"
|
|
24
|
+
<form class="cookie-consent-decline" action="{% url "cookie_consent_decline" %}" method="post">
|
|
24
25
|
{% csrf_token %}
|
|
26
|
+
<input type="hidden" name="cookie_groups" value="{{ cookie_group.varname }}">
|
|
25
27
|
<input type="submit" value="{% trans "Decline" %}">
|
|
26
28
|
</form>
|
|
27
29
|
{% endif %}
|