django-cookie-consent 0.4.0__tar.gz → 0.5.0b0__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 (46) hide show
  1. {django-cookie-consent-0.4.0/django_cookie_consent.egg-info → django-cookie-consent-0.5.0b0}/PKG-INFO +19 -4
  2. django-cookie-consent-0.5.0b0/cookie_consent/__init__.py +1 -0
  3. {django-cookie-consent-0.4.0 → django-cookie-consent-0.5.0b0}/cookie_consent/cache.py +4 -1
  4. {django-cookie-consent-0.4.0 → django-cookie-consent-0.5.0b0}/cookie_consent/compat.py +2 -0
  5. {django-cookie-consent-0.4.0 → django-cookie-consent-0.5.0b0}/cookie_consent/models.py +21 -1
  6. {django-cookie-consent-0.4.0 → django-cookie-consent-0.5.0b0}/cookie_consent/static/cookie_consent/cookiebar.js +2 -1
  7. django-cookie-consent-0.5.0b0/cookie_consent/static/cookie_consent/cookiebar.module.js +203 -0
  8. {django-cookie-consent-0.4.0 → django-cookie-consent-0.5.0b0}/cookie_consent/templatetags/cookie_consent_tags.py +39 -9
  9. {django-cookie-consent-0.4.0 → django-cookie-consent-0.5.0b0}/cookie_consent/urls.py +15 -9
  10. {django-cookie-consent-0.4.0 → django-cookie-consent-0.5.0b0}/cookie_consent/util.py +28 -11
  11. django-cookie-consent-0.5.0b0/cookie_consent/views.py +116 -0
  12. {django-cookie-consent-0.4.0 → django-cookie-consent-0.5.0b0/django_cookie_consent.egg-info}/PKG-INFO +19 -4
  13. {django-cookie-consent-0.4.0 → django-cookie-consent-0.5.0b0}/django_cookie_consent.egg-info/SOURCES.txt +3 -0
  14. {django-cookie-consent-0.4.0 → django-cookie-consent-0.5.0b0}/django_cookie_consent.egg-info/requires.txt +1 -0
  15. {django-cookie-consent-0.4.0 → django-cookie-consent-0.5.0b0}/setup.cfg +15 -2
  16. {django-cookie-consent-0.4.0 → django-cookie-consent-0.5.0b0}/tests/test_cache.py +4 -1
  17. django-cookie-consent-0.5.0b0/tests/test_javascript_cookiebar.py +76 -0
  18. django-cookie-consent-0.5.0b0/tests/test_legacy_javascript_cookiebar.py +77 -0
  19. {django-cookie-consent-0.4.0 → django-cookie-consent-0.5.0b0}/tests/test_models.py +5 -0
  20. django-cookie-consent-0.4.0/cookie_consent/__init__.py +0 -1
  21. django-cookie-consent-0.4.0/cookie_consent/views.py +0 -67
  22. {django-cookie-consent-0.4.0 → django-cookie-consent-0.5.0b0}/AUTHORS +0 -0
  23. {django-cookie-consent-0.4.0 → django-cookie-consent-0.5.0b0}/LICENSE +0 -0
  24. {django-cookie-consent-0.4.0 → django-cookie-consent-0.5.0b0}/MANIFEST.in +0 -0
  25. {django-cookie-consent-0.4.0 → django-cookie-consent-0.5.0b0}/README.md +0 -0
  26. {django-cookie-consent-0.4.0 → django-cookie-consent-0.5.0b0}/cookie_consent/admin.py +0 -0
  27. {django-cookie-consent-0.4.0 → django-cookie-consent-0.5.0b0}/cookie_consent/apps.py +0 -0
  28. {django-cookie-consent-0.4.0 → django-cookie-consent-0.5.0b0}/cookie_consent/conf.py +0 -0
  29. {django-cookie-consent-0.4.0 → django-cookie-consent-0.5.0b0}/cookie_consent/fixtures/common_cookies.json +0 -0
  30. {django-cookie-consent-0.4.0 → django-cookie-consent-0.5.0b0}/cookie_consent/middleware.py +0 -0
  31. {django-cookie-consent-0.4.0 → django-cookie-consent-0.5.0b0}/cookie_consent/migrations/0001_initial.py +0 -0
  32. {django-cookie-consent-0.4.0 → django-cookie-consent-0.5.0b0}/cookie_consent/migrations/0002_auto__add_logitem.py +0 -0
  33. {django-cookie-consent-0.4.0 → django-cookie-consent-0.5.0b0}/cookie_consent/migrations/__init__.py +0 -0
  34. {django-cookie-consent-0.4.0 → django-cookie-consent-0.5.0b0}/cookie_consent/templates/cookie_consent/_cookie_group.html +0 -0
  35. {django-cookie-consent-0.4.0 → django-cookie-consent-0.5.0b0}/cookie_consent/templates/cookie_consent/base.html +0 -0
  36. {django-cookie-consent-0.4.0 → django-cookie-consent-0.5.0b0}/cookie_consent/templates/cookie_consent/cookiegroup_list.html +0 -0
  37. {django-cookie-consent-0.4.0 → django-cookie-consent-0.5.0b0}/cookie_consent/templatetags/__init__.py +0 -0
  38. {django-cookie-consent-0.4.0 → django-cookie-consent-0.5.0b0}/django_cookie_consent.egg-info/dependency_links.txt +0 -0
  39. {django-cookie-consent-0.4.0 → django-cookie-consent-0.5.0b0}/django_cookie_consent.egg-info/not-zip-safe +0 -0
  40. {django-cookie-consent-0.4.0 → django-cookie-consent-0.5.0b0}/django_cookie_consent.egg-info/top_level.txt +0 -0
  41. {django-cookie-consent-0.4.0 → django-cookie-consent-0.5.0b0}/setup.py +0 -0
  42. {django-cookie-consent-0.4.0 → django-cookie-consent-0.5.0b0}/tests/test_middleware.py +0 -0
  43. {django-cookie-consent-0.4.0 → django-cookie-consent-0.5.0b0}/tests/test_settings.py +0 -0
  44. {django-cookie-consent-0.4.0 → django-cookie-consent-0.5.0b0}/tests/test_templatetags.py +0 -0
  45. {django-cookie-consent-0.4.0 → django-cookie-consent-0.5.0b0}/tests/test_util.py +0 -0
  46. {django-cookie-consent-0.4.0 → django-cookie-consent-0.5.0b0}/tests/test_views.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: django-cookie-consent
3
- Version: 0.4.0
3
+ Version: 0.5.0b0
4
4
  Summary: Django cookie consent application
5
5
  Home-page: https://github.com/jazzband/django-cookie-consent
6
6
  Author: Informatika Mihelac
@@ -11,7 +11,7 @@ Project-URL: Changelog, https://github.com/jazzband/django-cookie-consent/blob/m
11
11
  Project-URL: Bug Tracker, https://github.com/jazzband/django-cookie-consent/issues
12
12
  Project-URL: Source Code, https://github.com/jazzband/django-cookie-consent
13
13
  Keywords: cookies,cookie-consent,cookie bar
14
- Classifier: Development Status :: 3 - Alpha
14
+ Classifier: Development Status :: 4 - Beta
15
15
  Classifier: Framework :: Django
16
16
  Classifier: Framework :: Django :: 3.2
17
17
  Classifier: Framework :: Django :: 4.1
@@ -28,13 +28,28 @@ Classifier: Programming Language :: Python :: 3.10
28
28
  Classifier: Programming Language :: Python :: 3.11
29
29
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
30
30
  Description-Content-Type: text/markdown
31
+ License-File: LICENSE
32
+ License-File: AUTHORS
33
+ Requires-Dist: django>=3.2
34
+ Requires-Dist: django-appconf
31
35
  Provides-Extra: tests
36
+ Requires-Dist: pytest; extra == "tests"
37
+ Requires-Dist: pytest-django; extra == "tests"
38
+ Requires-Dist: pytest-playwright; extra == "tests"
39
+ Requires-Dist: tox; extra == "tests"
40
+ Requires-Dist: isort; extra == "tests"
41
+ Requires-Dist: black; extra == "tests"
42
+ Requires-Dist: flake8; extra == "tests"
32
43
  Provides-Extra: pep8
44
+ Requires-Dist: flake8; extra == "pep8"
33
45
  Provides-Extra: coverage
46
+ Requires-Dist: pytest-cov; extra == "coverage"
34
47
  Provides-Extra: docs
48
+ Requires-Dist: sphinx; extra == "docs"
49
+ Requires-Dist: sphinx-rtd-theme; extra == "docs"
35
50
  Provides-Extra: release
36
- License-File: LICENSE
37
- License-File: AUTHORS
51
+ Requires-Dist: tbump; extra == "release"
52
+ Requires-Dist: twine; extra == "release"
38
53
 
39
54
  Django cookie consent
40
55
  =====================
@@ -0,0 +1 @@
1
+ __version__ = "0.5.0b0"
@@ -31,7 +31,10 @@ def all_cookie_groups():
31
31
 
32
32
  qs = CookieGroup.objects.filter(is_required=False)
33
33
  qs = qs.prefetch_related("cookie_set")
34
- items = dict([(g.varname, g) for g in qs])
34
+ # items = qs.in_bulk(field_name="varname")
35
+ # FIXME -> doesn't work because varname is not a unique fieldl, we need to
36
+ # make this unique
37
+ items = {group.varname: group for group in qs}
35
38
  cache.set(CACHE_KEY, items, CACHE_TIMEOUT)
36
39
  return items
37
40
 
@@ -9,3 +9,5 @@ except ImportError:
9
9
  from django.contrib.auth.views import (
10
10
  SuccessURLAllowedHostsMixin as RedirectURLMixin,
11
11
  )
12
+
13
+ __all__ = ["url_has_allowed_host_and_scheme", "RedirectURLMixin"]
@@ -1,5 +1,6 @@
1
1
  # -*- coding: utf-8 -*-
2
2
  import re
3
+ from typing import TypedDict
3
4
 
4
5
  from django.core.validators import RegexValidator
5
6
  from django.db import models
@@ -18,6 +19,16 @@ validate_cookie_name = RegexValidator(
18
19
  )
19
20
 
20
21
 
22
+ class CookieGroupDict(TypedDict):
23
+ varname: str
24
+ name: str
25
+ description: str
26
+ is_required: bool
27
+ # TODO: should we output this? page cache busting would be
28
+ # required if we do this. Alternatively, set up a JSONView to output these?
29
+ # version: str
30
+
31
+
21
32
  class CookieGroup(models.Model):
22
33
  varname = models.CharField(
23
34
  _("Variable name"), max_length=32, validators=[validate_cookie_name]
@@ -45,7 +56,7 @@ class CookieGroup(models.Model):
45
56
  def __str__(self):
46
57
  return self.name
47
58
 
48
- def get_version(self):
59
+ def get_version(self) -> str:
49
60
  try:
50
61
  return str(self.cookie_set.all()[0].get_version())
51
62
  except IndexError:
@@ -59,6 +70,15 @@ class CookieGroup(models.Model):
59
70
  super(CookieGroup, self).save(*args, **kwargs)
60
71
  delete_cache()
61
72
 
73
+ def for_json(self) -> CookieGroupDict:
74
+ return {
75
+ "varname": self.varname,
76
+ "name": self.name,
77
+ "description": self.description,
78
+ "is_required": self.is_required,
79
+ # "version": self.get_version(),
80
+ }
81
+
62
82
 
63
83
  class Cookie(models.Model):
64
84
  cookiegroup = models.ForeignKey(
@@ -10,7 +10,7 @@ function evalXCookieConsent(script) {
10
10
  script.remove();
11
11
  }
12
12
 
13
- function showCookieBar (options) {
13
+ function lecacyShowCookieBar (options) {
14
14
  const defaults = {
15
15
  content: '',
16
16
  cookie_groups: [],
@@ -64,3 +64,4 @@ function showCookieBar (options) {
64
64
  });
65
65
  }
66
66
 
67
+ window.legacyShowCookieBar = window.showCookieBar = lecacyShowCookieBar;
@@ -0,0 +1,203 @@
1
+ /**
2
+ * Cookiebar functionality, as a Javascript module.
3
+ *
4
+ * About modules: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules
5
+ *
6
+ * The code is organized here in a way to make the templates work with Django's page
7
+ * cache. This means that anything user-specific (so different django session and even
8
+ * cookie consent cookies) cannot be baked into the templates, as that breaks caches.
9
+ *
10
+ * The cookie bar operates on the following principles:
11
+ *
12
+ * - The developer using the library includes the desired template in their django
13
+ * templates, using the HTML <template> element. This contains the content for the
14
+ * cookie bar.
15
+ * - The developer is responsible for loading some Javascript that loads this script.
16
+ * - The main export of this script needs to be called (showCookieBar), with the
17
+ * appropriate options.
18
+ * - The options include the backend URLs where the retrieve data, which selectors/DOM
19
+ * nodes to use for various functionality and the hooks to tap into the accept/decline
20
+ * life-cycle.
21
+ * - When a user accepts or declines (all) cookies, the call to the backend is made via
22
+ * a fetch request, bypassing any page caches and preventing full-page reloads.
23
+ */
24
+ const DEFAULTS = {
25
+ statusUrl: undefined,
26
+ // TODO: also accept element rather than selector?
27
+ templateSelector: '#cookie-consent__cookie-bar',
28
+ cookieGroupsSelector: '#cookie-consent__cookie-groups',
29
+ acceptSelector: '.cookie-consent__accept',
30
+ declineSelector: '.cookie-consent__decline',
31
+ /**
32
+ * Either a string (selector), DOMNode or null.
33
+ *
34
+ * If null, the bar is appended to the body. If provided, the node is used or looked
35
+ * up.
36
+ */
37
+ insertBefore: null,
38
+ onShow: null, // callback when the cookie bar is being shown -> add class to body...
39
+ onAccept: null, // callback when cookies are accepted
40
+ onDecline: null, // callback when cookies are declined
41
+ csrfHeaderName: 'X-CSRFToken', // Django's default, can be overridden with settings.CSRF_HEADER_NAME
42
+ };
43
+
44
+ const DEFAULT_HEADERS = {'X-Cookie-Consent-Fetch': '1'};
45
+
46
+ let CONFIGURATION = DEFAULTS;
47
+ /**
48
+ * Cookie accept status, including the accept/decline URLs, csrftoken... See
49
+ * backend view CookieStatusView.
50
+ */
51
+ let COOKIE_STATUS = null;
52
+
53
+ export const loadCookieGroups = (selector) => {
54
+ const node = document.querySelector(selector);
55
+ if (!node) {
56
+ throw new Error(`No cookie groups (script) tag found, using selector: '${selector}'`);
57
+ }
58
+ return JSON.parse(node.innerText);
59
+ };
60
+
61
+ const doInsertBefore = (beforeNode, newNode) => {
62
+ const parent = beforeNode.parentNode;
63
+ parent.insertBefore(newNode, beforeNode);
64
+ }
65
+
66
+ /**
67
+ * Register the accept/decline event handlers.
68
+ *
69
+ * Note that we can't just set the decline or accept cookie purely client-side, as the
70
+ * cookie possibly has the httpOnly flag set.
71
+ *
72
+ * @param {HTMLEelement} cookieBarNode The DOM node containing the cookiebar markup.
73
+ * @param {Array} cookieGroups The array of all configured cookie groups.
74
+ * @return {Void}
75
+ */
76
+ const registerEvents = (cookieBarNode, cookieGroups) => {
77
+ const {acceptSelector, onAccept, declineSelector, onDecline} = CONFIGURATION;
78
+ const {
79
+ acceptedCookieGroups: accepted,
80
+ declinedCookieGroups: declined,
81
+ notAcceptedOrDeclinedCookieGroups: undecided,
82
+ } = COOKIE_STATUS;
83
+
84
+ cookieBarNode
85
+ .querySelector(acceptSelector)
86
+ .addEventListener('click', event => {
87
+ event.preventDefault();
88
+ const acceptedGroups = filterCookieGroups(cookieGroups, accepted.concat(undecided));
89
+ onAccept?.(acceptedGroups, event);
90
+ acceptCookiesBackend();
91
+ cookieBarNode.parentNode.removeChild(cookieBarNode);
92
+ });
93
+
94
+ cookieBarNode
95
+ .querySelector(declineSelector)
96
+ .addEventListener('click', event => {
97
+ event.preventDefault();
98
+ const declinedGroups = filterCookieGroups(cookieGroups, declined.concat(undecided));
99
+ onDecline?.(declinedGroups, event);
100
+ declineCookiesBackend();
101
+ cookieBarNode.parentNode.removeChild(cookieBarNode);
102
+ });
103
+ };
104
+
105
+ const loadCookieStatus = async () => {
106
+ const {statusUrl} = CONFIGURATION;
107
+ if (!statusUrl) console.error('Missing status URL option, did you forget to pass the statusUrl option?');
108
+ const response = await window.fetch(
109
+ CONFIGURATION.statusUrl,
110
+ {
111
+ method: 'GET',
112
+ credentials: 'same-origin',
113
+ headers: DEFAULT_HEADERS
114
+ }
115
+ );
116
+ // assign to module level variable, once the page is loaded these details should
117
+ // not change.
118
+ COOKIE_STATUS = await response.json();
119
+ };
120
+
121
+ const saveCookiesStatusBackend = async (urlProperty) => {
122
+ const status = COOKIE_STATUS || {};
123
+ const url = status[urlProperty];
124
+ if (!url) {
125
+ console.error(`Missing url for ${urlProperty} - was the cookie status not loaded properly?`);
126
+ return;
127
+ }
128
+ await window.fetch(url, {
129
+ method: 'POST',
130
+ credentials: 'same-origin',
131
+ headers: {
132
+ ...DEFAULT_HEADERS,
133
+ [CONFIGURATION.csrfHeaderName]: status.csrftoken
134
+ }
135
+ });
136
+ }
137
+
138
+ /**
139
+ * Make the call to the backend to accept the cookies.
140
+ */
141
+ const acceptCookiesBackend = async () => await saveCookiesStatusBackend('acceptUrl');
142
+ /**
143
+ * Make the call to the backend to decline the cookies.
144
+ */
145
+ const declineCookiesBackend = async () => await saveCookiesStatusBackend('declineUrl');
146
+
147
+ /**
148
+ * Filter the cookie groups down to a subset of specified varnames.
149
+ */
150
+ const filterCookieGroups = (cookieGroups, varNames) => {
151
+ return cookieGroups.filter(group => varNames.includes(group.varname));
152
+ };
153
+
154
+ export const showCookieBar = async (options={}) => {
155
+ // merge defaults and provided options
156
+ CONFIGURATION = {...DEFAULTS, ...options};
157
+ const {
158
+ cookieGroupsSelector,
159
+ templateSelector,
160
+ insertBefore,
161
+ onShow,
162
+ onAccept,
163
+ onDecline,
164
+ } = CONFIGURATION;
165
+ const cookieGroups = loadCookieGroups(cookieGroupsSelector);
166
+
167
+ // no cookie groups -> abort
168
+ if (!cookieGroups.length) return;
169
+
170
+ const templateNode = document.querySelector(templateSelector);
171
+
172
+ // insert before a given node, if specified, or append to the body as default behaviour
173
+ const doInsert = insertBefore === null
174
+ ? (cookieBarNode) => document.querySelector('body').appendChild(cookieBarNode)
175
+ : typeof insertBefore === 'string'
176
+ ? (cookieBarNode) => doInsertBefore(document.querySelector(insertBefore), cookieBarNode)
177
+ : (cookieBarNode) => doInsertBefore(insertBefore, cookieBarNode)
178
+ ;
179
+ await loadCookieStatus();
180
+
181
+ // calculate the cookie groups to invoke the callbacks. We deliberately fire those
182
+ // without awaiting so that our cookie bar is shown/hidden as soon as possible.
183
+ const {
184
+ acceptedCookieGroups: accepted,
185
+ declinedCookieGroups: declined,
186
+ notAcceptedOrDeclinedCookieGroups
187
+ } = COOKIE_STATUS;
188
+
189
+ const acceptedGroups = filterCookieGroups(cookieGroups, accepted);
190
+ if (acceptedGroups.length) onAccept?.(acceptedGroups);
191
+ const declinedGroups = filterCookieGroups(cookieGroups, declined);
192
+ if (declinedGroups.length) onDecline?.(declinedGroups);
193
+
194
+ // there are no (more) cookie groups to accept, don't show the bar
195
+ if (!notAcceptedOrDeclinedCookieGroups.length) return;
196
+
197
+ // grab the contents from the template node and add them to the DOM, optionally
198
+ // calling the onShow callback
199
+ const cookieBarNode = templateNode.content.firstElementChild.cloneNode(true);
200
+ registerEvents(cookieBarNode, cookieGroups);
201
+ onShow?.();
202
+ doInsert(cookieBarNode);
203
+ };
@@ -1,13 +1,12 @@
1
- # -*- coding: utf-8 -*-
2
- from django import template
1
+ import warnings
3
2
 
4
- try:
5
- from django.urls import reverse
6
- except ImportError:
7
- from django.core.urlresolvers import reverse
3
+ from django import template
4
+ from django.urls import reverse
5
+ from django.utils.html import json_script
8
6
 
9
- from cookie_consent.conf import settings
10
- from cookie_consent.util import (
7
+ from ..cache import all_cookie_groups as get_all_cookie_groups
8
+ from ..conf import settings
9
+ from ..util import (
11
10
  are_all_cookies_accepted,
12
11
  get_accepted_cookies,
13
12
  get_cookie_dict_from_request,
@@ -90,10 +89,15 @@ def cookie_consent_decline_url(cookie_groups):
90
89
 
91
90
 
92
91
  @register.simple_tag
93
- def get_accept_cookie_groups_cookie_string(request, cookie_groups):
92
+ def get_accept_cookie_groups_cookie_string(request, cookie_groups): # pragma: no cover
94
93
  """
95
94
  Tag returns accept cookie string suitable to use in javascript.
96
95
  """
96
+ warnings.warn(
97
+ "Cookie string template tags for JS are deprecated and will be removed "
98
+ "in django-cookie-consent 1.0",
99
+ DeprecationWarning,
100
+ )
97
101
  cookie_dic = get_cookie_dict_from_request(request)
98
102
  for cookie_group in cookie_groups:
99
103
  cookie_dic[cookie_group.varname] = cookie_group.get_version()
@@ -105,6 +109,11 @@ def get_decline_cookie_groups_cookie_string(request, cookie_groups):
105
109
  """
106
110
  Tag returns decline cookie string suitable to use in javascript.
107
111
  """
112
+ warnings.warn(
113
+ "Cookie string template tags for JS are deprecated and will be removed "
114
+ "in django-cookie-consent 1.0",
115
+ DeprecationWarning,
116
+ )
108
117
  cookie_dic = get_cookie_dict_from_request(request)
109
118
  for cookie_group in cookie_groups:
110
119
  cookie_dic[cookie_group.varname] = settings.COOKIE_CONSENT_DECLINE
@@ -124,6 +133,13 @@ def js_type_for_cookie_consent(request, varname, cookie=None):
124
133
  alert("Social cookie accepted");
125
134
  </script>
126
135
  """
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
+ warnings.warn(
139
+ "Template tags for use in/with JS are deprecated and will be removed "
140
+ "in django-cookie-consent 1.0",
141
+ DeprecationWarning,
142
+ )
127
143
  enabled = is_cookie_consent_enabled(request)
128
144
  if not enabled:
129
145
  res = True
@@ -147,3 +163,17 @@ def accepted_cookies(request):
147
163
 
148
164
  """
149
165
  return [c.varname for c in get_accepted_cookies(request)]
166
+
167
+
168
+ @register.simple_tag
169
+ def all_cookie_groups(element_id: str):
170
+ """
171
+ Serialize all cookie groups to JSON and output them in a script tag.
172
+
173
+ :param element_id: The ID for the script tag so you can look it up in JS later.
174
+
175
+ This uses Django's core json_script filter under the hood.
176
+ """
177
+ groups = get_all_cookie_groups()
178
+ value = [group.for_json() for group in groups.values()]
179
+ return json_script(value, element_id)
@@ -1,31 +1,37 @@
1
1
  # -*- coding: utf-8 -*-
2
- from django.urls import re_path
2
+ from django.urls import path, re_path
3
3
  from django.views.decorators.csrf import csrf_exempt
4
4
 
5
- from .views import CookieGroupAcceptView, CookieGroupDeclineView, CookieGroupListView
5
+ from .views import (
6
+ CookieGroupAcceptView,
7
+ CookieGroupDeclineView,
8
+ CookieGroupListView,
9
+ CookieStatusView,
10
+ )
6
11
 
7
12
  urlpatterns = [
8
- re_path(
9
- r"^accept/$",
13
+ path(
14
+ "accept/",
10
15
  csrf_exempt(CookieGroupAcceptView.as_view()),
11
16
  name="cookie_consent_accept_all",
12
17
  ),
18
+ # TODO: use form or query string params for this instead?
13
19
  re_path(
14
20
  r"^accept/(?P<varname>.*)/$",
15
21
  csrf_exempt(CookieGroupAcceptView.as_view()),
16
22
  name="cookie_consent_accept",
17
23
  ),
24
+ # TODO: use form or query string params for this instead?
18
25
  re_path(
19
26
  r"^decline/(?P<varname>.*)/$",
20
27
  csrf_exempt(CookieGroupDeclineView.as_view()),
21
28
  name="cookie_consent_decline",
22
29
  ),
23
- re_path(
24
- r"^decline/$",
30
+ path(
31
+ "decline/",
25
32
  csrf_exempt(CookieGroupDeclineView.as_view()),
26
33
  name="cookie_consent_decline_all",
27
34
  ),
28
- re_path(
29
- r"^$", CookieGroupListView.as_view(), name="cookie_consent_cookie_group_list"
30
- ),
35
+ path("status/", CookieStatusView.as_view(), name="cookie_consent_status"),
36
+ path("", CookieGroupListView.as_view(), name="cookie_consent_cookie_group_list"),
31
37
  ]
@@ -1,11 +1,10 @@
1
1
  # -*- coding: utf-8 -*-
2
2
  import datetime
3
+ from typing import Union
3
4
 
4
- from django.utils.encoding import smart_str
5
-
6
- from cookie_consent.cache import all_cookie_groups, get_cookie, get_cookie_group
7
- from cookie_consent.conf import settings
8
- from cookie_consent.models import ACTION_ACCEPTED, ACTION_DECLINED, LogItem
5
+ from .cache import all_cookie_groups, get_cookie, get_cookie_group
6
+ from .conf import settings
7
+ from .models import ACTION_ACCEPTED, ACTION_DECLINED, LogItem
9
8
 
10
9
 
11
10
  def parse_cookie_str(cookie):
@@ -100,7 +99,7 @@ def accept_cookies(request, response, varname=None):
100
99
  def delete_cookies(response, cookie_group):
101
100
  if cookie_group.is_deletable:
102
101
  for cookie in cookie_group.cookie_set.all():
103
- response.delete_cookie(smart_str(cookie.name), cookie.path, cookie.domain)
102
+ response.delete_cookie(cookie.name, cookie.path, cookie.domain)
104
103
 
105
104
 
106
105
  def decline_cookies(request, response, varname=None):
@@ -132,17 +131,35 @@ def are_all_cookies_accepted(request):
132
131
  )
133
132
 
134
133
 
135
- def get_not_accepted_or_declined_cookie_groups(request):
136
- """
137
- Returns all cookie groups that are neither accepted or declined.
138
- """
134
+ def _get_cookie_groups_by_state(request, state: Union[bool, None]):
139
135
  return [
140
136
  cookie_group
141
137
  for cookie_group in get_cookie_groups()
142
- if get_cookie_value_from_request(request, cookie_group.varname) is None
138
+ if get_cookie_value_from_request(request, cookie_group.varname) is state
143
139
  ]
144
140
 
145
141
 
142
+ def get_not_accepted_or_declined_cookie_groups(request):
143
+ """
144
+ Returns all cookie groups that are neither accepted or declined.
145
+ """
146
+ return _get_cookie_groups_by_state(request, state=None)
147
+
148
+
149
+ def get_accepted_cookie_groups(request):
150
+ """
151
+ Returns all cookie groups that are accepted.
152
+ """
153
+ return _get_cookie_groups_by_state(request, state=True)
154
+
155
+
156
+ def get_declined_cookie_groups(request):
157
+ """
158
+ Returns all cookie groups that are declined.
159
+ """
160
+ return _get_cookie_groups_by_state(request, state=False)
161
+
162
+
146
163
  def is_cookie_consent_enabled(request):
147
164
  """
148
165
  Returns if django-cookie-consent is enabled for given request.
@@ -0,0 +1,116 @@
1
+ # -*- coding: utf-8 -*-
2
+ from django.core.exceptions import SuspiciousOperation
3
+ from django.http import HttpRequest, HttpResponse, HttpResponseRedirect, JsonResponse
4
+ from django.middleware.csrf import get_token as get_csrf_token
5
+ from django.urls import reverse
6
+ from django.views.generic import ListView, View
7
+
8
+ from .compat import RedirectURLMixin, url_has_allowed_host_and_scheme
9
+ from .models import CookieGroup
10
+ from .util import (
11
+ accept_cookies,
12
+ decline_cookies,
13
+ get_accepted_cookie_groups,
14
+ get_declined_cookie_groups,
15
+ get_not_accepted_or_declined_cookie_groups,
16
+ )
17
+
18
+
19
+ def is_ajax_like(request: HttpRequest) -> bool:
20
+ # legacy ajax, removed in Django 4.0 (used to be request.is_ajax())
21
+ ajax_header = request.headers.get("X-Requested-With")
22
+ if ajax_header == "XMLHttpRequest":
23
+ return True
24
+
25
+ # module-js uses fetch and a custom header
26
+ return bool(request.headers.get("X-Cookie-Consent-Fetch"))
27
+
28
+
29
+ class CookieGroupListView(ListView):
30
+ """
31
+ Display all cookies.
32
+ """
33
+
34
+ model = CookieGroup
35
+
36
+
37
+ class CookieGroupBaseProcessView(RedirectURLMixin, View):
38
+ def get_success_url(self):
39
+ """
40
+ If user adds a 'next' as URL parameter or hidden input,
41
+ redirect to the value of 'next'. Otherwise, redirect to
42
+ cookie consent group list
43
+ """
44
+ redirect_to = self.request.POST.get("next", self.request.GET.get("next"))
45
+ if redirect_to and not url_has_allowed_host_and_scheme(
46
+ url=redirect_to,
47
+ allowed_hosts=self.get_success_url_allowed_hosts(),
48
+ require_https=self.request.is_secure(),
49
+ ):
50
+ raise SuspiciousOperation("Unsafe open redirect suspected.")
51
+ return redirect_to or reverse("cookie_consent_cookie_group_list")
52
+
53
+ def process(self, request, response, varname): # pragma: no cover
54
+ raise NotImplementedError()
55
+
56
+ def post(self, request, *args, **kwargs):
57
+ varname = kwargs.get("varname", None)
58
+ if is_ajax_like(request):
59
+ response = HttpResponse()
60
+ else:
61
+ response = HttpResponseRedirect(self.get_success_url())
62
+ self.process(request, response, varname)
63
+ return response
64
+
65
+
66
+ class CookieGroupAcceptView(CookieGroupBaseProcessView):
67
+ """
68
+ View to accept CookieGroup.
69
+ """
70
+
71
+ def process(self, request, response, varname):
72
+ accept_cookies(request, response, varname)
73
+
74
+
75
+ class CookieGroupDeclineView(CookieGroupBaseProcessView):
76
+ """
77
+ View to decline CookieGroup.
78
+ """
79
+
80
+ def process(self, request, response, varname):
81
+ decline_cookies(request, response, varname)
82
+
83
+ def delete(self, request, *args, **kwargs):
84
+ return self.post(request, *args, **kwargs)
85
+
86
+
87
+ class CookieStatusView(View):
88
+ """
89
+ Check the current accept/decline status for cookies.
90
+
91
+ The returned accept and decline URLs are specific to this user and include the
92
+ cookie groups that weren't accepted or declined yet.
93
+
94
+ Note that this endpoint also returns a CSRF Token to be used by the frontend,
95
+ as baking a CSRFToken into a cached page will not reliably work.
96
+ """
97
+
98
+ def get(self, request: HttpRequest) -> JsonResponse:
99
+ accepted = get_accepted_cookie_groups(request)
100
+ declined = get_declined_cookie_groups(request)
101
+ not_accepted_or_declined = get_not_accepted_or_declined_cookie_groups(request)
102
+ # TODO: change this csv URL param into proper POST params
103
+ varnames = ",".join([group.varname for group in not_accepted_or_declined])
104
+ data = {
105
+ "csrftoken": get_csrf_token(request),
106
+ "acceptUrl": reverse("cookie_consent_accept", kwargs={"varname": varnames}),
107
+ "declineUrl": reverse(
108
+ "cookie_consent_decline", kwargs={"varname": varnames}
109
+ ),
110
+ "acceptedCookieGroups": [group.varname for group in accepted],
111
+ "declinedCookieGroups": [group.varname for group in declined],
112
+ "notAcceptedOrDeclinedCookieGroups": [
113
+ group.varname for group in not_accepted_or_declined
114
+ ],
115
+ }
116
+ return JsonResponse(data)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: django-cookie-consent
3
- Version: 0.4.0
3
+ Version: 0.5.0b0
4
4
  Summary: Django cookie consent application
5
5
  Home-page: https://github.com/jazzband/django-cookie-consent
6
6
  Author: Informatika Mihelac
@@ -11,7 +11,7 @@ Project-URL: Changelog, https://github.com/jazzband/django-cookie-consent/blob/m
11
11
  Project-URL: Bug Tracker, https://github.com/jazzband/django-cookie-consent/issues
12
12
  Project-URL: Source Code, https://github.com/jazzband/django-cookie-consent
13
13
  Keywords: cookies,cookie-consent,cookie bar
14
- Classifier: Development Status :: 3 - Alpha
14
+ Classifier: Development Status :: 4 - Beta
15
15
  Classifier: Framework :: Django
16
16
  Classifier: Framework :: Django :: 3.2
17
17
  Classifier: Framework :: Django :: 4.1
@@ -28,13 +28,28 @@ Classifier: Programming Language :: Python :: 3.10
28
28
  Classifier: Programming Language :: Python :: 3.11
29
29
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
30
30
  Description-Content-Type: text/markdown
31
+ License-File: LICENSE
32
+ License-File: AUTHORS
33
+ Requires-Dist: django>=3.2
34
+ Requires-Dist: django-appconf
31
35
  Provides-Extra: tests
36
+ Requires-Dist: pytest; extra == "tests"
37
+ Requires-Dist: pytest-django; extra == "tests"
38
+ Requires-Dist: pytest-playwright; extra == "tests"
39
+ Requires-Dist: tox; extra == "tests"
40
+ Requires-Dist: isort; extra == "tests"
41
+ Requires-Dist: black; extra == "tests"
42
+ Requires-Dist: flake8; extra == "tests"
32
43
  Provides-Extra: pep8
44
+ Requires-Dist: flake8; extra == "pep8"
33
45
  Provides-Extra: coverage
46
+ Requires-Dist: pytest-cov; extra == "coverage"
34
47
  Provides-Extra: docs
48
+ Requires-Dist: sphinx; extra == "docs"
49
+ Requires-Dist: sphinx-rtd-theme; extra == "docs"
35
50
  Provides-Extra: release
36
- License-File: LICENSE
37
- License-File: AUTHORS
51
+ Requires-Dist: tbump; extra == "release"
52
+ Requires-Dist: twine; extra == "release"
38
53
 
39
54
  Django cookie consent
40
55
  =====================
@@ -20,6 +20,7 @@ cookie_consent/migrations/0001_initial.py
20
20
  cookie_consent/migrations/0002_auto__add_logitem.py
21
21
  cookie_consent/migrations/__init__.py
22
22
  cookie_consent/static/cookie_consent/cookiebar.js
23
+ cookie_consent/static/cookie_consent/cookiebar.module.js
23
24
  cookie_consent/templates/cookie_consent/_cookie_group.html
24
25
  cookie_consent/templates/cookie_consent/base.html
25
26
  cookie_consent/templates/cookie_consent/cookiegroup_list.html
@@ -32,6 +33,8 @@ django_cookie_consent.egg-info/not-zip-safe
32
33
  django_cookie_consent.egg-info/requires.txt
33
34
  django_cookie_consent.egg-info/top_level.txt
34
35
  tests/test_cache.py
36
+ tests/test_javascript_cookiebar.py
37
+ tests/test_legacy_javascript_cookiebar.py
35
38
  tests/test_middleware.py
36
39
  tests/test_models.py
37
40
  tests/test_settings.py
@@ -18,6 +18,7 @@ twine
18
18
  [tests]
19
19
  pytest
20
20
  pytest-django
21
+ pytest-playwright
21
22
  tox
22
23
  isort
23
24
  black
@@ -1,6 +1,6 @@
1
1
  [metadata]
2
2
  name = django-cookie-consent
3
- version = 0.4.0
3
+ version = 0.5.0b0
4
4
  description = Django cookie consent application
5
5
  long_description = file: README.md
6
6
  long_description_content_type = text/markdown
@@ -15,7 +15,7 @@ author = Informatika Mihelac
15
15
  author_email = bmihelac@mihelac.org
16
16
  keywords = cookies, cookie-consent, cookie bar
17
17
  classifiers =
18
- Development Status :: 3 - Alpha
18
+ Development Status :: 4 - Beta
19
19
  Framework :: Django
20
20
  Framework :: Django :: 3.2
21
21
  Framework :: Django :: 4.1
@@ -42,6 +42,7 @@ install_requires =
42
42
  tests_require =
43
43
  pytest
44
44
  pytest-django
45
+ pytest-playwright
45
46
  tox
46
47
  isort
47
48
  black
@@ -56,6 +57,7 @@ include =
56
57
  tests =
57
58
  pytest
58
59
  pytest-django
60
+ pytest-playwright
59
61
  tox
60
62
  isort
61
63
  black
@@ -89,6 +91,8 @@ sections = FUTURE,STDLIB,DJANGO,THIRDPARTY,FIRSTPARTY,LOCALFOLDER
89
91
  [tool:pytest]
90
92
  testpaths = tests
91
93
  DJANGO_SETTINGS_MODULE = testapp.settings
94
+ markers =
95
+ e2e: mark tests as end-to-end tests, using playwright (deselect with '-m "not e2e"')
92
96
 
93
97
  [pep8]
94
98
 
@@ -96,6 +100,15 @@ DJANGO_SETTINGS_MODULE = testapp.settings
96
100
  max-line-length = 88
97
101
  exclude = env,.tox,docs
98
102
 
103
+ [coverage:run]
104
+ branch = True
105
+ source = cookie_consent
106
+ omit =
107
+ */migrations/*
108
+
109
+ [coverage:report]
110
+ skip_covered = True
111
+
99
112
  [egg_info]
100
113
  tag_build =
101
114
  tag_date = 0
@@ -1,12 +1,15 @@
1
1
  # -*- coding: utf-8 -*-
2
2
  from django.test import TestCase, override_settings
3
3
 
4
- from cookie_consent.cache import get_cookie, get_cookie_group
4
+ from cookie_consent.cache import delete_cache, get_cookie, get_cookie_group
5
5
  from cookie_consent.models import Cookie, CookieGroup
6
6
 
7
7
 
8
8
  class CacheTest(TestCase):
9
9
  def setUp(self):
10
+ super().setUp()
11
+ self.addCleanup(delete_cache)
12
+
10
13
  self.cookie_group = CookieGroup.objects.create(
11
14
  varname="optional",
12
15
  name="Optional",
@@ -0,0 +1,76 @@
1
+ """
2
+ Test the behaviour of the dynamic (JS based) cookiebar module.
3
+
4
+ See docs: https://playwright.dev/python/docs/test-runners for CLI options.
5
+ """
6
+ from django.urls import reverse
7
+
8
+ import pytest
9
+ from playwright.sync_api import Page, expect
10
+
11
+ pytestmark = [pytest.mark.django_db, pytest.mark.e2e]
12
+
13
+
14
+ COOKIE_BAR_CONTENT = """
15
+ This site uses Social, Optional cookies for better performance and user experience.
16
+ Do you agree to use these cookies?
17
+ """
18
+
19
+
20
+ @pytest.fixture(scope="function", autouse=True)
21
+ def before_each_after_each(live_server, page: Page, load_testapp_fixture):
22
+ test_page_url = f"{live_server.url}{reverse('test_page')}"
23
+ page.goto(test_page_url)
24
+ yield
25
+
26
+
27
+ def test_cookiebar_shows_initially(page: Page):
28
+ cookiebar = page.get_by_text(COOKIE_BAR_CONTENT)
29
+ expect(cookiebar).to_be_visible()
30
+
31
+
32
+ def test_cookiebar_accept_all(page: Page):
33
+ accept_button = page.get_by_role("button", name="Accept")
34
+ expect(accept_button).to_be_visible()
35
+
36
+ accept_button.click()
37
+
38
+ expect(page.get_by_text(COOKIE_BAR_CONTENT)).not_to_be_visible()
39
+ share_button = page.get_by_role("button", name="SHARE")
40
+ expect(share_button).to_be_visible()
41
+
42
+
43
+ def test_cookiebar_decline_all(page: Page):
44
+ decline_button = page.get_by_role("button", name="Decline")
45
+ expect(decline_button).to_be_visible()
46
+
47
+ decline_button.click()
48
+
49
+ expect(page.get_by_text(COOKIE_BAR_CONTENT)).not_to_be_visible()
50
+ share_button = page.get_by_role("button", name="SHARE")
51
+ expect(share_button).not_to_be_visible()
52
+
53
+
54
+ @pytest.mark.parametrize("btn_text", ["Accept", "Decline"])
55
+ def test_cookiebar_not_shown_anymore_after_accept_or_decline(btn_text: str, page: Page):
56
+ expect(page.get_by_text(COOKIE_BAR_CONTENT)).to_be_visible()
57
+
58
+ button = page.get_by_role("button", name=btn_text)
59
+ expect(button).to_be_visible()
60
+
61
+ button.click()
62
+ expect(page.get_by_text(COOKIE_BAR_CONTENT)).not_to_be_visible()
63
+
64
+ page.reload()
65
+ expect(page.get_by_text(COOKIE_BAR_CONTENT)).not_to_be_visible()
66
+
67
+
68
+ def test_on_accept_handler_runs_on_load(page: Page, live_server):
69
+ accept_button = page.get_by_role("button", name="Accept")
70
+ accept_button.click()
71
+
72
+ test_page_url = f"{live_server.url}{reverse('test_page')}"
73
+ page.goto(test_page_url)
74
+
75
+ share_button = page.get_by_role("button", name="SHARE")
76
+ expect(share_button).to_be_visible()
@@ -0,0 +1,77 @@
1
+ """
2
+ These tests mostly exist to assert code coverage while giving library users time
3
+ to transition to the new implementation.
4
+
5
+ TODO: remove in django-cookie-consent 1.0
6
+ """
7
+ from django.urls import reverse
8
+
9
+ import pytest
10
+ from playwright.sync_api import Page, expect
11
+
12
+ pytestmark = [pytest.mark.django_db, pytest.mark.e2e]
13
+
14
+
15
+ COOKIE_BAR_CONTENT = """
16
+ This site uses Social, Optional cookies for better performance and user experience.
17
+ Do you agree to use cookies?
18
+ """
19
+
20
+
21
+ @pytest.fixture(scope="function", autouse=True)
22
+ def before_each_after_each(live_server, page: Page, load_testapp_fixture):
23
+ test_page_url = f"{live_server.url}{reverse('legacy_test_page')}"
24
+ page.goto(test_page_url)
25
+ yield
26
+
27
+
28
+ def test_cookiebar_shows_initially(page: Page):
29
+ cookiebar = page.get_by_text(COOKIE_BAR_CONTENT)
30
+ expect(cookiebar).to_be_visible()
31
+
32
+
33
+ def test_cookiebar_accept_all(page: Page):
34
+ accept_link = page.get_by_role("link", name="Accept")
35
+ expect(accept_link).to_be_visible()
36
+
37
+ accept_link.click()
38
+
39
+ expect(page.get_by_text(COOKIE_BAR_CONTENT)).not_to_be_visible()
40
+ share_button = page.get_by_role("button", name="SHARE")
41
+ expect(share_button).to_be_visible()
42
+
43
+
44
+ def test_cookiebar_decline_all(page: Page):
45
+ decline_link = page.get_by_role("link", name="Decline")
46
+ expect(decline_link).to_be_visible()
47
+
48
+ decline_link.click()
49
+
50
+ expect(page.get_by_text(COOKIE_BAR_CONTENT)).not_to_be_visible()
51
+ share_button = page.get_by_role("button", name="SHARE")
52
+ expect(share_button).not_to_be_visible()
53
+
54
+
55
+ @pytest.mark.parametrize("btn_text", ["Accept", "Decline"])
56
+ def test_cookiebar_not_shown_anymore_after_accept_or_decline(btn_text: str, page: Page):
57
+ expect(page.get_by_text(COOKIE_BAR_CONTENT)).to_be_visible()
58
+
59
+ link = page.get_by_role("link", name=btn_text)
60
+ expect(link).to_be_visible()
61
+
62
+ link.click()
63
+ expect(page.get_by_text(COOKIE_BAR_CONTENT)).not_to_be_visible()
64
+
65
+ page.reload()
66
+ expect(page.get_by_text(COOKIE_BAR_CONTENT)).not_to_be_visible()
67
+
68
+
69
+ def test_on_accept_handler_runs_on_load(page: Page, live_server):
70
+ accept_link = page.get_by_role("link", name="Accept")
71
+ accept_link.click()
72
+
73
+ test_page_url = f"{live_server.url}{reverse('legacy_test_page')}"
74
+ page.goto(test_page_url)
75
+
76
+ share_button = page.get_by_role("button", name="SHARE")
77
+ expect(share_button).to_be_visible()
@@ -2,11 +2,14 @@
2
2
  from django.core.exceptions import ValidationError
3
3
  from django.test import TestCase
4
4
 
5
+ from cookie_consent.cache import delete_cache
5
6
  from cookie_consent.models import Cookie, CookieGroup, validate_cookie_name
6
7
 
7
8
 
8
9
  class CookieGroupTest(TestCase):
9
10
  def setUp(self):
11
+ self.addCleanup(delete_cache)
12
+
10
13
  self.cookie_group = CookieGroup.objects.create(
11
14
  varname="optional",
12
15
  name="Optional",
@@ -24,6 +27,8 @@ class CookieGroupTest(TestCase):
24
27
 
25
28
  class CookieTest(TestCase):
26
29
  def setUp(self):
30
+ self.addCleanup(delete_cache)
31
+
27
32
  self.cookie_group = CookieGroup.objects.create(
28
33
  varname="optional",
29
34
  name="Optional",
@@ -1 +0,0 @@
1
- __version__ = "0.4.0"
@@ -1,67 +0,0 @@
1
- # -*- coding: utf-8 -*-
2
- from django.core.exceptions import SuspiciousOperation
3
- from django.http import HttpResponse, HttpResponseRedirect
4
- from django.urls import reverse
5
- from django.views.generic import ListView, View
6
-
7
- from .compat import RedirectURLMixin, url_has_allowed_host_and_scheme
8
- from .models import CookieGroup
9
- from .util import accept_cookies, decline_cookies
10
-
11
-
12
- class CookieGroupListView(ListView):
13
- """
14
- Display all cookies.
15
- """
16
-
17
- model = CookieGroup
18
-
19
-
20
- class CookieGroupBaseProcessView(RedirectURLMixin, View):
21
- def get_success_url(self):
22
- """
23
- If user adds a 'next' as URL parameter or hidden input,
24
- redirect to the value of 'next'. Otherwise, redirect to
25
- cookie consent group list
26
- """
27
- redirect_to = self.request.POST.get("next", self.request.GET.get("next"))
28
- if redirect_to and not url_has_allowed_host_and_scheme(
29
- url=redirect_to,
30
- allowed_hosts=self.get_success_url_allowed_hosts(),
31
- require_https=self.request.is_secure(),
32
- ):
33
- raise SuspiciousOperation("Unsafe open redirect suspected.")
34
- return redirect_to or reverse("cookie_consent_cookie_group_list")
35
-
36
- def process(self, request, response, varname):
37
- raise NotImplementedError()
38
-
39
- def post(self, request, *args, **kwargs):
40
- varname = kwargs.get("varname", None)
41
- if request.META.get("HTTP_X_REQUESTED_WITH") == "XMLHttpRequest":
42
- response = HttpResponse()
43
- else:
44
- response = HttpResponseRedirect(self.get_success_url())
45
- self.process(request, response, varname)
46
- return response
47
-
48
-
49
- class CookieGroupAcceptView(CookieGroupBaseProcessView):
50
- """
51
- View to accept CookieGroup.
52
- """
53
-
54
- def process(self, request, response, varname):
55
- accept_cookies(request, response, varname)
56
-
57
-
58
- class CookieGroupDeclineView(CookieGroupBaseProcessView):
59
- """
60
- View to decline CookieGroup.
61
- """
62
-
63
- def process(self, request, response, varname):
64
- decline_cookies(request, response, varname)
65
-
66
- def delete(self, request, *args, **kwargs):
67
- return self.post(request, *args, **kwargs)