Giter Site home page Giter Site logo

jazzband / django-cookie-consent Goto Github PK

View Code? Open in Web Editor NEW
223.0 223.0 73.0 274 KB

Reusable application for managing various cookies and visitors consent for their use in Django project.

Home Page: https://django-cookie-consent.readthedocs.org/en/latest/

License: BSD 2-Clause "Simplified" License

Python 75.59% JavaScript 2.13% HTML 10.08% CSS 0.21% TypeScript 11.99%

django-cookie-consent's People

Contributors

adilhussain540 avatar alahdal avatar andreasnuesslein avatar bmihelac avatar callmeumer avatar jasper-koops avatar jonherr avatar mejans avatar mrcordeiro avatar pre-commit-ci[bot] avatar sergei-maertens avatar some1ataplace avatar tomquincey avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

django-cookie-consent's Issues

How can i set cookie on root domain?

Is there a way to set the cookie-consent cookie on the root domain? e.g. *.mydomain.com I have a wildcard subdomain and right now the consent cookie includes the subdomain, making the consent banner popup appear for each subdomain i visit.

Required config line missing in documentation

I had some trouble setting up this app in my project, until I realized that in my settings.py I need the to set the COOKIE_CONSENT_NAME variable. That is, I added the following line:

COOKIE_CONSENT_NAME = "cookie_consent"

This step does seem rather important, but I didn't see it mentioned anywhere in the docs for setting up, specifically here: https://django-cookie-consent.readthedocs.io/en/latest/configuration.html.

I figured, this is the best place to let someone know ;-)

Roadmap

Link to project board: https://github.com/orgs/jazzband/projects/13/views/1


Now that 0.4.0 is released to PyPI and the most pressing matters are resolved, we can look ahead to the future and get a stable version published.

There is a milestone for the 1.0 release and any issues that must be resolved before that release are attached to it.

Note that jumping from the 0.x.y versioning scheme to 1.0.z allows for introducing breaking changes. The breaking changes towards 1.0 will likely require updating your code, but we should aim to do these as soon as possible if they are needed to get them out of the way.

Breaking changes after that will require a bump to 2.0, 3.0 etc.. Dropping support for no-longer maintained Django versions technically qualifies as a breaking change, so we will handle them as such. Those changes typically won't require you updating your code. If, however, we do introduce future breaking changes, they will be clearly communicated.

Now, for the roadmap - these are roughly the goals/reworks that must be completed before we can publish 1.0

  • Solving the Javascript integration properly (#15, #49). In short, there should probably be a move towards a JSON view for async fetching of the cookie groups/configuration and rewrite the cookiebar JS (and supporting JS) to handle this. Inline JS is annoying in general in combination with Content-Security-Policy too, so in general this should be a good move. Please try 0.5.0b0 and report back your experiences!
  • Scripting cleanup and tooling: ideally, people should be able to drop the shipped scripts/modules in Webpack or their bundler/toolchain of choice. I'd also like to explore <script type="module" ... /> to support modern browsers. This may not drop support for simple staticfiles integration and will still be available. We may set up a bit more of a toolchain for JS (and possibly look into Typescript so we can also ship types for users on a typescript toolchain). Modules are now available in 0.5.0b0, but integrating in your frontend toolchain depends on ideas/feedback from the community.
  • Fixture support (for moving between staging/prod environment) via natural primary key #103
  • Good defaults but extensible architecture - see #34

Views ignore 'next' url parameter

I can't make the redirect from CookieGroupBaseProcessView.get_success_url to work.

My guess is that's because the request object doesn't have a get method. Only its GET or POST QueryDict does.
So
url = self.request.get("next")
Should be replaced for
url = self.request.POST.get("next")

I noticed there weren't unit tests for that, so I'll try to add some later this weekend.

HTTPONLY setting

In version django-cookie-consent==0.3.2.dev1 the cookie defaults to HTTPONLY = True, which means you can't manipulate the cookie via javascript.

Would be good to be able to override from settings.py with, for example,
COOKIE_CONSENT_HTTPONLY = False

Needless to say I tried this and it didn't work

Clean up project structure (amongs others to meet Jazzband requirements)

Tasks:

  • Migrate CI from Travis CI to Github actions
    • Use pytest
    • Use tox
  • Set up black and isort
  • Set up test coverage (on codecov)
  • Check if package metadata is up to date
  • Make supported python/django versions explicit
  • Check documentation (badges, CI references...)
  • Check package metadata for PyPI
  • Drop support for end-of-life python and django versions
  • Add CONTRIBUTING.md
  • Add code of conduct file referening jazzband CoC
  • Add bump2version for version management?

Refresh page after clicking accept cookie bar

Allow users to click the accept button on the cookie bar and automatically refresh the page and load in the cookies that they did not have before on that page.

Use case:

  • Blog website that uses Google Adsense & Google Analytics cookies
  • User reads through half the blog and sees something like a cookie wall which cuts off blog text unless you accept cookies
  • User accepts cookies from the cookie bar
  • The page automatically refreshes and the user could now click ads, get analytics tracked, and read the rest of the blog page

The user does not have to remember to refresh the page themselves to load in the accepted cookies.

Maybe this should probably be a django-cookie-consent optional setting to enable/disable page refreshes after accepting the cookie bar.

We can use window.location.reload(); inside of cookiebar.js after body.classList.remove('with-cookie-bar');

But for a custom django setting, I am thinking we may need to put a django template if statement to see if the setting is True/False. Then if True, run something like this when Accept is clicked:

const elems = document.querySelectorAll('.cc-cookie-accept');

elems.forEach( el => el .addEventListener('click', fn, false) );

function fn(e) {
    e.preventDefault();
    window.location.reload();
}

Decode / unescape unicode:

<div class="cookie-bar">This site uses Social media, Optional (test) cookies cookies for better performance and user experience. Do you agree to use cookies? <a href="/cookies/accept/social,optional/" class="cc-cookie-accept">Accept</a>
  <a href="/cookies/decline/social,optional/" class="cc-cookie-decline">Decline</a>
  <a href="/cookies/">Cookies info</a>
</div>

https://www.online-toolz.com/tools/text-unicode-entities-convertor.php

I am not sure if we were to include this if it would cause rate limiting/DDoS issues if abused. Example - use selenium to accept cookies, refresh, delete cookies, repeat.

I am also not sure if doing this would effect https://support.google.com/adsense/answer/1346295?hl=en#Auto-refreshing_ads. I don't think it would because the ads are not even present on the site until the user accepts cookies and refreshes. Therefore, we are not refreshing the ads like that link says since we are not doing any programmatic refreshing when cookies are already accepted and the ads are already shown on the page.

Remove Third Party Cookies When Cookie Group Declined After Originally Accepted

When accepting a cookie group, the cookies for that cookie group get loaded into the browser normally. Then for that same cookie group, declining right after accepting will keep those same third party cookies from that cookie group loaded in the browser.

The user might not know how to go into their browser settings and delete the cookies themselves. They might think that declining them will get rid of the cookies.

Perhaps adding in logic to do the deleting of the third party cookies from that cookie group automatically when declined (must be originally accepted - not declined on the first time visiting your site) would help the untechnical user and lawyers from taking action against you because the users are still technically being tracked via the previously accepted cookies.

Maybe you could loop through all of the cookie names from that cookie group in the database and if that cookie group is declined, delete those same cookie names from the database that are in the browser. The only problem I see with this is if the third party's cookies change themselves automatically in the future or more are added by the third party over time without you knowing and not all of the new third party cookies are added in the database. The django-cookie-consent admin might not add the domain to the database either.

There are lots of Google results for javascript deleting cookies by domain or just deleting cookies in general but I am not sure which method is best. There are even ways to delete cookies via django instead of javascript.

Would adding a setting like COOKIE_DELETE_ON_DECLINED = True|False or COOKIE_DELETE = True|False work? Maybe instead of tying this feature to each group's decline button, have one separate button called "Delete" (so you will have "Accept", "Decline", "Delete") for each group or all groups to delete all of the cookies used on your site (separate group buttons for deleting would probably be more personalized for the user since they might not want to accept/decline each cookie group - otherwise additionally adding to the top of your cookie page one button to delete all cookies from all groups would need to do the same exact thing as clearing cookies in your browser which would reset the accept/decline buttons all together).

Maybe someone else has a better idea / vision how to handle this? I defer to the maintainers/authors. @MrCordeiro @bmihelac @andreasnuesslein @JonHerr @Jasper-Koops

Compatibility Issue with django-cookie-consent 0.3.1 and Django 4.2.2

Issue Description

I am encountering an ImportError while trying to use the django-cookie-consent package with Django 4.2.2. The specific error message I receive is as follows:

ImportError: cannot import name 'SuccessURLAllowedHostsMixin' from 'django.contrib.auth.views'

Steps to Reproduce

  1. Install Django 4.2.2.
  2. Install the latest version of django-cookie-consent (0.3.1).
  3. Followed the installation instructions mentioned in the documentation.
  4. Run the migrate command.

Expected Behavior

The migrate command should run successfully without any import errors.

Actual Behavior

The migrate command fails with the ImportError mentioned above.

Environment

  • Django version: 4.2.2
  • django-cookie-consent version: 0.3.1

Additional Information

I have checked that I am using the latest version of django-cookie-consent, and I have confirmed that Django 4.2.2 does include the SuccessURLAllowedHostsMixin in the django.contrib.auth.views module.

Please let me know if there are any compatibility fixes or workarounds available for this issue. Thank you.

Support for multiple languages?

Would it be possible to add support for multiple languages?

This module seems to be the closest to a throughout cookie manager for Django that is compatible with EU laws. However, the actual cookie descriptions would need to be translated as well which is not possible at this time. Maybe it would be possible to create an interface to Parler or something similar

Spam Protection

https://github.com/bmihelac/django-cookie-consent/blob/master/cookie_consent/templates/cookie_consent/_cookie_group.html

Problem:

The database can get really spammed / filled up with log items with people / bots spam clicking accept or decline more than once on the cookie group page.

Ideas:

  1. Delete the log database entries with a cron job with a manage.py command or perhaps a postgres mechanism of some kind?

  2. Create a setting to stop logging all together as an option. This would be a great feature to have in my opinion in addition to some sort of spam click protection on the cookie group page.

  3. Create a setting/JavaScript code for only accepting or declining every X number of minutes? Similar to Reddit and Stackoverflow. https://i.stack.imgur.com/4J3jr.png

  4. JavaScript disable/hide the accept or decline buttons after clicking the first time for that particular cookie. You are only able to submit the form once then the button gets disabled/hidden afterwards for that page load unless the user refreshes. Unless you do not use a for loop and generate each cookie group separately.

  5. If do deciding to keep logging enabled, change the accept/decline views to prevent clicking so many times and filling up the database.

https://stackoverflow.com/questions/31591356/allow-form-submission-only-once-a-day-django

Docs have blank sections

Tests do not appear to run in CI

Travis CI has become very open-source unfriendly, even to the point that now the link behind the "build passing" badge just 404s.

I can set up CI in Github Actions to get tests to run for pull requests and regular pushes.

Cache instance is resolved at import time rather than runtime

The cache to be used by django-cookie-consent is frozen at import time and makes it annoying to swap out cache backends during tests using django.test.override_settings.

Our default settings use the real cache backend, but in testing we're replacing this with the no-op cache backend. Except, because the cache instance is resolved at instance time, using override_settings has no effect. This means that during testing, test isolation is broken because there's now cache entries that span multiple tests.

https://github.com/bmihelac/django-cookie-consent/blob/510887228a5209d6355a99fca62bb6683d84b4d3/cookie_consent/cache.py#L6

I can resolve this for now using unittest.mock.patch, however, the get_cache call should really happen inside the functions exposed in the module rather than at import time.

beforeDeclined Not Working/Google Analytics Issue

BACKGROUND
I have a project that uses google analytics to track scrolling. To disable google analytics cookies you have to use this window['ga-disable-YOUR-GA-TAG'] = true; to prevent scroll tracking. (Otherwise when you scroll it will just make the cookie come back.)

Attempt At Using beforeDeclined
I saw in the test project for this package a beforeDeclined option in the "showCookieBar" JS function. To test this, I did the following:

var cookie_groups = [];
{% for cookie_group in cookie_groups %}
  cookie_groups.push("{{ cookie_group.varname }}");
{% endfor %}

function ready(fn) {
    if (document.readyState != 'loading') {
      fn();
    } else if (document.addEventListener) {
      document.addEventListener('DOMContentLoaded', fn);
    } else {
      document.attachEvent('onreadystatechange', function() {
        if (document.readyState != 'loading')
          fn();
      });
    }
}

ready(function() {
    showCookieBar({
    content: "... My Banner",
    cookie_groups: cookie_groups,
    cookie_decline: "{% get_decline_cookie_groups_cookie_string request cookie_groups %}",
    beforeDeclined: function() {
      console.log("Sanity Check") // Does not show up in the console
    }
  });
});

The console log "Sanity Check" does not show up in the console, making me believe the beforeDeclined option does not work. If so, I cannot use the window['ga-disable-YOUR-GA-TAG'] = true; when a user declines cookies using the beforeDeclined option.

My Workaround
I simply copied the JS file in the cookie_consent/cookiebar.js file and added in the window['ga-disable-YOUR-GA-TAG'] = true; in the declined event listener, and used that JS instead.

Conclusion
This brings up several questions,

  1. Are we meant to use the cookie_consent/cookiebar.js file? Or was that just meant as a demonstration, and we should be using our own method of handling this?
  2. Does anyone else have the same problem?
  3. Am I using the beforeDeclined option correctly?

How do I add a google analytics

I looked at the test app but I was unable to see how to include a google analytics tag. is it supposed to placed in the field called "Path" from the admin? or in the script. google analytics produces a tag for every account. how do I implement this tag so it is accepted or declined by Django cookie consent?

Manage.py Missing And New Structure For Test App

In previous versions, manage.py existed and it was easy for people to use the testapp and make changes to the code.

Manage.py used to be inside the tests folder. Now we have other files located here:

https://github.com/jazzband/django-cookie-consent/tree/master/tests

Conftest.py is blank:

https://github.com/jazzband/django-cookie-consent/blob/master/tests/conftest.py

There was some restructuring done to the app.

47bced6

Versions:

https://github.com/jazzband/django-cookie-consent/tags

If anyone could figure out how to run the testapp using this new structure, it would be appreciated. We should really add docs about how to do this for people who are not familiar with running an app or tests this way.

It seems like we would need to install pytest.

pip3 install pytest pytest-django
cd django-cookie-consent/tests
python3 -m pytest

I recreated the manage.py file inside of the tests folder.

https://github.com/jazzband/django-cookie-consent/blob/443dc60c020867e81349cb5b1fed285ef45a1e19/tests/manage.py

Then I ran

export DJANGO_SETTINGS_MODULE=testapp.settings
python3 manage.py makemigrations
python3 manage.py migrate

manage.py is more or less a wrapper around django-admin
DJANGO_SETTINGS_MODULE=testapp.settings django-admin test

Now when you run python3 manage.py runserver in the tests folder you see 'testapp.settings' in your terminal output:

Django version 4.1.7, using settings 'testapp.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.

'str' object has no attribute 'COOKIES' when using django-honeypot {% extends "app/base.html" %}

When using this package with django-honeypot (middleware to add honeypot to all forms) and customizing the honeypot_error.html page by using {% extends "app/base.html" %} I get this error: 'str' object has no attribute 'COOKIES'.

cookie_consent/util.py in get_cookie_dict_from_request

Django debug when true is highlighting this line:
{% not_accepted_or_declined_cookie_groups request as cookie_groups %}

https://github.com/bmihelac/django-cookie-consent/blob/master/cookie_consent/util.py#L36

https://github.com/bmihelac/django-cookie-consent/blob/master/cookie_consent/util.py#L53

https://github.com/bmihelac/django-cookie-consent/blob/master/cookie_consent/templatetags/cookie_consent_tags.py#L63

https://github.com/bmihelac/django-cookie-consent/blob/master/cookie_consent/util.py#L134

https://github.com/bmihelac/django-cookie-consent/blob/master/cookie_consent/util.py#L136

Honeypot Related Stuff shown in django debug true:

https://github.com/jamesturk/django-honeypot/blob/master/honeypot/middleware.py#L34

https://github.com/jamesturk/django-honeypot/blob/master/honeypot/decorators.py#L30

This error does not happen when I take out the {% extends "app/base.html" %} of honeypot_error.html.

Let me know if it is a django-cookie-consent issue or a django-honeypot issue so I could put the issue on django-honeypot if it is more related to them.

Allow storing additional metadata

While navigating other websites and how they setup their cookie pages I see the following things:

  1. Last updated all cookies on X date (maybe this could be tied to each group or all groups) - since third parties could add/remove cookies that they use over time.
  2. Expiration/Lifespan/Duration for each cookie
  3. Type for each cookie (Essential / Strictly Necessary, Targeting/Advertising, Analytics / Performance, Unclassified, etc)
  4. First party or third party for each cookie
  5. A <details> tag to save page space (optional - nice to have, but not sure if lawyers would like it you are "hiding" them unless clicked to be revealed)

Maybe adding these things to the database as non-required fields would help maintenance via admin and give users the option to include them or not. Otherwise, not a big deal if no PR is made since this stuff could be added to the cookie description as their own sentence or a programmer could make changes by overriding the templates.

There may be laws that require these small pieces of information to be stated which is why I bring it up but I am not a lawyer so not entirely sure. Erroring on the side of caution here. Plus hopefully helping others out by giving them more info that they might want to include.

Example:
https://stripe.com/cookie-settings
https://stripe.com/cookies-policy/legal

Implement Jazzband guidelines for django-cookie-consent

This issue tracks the implementation of the Jazzband guidelines for the project django-cookie-consent

It was initiated by @bmihelac who was automatically assigned in addition to the Jazzband roadies.

See the TODO list below for the generally required tasks, but feel free to update it in case the project requires it.

Feel free to ping a Jazzband roadie if you have any question.

TODOs

Project details

Description Reusable application for managing various cookies and visitors consent for their use in Django project.
Homepage https://django-cookie-consent.readthedocs.org/en/latest/
Stargazers 92
Open issues 28
Forks 42
Default branch master
Is a fork False
Has Wiki False
Has Pages False

CleanCookiesMiddleware does not work

It seems that at the current state the CleanCookiesMiddleware does not work in Django 2.x as is:

TypeError: 'CleanCookiesMiddleware' object is not callable

This error is solved by:

   def __call__(self, request):
        response = self.get_response(request)
        self.process_response(request, response)
        return response

However, then a new error appears:
cookie_consent\middleware.py", line 43, in process_response
if group_version < cookie.get_version() and not settings.COOKIE_CONSENT_OPT_OUT:
TypeError: '<' not supported between instances of 'NoneType' and 'str'

I think this one can be fixed by modifying the code as below, but I might have missed something as I am not understanding the code to the last bit

    def process_response(self, request, response):
        if not is_cookie_consent_enabled(request):
            return response
        cookie_dic = get_cookie_dict_from_request(request)
        for cookie_group in all_cookie_groups().values():
            if not cookie_group.is_deletable:
                continue
            group_version = cookie_dic.get(cookie_group.varname, None)
            for cookie in cookie_group.cookie_set.all():
                if cookie.name not in request.COOKIES:
                    continue
                if group_version == None:
                    if not settings.COOKIE_CONSENT_OPT_OUT:
                        response.delete_cookie(
                            smart_str(cookie.name),
                            cookie.path, cookie.domain
                        )
                    continue
                    
                if group_version == settings.COOKIE_CONSENT_DECLINE:
                    response.delete_cookie(smart_str(cookie.name),
                                           cookie.path, cookie.domain)

                if group_version < cookie.get_version() and not settings.COOKIE_CONSENT_OPT_OUT:
                    response.delete_cookie(
                        smart_str(cookie.name),
                        cookie.path, cookie.domain
                    )
        return response

Problems With Django-Honeypot Combined Middleware

I also mentioned this issue in django-honeypot. Maybe both maintainers could work together to sort this out.

jamesturk/django-honeypot#31

2 issues when using the combined django-honeypot middleware with django-cookie-consent.

Removing the combined middleware and django-honeypot completely caused both issues to go away.

First one is that I am unable to accept or decline cookies from the cookie bar that django-cookie-consent uses:
https://github.com/bmihelac/django-cookie-consent/blob/master/tests/core/templates/test_page.html

POST http://127.0.0.1:8000/cookies/accept/Stripe,Youtube/ 400 (Bad Request)

https://github.com/bmihelac/django-cookie-consent/blob/master/cookie_consent/static/cookie_consent/cookiebar.js

django-cookie-consent line showing the problem:
fetch(e.target.getAttribute("href"), {method: "POST"})

Failed to load resource. Responded a status of 400.

Tried changing the field name setting and that did not work.

The second one is that I am unable to use {% extends "app/file.html" %} to customize the honeypot_error.html file to make it look like the CSS the rest of my project uses.

If accepting or declining from the cookie bar:

POST http://127.0.0.1:8000/cookies/decline/Stripe,Youtube/ 500 (Internal Server Error)

https://github.com/bmihelac/django-cookie-consent/blob/master/cookie_consent/static/cookie_consent/cookiebar.js

django-cookie-consent line showing the problem:
fetch(e.target.getAttribute("href"), {method: "POST"})

If filling out the form (at /contact/ for example) and passing a value to the hidden django-honeypot value:

AttributeError at /contact/
'str' object has no attribute 'COOKIES'

cookie_consent/util.py in get_cookie_dict_from_request, line 36

https://github.com/bmihelac/django-cookie-consent/blob/master/cookie_consent/util.py

Somewhere what presumably should have been a django.http.HttpRequest object got replaced with a string. A guess would be it's because template tags get strings passed to them, not requests.

Completely commenting out this code got rid of the AttributeError error:
https://github.com/bmihelac/django-cookie-consent/blob/master/cookie_consent/templatetags/cookie_consent_tags.py#L105-L113

https://github.com/bmihelac/django-cookie-consent/blob/master/cookie_consent/util.py#L35-L37
โ€‹
Deleting these lines helped get rid of the AttributeError error:
https://github.com/bmihelac/django-cookie-consent/blob/master/tests/core/templates/test_page.html#L100-L102

Maybe putting try except blocks inside of get_decline_cookie_groups_cookie_string (cookie_consent_tags.py) and get_cookie_dict_from_request (util.py) will solve the problem?

Unable To Use Latest jQuery

I am using the test_page.html as my cookie bar setup. But cannot switch out https://github.com/bmihelac/django-cookie-consent/blob/master/tests/core/templates/test_page.html#L58 with the latest jQuery:

<script src="https://code.jquery.com/jquery-3.5.1.slim.min.js" integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj" crossorigin="anonymous"></script>

The accept and decline links for the cookie bar on line 85 does not work.

It is kind of undesirable to use 2 versions of jQuery 3.3.1 and the newest in my code which is a quick way around this when you should just be able to use the latest. Otherwise the jQuery that this app uses needs to be modernized.

Maybe there is a way to completely avoid jQuery and use JavaScript only?

Fails in Django 2.0

File "./../cookie_consent/cache.py", line 4, in
from django.core.cache import get_cache
ImportError: cannot import name 'get_cache'

Python 2 support.

Let me begin by acknowledging that python 2 is EOL and that the best course of action may be to simple update the README and remove all mentions of python 2.

Currently the README does indicate that the app supports python 2, but one of its dependencies django-appconf no longer does as of version 1.0.4

It seems they dropped support in a point release and as such it may not have been clear to the authors of django-cookie-consent that the 1.0.4 release of django-appconf actually included a 'breaking change'. (Though again, I acknowledge that python 2 is no longer supported in general, and as such it may be somewhat disingenuous of me to treat it as a 'breaking change').

I ran into this issue when trying to install django-cookie-consent in a python2/django1.11 project at work (whilst simultaneously lobbying for a python 3 upgrade) which will now throw the following ImportError: No module named importlib error:

File "***/venv/local/lib/python2.7/site-packages/cookie_consent/conf.py", line 9, in <module> class CookieConsentConf(AppConf): File "***/venv/local/lib/python2.7/site-packages/appconf/base.py", line 53, in __new__ new_class.add_to_class('_meta', AppConfOptions(meta, prefix)) File "***/venv/local/lib/python2.7/site-packages/appconf/base.py", line 12, in __init__ self.holder = import_attribute(self.holder_path) File "***/venv/local/lib/python2.7/site-packages/appconf/utils.py", line 5, in import_attribute from django.utils.importlib import import_module ImportError: No module named importlib

Suggested fixes:

  1. Dropping the mention of python 2 support, its EOL after all.
  2. If the decision is made to keep python 2 support - settings the requirement of django-appconf to version <=1.0.3

Thanks for your hard work!

Incorrect app name in README

It says coookie_consent instead of cookie_consent.

Add coookie_consent to your INSTALLED_APPS.

Add cookie_consent to your INSTALLED_APPS.

500 error without traceback

Since the merge of PR #6 I was encountering problems when rendering templates.
Narrowing it down showed that the usage of {% if request|cookie_consent_enabled %} as in the documentation and the test app is no longer working, the function has been renamed to is_cookie_consent_enabled - but it also uses different parameter handling.

Ended up removing the function from the template - as the check would lead to a server error.
Template tag issue without stacktrace, was a headache but easily mitigated.

No prompt after implementing the package

I added the package to my project (python3 -m pip) and followed the installation guide, however there is no prompt that appears in my test page template. On the page, I have the following line " "optional" cookies not accepted or declined ", which appears. On the cookies page I do not have any type of cookies, which appear.

Do I need to make any other modifications than the ones mentioned in the installation guide?

Cookiebar.js lacks documentation

This project has a pretty nifty cookie bar util whose use is not mentioned anywhere in the documentation.
It makes me: (ยดใ€‚๏ผฟใ€‚๏ฝ€)

Internal server error when clicking 'accept' on `/cookies` when using the pip package

I'm using the current pip package of django-cookie-consent

pip install django-cookie-consent

It seems though if I use the repo directly, everything works fine ( by copying cookie_consent folder from the repo to my django app ).

The code in cookie_consent/util.py which causess the error from the pip package also seems to have changed.
So I suppose this is only an issue with the PyPI package...

        MESSAGE: Internal Server Error: /cookies/accept/AnalyticsCookies/
Traceback (most recent call last):
  File "/env/lib/python3.7/site-packages/asgiref/sync.py", line 439, in thread_handler
    raise exc_info[1]
  File "/env/lib/python3.7/site-packages/django/core/handlers/exception.py", line 38, in inner
    response = await get_response(request)
  File "/env/lib/python3.7/site-packages/django/core/handlers/base.py", line 233, in _get_response_async
    response = await wrapped_callback(request, *callback_args, **callback_kwargs)
  File "/env/lib/python3.7/site-packages/asgiref/sync.py", line 404, in __call__
    ret = await asyncio.wait_for(future, timeout=None)
  File "/usr/local/lib/python3.7/asyncio/tasks.py", line 414, in wait_for
    return await fut
  File "/env/lib/python3.7/site-packages/asgiref/current_thread_executor.py", line 22, in run
    result = self.fn(*self.args, **self.kwargs)
  File "/env/lib/python3.7/site-packages/asgiref/sync.py", line 443, in thread_handler
    return func(*args, **kwargs)
  File "/env/lib/python3.7/site-packages/django/views/decorators/csrf.py", line 54, in wrapped_view
    return view_func(*args, **kwargs)
  File "/env/lib/python3.7/site-packages/django/views/generic/base.py", line 70, in view
    return self.dispatch(request, *args, **kwargs)
  File "/env/lib/python3.7/site-packages/django/views/generic/base.py", line 98, in dispatch
    return handler(request, *args, **kwargs)
  File "/env/lib/python3.7/site-packages/cookie_consent/views.py", line 54, in post
    self.process(request, response, varname)
  File "/env/lib/python3.7/site-packages/cookie_consent/views.py", line 64, in process
    accept_cookies(request, response, varname)
  File "/env/lib/python3.7/site-packages/cookie_consent/util.py", line 97, in accept_cookies
    set_cookie_dict_to_response(response, cookie_dic)
  File "/env/lib/python3.7/site-packages/cookie_consent/util.py", line 41, in set_cookie_dict_to_response
    settings.COOKIE_CONSENT_MAX_AGE)
  File "/env/lib/python3.7/site-packages/django/http/response.py", line 203, in set_cookie
    self.cookies[key] = value
  File "/usr/local/lib/python3.7/http/cookies.py", line 495, in __setitem__
    self.__set(key, rval, cval)
  File "/usr/local/lib/python3.7/http/cookies.py", line 485, in __set
    M.set(key, real_value, coded_value)
  File "/usr/local/lib/python3.7/http/cookies.py", line 351, in set
    if not _is_legal_key(key):
TypeError: cannot use a string pattern on a bytes-like object
INFO:     172.17.0.1:32962 - "POST /cookies/accept/AnalyticsCookies/ HTTP/1.1" 500 Internal Server Error

conent in query selector and cookie attributes.

Hello,
First of all, thank you so much for the package, it saved a lot of time for me.

I have found the following problem when using the cookies consent.

  1. .querySelector(".cc-cookie-decline", content) https://github.com/bmihelac/django-cookie-consent/blob/b2c88a7fabf557f39adc22340995c60dbecae54a/cookie_consent/static/cookie_consent/cookiebar.js#L33
    Same issue in line 50 as well in the same file. Firefox showing a warning and working without any issue. But Chrome is throwing an error, so not setting the cookies.
    Working fine when changed to 'window.top' as suggested by Firefox console.

  2. set_cookies without 'lax' and secure attributes.
    Cookie โ€œmessagesโ€ will be soon rejected because it has the โ€œsameSiteโ€ attribute set to โ€œnoneโ€ or an invalid value, without the โ€œsecureโ€ attribute. To know more about the โ€œsameSiteโ€œ attribute, read https://developer.mozilla.org/docs/Web/HTTP/Headers/Set-Cookie/SameSite
    Warning by Firefox. So I have to set directly on response

response.set_cookie(settings.COOKIE_CONSENT_NAME,
                                 dict_to_cookie_str(cookie_dic),
                                 settings.COOKIE_CONSENT_MAX_AGE,
                                 secure=settings.SESSION_COOKIE_SECURE,
                                 samesite='lax')

I think better to pass all option from settings if available from set_cookie_dict_to_response.

I'm not sure about the backward compatibility to make a pull request.

Firefox version : 77.0.1 64 bit
Chrome version: 83.04103.106 64bit
OS: Ubuntu 20.04
Django : 3.0.3

SuccessURLAllowedHostsMixin is replaced by RedirectURLMixin in Django 4.1

Hi, I've just install django-cookie-consent for Django 4.1 a I've got an error: "cannot import name 'SuccessURLAllowedHostsMixin' from 'django.contrib.auth.views'"

In Django 4.1 'SuccessURLAllowedHostsMixin' is replaced by 'RedirectURLMixin' - see following release notes https://docs.djangoproject.com/en/4.1/releases/4.1/

I've just solved it by replacing line 3 in cookie_consent/views.py
from django.contrib.auth.views import RedirectURLMixin as SuccessURLAllowedHostsMixin

So at least such a change is necessary to make it work for Django 4.1

PyPI version is outdated - install from github instead

Currently, on Github the latest version in 0.4.0-dev0, while PyPI has 0.3.1

We are not able to publish new versions to PyPI at the moment, so the best option at the moment is to install the package directly from Github:

pip install git+https://github.com/jazzband/[email protected]#egg=django-cookie-consent

Missing migration 0003_alter_cookie_id_alter_cookiegroup_id_and_more

I ran into a problem while trying to convert a site over to docker. I discovered that there is a missing migration 0003.

It would seem that running migrate on a new install will cause an error about the missing file. In a dev or local situation in which it's being run by a privileged user, it's fairly easy to solve by running makemigrations once, which will generate the missing file.

But, in docker, it's common to switch to a non-privileged user before running migrations, and it's generally discouraged from running makemigrations on prod servers. Attempting to run makemigrations as a non-privileged user causes an error when attempting to write out the missing migration file:

PermissionError: [Errno 13] Permission denied: '/usr/local/lib/python3.9/site-packages/cookie_consent/migrations/0003_alter_cookie_id_alter_cookiegroup_id_and_more.py'

So, ideally the migration file needs to be added to the package before that point. It looks like there is a pending PR for this very thing, which is quite convenient. I'm not entirely sure what the process is for approving PR's and getting those changes published out, but I'm willing to learn and help out where able.

In the mean time, my temporary fix which might work for others that are dealing with similar issues, is I just chowned the migrations directory and ran makemigrations. So the end of the dockerfile looks like:

# hack to fix cookie toolbar
RUN chown -R appuser:appuser /usr/local/lib/python3.9/site-packages/cookie_consent/migrations

# change to the app user
USER appuser

# run entrypoint.prod.sh
ENTRYPOINT ["/home/appuser/web/entrypoint.sh"]

and these two lines in entrypoint.sh:

python manage.py makemigrations
python manage.py migrate

Unable To Minify / Compress Changing JavaScript Into 1 File

This package has JavaScript code that creates a new cookie age for how long a cookie will be in the user's browser when they accept. When cookies are not accepted, and the user keeps refreshing the page with a cookie banner shown, new files get inserted into the CACHE folder that django-compressor uses (not sure if django-pipeline or similar packages does the same thing). This seems to be because each cookie javascript code has a brand new timestamp for the cookie age that generates each time the page refreshes. Because of the new timestamps on each page refresh, the cookie JavaScript code changes each time, thus creating a new CACHE file instead of just having one. This could be problematic if the website has tons of users visiting the site since it will create lots of cache files (minified javascript files) instead of just reading from one JavaScript file where the code does not change.

This isn't really a big deal if users decide not to minify / compress their website. But maybe there is a way to deal with this? If not, let me know and feel free to close the issue I just won't try to compress the JavaScript this package uses.

Add Signal For Clearing Cache When Cookie Model Updates

If the django admin updates the Cookie model with new third party cookies, there should be a way to delete the cache with the old cookies and then set the cache with the newly added cookies.

I made a separate django project based off the django-cookie-consent library that does this. The code is somewhat different though. Not sure when I will have time to make a PR for the django-cookie-consent that implements this feature but here is what I did below in that other project in case someone wants to make a PR. It has been a while since I wrote this code so I apologize for the mess - especially in the middleware file (but the middleware file below only queries off the cache rather than the database).

We may need to account for people who do not want to use the cache and for people who want to use the database for the middleware with a setting.

Models.py:

from django.db.models.signals import post_save
from django.dispatch import receiver
from django.core.cache import cache
from django.conf import settings

#If the Cookie model updates, delete the cookie_consent key in the cache. This is used for middleware to update the cached template tag query with updated cookies.
@receiver(post_save, sender=Cookie)
def clear_cache(sender, instance, **kwargs):
    cache.delete(settings.COOKIE_CONSENT_NAME)

Template:

{% cookie_cache_tag request %}

cookie_consent_tags.py:

from django import template
from django.conf import settings
from cookie_consent.models import CookieGroup, Cookie, CookieType
from django.core.cache import cache

register = template.Library()

@register.simple_tag
def cookie_cache_tag(request):
    items = cache.get(settings.COOKIE_CONSENT_NAME)
    if items is None:
        qs = CookieGroup.objects.filter(is_required=False) #get all third party cookie groups. Maybe add is_deletable=True to this
        qs = qs.prefetch_related('cookie_set') 
        #items = dict([(g.name, g) for g in qs])
        items = {g.name: g for g in qs}
        #print(qs.values())
        cache.set(settings.COOKIE_CONSENT_NAME, items, settings.COOKIE_CONSENT_MAX_AGE)
    return '' #return nothing for the template tag, otherwise it will set the cookie_consent cache key

Middleware:

    """
    #This code works but does not use cache
    def process_response(self, request, response):
        #Remove all third party cookies when there is no consent cookie
        if request.COOKIES.get('cookie_consent', None) is None:
            for x in CookieGroup.objects.filter(is_required=0).values_list('name',flat='true'):
                for y in Cookie.objects.filter(cookiegroup__name=x).values_list('name',flat='true'):
                    response.delete_cookie(y)
        #Remove all third party cookies when there are declined consent cookie values
        if request.COOKIES.get('cookie_consent', None) is not None:
            dic = {}
            for c in request.COOKIES.get('cookie_consent', '').split("|"):
                key, value = c.split("=")
                dic[key] = value
            for k, v in dic.items():
                if v == '0':
                    declined_cookie_groups = k
                    for x in Cookie.objects.filter(cookiegroup__name=declined_cookie_groups).values_list('name',flat='true'):
                        response.delete_cookie(x)
        return response
    """

    def process_response(self, request, response):

        """
        if cache.get(settings.COOKIE_CONSENT_NAME): #check for cookie_consent key in redis
            cookie_consent_cache_dic = cache.get(settings.COOKIE_CONSENT_NAME).values()
            list(cache.get(settings.COOKIE_CONSENT_NAME).values())  # [<CookieGroup: Group2>, <CookieGroup: Group3>]
            print(list(cache.get(settings.COOKIE_CONSENT_NAME).values()))
            for z in list(cache.get(settings.COOKIE_CONSENT_NAME).values()):
                print(z) #z.COOKIE_GROUP_MODEL_FIELD_NAME
        """

        #Remove all third party cookies when there is no consent cookie
        if request.COOKIES.get(settings.COOKIE_CONSENT_NAME, None) is None: #get cookie_consent cookie from browser
            if cache.get(settings.COOKIE_CONSENT_NAME): #check for cookie_consent key in redis
                cookie_consent_cache_dic = cache.get(settings.COOKIE_CONSENT_NAME).values() #dict_values([<CookieGroup: Group2>, <CookieGroup: Group3>])
                for x in cookie_consent_cache_dic: #Loop through group model. x.COOKIE_GROUP_MODEL_FIELD_NAME becomes accessible in this loop.
                    for y in x.cookie_set.all(): #Loop through y.COOKIE_MODEL_FIELD_NAME from the cache query - <QuerySet [<Cookie: third_party_cookie_name 127.0.0.1:8000/>]> - cookie_set comes from the template tag
                        if y.name not in request.COOKIES.keys(): #if COOKIE_MODEL_FIELD_NAME not in the browser cookies - not sure if I need this check or not
                            continue
                        response.delete_cookie(y.name, path=y.path, domain=y.domain)
        #Remove all third party cookies when there are declined consent cookie values
        if request.COOKIES.get(settings.COOKIE_CONSENT_NAME, None) is not None:
            dic = {}
            for c in request.COOKIES.get(settings.COOKIE_CONSENT_NAME, '').split("|"):
                key, value = c.split("=")
                dic[key] = value
            for k, v in dic.items():
                if v == '0':
                    declined_cookie_groups = k
                    if cache.get(settings.COOKIE_CONSENT_NAME):
                        cookie_consent_cache_dic = cache.get(settings.COOKIE_CONSENT_NAME).values()
                        for x in cookie_consent_cache_dic:
                            if x.name == declined_cookie_groups:
                                for y in x.cookie_set.all():
                                    if y.name not in request.COOKIES.keys(): #if COOKIE_MODEL_FIELD_NAME not in the browser cookies - not sure if I need this check or not
                                        continue
                                    response.delete_cookie(y.name, y.path, y.domain)
        return response

Not compatible with page cache

I'm interested in using this project to deal with the GDPR, etc. but, unfortunately, it seems to not be compatible with Django page cache that I'm using intensively, as most of the pages of my site don't really change often. The problem I see is that it need to write the expires (that changes all the time) on each request modifying the page every time.

I see two solutions:

  1. Instead of writing options passed to showCookieBar directly in the page, make a XHR to get those values.
  2. Put "stable" option values in the page and then calculate the expires in Javascript.

For my project I implemented the second, because I prefer to avoid the supplemental XHR. Firstly, I made a template tag that add all the necessary JS in the page. I then made a function in a JS file that check the presence of the cookie and if some groups have changed to show or not the cookie bar, and then calculate the expires and call showCookieBar when needed.

Template tag

@register.simple_tag()
def add_cookie_consent():
    cookie_groups = all_cookie_groups().values()

    cookie_groups_names = ', '.join(
        [str(cookie_group) for cookie_group in cookie_groups]
    )
    url_accept = cookie_consent_accept_url(cookie_groups)
    url_decline = cookie_consent_decline_url(cookie_groups)
    url_cookies = reverse('cookie_consent_cookie_group_list')
    content = f'<div class="cookie-bar">This site use  {cookie_groups_names} cookies. Do you accept all the cookies?<a href="{url_accept}" class="cc-cookie-accept">Accept</a> <a href="{url_decline}" class="cc-cookie-decline">Decline</a> <a href="{url_cookies}">Cookie options</a></div>'

    cookie_groups_varnames = json.dumps(
        [cookie_group.varname for cookie_group in cookie_groups]
    )

    cookie_value_string = dict_to_cookie_str(
        dict(
            (cookie_group.varname, settings.COOKIE_CONSENT_DECLINE)
            for cookie_group in cookie_groups
        )
    )

    return format_html(
        """
    <script type="text/javascript" src="{}cookie_consent/cookiebar.js"></script>

    <script type="text/javascript">
        const cookieConsentOptions = {{
            content: '{}',
            cookie_groups: {},
            cookie_max_age: {},
            cookie_value_string: '{}={}'
        }}
    </script>
    """,
        settings.STATIC_URL,
        mark_safe(content),
        mark_safe(cookie_groups_varnames),
        settings.COOKIE_CONSENT_MAX_AGE,
        settings.COOKIE_CONSENT_NAME,
        cookie_value_string,
    )

Javascript functions

function booleanTrim(str, ch) {
  return str.split(ch).filter(Boolean).join(ch)
}

function cookieConsent() {
  /*global cookieConsentOptions*/
  const cookieConsentName = cookieConsentOptions.cookie_value_string.split(
    '=',
    1
  )[0]
  const consentCookie = document.cookie
    .split(';')
    .map((cookie) => cookie.trim())
    .filter((cookie) => cookie.startsWith(cookieConsentName))[0]
  if (consentCookie === undefined) {
    showCookieConsent()
  } else {
    const consentCookieKeysValues = booleanTrim(
      consentCookie.substring(cookieConsentName.length + 1),
      '"'
    ).split('|')
    for (let cookieGroup of cookieConsentOptions.cookie_groups) {
      if (
        !consentCookieKeysValues.some((item) =>
          item.trim().startsWith(cookieGroup + '=')
        )
      ) {
        showCookieConsent()
        break
      }
    }
  }
}

function showCookieConsent() {
  /*global showCookieBar*/
  const expires = new Date(
    Date.now() + cookieConsentOptions.cookie_max_age * 1000
  ).toUTCString()
  const cookieData =
    cookieConsentOptions.cookie_value_string +
    '; expires=' +
    expires +
    '; path=/'
  const options = {
    content: cookieConsentOptions.content,
    cookie_groups: cookieConsentOptions.cookie_groups,
    cookie_decline: cookieData,
    beforeDeclined: function () {
      document.cookie = cookieData
    }
  }
  showCookieBar(options)
}

document.addEventListener('DOMContentLoaded', function () {
  cookieConsent()
}

Is good to note that with the second solution, as I implemented it, if I'm adding/modifying/removing a cookie or a cookie group, I need to flush the page cache because the "stable" options values written in the page will changed.

Can't pickle <function QuerySet._clone at 0x000001EAA19615E0>: it's not the same object as django.db.models.query.QuerySet._clone

Trying to implement this nice coockie-consent library to my page but i get an error.

I have tryed to recreate the same values in the CookieGroup and Cookies tables and added the test_page.html and TestPageView to my page just to see if it is working.
When i access the TestPageView i get the following error:

Template error:
In template D:\Jure\django-apps\cobs_dev\cobs\templates\cobs\test_page.html, error at line 71
   Can't pickle <function QuerySet._clone at 0x000001EAA19615E0>: it's not the same object as django.db.models.query.QuerySet._clone
   61 :     <script type="{% js_type_for_cookie_consent request "social" "*:.google.com" %}" data-varname="social">
   62 :       (function() {
   63 :         var po = document.createElement('script'); po.type = 'text/javascript'; po.async = true;
   64 :         po.src = 'https://apis.google.com/js/plusone.js';
   65 :         var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(po, s);
   66 :       })();
   67 :     </script>
   68 : 
   69 : 
   70 :     {% if request|cookie_consent_enabled %}
   71 :        {% not_accepted_or_declined_cookie_groups request as cookie_groups %}
   72 : 
   73 :       {% if cookie_groups %}
   74 :         {% url "cookie_consent_cookie_group_list" as url_cookies %}
   75 :         {% cookie_consent_accept_url cookie_groups as url_accept %}
   76 :         {% cookie_consent_decline_url cookie_groups as url_decline %}
   77 :         <script type="text/javascript">
   78 :           var cookie_groups = [];
   79 :           {% for cookie_group in cookie_groups %}
   80 :             cookie_groups.push("{{ cookie_group.varname }}");
   81 :           {% endfor %}


Traceback (most recent call last):
  File "D:\Jure\django-apps\cobs_env\lib\site-packages\django\core\handlers\exception.py", line 47, in inner
    response = get_response(request)
  File "D:\Jure\django-apps\cobs_env\lib\site-packages\django\core\handlers\base.py", line 204, in _get_response
    response = response.render()
  File "D:\Jure\django-apps\cobs_env\lib\site-packages\django\template\response.py", line 105, in render
    self.content = self.rendered_content
  File "D:\Jure\django-apps\cobs_env\lib\site-packages\django\template\response.py", line 83, in rendered_content
    return template.render(context, self._request)
  File "D:\Jure\django-apps\cobs_env\lib\site-packages\django\template\backends\django.py", line 61, in render
    return self.template.render(context)
  File "D:\Jure\django-apps\cobs_env\lib\site-packages\django\template\base.py", line 170, in render
    return self._render(context)
  File "D:\Jure\django-apps\cobs_env\lib\site-packages\django\test\utils.py", line 100, in instrumented_test_render
    return self.nodelist.render(context)
  File "D:\Jure\django-apps\cobs_env\lib\site-packages\django\template\base.py", line 938, in render
    bit = node.render_annotated(context)
  File "D:\Jure\django-apps\cobs_env\lib\site-packages\django\template\base.py", line 905, in render_annotated
    return self.render(context)
  File "D:\Jure\django-apps\cobs_env\lib\site-packages\django\template\defaulttags.py", line 315, in render
    return nodelist.render(context)
  File "D:\Jure\django-apps\cobs_env\lib\site-packages\django\template\base.py", line 938, in render
    bit = node.render_annotated(context)
  File "D:\Jure\django-apps\cobs_env\lib\site-packages\django\template\base.py", line 905, in render_annotated
    return self.render(context)
  File "D:\Jure\django-apps\cobs_env\lib\site-packages\django\template\library.py", line 192, in render
    output = self.func(*resolved_args, **resolved_kwargs)
  File "D:\Jure\django-apps\cobs_env\lib\site-packages\cookie_consent\templatetags\cookie_consent_tags.py", line 61, in not_accepted_or_declined_cookie_groups
    return get_not_accepted_or_declined_cookie_groups(request)
  File "D:\Jure\django-apps\cobs_env\lib\site-packages\cookie_consent\util.py", line 134, in get_not_accepted_or_declined_cookie_groups
    return [cookie_group for cookie_group in get_cookie_groups()
  File "D:\Jure\django-apps\cobs_env\lib\site-packages\cookie_consent\util.py", line 81, in get_cookie_groups
    return all_cookie_groups().values()
  File "D:\Jure\django-apps\cobs_env\lib\site-packages\cookie_consent\cache.py", line 25, in all_cookie_groups
    cache.set(CACHE_KEY, items, CACHE_TIMEOUT)
  File "D:\Jure\django-apps\cobs_env\lib\site-packages\django_redis\cache.py", line 31, in _decorator
    return method(self, *args, **kwargs)
  File "D:\Jure\django-apps\cobs_env\lib\site-packages\django_redis\cache.py", line 80, in set
    return self.client.set(*args, **kwargs)
  File "D:\Jure\django-apps\cobs_env\lib\site-packages\django_redis\client\default.py", line 143, in set
    nvalue = self.encode(value)
  File "D:\Jure\django-apps\cobs_env\lib\site-packages\django_redis\client\default.py", line 457, in encode
    value = self._serializer.dumps(value)
  File "D:\Jure\django-apps\cobs_env\lib\site-packages\django_redis\serializers\pickle.py", line 29, in dumps
    return pickle.dumps(value, self._pickle_version)

Exception Type: PicklingError at /cookies/
Exception Value: Can't pickle <function QuerySet._clone at 0x000001EAA19615E0>: it's not the same object as django.db.models.query.QuerySet._clone

This tamplate tag seems to be the source of this error:
{% not_accepted_or_declined_cookie_groups request as cookie_groups %}

I'm running the website on:
Djnago 3.2.13
django-redis 5.2.0

Re-implement showCookieBar Javascript

This will introduce some (pending) breaking changes, but in the long run things should be more maintainable.

Architecture rework:

  • Do not expect HTML to be passed to the function, but instead expect a selector to an HTML DOM node
    • The template should output a <template id="cookie-consent-template">...</template> somewhere
    • Provide the #cookie-consent-template selector to the function
    • Support outputting the cookie list to a {{ cookiegroups|json_script:'cookie-consent-groups' }}
    • Pass the ID of the list to the function and parse those as JSON
  • Get rid of the hardcoded .cc-accept etc. selectors, but allow providing them as options (and use the current values as defaults)
  • Set up the script as a proper module so that <script type="module"> can be used and it can be integrated in bundlers like webpack
  • Explore/set up an example for htmx support instead of using fetch ourselves
  • Check if we can get rid of the eval which conflicts with Content-Security-Policies -> optional an afterAccept hook can be added

What does this achieve

  • It should make it easier to work with page cache (keeping into account some Vary headers), see #49
  • Modern JS + possibly easier to integrate with npm-based frontend toolchains
  • Ability to not have to rely on JS at all by using htmx

More thoughts re backwards compatibility:

  • Keep existing functions (and window scope), but rename to deprecatedShowCookieBar
  • emit console.warn when legacy function is used
  • allow disabling warnings via options

This will still make it a breaking change, but allows us to introduce the new API while keeping the old one via a simple rename. Users can then start implementing the new API on their own schedule, and we remove the deprecated API in 1.0. Likely there will be a 0.5.0 release before that, to also acquire feedback from users, most notably the ones who have opened issues about this topic.

@etienned @9mido - can I approach you folks when this ready for testing? Are there any aspects in this issue that stand out to you that I should focus, e.g. integrating it in webpack or providing HTMX support?

when declined the bar shows up at next page loading

Is there a specific set-up for preventing the cookie bar to show up again when reloading the page even though it had been declined? or is it normal behaviour?

To reproduce:
Step 1) I load the home page and I click Decline on the cookie bar
Step 2) I reload the home page and the cookie bar shows up again, which I would like to prevent

Cache invalidation bugs

The cache invalidation functions only seem to run on single-object operations (via the method overrides in https://github.com/jazzband/django-cookie-consent/blob/master/cookie_consent/models.py), however bulk actions do not invalidate the cache.

Steps to reproduce:

  1. Create two cookiegroups
  2. Fetch the cookies (using all_cookie_groups is the easiest)
  3. Delete one cookie group via the admin list view
  4. Verify that the cache still returns both objects.

Probably a patch needs to hook into the post_delete signal, as that fires for bulk deletes. Bulk updates however don't have this behaviour, so perhaps overriding the model manager and hooking into those update and delete methods is a better solution.

Signed Cookies

I am wondering if we could use signed cookies anywhere in django-cookie-consent. Maybe we could turn the settings COOKIE_CONSENT_NAME cookie into a signed cookie?

https://code.djangoproject.com/wiki/Signing

https://docs.djangoproject.com/en/4.0/ref/request-response/#django.http.HttpRequest.get_signed_cookie

https://docs.djangoproject.com/en/4.0/ref/request-response/#django.http.HttpResponse.set_signed_cookie

https://stackoverflow.com/questions/6468507/what-are-signed-cookies-and-why-are-they-useful

https://stackoverflow.com/questions/12142058/performance-comparison-of-using-django-signed-cookie-session-over-django-db-ca

https://www.reddit.com/r/crypto/comments/esahns/why_does_a_server_sign_a_cookie/

For example, we can do something like this in util.py:

def set_cookie_dict_to_response(response, dic):
    if settings.COOKIE_CONSENT_SIGNED:
        response.set_signed_cookie(settings.COOKIE_CONSENT_NAME,
                            dict_to_cookie_str(dic),
                            expires=settings.COOKIE_CONSENT_MAX_AGE,
                            domain=settings.COOKIE_CONSENT_DOMAIN,
                            samesite=settings.COOKIE_CONSENT_SAMESITE,
                            secure=settings.COOKIE_CONSENT_SECURE,
                            httponly=settings.COOKIE_CONSENT_HTTPONLY
                            )

conf.py:

SIGNED = True

If I do this though, it works in the browser but tests will fail when running python3 tests/manage.py test:

ERROR: test_get_success_url (tests.core.tests.test_views.CookieGroupBaseProcessViewTests)
If user adds a 'next' as URL parameter it should,
cookie_consent/util.py", line 24, in parse_cookie_str
key, value = c.split("=")
ValueError: not enough values to unpack (expected 2, got 1)

FAIL: test_cookies (tests.core.tests.test_views.IntegrationTest)
tests/core/tests/test_views.py", line 116, in test_cookies
self.assertContains(response, '"optional" cookies declined')
AssertionError: False is not true : Couldn't find '"optional" cookies declined' in response

FAIL: test_decline_cookie (tests.core.tests.test_views.IntegrationTest)
tests/core/tests/test_views.py", line 81, in test_decline_cookie
AssertionError: False is not true : Couldn't find 'Declined' in response

The error message suggests that the 'c' variable in the 'for' loop in the 'parse_cookie_str' function contains only one value (expected 2). This might happen if the cookie string is not formatted correctly. One possible solution is to add a check in the 'parse_cookie_str' function to ensure that the cookie string is not empty and contains at least one "=" character before applying the split() method. For example, you can modify the 'parse_cookie_str' function as follows:

def parse_cookie_str(cookie_str):
    if not cookie_str or "=" not in cookie_str:
        return {}
    cookie_dict = {}
    for c in cookie_str.split("|"):
        key, value = c.split("=")
        cookie_dict[key] = value
    return cookie_dict

This will ensure that the 'split("=")' method is only applied if the cookie string is not empty and contains at least one "=" character. This should fix the 'ValueError' that you're getting.

It looks like the issue with the failing tests is related to how the cookies are being accessed and checked in the test cases. The cookie consent module in Django provides a method for getting the cookie value, which should be used instead of directly accessing the response cookies.

To get the cookie value, you can use the 'get_cookie_dict_from_request' function from the 'cookie_consent/util.py' module. For example:

def test_cookies(self):
    response = self.client.get("/")
    cookies = get_cookie_dict_from_request(response.wsgi_request)
    assert "OptionalCookie" in cookies  # check if the cookie exists

def test_decline_cookie(self):
    response = self.client.post("/cookie/decline/")
    cookies = get_cookie_dict_from_request(response.wsgi_request)
    assert cookies["optional"] == "false"  # check the value of the cookie

Using this method to get the cookie value should work both in the browser and in the test cases.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.