Source code for duck.utils.cookie_consent

"""
Utility functions for managing cookie consent in Duck framework apps.

**This module helps you:**
- Parse and check cookie consent categories from incoming requests.
- Set cookie consent on the response (server-driven).
- Generate a cookie string for use in client-side (JavaScript) dynamic consent banners.

Consent is stored as a JSON-encoded cookie, e.g.:

```json
{"analytics": true, "marketing": false}
```

**Recommended usage:**
- Use the same cookie name (default: "cookie_consent") on both backend and frontend.
- Set cookie via backend `set_cookie_consent()` after user accepts/rejects consent, or
- Generate a cookie string with `generate_cookie_consent_str()` for frontend use in dynamic modals.

**Example:** 

```py
# Check consent in a view
if has_cookie_consent(request, "analytics"):
    # Run analytics logic
    ...

# Set consent on response (server-driven)
set_cookie_consent(response, {"analytics": True, "marketing": False})

# Generate cookie string for JS (dynamic modal)
cookie_str = generate_cookie_consent_str({"analytics": True}, max_age=31536000)

# In JS: document.cookie = "{{ cookie_str }}";
```

**Functions:**
- get_cookie_consents(request, cookie_name)
- has_cookie_consent(request, category, cookie_name)
- set_cookie_consent(response, consents, cookie_name, **kwargs)
- generate_cookie_consent_str(consents, cookie_name, **kwargs)

------
USAGE EXAMPLES

1. **Server-Driven Approach (Recommended for backend-controlled consent):**

```py
from duck.utils.cookie_consent import set_cookie_consent, has_cookie_consent

def accept_cookies_view(request):
    response = HttpResponse("Consent updated")
    set_cookie_consent(response, {"analytics": True, "marketing": False})
    return response

def view(request):
    if has_cookie_consent(request, "analytics"):
        # Analytics code here
        pass
```

2. **Dynamic JS Approach (Frontend-driven banners):**

```py
from duck.utils.cookie_consent import generate_cookie_consent_str
from duck.html.components.page import Page
from duck.html.components.button import Button

def consent_banner_js(request):
    # Used to prefill consent state in JS, or as an example for your modal
    page = Page(request)
    accept_btn = Button(text="Accept All")
    page.add_to_body(accept_btn)
    
    async def on_accept(btn, event, value, ws):
        cookie_str = generate_cookie_consent_str({"analytics": True, "marketing": True})
        await ws.execute_js(f"document.cookie='{cookie_str}'';")
    
    # Bind event on click
    accept_btn.bind("click", on_accept, update_self=False, update_targets=[])    
    return page
```
"""

import json
from datetime import timedelta, datetime


















[docs] def _urlencode(value): """ Helper for JS-safe cookie encoding. """ try: from urllib.parse import quote return quote(value) except ImportError: # Python 2 fallback, not needed in Duck/Python3 import urllib return urllib.quote(value)
[docs] def _urldecode(value): """ Helper for JS-safe cookie decoding. """ try: from urllib.parse import unquote return unquote(value) except ImportError: # Python 2 fallback, not needed in Duck/Python3 import urllib return urllib.unquote(value)