Repository: jazzband/django-cookie-consent (language: python)

Issue:
No need to show cookie bar if only required cookies are used (no third party cookies)
I don't think it is necessary to show the cookie bar and have users accept/decline anything if only required cookies are involved and not third party cookies. Unless of course the law requires you to show it no matter what. 

Maybe this should be an optional setting just in case it needs to turn on again even for required cookies. 


Discussion / hints:
I may be missing something but I only created a required cookie group and a cookie within that required cookie group. The cookie bar did not show up when I did this and buttons for accept/decline did not show up on cookiegroup_list.html for that required cookie group. There are no other non-required cookie groups in the database. Can someone else confirm that this is the current behaviour? I don't think anything needs to be done with this unless it is necessary to accept/decline required cookies.
Added this for 0.4.0 to verify:

- [ ] Check that there is a unit test, if not, write it
- [ ] Confirm the desired behaviour is in place, or implement it (possibly behind an option)
Okay, I checked the docs and code, and as far as I can see:

* ~~cookie-consent does not ship with a consent bar by default~~ this is the javascript cookie bar function :)
* there is also no way to currently determine if only required cookie groups exist, which would inform your template whether to render a bar or not
* You can (we have) build this based on the `not_accepted_or_declined_cookie_groups` template tag, which indeed seems to not take into account required cookie groups
cookie_consent_tags.py:

```
from cookie_consent.models import CookieGroup

@register.simple_tag()
def only_required_cookie_groups_exists():
    if CookieGroup.objects.filter(is_required=False).count() == 0 and CookieGroup.objects.filter(is_required=True).count() > 0:
        return True
    else:
        return False
```


test_page.html:

`{% only_required_cookie_groups_exists %}`

Util.py not_accepted_or_declined_cookie_groups:

get_cookie_groups() 
dict_values([<CookieGroup: Stripe>])

[cookie_group for cookie_group in get_cookie_groups() if get_cookie_value_from_request(request, cookie_group.varname) is None]
[<CookieGroup: Stripe>]

Indeed, this not_accepted_or_declined_cookie_groups function does not account for required cookies. 

Sites need to have required cookies like sessionid and csrftoken loaded to be able to view the webpage without acceptance. 

Are we suggesting to allow users to accept required cookies? I don't think it is necessary to change anything here besides adding the template tag. The django developer could choose to have required cookies that do not need acceptance and then for other first party cookies that do some sort of intrusive privacy thing, the django developer could make another Cookie Group that gives the site visitor the option to accept/decline that first party Cookie Group. 

Fix patch:
diff --git a/.gitignore b/.gitignore
index 8cc286e..b0196e7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,8 +2,9 @@
 *.pot
 *.pyc
 local_settings.py
+env/
 
-# build artificats
+# build artifacts
 docs/_build
 build/
 dist/
diff --git a/docs/example_app.rst b/docs/example_app.rst
index 4f97cd8..98d8c8d 100644
--- a/docs/example_app.rst
+++ b/docs/example_app.rst
@@ -2,4 +2,50 @@
 Example app
 ===========
 
-See ``testapp`` and ``tests`` directories for an example app structure and usage.
+The ``testapp`` project is both an example of how you could use this library and serves
+as the reference for our test suite.
+
+Running the testapp
+-------------------
+
+The testapp is essentially a standard django project, however there is no ``manage.py``
+file. Instead, you have to use the ``django-admin`` command (``manage.py`` is only
+a wrapper around this anyway).
+
+#. First, clone the repository to get all the necessary files:
+
+   .. code-block:: bash
+
+       git clone https://github.com/jazzband/django-cookie-consent.git
+       cd django-cookie-consent
+
+#. Create a virtual environment for the project, using any supported Python version
+   (3.8+) and activate it
+
+   .. code-block:: bash
+
+       python3.10 -m venv ./env
+       source ./env/bin/activate
+
+#. Install the application and dependencies
+
+   .. code-block:: bash
+
+       pip install .
+
+#. Prepare your settings and local project instance
+
+   .. code-block:: bash
+
+       export DJANGO_SETTINGS_MODULE=testapp.settings PYTHONPATH=.
+       django-admin migrate
+       django-admin loaddata testapp/fixture.json
+       django-admin createsuperuser
+
+#. Start the development server
+
+   .. code-block:: bash
+
+       django-admin runserver
+
+You can now navigate to ``http://127.0.0.1:8000`` and ``http://127.0.0.1:8000/admin/``.


Test patch:
diff --git a/testapp/fixture.json b/testapp/fixture.json
new file mode 100644
index 0000000..3e4c452
--- /dev/null
+++ b/testapp/fixture.json
@@ -0,0 +1,77 @@
+[
+{
+    "model": "cookie_consent.cookiegroup",
+    "pk": 1,
+    "fields": {
+        "varname": "social",
+        "name": "Social",
+        "description": "",
+        "is_required": false,
+        "is_deletable": true,
+        "ordering": 1,
+        "created": "2023-06-11T13:31:03.677Z"
+    }
+},
+{
+    "model": "cookie_consent.cookiegroup",
+    "pk": 2,
+    "fields": {
+        "varname": "optional",
+        "name": "Optional",
+        "description": "",
+        "is_required": false,
+        "is_deletable": true,
+        "ordering": 2,
+        "created": "2023-06-11T13:40:26.842Z"
+    }
+},
+{
+    "model": "cookie_consent.cookiegroup",
+    "pk": 3,
+    "fields": {
+        "varname": "required",
+        "name": "Required",
+        "description": "",
+        "is_required": true,
+        "is_deletable": false,
+        "ordering": 0,
+        "created": "2023-06-11T13:40:58.798Z"
+    }
+},
+{
+    "model": "cookie_consent.cookie",
+    "pk": 1,
+    "fields": {
+        "cookiegroup": 3,
+        "name": "sessionid",
+        "description": "Session ID to stay logged in.",
+        "path": "/",
+        "domain": "",
+        "created": "2023-06-11T13:41:39.982Z"
+    }
+},
+{
+    "model": "cookie_consent.cookie",
+    "pk": 2,
+    "fields": {
+        "cookiegroup": 1,
+        "name": "dummy",
+        "description": "",
+        "path": "/",
+        "domain": ".google.com",
+        "created": "2023-06-11T13:43:26.360Z"
+    }
+},
+{
+    "model": "cookie_consent.cookie",
+    "pk": 3,
+    "fields": {
+        "cookiegroup": 2,
+        "name": "sample",
+        "description": "",
+        "path": "/",
+        "domain": "",
+        "created": "2023-06-11T13:43:43.888Z"
+    }
+}
+]
diff --git a/tests/conftest.py b/tests/conftest.py
index e69de29..02ac3f4 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -0,0 +1,27 @@
+import pytest
+
+from cookie_consent.models import Cookie, CookieGroup
+
+
+@pytest.fixture
+def required_cookiegroup(db):
+    group = CookieGroup.objects.create(
+        varname="required",
+        name="Functional cookies",
+        is_required=True,
+        is_deletable=False,
+    )
+    Cookie.objects.create(cookiegroup=group, name="sessionid")
+    return group
+
+
+@pytest.fixture
+def optional_cookiegroup(db):
+    group = CookieGroup.objects.create(
+        varname="optional",
+        name="Optional cookies",
+        is_required=False,
+        is_deletable=True,
+    )
+    Cookie.objects.create(cookiegroup=group, name="evil-tracking")
+    return group
diff --git a/tests/test_templatetags.py b/tests/test_templatetags.py
new file mode 100644
index 0000000..f01ba3c
--- /dev/null
+++ b/tests/test_templatetags.py
@@ -0,0 +1,50 @@
+from textwrap import dedent
+from typing import Any, Dict, Optional
+
+from django.template import Context, Template
+
+import pytest
+
+
+def render(tpl: str, context: Optional[Dict[str, Any]] = None) -> str:
+    template = Template(dedent(tpl).strip())
+    return template.render(Context(context))
+
+
+NOT_ACCEPT_OR_DECLINED_TEMPLATE = """
+{% load cookie_consent_tags %}
+{% not_accepted_or_declined_cookie_groups request as cookie_groups %}
+{% if cookie_groups %}FOUND COOKIES{% else %}NO COOKIES{% endif %}
+"""
+
+
+@pytest.mark.django_db
+def test_not_accepted_or_declined_cookie_groups_only_required_cookies(
+    required_cookiegroup, rf
+):
+    context = {"request": rf.get("/")}
+
+    output = render(NOT_ACCEPT_OR_DECLINED_TEMPLATE, context).strip()
+
+    assert output == "NO COOKIES"
+
+
+@pytest.mark.django_db
+def test_not_accepted_or_declined_cookie_groups_only_optional_cookies(
+    optional_cookiegroup, rf
+):
+    context = {"request": rf.get("/")}
+
+    output = render(NOT_ACCEPT_OR_DECLINED_TEMPLATE, context).strip()
+
+    assert output == "FOUND COOKIES"
+
+
+def test_not_accepted_or_declined_cookie_groups_required_and_optional_cookies(
+    required_cookiegroup, optional_cookiegroup, rf
+):
+    context = {"request": rf.get("/")}
+
+    output = render(NOT_ACCEPT_OR_DECLINED_TEMPLATE, context).strip()
+
+    assert output == "FOUND COOKIES"