django-cookie-consent 0.7.0__tar.gz → 0.9.0__tar.gz

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.
Files changed (54) hide show
  1. django_cookie_consent-0.9.0/PKG-INFO +95 -0
  2. {django_cookie_consent-0.7.0 → django_cookie_consent-0.9.0}/README.md +12 -13
  3. django_cookie_consent-0.9.0/cookie_consent/__init__.py +1 -0
  4. {django_cookie_consent-0.7.0 → django_cookie_consent-0.9.0}/cookie_consent/admin.py +29 -2
  5. {django_cookie_consent-0.7.0 → django_cookie_consent-0.9.0}/cookie_consent/cache.py +0 -1
  6. {django_cookie_consent-0.7.0 → django_cookie_consent-0.9.0}/cookie_consent/conf.py +3 -1
  7. {django_cookie_consent-0.7.0 → django_cookie_consent-0.9.0}/cookie_consent/middleware.py +1 -4
  8. {django_cookie_consent-0.7.0 → django_cookie_consent-0.9.0}/cookie_consent/migrations/0001_initial.py +2 -1
  9. {django_cookie_consent-0.7.0 → django_cookie_consent-0.9.0}/cookie_consent/migrations/0003_alter_cookiegroup_varname.py +2 -2
  10. {django_cookie_consent-0.7.0 → django_cookie_consent-0.9.0}/cookie_consent/migrations/0004_cookie_natural_key.py +0 -1
  11. {django_cookie_consent-0.7.0 → django_cookie_consent-0.9.0}/cookie_consent/models.py +6 -6
  12. django_cookie_consent-0.9.0/cookie_consent/static/cookie_consent/cookiebar.module.js.map +7 -0
  13. django_cookie_consent-0.9.0/cookie_consent/templatetags/__init__.py +1 -0
  14. {django_cookie_consent-0.7.0 → django_cookie_consent-0.9.0}/cookie_consent/templatetags/cookie_consent_tags.py +12 -2
  15. {django_cookie_consent-0.7.0 → django_cookie_consent-0.9.0}/cookie_consent/urls.py +0 -1
  16. {django_cookie_consent-0.7.0 → django_cookie_consent-0.9.0}/cookie_consent/util.py +53 -14
  17. {django_cookie_consent-0.7.0 → django_cookie_consent-0.9.0}/cookie_consent/views.py +2 -2
  18. django_cookie_consent-0.9.0/django_cookie_consent.egg-info/PKG-INFO +95 -0
  19. {django_cookie_consent-0.7.0 → django_cookie_consent-0.9.0}/django_cookie_consent.egg-info/SOURCES.txt +1 -1
  20. {django_cookie_consent-0.7.0 → django_cookie_consent-0.9.0}/django_cookie_consent.egg-info/requires.txt +3 -6
  21. {django_cookie_consent-0.7.0 → django_cookie_consent-0.9.0}/pyproject.toml +38 -24
  22. django_cookie_consent-0.9.0/setup.cfg +4 -0
  23. django_cookie_consent-0.9.0/tests/test_admin.py +24 -0
  24. {django_cookie_consent-0.7.0 → django_cookie_consent-0.9.0}/tests/test_cache.py +0 -1
  25. {django_cookie_consent-0.7.0 → django_cookie_consent-0.9.0}/tests/test_models.py +30 -15
  26. {django_cookie_consent-0.7.0 → django_cookie_consent-0.9.0}/tests/test_templatetags.py +2 -2
  27. {django_cookie_consent-0.7.0 → django_cookie_consent-0.9.0}/tests/test_util.py +24 -1
  28. {django_cookie_consent-0.7.0 → django_cookie_consent-0.9.0}/tests/test_views.py +34 -17
  29. django_cookie_consent-0.7.0/PKG-INFO +0 -125
  30. django_cookie_consent-0.7.0/cookie_consent/__init__.py +0 -1
  31. django_cookie_consent-0.7.0/cookie_consent/static/cookie_consent/cookiebar.module.js.map +0 -7
  32. django_cookie_consent-0.7.0/cookie_consent/templatetags/__init__.py +0 -2
  33. django_cookie_consent-0.7.0/django_cookie_consent.egg-info/PKG-INFO +0 -125
  34. django_cookie_consent-0.7.0/setup.cfg +0 -10
  35. {django_cookie_consent-0.7.0 → django_cookie_consent-0.9.0}/AUTHORS +0 -0
  36. {django_cookie_consent-0.7.0 → django_cookie_consent-0.9.0}/LICENSE +0 -0
  37. {django_cookie_consent-0.7.0 → django_cookie_consent-0.9.0}/MANIFEST.in +0 -0
  38. {django_cookie_consent-0.7.0 → django_cookie_consent-0.9.0}/cookie_consent/apps.py +0 -0
  39. {django_cookie_consent-0.7.0 → django_cookie_consent-0.9.0}/cookie_consent/fixtures/common_cookies.json +0 -0
  40. {django_cookie_consent-0.7.0 → django_cookie_consent-0.9.0}/cookie_consent/migrations/0002_auto__add_logitem.py +0 -0
  41. {django_cookie_consent-0.7.0 → django_cookie_consent-0.9.0}/cookie_consent/migrations/__init__.py +0 -0
  42. {django_cookie_consent-0.7.0 → django_cookie_consent-0.9.0}/cookie_consent/static/cookie_consent/cookiebar.js +0 -0
  43. {django_cookie_consent-0.7.0 → django_cookie_consent-0.9.0}/cookie_consent/static/cookie_consent/cookiebar.module.js +0 -0
  44. {django_cookie_consent-0.7.0 → django_cookie_consent-0.9.0}/cookie_consent/templates/cookie_consent/_cookie_group.html +0 -0
  45. {django_cookie_consent-0.7.0 → django_cookie_consent-0.9.0}/cookie_consent/templates/cookie_consent/base.html +0 -0
  46. {django_cookie_consent-0.7.0 → django_cookie_consent-0.9.0}/cookie_consent/templates/cookie_consent/cookiegroup_list.html +0 -0
  47. {django_cookie_consent-0.7.0 → django_cookie_consent-0.9.0}/django_cookie_consent.egg-info/dependency_links.txt +0 -0
  48. {django_cookie_consent-0.7.0 → django_cookie_consent-0.9.0}/django_cookie_consent.egg-info/top_level.txt +0 -0
  49. {django_cookie_consent-0.7.0 → django_cookie_consent-0.9.0}/tests/test_cookie_group_model.py +0 -0
  50. {django_cookie_consent-0.7.0 → django_cookie_consent-0.9.0}/tests/test_cookie_model.py +0 -0
  51. {django_cookie_consent-0.7.0 → django_cookie_consent-0.9.0}/tests/test_javascript_cookiebar.py +0 -0
  52. {django_cookie_consent-0.7.0 → django_cookie_consent-0.9.0}/tests/test_legacy_javascript_cookiebar.py +0 -0
  53. {django_cookie_consent-0.7.0 → django_cookie_consent-0.9.0}/tests/test_middleware.py +0 -0
  54. {django_cookie_consent-0.7.0 → django_cookie_consent-0.9.0}/tests/test_settings.py +0 -0
@@ -0,0 +1,95 @@
1
+ Metadata-Version: 2.4
2
+ Name: django-cookie-consent
3
+ Version: 0.9.0
4
+ Summary: Django cookie consent application
5
+ Author-email: Informatika Mihelac <bmihelac@mihelac.org>
6
+ License-Expression: BSD-2-Clause-first-lines
7
+ Project-URL: Documentation, https://django-cookie-consent.readthedocs.io/en/latest/
8
+ Project-URL: Changelog, https://github.com/django-commons/django-cookie-consent/blob/master/docs/changelog.rst
9
+ Project-URL: Bug Tracker, https://github.com/django-commons/django-cookie-consent/issues
10
+ Project-URL: Source Code, https://github.com/django-commons/django-cookie-consent
11
+ Keywords: cookies,cookie-consent,cookie bar
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Framework :: Django
14
+ Classifier: Framework :: Django :: 4.2
15
+ Classifier: Framework :: Django :: 5.1
16
+ Classifier: Framework :: Django :: 5.2
17
+ Classifier: Intended Audience :: Developers
18
+ Classifier: Operating System :: Unix
19
+ Classifier: Operating System :: MacOS
20
+ Classifier: Operating System :: Microsoft :: Windows
21
+ Classifier: Operating System :: OS Independent
22
+ Classifier: Programming Language :: Python :: 3.10
23
+ Classifier: Programming Language :: Python :: 3.11
24
+ Classifier: Programming Language :: Python :: 3.12
25
+ Classifier: Programming Language :: Python :: 3.13
26
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
27
+ Requires-Python: >=3.10
28
+ Description-Content-Type: text/markdown
29
+ License-File: LICENSE
30
+ Requires-Dist: django>=4.2
31
+ Requires-Dist: django-appconf
32
+ Provides-Extra: tests
33
+ Requires-Dist: pytest; extra == "tests"
34
+ Requires-Dist: pytest-cov; extra == "tests"
35
+ Requires-Dist: pytest-django; extra == "tests"
36
+ Requires-Dist: pytest-playwright; extra == "tests"
37
+ Requires-Dist: hypothesis; extra == "tests"
38
+ Requires-Dist: tox; extra == "tests"
39
+ Requires-Dist: ruff; extra == "tests"
40
+ Provides-Extra: docs
41
+ Requires-Dist: sphinx; extra == "docs"
42
+ Requires-Dist: sphinx-rtd-theme; extra == "docs"
43
+ Provides-Extra: release
44
+ Requires-Dist: tbump; extra == "release"
45
+ Dynamic: license-file
46
+
47
+ Django cookie consent
48
+ =====================
49
+
50
+ Manage cookie information and let visitors give or reject consent for them.
51
+
52
+ ![License](https://img.shields.io/pypi/l/django-cookie-consent)
53
+ [![Build status][badge:GithubActions:CI]][GithubActions:CI]
54
+ [![Code Quality][badge:GithubActions:CQ]][GithubActions:CQ]
55
+ [![Code style: ruff][badge:ruff]][ruff]
56
+ [![Test coverage][badge:codecov]][codecov]
57
+ [![Documentation][badge:docs]][docs]
58
+
59
+ ![Supported python versions](https://img.shields.io/pypi/pyversions/django-cookie-consent)
60
+ ![Supported Django versions](https://img.shields.io/pypi/djversions/django-cookie-consent)
61
+ [![PyPI version][badge:pypi]][pypi]
62
+ [![NPM version][badge:npm]][npm]
63
+
64
+ **Features**
65
+
66
+ * cookies and cookie groups are stored in models for easy management
67
+ through Django admin interface
68
+ * support for both opt-in and opt-out cookie consent schemes
69
+ * removing declined cookies (or non accepted when opt-in scheme is used)
70
+ * logging user actions when they accept and decline various cookies
71
+ * easy adding new cookies and seamlessly re-asking for consent for new cookies
72
+
73
+ Documentation
74
+ -------------
75
+
76
+ The documentation is hosted on [readthedocs][docs] and contains all instructions
77
+ to get started.
78
+
79
+ Alternatively, if the documentation is not available, you can consult or build the docs
80
+ from the `docs` directory in this repository.
81
+
82
+ [GithubActions:CI]: https://github.com/django-commons/django-cookie-consent/actions?query=workflow%3A%22Run+CI%22
83
+ [badge:GithubActions:CI]: https://github.com/django-commons/django-cookie-consent/workflows/Run%20CI/badge.svg
84
+ [GithubActions:CQ]: https://github.com/django-commons/django-cookie-consent/actions?query=workflow%3A%22Code+quality+checks%22
85
+ [badge:GithubActions:CQ]: https://github.com/django-commons/django-cookie-consent/workflows/Code%20quality%20checks/badge.svg
86
+ [ruff]: https://github.com/astral-sh/ruff
87
+ [badge:ruff]: https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json
88
+ [codecov]: https://codecov.io/gh/django-commons/django-cookie-consent
89
+ [badge:codecov]: https://codecov.io/gh/django-commons/django-cookie-consent/branch/master/graph/badge.svg
90
+ [docs]: https://django-cookie-consent.readthedocs.io/en/latest/?badge=latest
91
+ [badge:docs]: https://readthedocs.org/projects/django-cookie-consent/badge/?version=latest
92
+ [pypi]: https://pypi.org/project/django-cookie-consent/
93
+ [badge:pypi]: https://img.shields.io/pypi/v/django-cookie-consent.svg
94
+ [npm]: https://www.npmjs.com/package/django-cookie-consent
95
+ [badge:npm]: https://img.shields.io/npm/v/django-cookie-consent
@@ -3,18 +3,17 @@ Django cookie consent
3
3
 
4
4
  Manage cookie information and let visitors give or reject consent for them.
5
5
 
6
- [![Jazzband][badge:jazzband]][jazzband]
7
-
8
6
  ![License](https://img.shields.io/pypi/l/django-cookie-consent)
9
7
  [![Build status][badge:GithubActions:CI]][GithubActions:CI]
10
8
  [![Code Quality][badge:GithubActions:CQ]][GithubActions:CQ]
11
- [![Code style: black][badge:black]][black]
9
+ [![Code style: ruff][badge:ruff]][ruff]
12
10
  [![Test coverage][badge:codecov]][codecov]
13
11
  [![Documentation][badge:docs]][docs]
14
12
 
15
13
  ![Supported python versions](https://img.shields.io/pypi/pyversions/django-cookie-consent)
16
14
  ![Supported Django versions](https://img.shields.io/pypi/djversions/django-cookie-consent)
17
15
  [![PyPI version][badge:pypi]][pypi]
16
+ [![NPM version][badge:npm]][npm]
18
17
 
19
18
  **Features**
20
19
 
@@ -34,17 +33,17 @@ to get started.
34
33
  Alternatively, if the documentation is not available, you can consult or build the docs
35
34
  from the `docs` directory in this repository.
36
35
 
37
- [jazzband]: https://jazzband.co/
38
- [badge:jazzband]: https://jazzband.co/static/img/badge.svg
39
- [GithubActions:CI]: https://github.com/jazzband/django-cookie-consent/actions?query=workflow%3A%22Run+CI%22
40
- [badge:GithubActions:CI]: https://github.com/jazzband/django-cookie-consent/workflows/Run%20CI/badge.svg
41
- [GithubActions:CQ]: https://github.com/jazzband/django-cookie-consent/actions?query=workflow%3A%22Code+quality+checks%22
42
- [badge:GithubActions:CQ]: https://github.com/jazzband/django-cookie-consent/workflows/Code%20quality%20checks/badge.svg
43
- [black]: https://github.com/psf/black
44
- [badge:black]: https://img.shields.io/badge/code%20style-black-000000.svg
45
- [codecov]: https://codecov.io/gh/jazzband/django-cookie-consent
46
- [badge:codecov]: https://codecov.io/gh/jazzband/django-cookie-consent/branch/master/graph/badge.svg
36
+ [GithubActions:CI]: https://github.com/django-commons/django-cookie-consent/actions?query=workflow%3A%22Run+CI%22
37
+ [badge:GithubActions:CI]: https://github.com/django-commons/django-cookie-consent/workflows/Run%20CI/badge.svg
38
+ [GithubActions:CQ]: https://github.com/django-commons/django-cookie-consent/actions?query=workflow%3A%22Code+quality+checks%22
39
+ [badge:GithubActions:CQ]: https://github.com/django-commons/django-cookie-consent/workflows/Code%20quality%20checks/badge.svg
40
+ [ruff]: https://github.com/astral-sh/ruff
41
+ [badge:ruff]: https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json
42
+ [codecov]: https://codecov.io/gh/django-commons/django-cookie-consent
43
+ [badge:codecov]: https://codecov.io/gh/django-commons/django-cookie-consent/branch/master/graph/badge.svg
47
44
  [docs]: https://django-cookie-consent.readthedocs.io/en/latest/?badge=latest
48
45
  [badge:docs]: https://readthedocs.org/projects/django-cookie-consent/badge/?version=latest
49
46
  [pypi]: https://pypi.org/project/django-cookie-consent/
50
47
  [badge:pypi]: https://img.shields.io/pypi/v/django-cookie-consent.svg
48
+ [npm]: https://www.npmjs.com/package/django-cookie-consent
49
+ [badge:npm]: https://img.shields.io/npm/v/django-cookie-consent
@@ -0,0 +1 @@
1
+ __version__ = "0.9.0"
@@ -1,5 +1,9 @@
1
- # -*- coding: utf-8 -*-
2
1
  from django.contrib import admin
2
+ from django.db.models import Count
3
+ from django.templatetags.l10n import localize
4
+ from django.templatetags.static import static
5
+ from django.utils.html import format_html
6
+ from django.utils.translation import gettext_lazy as _
3
7
 
4
8
  from .conf import settings
5
9
  from .models import Cookie, CookieGroup, LogItem
@@ -13,7 +17,14 @@ class CookieAdmin(admin.ModelAdmin):
13
17
 
14
18
 
15
19
  class CookieGroupAdmin(admin.ModelAdmin):
16
- list_display = ("varname", "name", "is_required", "is_deletable", "get_version")
20
+ list_display = (
21
+ "varname",
22
+ "name",
23
+ "is_required",
24
+ "is_deletable",
25
+ "num_cookies",
26
+ "get_version",
27
+ )
17
28
  search_fields = (
18
29
  "varname",
19
30
  "name",
@@ -23,6 +34,22 @@ class CookieGroupAdmin(admin.ModelAdmin):
23
34
  "is_deletable",
24
35
  )
25
36
 
37
+ def get_queryset(self, request):
38
+ qs = super().get_queryset(request)
39
+ return qs.annotate(num_cookies=Count("cookie"))
40
+
41
+ @admin.display(ordering="num_cookies", description=_("# cookies"))
42
+ def num_cookies(self, obj):
43
+ if (count := obj.num_cookies) > 0:
44
+ return localize(count)
45
+
46
+ return format_html(
47
+ '{count} <img src="{src}" alt="{alt}">',
48
+ count=localize(count),
49
+ src=static("admin/img/icon-alert.svg"),
50
+ alt=_("Warning icon for missing cookies in cookie group."),
51
+ )
52
+
26
53
 
27
54
  class LogItemAdmin(admin.ModelAdmin):
28
55
  list_display = ("action", "cookiegroup", "version", "created")
@@ -1,4 +1,3 @@
1
- # -*- coding: utf-8 -*-
2
1
  from django.core.cache import caches
3
2
 
4
3
  from .conf import settings
@@ -1,7 +1,7 @@
1
- # -*- coding: utf-8 -*-
2
1
  from django.conf import settings # NOQA
3
2
 
4
3
  from appconf import AppConf
4
+ from django.urls import reverse_lazy
5
5
 
6
6
  __all__ = ["settings"]
7
7
 
@@ -25,3 +25,5 @@ class CookieConsentConf(AppConf):
25
25
  CACHE_BACKEND = "default"
26
26
 
27
27
  LOG_ENABLED = True
28
+
29
+ SUCCESS_URL = reverse_lazy("cookie_consent_cookie_group_list")
@@ -1,12 +1,9 @@
1
- # -*- coding: utf-8 -*-
2
- from typing import Optional
3
-
4
1
  from .cache import all_cookie_groups
5
2
  from .conf import settings
6
3
  from .util import get_cookie_dict_from_request, is_cookie_consent_enabled
7
4
 
8
5
 
9
- def _should_delete_cookie(group_version: Optional[str]) -> bool:
6
+ def _should_delete_cookie(group_version: str | None) -> bool:
10
7
  # declined after it was accepted (and set) before
11
8
  if group_version == settings.COOKIE_CONSENT_DECLINE:
12
9
  return True
@@ -68,7 +68,8 @@ class Migration(migrations.Migration):
68
68
  validators=[
69
69
  django.core.validators.RegexValidator(
70
70
  re.compile("^[-_a-zA-Z0-9]+$"),
71
- "Enter a valid 'varname' consisting of letters, numbers, underscores or hyphens.",
71
+ "Enter a valid 'varname' consisting of letters, "
72
+ "numbers, underscores or hyphens.",
72
73
  "invalid",
73
74
  )
74
75
  ],
@@ -7,7 +7,6 @@ from django.db import migrations, models
7
7
 
8
8
 
9
9
  class Migration(migrations.Migration):
10
-
11
10
  dependencies = [
12
11
  ("cookie_consent", "0002_auto__add_logitem"),
13
12
  ]
@@ -22,7 +21,8 @@ class Migration(migrations.Migration):
22
21
  validators=[
23
22
  django.core.validators.RegexValidator(
24
23
  re.compile("^[-_a-zA-Z0-9]+$"),
25
- "Enter a valid 'varname' consisting of letters, numbers, underscores or hyphens.",
24
+ "Enter a valid 'varname' consisting of letters, numbers, "
25
+ "underscores or hyphens.",
26
26
  "invalid",
27
27
  )
28
28
  ],
@@ -4,7 +4,6 @@ from django.db import migrations, models
4
4
 
5
5
 
6
6
  class Migration(migrations.Migration):
7
-
8
7
  dependencies = [
9
8
  ("cookie_consent", "0003_alter_cookiegroup_varname"),
10
9
  ]
@@ -1,4 +1,3 @@
1
- # -*- coding: utf-8 -*-
2
1
  import re
3
2
  from typing import TypedDict
4
3
 
@@ -144,7 +143,7 @@ class Cookie(models.Model):
144
143
  ordering = ["-created"]
145
144
 
146
145
  def __str__(self):
147
- return "%s %s%s" % (self.name, self.domain, self.path)
146
+ return f"{self.name} {self.domain}{self.path}"
148
147
 
149
148
  @clear_cache_after
150
149
  def save(self, *args, **kwargs):
@@ -161,7 +160,8 @@ class Cookie(models.Model):
161
160
 
162
161
  @property
163
162
  def varname(self):
164
- return "%s=%s:%s" % (self.cookiegroup.varname, self.name, self.domain)
163
+ group_varname = self.cookiegroup.varname
164
+ return f"{group_varname}={self.name}:{self.domain}"
165
165
 
166
166
  def get_version(self):
167
167
  return self.created.isoformat()
@@ -185,10 +185,10 @@ class LogItem(models.Model):
185
185
  version = models.CharField(_("Version"), max_length=32)
186
186
  created = models.DateTimeField(_("Created"), auto_now_add=True, blank=True)
187
187
 
188
- def __str__(self):
189
- return "%s %s" % (self.cookiegroup.name, self.version)
190
-
191
188
  class Meta:
192
189
  verbose_name = _("Log item")
193
190
  verbose_name_plural = _("Log items")
194
191
  ordering = ["-created"]
192
+
193
+ def __str__(self):
194
+ return f"{self.cookiegroup.name} {self.version}"
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 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": "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",
6
+ "names": ["cookieBarNode"]
7
+ }
@@ -0,0 +1 @@
1
+ #!/usr/bin/env python
@@ -97,6 +97,7 @@ def get_accept_cookie_groups_cookie_string(request, cookie_groups): # pragma: n
97
97
  "Cookie string template tags for JS are deprecated and will be removed "
98
98
  "in django-cookie-consent 1.0",
99
99
  DeprecationWarning,
100
+ stacklevel=1,
100
101
  )
101
102
  cookie_dic = get_cookie_dict_from_request(request)
102
103
  for cookie_group in cookie_groups:
@@ -113,6 +114,7 @@ def get_decline_cookie_groups_cookie_string(request, cookie_groups):
113
114
  "Cookie string template tags for JS are deprecated and will be removed "
114
115
  "in django-cookie-consent 1.0",
115
116
  DeprecationWarning,
117
+ stacklevel=1,
116
118
  )
117
119
  cookie_dic = get_cookie_dict_from_request(request)
118
120
  for cookie_group in cookie_groups:
@@ -133,12 +135,14 @@ def js_type_for_cookie_consent(request, varname, cookie=None):
133
135
  alert("Social cookie accepted");
134
136
  </script>
135
137
  """
136
- # This approach doesn't work with page caches and/or strict Content-Security-Policies
137
- # (unless you use nonces, which again doesn't work with aggressive page caching).
138
+ # This approach doesn't work with page caches and/or strict
139
+ # Content-Security-Policies (unless you use nonces, which again doesn't work with
140
+ # aggressive page caching).
138
141
  warnings.warn(
139
142
  "Template tags for use in/with JS are deprecated and will be removed "
140
143
  "in django-cookie-consent 1.0",
141
144
  DeprecationWarning,
145
+ stacklevel=1,
142
146
  )
143
147
  enabled = is_cookie_consent_enabled(request)
144
148
  if not enabled:
@@ -162,6 +166,12 @@ def accepted_cookies(request):
162
166
  {{ request|accepted_cookies }}
163
167
 
164
168
  """
169
+ warnings.warn(
170
+ "The 'accepted_cookies' template filter is deprecated and will be removed"
171
+ "in django-cookie-consent 1.0.",
172
+ DeprecationWarning,
173
+ stacklevel=1,
174
+ )
165
175
  return [c.varname for c in get_accepted_cookies(request)]
166
176
 
167
177
 
@@ -1,4 +1,3 @@
1
- # -*- coding: utf-8 -*-
2
1
  from django.urls import path, re_path
3
2
  from django.views.decorators.csrf import csrf_exempt
4
3
 
@@ -1,24 +1,62 @@
1
- # -*- coding: utf-8 -*-
2
1
  import datetime
3
- from typing import Union
2
+ import logging
4
3
 
5
4
  from .cache import all_cookie_groups, get_cookie, get_cookie_group
6
5
  from .conf import settings
7
6
  from .models import ACTION_ACCEPTED, ACTION_DECLINED, LogItem
8
7
 
8
+ logger = logging.getLogger(__name__)
9
9
 
10
- def parse_cookie_str(cookie):
11
- dic = {}
10
+ COOKIE_GROUP_SEP = "|"
11
+ KEY_VALUE_SEP = "="
12
+
13
+
14
+ def parse_cookie_str(cookie: str) -> dict[str, str]:
12
15
  if not cookie:
13
- return dic
14
- for c in cookie.split("|"):
15
- key, value = c.split("=")
16
- dic[key] = value
17
- return dic
16
+ return {}
17
+
18
+ bits = cookie.split(COOKIE_GROUP_SEP)
19
+
20
+ def _gen_pairs():
21
+ for possible_pair in bits:
22
+ parts = possible_pair.split(KEY_VALUE_SEP)
23
+ if len(parts) == 2:
24
+ yield parts
25
+ else:
26
+ logger.debug("cookie_value_discarded", extra={"value": possible_pair})
27
+
28
+ return dict(_gen_pairs())
29
+
30
+
31
+ def _contains_invalid_characters(*inputs: str) -> bool:
32
+ # = and | are special separators. They are unexpected characters in both
33
+ # keys and values.
34
+ for separator in (COOKIE_GROUP_SEP, KEY_VALUE_SEP):
35
+ for value in inputs:
36
+ if separator in value:
37
+ logger.debug("skip_separator", extra={"value": value, "sep": separator})
38
+ return True
39
+ return False
40
+
41
+
42
+ def dict_to_cookie_str(dic) -> str:
43
+ """
44
+ Serialize a dictionary of cookie-group metadata to a string.
45
+
46
+ The result is stored in a cookie itself. Note that the dictionary keys are expected
47
+ to be cookie group ``varname`` fields, which are validated against a slug regex. The
48
+ values are supposed to be ISO-8601 timestamps.
49
+
50
+ Invalid key/value pairs are dropped.
51
+ """
18
52
 
53
+ def _gen_pairs():
54
+ for key, value in dic.items():
55
+ if _contains_invalid_characters(key, value):
56
+ continue
57
+ yield f"{key}={value}"
19
58
 
20
- def dict_to_cookie_str(dic):
21
- return "|".join(["%s=%s" % (k, v) for k, v in dic.items() if v])
59
+ return "|".join(_gen_pairs())
22
60
 
23
61
 
24
62
  def get_cookie_dict_from_request(request):
@@ -131,7 +169,7 @@ def are_all_cookies_accepted(request):
131
169
  )
132
170
 
133
171
 
134
- def _get_cookie_groups_by_state(request, state: Union[bool, None]):
172
+ def _get_cookie_groups_by_state(request, state: bool | None):
135
173
  return [
136
174
  cookie_group
137
175
  for cookie_group in get_cookie_groups()
@@ -171,6 +209,7 @@ def is_cookie_consent_enabled(request):
171
209
  return enabled
172
210
 
173
211
 
212
+ # Deprecated
174
213
  def get_cookie_string(cookie_dic):
175
214
  """
176
215
  Returns cookie in format suitable for use in javascript.
@@ -178,7 +217,7 @@ def get_cookie_string(cookie_dic):
178
217
  expires = datetime.datetime.now() + datetime.timedelta(
179
218
  seconds=settings.COOKIE_CONSENT_MAX_AGE
180
219
  )
181
- cookie_str = "%s=%s; expires=%s; path=/" % (
220
+ cookie_str = "{}={}; expires={}; path=/".format(
182
221
  settings.COOKIE_CONSENT_NAME,
183
222
  dict_to_cookie_str(cookie_dic),
184
223
  expires.strftime("%a, %d %b %Y %H:%M:%S GMT"),
@@ -198,5 +237,5 @@ def get_accepted_cookies(request):
198
237
  continue
199
238
  for cookie in cookie_group.cookie_set.all():
200
239
  if version >= cookie.get_version():
201
- accepted_cookies.append(cookie)
240
+ accepted_cookies.append(cookie) # noqa: PERF401
202
241
  return accepted_cookies
@@ -1,4 +1,3 @@
1
- # -*- coding: utf-8 -*-
2
1
  from django.contrib.auth.views import RedirectURLMixin
3
2
  from django.core.exceptions import SuspiciousOperation
4
3
  from django.http import HttpRequest, HttpResponse, HttpResponseRedirect, JsonResponse
@@ -7,6 +6,7 @@ from django.urls import reverse
7
6
  from django.utils.http import url_has_allowed_host_and_scheme
8
7
  from django.views.generic import ListView, View
9
8
 
9
+ from .conf import settings
10
10
  from .models import CookieGroup
11
11
  from .util import (
12
12
  accept_cookies,
@@ -49,7 +49,7 @@ class CookieGroupBaseProcessView(RedirectURLMixin, View):
49
49
  require_https=self.request.is_secure(),
50
50
  ):
51
51
  raise SuspiciousOperation("Unsafe open redirect suspected.")
52
- return redirect_to or reverse("cookie_consent_cookie_group_list")
52
+ return redirect_to or settings.COOKIE_CONSENT_SUCCESS_URL
53
53
 
54
54
  def process(self, request, response, varname): # pragma: no cover
55
55
  raise NotImplementedError()
@@ -0,0 +1,95 @@
1
+ Metadata-Version: 2.4
2
+ Name: django-cookie-consent
3
+ Version: 0.9.0
4
+ Summary: Django cookie consent application
5
+ Author-email: Informatika Mihelac <bmihelac@mihelac.org>
6
+ License-Expression: BSD-2-Clause-first-lines
7
+ Project-URL: Documentation, https://django-cookie-consent.readthedocs.io/en/latest/
8
+ Project-URL: Changelog, https://github.com/django-commons/django-cookie-consent/blob/master/docs/changelog.rst
9
+ Project-URL: Bug Tracker, https://github.com/django-commons/django-cookie-consent/issues
10
+ Project-URL: Source Code, https://github.com/django-commons/django-cookie-consent
11
+ Keywords: cookies,cookie-consent,cookie bar
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Framework :: Django
14
+ Classifier: Framework :: Django :: 4.2
15
+ Classifier: Framework :: Django :: 5.1
16
+ Classifier: Framework :: Django :: 5.2
17
+ Classifier: Intended Audience :: Developers
18
+ Classifier: Operating System :: Unix
19
+ Classifier: Operating System :: MacOS
20
+ Classifier: Operating System :: Microsoft :: Windows
21
+ Classifier: Operating System :: OS Independent
22
+ Classifier: Programming Language :: Python :: 3.10
23
+ Classifier: Programming Language :: Python :: 3.11
24
+ Classifier: Programming Language :: Python :: 3.12
25
+ Classifier: Programming Language :: Python :: 3.13
26
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
27
+ Requires-Python: >=3.10
28
+ Description-Content-Type: text/markdown
29
+ License-File: LICENSE
30
+ Requires-Dist: django>=4.2
31
+ Requires-Dist: django-appconf
32
+ Provides-Extra: tests
33
+ Requires-Dist: pytest; extra == "tests"
34
+ Requires-Dist: pytest-cov; extra == "tests"
35
+ Requires-Dist: pytest-django; extra == "tests"
36
+ Requires-Dist: pytest-playwright; extra == "tests"
37
+ Requires-Dist: hypothesis; extra == "tests"
38
+ Requires-Dist: tox; extra == "tests"
39
+ Requires-Dist: ruff; extra == "tests"
40
+ Provides-Extra: docs
41
+ Requires-Dist: sphinx; extra == "docs"
42
+ Requires-Dist: sphinx-rtd-theme; extra == "docs"
43
+ Provides-Extra: release
44
+ Requires-Dist: tbump; extra == "release"
45
+ Dynamic: license-file
46
+
47
+ Django cookie consent
48
+ =====================
49
+
50
+ Manage cookie information and let visitors give or reject consent for them.
51
+
52
+ ![License](https://img.shields.io/pypi/l/django-cookie-consent)
53
+ [![Build status][badge:GithubActions:CI]][GithubActions:CI]
54
+ [![Code Quality][badge:GithubActions:CQ]][GithubActions:CQ]
55
+ [![Code style: ruff][badge:ruff]][ruff]
56
+ [![Test coverage][badge:codecov]][codecov]
57
+ [![Documentation][badge:docs]][docs]
58
+
59
+ ![Supported python versions](https://img.shields.io/pypi/pyversions/django-cookie-consent)
60
+ ![Supported Django versions](https://img.shields.io/pypi/djversions/django-cookie-consent)
61
+ [![PyPI version][badge:pypi]][pypi]
62
+ [![NPM version][badge:npm]][npm]
63
+
64
+ **Features**
65
+
66
+ * cookies and cookie groups are stored in models for easy management
67
+ through Django admin interface
68
+ * support for both opt-in and opt-out cookie consent schemes
69
+ * removing declined cookies (or non accepted when opt-in scheme is used)
70
+ * logging user actions when they accept and decline various cookies
71
+ * easy adding new cookies and seamlessly re-asking for consent for new cookies
72
+
73
+ Documentation
74
+ -------------
75
+
76
+ The documentation is hosted on [readthedocs][docs] and contains all instructions
77
+ to get started.
78
+
79
+ Alternatively, if the documentation is not available, you can consult or build the docs
80
+ from the `docs` directory in this repository.
81
+
82
+ [GithubActions:CI]: https://github.com/django-commons/django-cookie-consent/actions?query=workflow%3A%22Run+CI%22
83
+ [badge:GithubActions:CI]: https://github.com/django-commons/django-cookie-consent/workflows/Run%20CI/badge.svg
84
+ [GithubActions:CQ]: https://github.com/django-commons/django-cookie-consent/actions?query=workflow%3A%22Code+quality+checks%22
85
+ [badge:GithubActions:CQ]: https://github.com/django-commons/django-cookie-consent/workflows/Code%20quality%20checks/badge.svg
86
+ [ruff]: https://github.com/astral-sh/ruff
87
+ [badge:ruff]: https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json
88
+ [codecov]: https://codecov.io/gh/django-commons/django-cookie-consent
89
+ [badge:codecov]: https://codecov.io/gh/django-commons/django-cookie-consent/branch/master/graph/badge.svg
90
+ [docs]: https://django-cookie-consent.readthedocs.io/en/latest/?badge=latest
91
+ [badge:docs]: https://readthedocs.org/projects/django-cookie-consent/badge/?version=latest
92
+ [pypi]: https://pypi.org/project/django-cookie-consent/
93
+ [badge:pypi]: https://img.shields.io/pypi/v/django-cookie-consent.svg
94
+ [npm]: https://www.npmjs.com/package/django-cookie-consent
95
+ [badge:npm]: https://img.shields.io/npm/v/django-cookie-consent
@@ -3,7 +3,6 @@ LICENSE
3
3
  MANIFEST.in
4
4
  README.md
5
5
  pyproject.toml
6
- setup.cfg
7
6
  cookie_consent/__init__.py
8
7
  cookie_consent/admin.py
9
8
  cookie_consent/apps.py
@@ -33,6 +32,7 @@ django_cookie_consent.egg-info/SOURCES.txt
33
32
  django_cookie_consent.egg-info/dependency_links.txt
34
33
  django_cookie_consent.egg-info/requires.txt
35
34
  django_cookie_consent.egg-info/top_level.txt
35
+ tests/test_admin.py
36
36
  tests/test_cache.py
37
37
  tests/test_cookie_group_model.py
38
38
  tests/test_cookie_model.py