django-cooco 0.0.1__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.
File without changes
django_cooco/admin.py ADDED
@@ -0,0 +1,14 @@
1
+ from django.contrib.admin import ModelAdmin, register
2
+ from solo.admin import SingletonModelAdmin
3
+
4
+ from django_cooco.models import BannerConfig, CookieGroup
5
+
6
+
7
+ @register(BannerConfig)
8
+ class BannerConfigAdmin(SingletonModelAdmin):
9
+ pass
10
+
11
+
12
+ @register(CookieGroup)
13
+ class CookieGroupAdmin(ModelAdmin):
14
+ readonly_fields = ("version",)
django_cooco/apps.py ADDED
@@ -0,0 +1,7 @@
1
+ from django.apps import AppConfig
2
+
3
+
4
+ class CooCoConfig(AppConfig):
5
+ default_auto_field = "django.db.models.BigAutoField"
6
+ name = "django_cooco"
7
+ label = "cooco"
@@ -0,0 +1,34 @@
1
+ # Generated by Django 5.1.5 on 2025-01-20 19:44
2
+
3
+ from django.db import migrations, models
4
+
5
+
6
+ class Migration(migrations.Migration):
7
+ initial = True
8
+
9
+ dependencies = ()
10
+
11
+ operations = (
12
+ migrations.CreateModel(
13
+ name="BannerConfig",
14
+ fields=[
15
+ ("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")),
16
+ ("title", models.CharField(max_length=100, unique=True)),
17
+ ("text", models.CharField(max_length=200)),
18
+ ("show_banner", models.BooleanField(default=False)),
19
+ ],
20
+ options={
21
+ "abstract": False,
22
+ },
23
+ ),
24
+ migrations.CreateModel(
25
+ name="CookieGroup",
26
+ fields=[
27
+ ("cookie_id", models.CharField(max_length=50, primary_key=True, serialize=False)),
28
+ ("name", models.CharField(max_length=50)),
29
+ ("description", models.TextField()),
30
+ ("is_required", models.BooleanField()),
31
+ ("version", models.PositiveBigIntegerField(default=0, editable=False)),
32
+ ],
33
+ ),
34
+ )
File without changes
django_cooco/models.py ADDED
@@ -0,0 +1,26 @@
1
+ from django.db import models
2
+ from solo.models import SingletonModel
3
+
4
+
5
+ class BannerConfig(SingletonModel): # type: ignore[django-manager-missing] # https://github.com/typeddjango/django-stubs/issues/1023
6
+ title = models.CharField(max_length=100, unique=True)
7
+ text = models.CharField(max_length=200)
8
+ show_banner = models.BooleanField(default=False)
9
+
10
+ def __str__(self) -> str:
11
+ return "Cookie consent banner"
12
+
13
+
14
+ class CookieGroup(models.Model): # type: ignore[django-manager-missing] # https://github.com/typeddjango/django-stubs/issues/1023
15
+ cookie_id = models.CharField(max_length=50, primary_key=True)
16
+ name = models.CharField(max_length=50)
17
+ description = models.TextField()
18
+ is_required = models.BooleanField()
19
+ version = models.PositiveBigIntegerField(default=0, editable=False)
20
+
21
+ def __str__(self) -> str:
22
+ return self.name
23
+
24
+ def save(self, *args, **kwargs):
25
+ self.version += 1
26
+ super().save(*args, **kwargs)
django_cooco/py.typed ADDED
File without changes
File without changes
@@ -0,0 +1,39 @@
1
+ from collections.abc import Iterable
2
+
3
+ from django import template
4
+ from django.http import HttpRequest
5
+
6
+ from django_cooco.models import BannerConfig, CookieGroup
7
+ from django_cooco.utils import CooCoManager
8
+
9
+ register = template.Library()
10
+
11
+
12
+ @register.simple_tag
13
+ def get_cooco_banner_config() -> BannerConfig:
14
+ return BannerConfig.get_solo()
15
+
16
+
17
+ @register.simple_tag
18
+ def get_cookie_groups() -> Iterable[CookieGroup]:
19
+ return CookieGroup.objects.all() # type: ignore[attr-defined]
20
+
21
+
22
+ @register.simple_tag
23
+ def get_cooco_manager(request: HttpRequest) -> CooCoManager:
24
+ return CooCoManager.from_request(request)
25
+
26
+
27
+ @register.filter
28
+ def ask_for_cooco(cooco_manager: CooCoManager) -> bool:
29
+ return cooco_manager.is_cooco_outdated()
30
+
31
+
32
+ @register.filter
33
+ def is_cookie_group_accepted(cooco_manager: CooCoManager, cookie_group: CookieGroup) -> bool:
34
+ return cookie_group.is_required or cooco_manager.is_cookie_group_accepted(cookie_group.cookie_id)
35
+
36
+
37
+ @register.filter
38
+ def any_optional_cookie_group(cookie_groups: Iterable[CookieGroup]) -> bool:
39
+ return any(cookie_group for cookie_group in cookie_groups if not cookie_group.is_required)
django_cooco/urls.py ADDED
@@ -0,0 +1,8 @@
1
+ from django.urls import path
2
+
3
+ from django_cooco.views import AcceptAllCookies, SetCookiePreferences
4
+
5
+ urlpatterns = (
6
+ path("accept-all", AcceptAllCookies.as_view(), name="accept_all_cookies"),
7
+ path("set-preferences", SetCookiePreferences.as_view(), name="set_cookie_preferences"),
8
+ )
django_cooco/utils.py ADDED
@@ -0,0 +1,132 @@
1
+ import json
2
+ from collections.abc import Iterable, Sequence
3
+ from typing import Any, NamedTuple, TypeVar
4
+
5
+ from django.conf import settings
6
+ from django.db.models.query import QuerySet
7
+ from django.http import HttpRequest, HttpResponse
8
+ from typing_extensions import Self
9
+
10
+ from django_cooco.models import CookieGroup
11
+
12
+ THttpResponse = TypeVar("THttpResponse", bound=HttpResponse)
13
+
14
+
15
+ class CooCoStatus(NamedTuple):
16
+ version: int
17
+ is_accepted: bool
18
+
19
+
20
+ class CooCo(NamedTuple):
21
+ cookie_id: str
22
+ current_version: int
23
+ cooco_status: CooCoStatus
24
+
25
+ @property
26
+ def is_version_outdated(self) -> bool:
27
+ return self.current_version != self.cooco_status.version
28
+
29
+ @property
30
+ def is_accepted(self) -> bool:
31
+ return not self.is_version_outdated and self.cooco_status.is_accepted
32
+
33
+
34
+ class CooCoManager:
35
+ COOCO_COOKIE_NAME = getattr(settings, "COOCO_COOKIE_NAME", "cooco")
36
+ COOCO_COOKIE_MAX_AGE = getattr(settings, "COOCO_COOKIE_MAX_AGE", 60 * 60 * 24 * 365 * 1)
37
+ DEFAULT_COOCO_STATUS = CooCoStatus(0, False)
38
+
39
+ def __init__(self, cooco: Iterable[CooCo] | None) -> None:
40
+ if cooco is not None:
41
+ self.__coocos = tuple(cooco)
42
+ self.__is_cooco_set = True
43
+ else:
44
+ self.__coocos = ()
45
+ self.__is_cooco_set = False
46
+
47
+ def __getitem__(self, cookie_id: str) -> CooCo:
48
+ for cooco in self.__coocos:
49
+ if cooco.cookie_id == cookie_id:
50
+ return cooco
51
+
52
+ message = f"Cookie ID '{cookie_id}' not found"
53
+ raise KeyError(message)
54
+
55
+ @staticmethod
56
+ def __get_optional_cookie_groups() -> QuerySet[CookieGroup]:
57
+ return CookieGroup.objects.exclude(is_required=True) # type: ignore[attr-defined]
58
+
59
+ @classmethod
60
+ def from_request(cls, request: HttpRequest) -> Self:
61
+ try:
62
+ cooco_dict = json.loads(request.COOKIES[cls.COOCO_COOKIE_NAME])
63
+ except (KeyError, json.decoder.JSONDecodeError):
64
+ return cls(None)
65
+
66
+ _coocos = []
67
+
68
+ for cookie_group in cls.__get_optional_cookie_groups():
69
+ cooco_status = cooco_dict.get(cookie_group.cookie_id)
70
+
71
+ if (
72
+ not isinstance(cooco_status, Sequence)
73
+ or len(cooco_status) != len(cls.DEFAULT_COOCO_STATUS)
74
+ or not isinstance(cooco_status[0], int)
75
+ or not isinstance(cooco_status[1], bool)
76
+ ):
77
+ cooco_status = cls.DEFAULT_COOCO_STATUS
78
+
79
+ _coocos.append(
80
+ CooCo(
81
+ cookie_id=cookie_group.cookie_id,
82
+ current_version=cookie_group.version,
83
+ cooco_status=CooCoStatus(*cooco_status),
84
+ )
85
+ )
86
+
87
+ return cls(_coocos)
88
+
89
+ @classmethod
90
+ def parse_cooco_form(cls, cooco_form: dict[str, Any]) -> Self:
91
+ return cls(
92
+ (
93
+ CooCo(
94
+ cookie_id=cookie_group.cookie_id,
95
+ current_version=cookie_group.version,
96
+ cooco_status=CooCoStatus(
97
+ cookie_group.version,
98
+ cooco_form.get(cookie_group.cookie_id) == "on",
99
+ ),
100
+ )
101
+ for cookie_group in cls.__get_optional_cookie_groups()
102
+ )
103
+ )
104
+
105
+ @classmethod
106
+ def all_cookies_accepted(cls) -> Self:
107
+ return cls(
108
+ (
109
+ CooCo(
110
+ cookie_id=cookie_group.cookie_id,
111
+ current_version=cookie_group.version,
112
+ cooco_status=CooCoStatus(cookie_group.version, True),
113
+ )
114
+ for cookie_group in cls.__get_optional_cookie_groups()
115
+ )
116
+ )
117
+
118
+ def set_cooco_cookie(self, response: THttpResponse) -> THttpResponse:
119
+ response.set_cookie(
120
+ self.COOCO_COOKIE_NAME,
121
+ json.dumps({cooco.cookie_id: cooco.cooco_status for cooco in self.__coocos}),
122
+ max_age=self.COOCO_COOKIE_MAX_AGE,
123
+ samesite="Lax",
124
+ )
125
+
126
+ return response
127
+
128
+ def is_cooco_outdated(self) -> bool:
129
+ return not self.__is_cooco_set or any(cooco.is_version_outdated for cooco in self.__coocos)
130
+
131
+ def is_cookie_group_accepted(self, cookie_id: str) -> bool:
132
+ return self.__is_cooco_set and self[cookie_id].is_accepted
django_cooco/views.py ADDED
@@ -0,0 +1,25 @@
1
+ from abc import ABC, abstractmethod
2
+
3
+ from django.http import HttpRequest, HttpResponseRedirect
4
+ from django.views.generic.base import View
5
+
6
+ from django_cooco.utils import CooCoManager
7
+
8
+
9
+ class SetCookiesBaseView(View, ABC):
10
+ @staticmethod
11
+ def _set_cookie_and_redirect(request: HttpRequest, cookie_group_statuses: CooCoManager) -> HttpResponseRedirect:
12
+ return cookie_group_statuses.set_cooco_cookie(HttpResponseRedirect(request.POST.get("next", "/")))
13
+
14
+ @abstractmethod
15
+ def post(self, request: HttpRequest) -> HttpResponseRedirect: ...
16
+
17
+
18
+ class AcceptAllCookies(SetCookiesBaseView):
19
+ def post(self, request: HttpRequest) -> HttpResponseRedirect:
20
+ return self._set_cookie_and_redirect(request, CooCoManager.all_cookies_accepted())
21
+
22
+
23
+ class SetCookiePreferences(SetCookiesBaseView):
24
+ def post(self, request: HttpRequest) -> HttpResponseRedirect:
25
+ return self._set_cookie_and_redirect(request, CooCoManager.parse_cooco_form(request.POST))
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Roberto Fernández Iglesias
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,329 @@
1
+ Metadata-Version: 2.3
2
+ Name: django-cooco
3
+ Version: 0.0.1
4
+ Summary: Django app to manage cookie consents
5
+ License: MIT
6
+ Author: roberfi
7
+ Requires-Python: >=3.10
8
+ Classifier: Development Status :: 4 - Beta
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: Programming Language :: Python :: 3.10
11
+ Classifier: Programming Language :: Python :: 3.11
12
+ Classifier: Programming Language :: Python :: 3.12
13
+ Classifier: Programming Language :: Python :: 3.13
14
+ Classifier: Framework :: Django
15
+ Classifier: Framework :: Django :: 5.0
16
+ Classifier: Framework :: Django :: 5.1
17
+ Classifier: Framework :: Django :: 5.2
18
+ Classifier: Operating System :: Unix
19
+ Classifier: Operating System :: MacOS
20
+ Classifier: Operating System :: Microsoft :: Windows
21
+ Classifier: Operating System :: OS Independent
22
+ Classifier: Intended Audience :: Developers
23
+ Classifier: License :: OSI Approved :: MIT License
24
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
25
+ Requires-Dist: django (>=5.0,<6.0)
26
+ Requires-Dist: django-solo (>=2.4,<2.5)
27
+ Project-URL: Repository, https://github.com/roberfi/django-cooco
28
+ Description-Content-Type: text/markdown
29
+
30
+ # django-cooco
31
+
32
+ django-cooco is a Django app to manage cookie consent in Django projects. This library was inspired by [django-cookie-consent](https://github.com/jazzband/django-cookie-consent), which I have taken as referece to create a simplier and more customizable solution.
33
+
34
+ ## Quick start
35
+
36
+ 1. Add "django_cooco" to your `INSTALLED_APPS` setting like this:
37
+ ```python
38
+ INSTALLED_APPS = [
39
+ ...,
40
+ "django_cooco",
41
+ ]
42
+ ```
43
+
44
+ 2. Include the django_cooco URLconf in your project `urls.py` like this:
45
+
46
+ ```python
47
+ urlpatterns = (
48
+ ...,
49
+ path("cooco/", include("django_cooco.urls")),
50
+ )
51
+ ```
52
+
53
+ 3. Run `python manage.py migrate` to create the models.
54
+
55
+ ## Settings
56
+ You can configure the following settings in your project `settings.py` file:
57
+
58
+ | Setting | Type | Definition | Mandatory | Default value |
59
+ | ---------------------- | ----- | --------------------------------------------------------------- | --------- | ------------------- |
60
+ | `COOCO_COOKIE_NAME` | `str` | Name of the cookie to store the cookie consent status | False | `"cooco"` |
61
+ | `COOCO_COOKIE_MAX_AGE` | `int` | Max age of the cookie where the cookie consent status is stored | False | `31536000` (1 year) |
62
+
63
+ ## Database
64
+ ### Models
65
+
66
+ #### `BannerConfig`
67
+ A singleton model to configure the cookie consent banner title and text:
68
+
69
+ | Field | Type | Definition |
70
+ | ------------- | -------------- | ---------------------------------------------------- |
71
+ | `title` | `CharField` | Title of the cookie consent banner |
72
+ | `text` | `CharField` | Text to show in the cookie consent banner |
73
+ | `show_banner` | `BooleanField` | Whether cookie consent banner should be shown or not |
74
+
75
+ #### `CookieGroup`
76
+ The model to set the groups of cookies that will be used by the web:
77
+
78
+ | Field | Type | Definition |
79
+ | ------------- | ------------------------- | -------------------------------------------------------------------------------------------------------------------- |
80
+ | `cookie_id` | `CharField` | Unique id of the cookie group |
81
+ | `name` | `CharField` | Human readable name of the cookie group |
82
+ | `description` | `TextField` | Description of the cookies of that cookie group |
83
+ | `is_required` | `BooleanField` | Whether the cookie group is required or optional |
84
+ | `version` | `PositiveBigIntegerField` | Non-editable version of the cookie group object. It will be automatically increased after every saving of the model. |
85
+
86
+ ### Database Relationships
87
+ To use optional cookies, you could add a `ForeignKey` field to your cookie consent dependent model. For example:
88
+ ```python
89
+ from django.db import models
90
+ from django_cooco.models import CookieGroup
91
+
92
+
93
+ class GoogleAnalytics(models.Model):
94
+ gtag = models.CharField(max_length=20)
95
+ cookie_consent = models.ForeignKey(CookieGroup, on_delete=models.RESTRICT)
96
+
97
+ def __str__(self):
98
+ return "Google Analytics"
99
+
100
+ ```
101
+
102
+ ## Tags and filters
103
+ To use them you need to load `cooco` tags:
104
+ ```
105
+ {% load cooco %}
106
+ ```
107
+
108
+ ### Tags
109
+ #### `get_cooco_banner_config`
110
+
111
+ Returns the needed data to display the cookie consent banner.
112
+
113
+ **Returns:**
114
+ - (`BannerConfig`): the data stored in the BannerConfig database model.
115
+
116
+ **Usage example:**
117
+ ```html
118
+ {% get_cooco_banner_config as banner %}
119
+ {% if banner.show_banner %}
120
+ <div id="cooco_banner">
121
+ <div>
122
+ <h2>
123
+ {{ banner.title }}
124
+ </h2>
125
+ <p>
126
+ {{ banner.text }}
127
+ </p>
128
+ </div>
129
+ </div>
130
+ {% endif %}
131
+ ```
132
+
133
+ #### `get_cookie_groups`
134
+
135
+ Returns the data of the configured cookie groups.
136
+
137
+ **Returns:**
138
+ - (`Iterable[CookieGroup]`): the data stored in the CookieGroup database model.
139
+
140
+ **Usage example:**
141
+ ```
142
+ {% get_cookie_groups as cookie_groups %}
143
+ ```
144
+
145
+ #### `get_cooco_manager`
146
+
147
+ Returns the status of the cookie consent in the request to the server.
148
+
149
+ **Arguments:**
150
+ - `request` (`HttpRequest`): the request to the server.
151
+
152
+ **Returns:**
153
+ - (`CooCoManager`): object containing the status of the cookie consent built from the request that can be used in some filters (see below).
154
+
155
+ **Usage example:**
156
+ ```
157
+ {% get_cooco_manager request as cooco_manager %}
158
+ ```
159
+
160
+ ### Filters
161
+ #### `ask_for_cooco`
162
+
163
+ Checks whether the cookie consent banner should be shown or not giving the object returned from `get_cooco_manager` tag.
164
+
165
+ **Arguments:**
166
+ - `cooco_manager` (`CooCoManager`): object containing the status of the cookie consent.
167
+
168
+ **Returns:**
169
+ - (`bool`): whether the cookie consent banner should be shown or not.
170
+
171
+ **Usage example:**
172
+ ```
173
+ {% if cooco_manager|ask_for_cooco %}
174
+ ...
175
+ {% endif %}
176
+ ```
177
+
178
+ #### `is_cookie_group_accepted`
179
+
180
+ Checks whether the given cookie group is accepted by the user or not giving the object returned from `get_cooco_manager` tag.
181
+
182
+ **Arguments:**
183
+ - `cooco_manager` (`CooCoManager`): object containing the status of the cookie consent.
184
+ - `cookie_group` (`CookieGroup`): cookie group to check.
185
+
186
+ **Returns:**
187
+ - (`bool`): whether the given cookie group is accepted by the user or not.
188
+
189
+ **Usage example:**
190
+ ```
191
+ {% if cooco_manager|is_cookie_group_accepted:cookie_consent %}
192
+ ...
193
+ {% endif %}
194
+ ```
195
+
196
+ #### `any_optional_cookie_group`
197
+
198
+ Checks if any of the given cookies is optional. This could be useful if you have to display a settings button or just "accept" option.
199
+
200
+ **Arguments:**
201
+ - `cookie_groups` (`Iterable[CookieGroup]`): iterable of cookie groups to check.
202
+
203
+ **Returns:**
204
+ - (`bool`): where there is any optional cookie group or not.
205
+
206
+ **Usage example:**
207
+ ```
208
+ {% if cookie_groups|any_optional_cookie_group %}
209
+ ...
210
+ {% endif %}
211
+ ```
212
+
213
+ ## URLs
214
+ #### `POST 'set_cookie_preferences'`
215
+ Set cookie preferences.
216
+
217
+ **Request fields:**
218
+ | Field | Type | Definition |
219
+ | ---------------------------------- | ----- | ------------------------------------- |
220
+ | `next` | `str` | Path to redirect when request is sent |
221
+ | cookie_id* (additional properties) | `str` | "on" if accepted, otherwise rejected |
222
+
223
+ **Example:**
224
+ ```json
225
+ {
226
+ "next": "/home",
227
+ "analytics": "on",
228
+ "ads": "off",
229
+ }
230
+ ```
231
+
232
+ #### `POST 'accept_all_cookies'`
233
+ Accept all cookies.
234
+
235
+ **Request fields:**
236
+ | Field | Type | Definition |
237
+ | ------ | ----- | ------------------------------------- |
238
+ | `next` | `str` | Path to redirect when request is sent |
239
+
240
+ **Example:**
241
+ ```json
242
+ {
243
+ "next": "/home",
244
+ }
245
+ ```
246
+
247
+ ## Full example
248
+ Here you can see an example of how the full functionality implementation could look like. Note that `"analytics"` entry is present in the template context, which contains a database entry with a `ForeignKey` field to `CookieGroup` table called `"cookie_consent"` (see "Database Relationships" section of this document).
249
+
250
+ ```html
251
+ {% load cooco %}
252
+
253
+ {% get_cooco_manager request as cooco_manager %}
254
+
255
+ <!DOCTYPE html>
256
+ <html>
257
+ <head>
258
+ <title>Cooco Example</title>
259
+
260
+ {% if cooco_manager|is_cookie_group_accepted:analytics.cookie_consent %}
261
+ <script>
262
+ // Script to use Analytics
263
+ </script>
264
+ {% endif %}
265
+ </head>
266
+
267
+ <body>
268
+ <p>This is an example of Cooco usage</p>
269
+
270
+ {% if cooco_manager|ask_for_cooco %}
271
+ {% get_cooco_banner_config as banner %}
272
+
273
+ {% if banner.show_banner %}
274
+ <div id="cooco_banner">
275
+ <div>
276
+ <h2>
277
+ {{ banner.title }}
278
+ </h2>
279
+ <p>
280
+ {{ banner.text }}
281
+ </p>
282
+
283
+ {% get_cookie_groups as cookie_groups %}
284
+
285
+ {% if cookie_groups|any_optional_cookie_group %}
286
+ <button onclick="showCookiesModal()">Settings</button>
287
+ <dialog>
288
+ <form class="inline-block"
289
+ action="{% url 'set_cookie_preferences' %}"
290
+ method="post">
291
+ {% csrf_token %}
292
+ <input name="next" type="hidden" value="{{ request.path }}" />
293
+ {% for cookie_group in cookie_groups %}
294
+ <div>
295
+ <h1>
296
+ {{ cookie_group.name }}
297
+ </h1>
298
+ <p>
299
+ {{ cookie_group.description }}
300
+ </p>
301
+ <input name="{{ cookie_group.cookie_id }}"
302
+ type="checkbox"
303
+ checked="checked"
304
+ {% if cookie_group.is_required %}disabled{% endif %} />
305
+ </div>
306
+ {% endfor %}
307
+ <button type="submit">Save</button>
308
+ </form>
309
+ </dialog>
310
+ {% endif %}
311
+
312
+ <form class="inline-block"
313
+ action="{% url 'accept_all_cookies' %}"
314
+ method="post">
315
+ {% csrf_token %}
316
+ <input name="next" type="hidden" value="{{ request.path }}">
317
+ <button type="submit">Accept</button>
318
+ </form>
319
+ </div>
320
+ </div>
321
+ {% endif %}
322
+
323
+ {% endif %}
324
+
325
+ </body>
326
+
327
+ </html>
328
+ ```
329
+
@@ -0,0 +1,16 @@
1
+ django_cooco/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ django_cooco/admin.py,sha256=DfkualLHgCpy6PeOo-LqCQtn-5ILyJfX-sTYycKjh90,333
3
+ django_cooco/apps.py,sha256=rnwVVKl7XMIFi6-0zSBy9Xs2n56VL_92aRd2lrS5DdU,169
4
+ django_cooco/migrations/0001_initial.py,sha256=ZEegJyMtcrxTlehcViMn-e7n_P9iW43BmmVdefPVRCU,1170
5
+ django_cooco/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
+ django_cooco/models.py,sha256=KnLDecv4OiFwdIWrPPKX7Y5vxqnfcS8D4imqenlR4hg,978
7
+ django_cooco/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
+ django_cooco/templatetags/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
9
+ django_cooco/templatetags/cooco.py,sha256=d07Tji5JFTIhAlCAp-5obJ4xczzQNoTq__545ubbhyc,1137
10
+ django_cooco/urls.py,sha256=pAXW1Q3Qd5S6EaQAHSKCTJoCmmMH-nyQurm__wwcos0,290
11
+ django_cooco/utils.py,sha256=HPApab38G8w6OXV8Pwdzsz82XDQ_nzu5wc7yG3aTqFA,4319
12
+ django_cooco/views.py,sha256=8b25_KErgiL5T-rv0ovkPChA0p_P7cMHDuUrTpzEzXA,973
13
+ django_cooco-0.0.1.dist-info/LICENSE.md,sha256=vmr_jObZmNHVZH-gySvJQv6Kz0HrEfy-Sq1QcSzPiTA,1084
14
+ django_cooco-0.0.1.dist-info/METADATA,sha256=oaWNrcYzzsYad-Svb3j_jOUDheq1PQN3snIjWufmclA,11617
15
+ django_cooco-0.0.1.dist-info/WHEEL,sha256=IYZQI976HJqqOpQU6PHkJ8fb3tMNBFjg-Cn-pwAbaFM,88
16
+ django_cooco-0.0.1.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: poetry-core 2.0.1
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any