├── .gitignore ├── AUTHORS.md ├── LICENSE.md ├── README.md ├── assets ├── modal-dialog-bottom.png ├── modal-dialog-center.png └── modal-dialog-right.png ├── demo_project ├── __init__.py ├── asgi.py ├── management │ ├── __init__.py │ └── commands │ │ ├── __init__.py │ │ └── download_chromedriver.py ├── settings.py ├── site_static │ ├── css │ │ ├── bootstrap.min.css │ │ └── style.css │ ├── img │ │ ├── about-1.jpg │ │ ├── about-2.jpg │ │ ├── carousel-1.jpg │ │ ├── carousel-2.jpg │ │ ├── favicon.ico │ │ ├── product-1.jpg │ │ ├── product-2.jpg │ │ ├── product-3.jpg │ │ ├── service-1.jpg │ │ ├── service-2.jpg │ │ ├── team-1.jpg │ │ ├── team-2.jpg │ │ ├── team-3.jpg │ │ ├── team-4.jpg │ │ ├── testimonial-1.jpg │ │ ├── testimonial-2.jpg │ │ ├── testimonial-3.jpg │ │ └── testimonial-4.jpg │ └── js │ │ └── main.js ├── templates │ ├── base.html │ ├── conditional_html │ │ ├── functionality.html │ │ ├── marketing.html │ │ └── performance.html │ ├── gdpr_cookie_consent │ │ └── descriptions │ │ │ └── extra.html │ ├── index.html │ └── test.html ├── tests │ ├── __init__.py │ ├── test_checks.py │ └── test_frontend.py ├── urls.py └── wsgi.py ├── manage.py ├── media └── .gitignore ├── private_wheels ├── .gitignore └── README.md ├── requirements.txt └── tmp └── .gitignore /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### Python template 3 | # Byte-compiled / optimized / DLL files 4 | __pycache__/ 5 | *.py[cod] 6 | *$py.class 7 | 8 | # C extensions 9 | *.so 10 | 11 | # Distribution / packaging 12 | .Python 13 | build/ 14 | develop-eggs/ 15 | dist/ 16 | downloads/ 17 | eggs/ 18 | .eggs/ 19 | lib/ 20 | lib64/ 21 | parts/ 22 | sdist/ 23 | var/ 24 | wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *.cover 49 | .hypothesis/ 50 | 51 | # Translations 52 | *.pot 53 | 54 | # Django stuff: 55 | *.log 56 | local_settings.py 57 | 58 | # Flask stuff: 59 | instance/ 60 | .webassets-cache 61 | 62 | # Scrapy stuff: 63 | .scrapy 64 | 65 | # Sphinx documentation 66 | docs/_build/ 67 | 68 | # PyBuilder 69 | target/ 70 | 71 | # Jupyter Notebook 72 | .ipynb_checkpoints 73 | 74 | # pyenv 75 | .python-version 76 | 77 | # celery beat schedule file 78 | celerybeat-schedule 79 | 80 | # SageMath parsed files 81 | *.sage.py 82 | 83 | # Environments 84 | .env 85 | .venv 86 | env/ 87 | venv/ 88 | ENV/ 89 | env.bak/ 90 | venv.bak/ 91 | 92 | # Spyder project settings 93 | .spyderproject 94 | .spyproject 95 | 96 | # Rope project settings 97 | .ropeproject 98 | 99 | # mkdocs documentation 100 | /site 101 | 102 | # mypy 103 | .mypy_cache/ 104 | 105 | # database 106 | db.sqlite3 107 | -------------------------------------------------------------------------------- /AUTHORS.md: -------------------------------------------------------------------------------- 1 | # Authors 2 | 3 | - Aidas Bendoraitis (@archatas / @DjangoTricks) 4 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | Copyright (c) Websightful UG 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. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Django GDPR Cookie Consent Demo Project 2 | 3 | ## Description 4 | 5 | This project shows an example of [Django GDPR Cookie Consent](https://websightful.gumroad.com/l/django-gdpr-cookie-consent) app integration into a Django project. 6 | 7 | As stated by [GDPR cookie law](https://gdpr.eu/cookies/), websites that serve content for people from European Union must get consent from website visitors before storing any cookies that are not strictly necessary for the website to function. Not complying with GDPR laws can result in a fine of up to €20 million or 4% of the company's annual revenue, whichever is greater. 8 | 9 | Django GDPR Cookie Consent app allows you to set up a modal dialog for cookie explanations and preferences. When a specific cookie section is accepted, the widget loads or renders HTML snippets related to that section. For example, if a visitor approved Performance cookies, they would get Google Analytics loaded. 10 | 11 | Using the Django GDPR Cookie Consent app, you store the following information about the cookies in Django project settings: 12 | 13 | - What are the cookie sections (e.g. "Essential", "Functionality", "Performance", "Marketing")? Are they bounded with any conditional HTML snippets? 14 | - What are the cookie providers within each section (e.g., "This website," "Google Analytics," "Facebook," "Youtube," etc.)? 15 | - What are the cookies set by each of those providers? 16 | 17 | Descriptions for sections, providers, or cookies are translatable. User preferences are saved in a cookie too. If a particular section is unselected later, cookies related to that section are attempted to get deleted. 18 | 19 | ## Demo 20 | 21 | The modal dialog centered: 22 | 23 | ![Django GDPR Cookie Consent centered](https://raw.githubusercontent.com/archatas/django-gdpr-cookie-consent-demo-project/primary/assets/modal-dialog-center.png) 24 | 25 | The modal dialog at the bottom: 26 | 27 | ![Django GDPR Cookie Consent at the bottom](https://raw.githubusercontent.com/archatas/django-gdpr-cookie-consent-demo-project/primary/assets/modal-dialog-bottom.png) 28 | 29 | The modal dialog on the right: 30 | 31 | ![Django GDPR Cookie Consent on the right](https://raw.githubusercontent.com/archatas/django-gdpr-cookie-consent-demo-project/primary/assets/modal-dialog-right.png) 32 | 33 | ## Django GDPR Cookie Consent in Production 34 | 35 | Django GDPR Cookie Consent is used at 36 | 37 | - [1st things 1st](https://www.1st-things-1st.com) 38 | - [DjangoTricks](https://www.djangotricks.com) 39 | - [PyBazaar](https://www.pybazaar.com) 40 | 41 | ## How to Install this Demo Project 42 | 43 | ### 1. Create and activate a virtual environment 44 | 45 | ```shell 46 | $ python3 -m venv venv 47 | $ source venv/bin/activate 48 | ``` 49 | 50 | ### 2. Purchase and download Django GDPR Cookie Consent app 51 | 52 | [Get Django GDPR Cookie Consent from Gumroad](https://websightful.gumroad.com/l/django-gdpr-cookie-consent). 53 | 54 | Put the `*.whl` file into `private_wheels/` directory. 55 | 56 | ### 3. Install pip requirements into your virtual environment 57 | 58 | With the virtual environment activated, install pip requirements: 59 | 60 | ```shell 61 | (venv)$ pip install -r requirements.txt 62 | ``` 63 | 64 | ### 4. Download Webdriver for Chrome 65 | 66 | Upgrade your Chrome to the latest version and then run the management command: 67 | 68 | ```shell 69 | (venv)$ python manage.py download_chromedriver 70 | ``` 71 | 72 | It will download and extract the latest stable webdriver for your Chrome browser to the `drivers` directory. 73 | 74 | ### 5. Run database migrations and collect static files 75 | 76 | With the virtual environment activated, run database migrations: 77 | 78 | ```shell 79 | (venv)$ python manage.py migrate 80 | (venv)$ python manage.py collectstatic --noinput 81 | ``` 82 | 83 | ### 6. Run Selenium tests 84 | 85 | With the virtual environment activated, run the tests: 86 | 87 | ```shell 88 | (venv)$ python manage.py test 89 | ``` 90 | 91 | ### 7. Browse 92 | 93 | With the virtual environment activated, run development server: 94 | 95 | ```shell 96 | (venv)$ python manage.py runserver 97 | ``` 98 | 99 | Browse the local website under and inspect the cookies in web development tools. 100 | 101 | Compare the functionality with the source code. 102 | 103 | ### 8. Play around 104 | 105 | [Check the docs](https://websightful.github.io/django-gdpr-cookie-consent-docs/) and try to modify the functionality. 106 | 107 | ## Disclaimer 108 | 109 | The actual website's compliance with the GDRP Cookie Law depends on the configuration of each use case. The Django GDPR Cookie Consent app provides the mechanism to make that possible, but it's up to you how you configure and integrate it. 110 | 111 | ## Contact 112 | 113 | For technical questions or bug reports, please contact Aidas Bendoraitis at . 114 | -------------------------------------------------------------------------------- /assets/modal-dialog-bottom.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/archatas/django-gdpr-cookie-consent-demo-project/2b72a07799dbfba5e05687c4a656451017fca847/assets/modal-dialog-bottom.png -------------------------------------------------------------------------------- /assets/modal-dialog-center.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/archatas/django-gdpr-cookie-consent-demo-project/2b72a07799dbfba5e05687c4a656451017fca847/assets/modal-dialog-center.png -------------------------------------------------------------------------------- /assets/modal-dialog-right.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/archatas/django-gdpr-cookie-consent-demo-project/2b72a07799dbfba5e05687c4a656451017fca847/assets/modal-dialog-right.png -------------------------------------------------------------------------------- /demo_project/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/archatas/django-gdpr-cookie-consent-demo-project/2b72a07799dbfba5e05687c4a656451017fca847/demo_project/__init__.py -------------------------------------------------------------------------------- /demo_project/asgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | ASGI config for demo_project project. 3 | 4 | It exposes the ASGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/3.2/howto/deployment/asgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.asgi import get_asgi_application 13 | 14 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'demo_project.settings') 15 | 16 | application = get_asgi_application() 17 | -------------------------------------------------------------------------------- /demo_project/management/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/archatas/django-gdpr-cookie-consent-demo-project/2b72a07799dbfba5e05687c4a656451017fca847/demo_project/management/__init__.py -------------------------------------------------------------------------------- /demo_project/management/commands/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/archatas/django-gdpr-cookie-consent-demo-project/2b72a07799dbfba5e05687c4a656451017fca847/demo_project/management/commands/__init__.py -------------------------------------------------------------------------------- /demo_project/management/commands/download_chromedriver.py: -------------------------------------------------------------------------------- 1 | from django.core.management.base import BaseCommand 2 | 3 | 4 | class Command(BaseCommand): 5 | help = "Downloads the latest stable ChromeDriver" 6 | 7 | JSON_URL = "https://googlechromelabs.github.io/chrome-for-testing/last-known-good-versions-with-downloads.json" 8 | 9 | def get_platform(self): 10 | import sys 11 | import platform 12 | 13 | system = platform.system().lower() 14 | if system == "linux": 15 | return "linux64" 16 | elif system == "darwin": 17 | return "mac-arm64" if platform.machine() == "arm64" else "mac-x64" 18 | elif system == "windows": 19 | return "win64" if platform.machine().endswith("64") else "win32" 20 | else: 21 | self.stdout.write( 22 | self.style.ERROR(f"Unsupported operating system: {system}") 23 | ) 24 | sys.exit(1) 25 | 26 | def download_file(self, url, filename): 27 | import requests 28 | 29 | response = requests.get(url, stream=True) 30 | response.raise_for_status() 31 | with open(filename, "wb") as file: 32 | for chunk in response.iter_content(chunk_size=8192): 33 | file.write(chunk) 34 | 35 | def handle(self, *args, **options): 36 | import os 37 | import sys 38 | import zipfile 39 | import requests 40 | from pathlib import Path 41 | import shutil 42 | 43 | platform = self.get_platform() 44 | 45 | response = requests.get(self.JSON_URL) 46 | response.raise_for_status() 47 | data = response.json() 48 | 49 | chromedriver_url = next( 50 | ( 51 | item["url"] 52 | for item in data["channels"]["Stable"]["downloads"]["chromedriver"] 53 | if item["platform"] == platform 54 | ), 55 | None, 56 | ) 57 | 58 | if not chromedriver_url: 59 | self.stdout.write( 60 | self.style.ERROR( 61 | f"Failed to find ChromeDriver URL for platform: {platform}" 62 | ) 63 | ) 64 | sys.exit(1) 65 | 66 | version = chromedriver_url.split("/")[-2] 67 | 68 | project_root = Path(os.getcwd()) 69 | temp_dir = project_root / "tmp" 70 | 71 | zip_filename = temp_dir / f"chromedriver-{platform}.zip" 72 | extract_directory = temp_dir / f"chromedriver-{platform}" 73 | self.stdout.write( 74 | self.style.SUCCESS( 75 | f"Downloading ChromeDriver version {version} for {platform}..." 76 | ) 77 | ) 78 | self.stdout.write(chromedriver_url) 79 | self.download_file(chromedriver_url, zip_filename) 80 | 81 | with zipfile.ZipFile(zip_filename, "r") as zip_ref: 82 | zip_ref.extractall(temp_dir) 83 | 84 | chromedriver_name = ( 85 | "chromedriver.exe" if platform.startswith("win") else "chromedriver" 86 | ) 87 | source = extract_directory / chromedriver_name 88 | 89 | # Create drivers directory if it doesn't exist 90 | drivers_dir = project_root / "drivers" 91 | drivers_dir.mkdir(exist_ok=True) 92 | 93 | destination = drivers_dir / chromedriver_name 94 | 95 | # If chromedriver already exists, remove it 96 | if destination.exists(): 97 | destination.unlink() 98 | 99 | # Move the new chromedriver to the drivers directory 100 | shutil.move(str(source), str(destination)) 101 | 102 | # Delete the zip file 103 | os.remove(zip_filename) 104 | 105 | # Delete the extracted directory 106 | shutil.rmtree(str(extract_directory)) 107 | 108 | if not platform.startswith("win"): 109 | os.chmod(destination, 0o755) 110 | 111 | self.stdout.write( 112 | self.style.SUCCESS( 113 | f"ChromeDriver {version} has been downloaded, extracted, and placed in the drivers directory." 114 | ) 115 | ) 116 | -------------------------------------------------------------------------------- /demo_project/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for demo_project project. 3 | 4 | Generated by 'django-admin startproject' using Django 3.2.5. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/3.2/topics/settings/ 8 | 9 | For the full list of settings and their values, see 10 | https://docs.djangoproject.com/en/3.2/ref/settings/ 11 | """ 12 | 13 | from pathlib import Path 14 | 15 | from django.utils.translation import gettext_lazy as _ 16 | 17 | # Build paths inside the project like this: BASE_DIR / 'subdir'. 18 | BASE_DIR = Path(__file__).resolve().parent.parent 19 | 20 | 21 | # Quick-start development settings - unsuitable for production 22 | # See https://docs.djangoproject.com/en/3.2/howto/deployment/checklist/ 23 | 24 | # SECURITY WARNING: This is a dummy secret. Keep the secret key used in production secret! 25 | SECRET_KEY = ("-*27w=!r@^k*" + "&b+&1is1@6o%m-j=u01" + "apoqx-d(7d&1hci^@g_!") 26 | 27 | # SECURITY WARNING: don't run with debug turned on in production! 28 | DEBUG = True 29 | 30 | ALLOWED_HOSTS = [] 31 | 32 | 33 | # Application definition 34 | 35 | INSTALLED_APPS = [ 36 | "django.contrib.admin", 37 | "django.contrib.auth", 38 | "django.contrib.contenttypes", 39 | "django.contrib.sessions", 40 | "django.contrib.messages", 41 | "django.contrib.staticfiles", 42 | "demo_project", # for the management commands 43 | "gdpr_cookie_consent", 44 | ] 45 | 46 | MIDDLEWARE = [ 47 | "django.middleware.security.SecurityMiddleware", 48 | "django.contrib.sessions.middleware.SessionMiddleware", 49 | "django.middleware.common.CommonMiddleware", 50 | "django.middleware.csrf.CsrfViewMiddleware", 51 | "django.contrib.auth.middleware.AuthenticationMiddleware", 52 | "django.contrib.messages.middleware.MessageMiddleware", 53 | "django.middleware.clickjacking.XFrameOptionsMiddleware", 54 | ] 55 | 56 | ROOT_URLCONF = "demo_project.urls" 57 | 58 | TEMPLATES = [ 59 | { 60 | "BACKEND": "django.template.backends.django.DjangoTemplates", 61 | "DIRS": [ 62 | BASE_DIR / "demo_project" / "templates", 63 | ], 64 | "APP_DIRS": True, 65 | "OPTIONS": { 66 | "context_processors": [ 67 | "django.template.context_processors.debug", 68 | "django.template.context_processors.request", 69 | "django.contrib.auth.context_processors.auth", 70 | "django.contrib.messages.context_processors.messages", 71 | ], 72 | }, 73 | }, 74 | ] 75 | 76 | WSGI_APPLICATION = "demo_project.wsgi.application" 77 | 78 | 79 | # Database 80 | # https://docs.djangoproject.com/en/3.2/ref/settings/#databases 81 | 82 | DATABASES = { 83 | "default": { 84 | "ENGINE": "django.db.backends.sqlite3", 85 | "NAME": str(BASE_DIR / "db.sqlite3"), 86 | } 87 | } 88 | 89 | 90 | # Password validation 91 | # https://docs.djangoproject.com/en/3.2/ref/settings/#auth-password-validators 92 | 93 | AUTH_PASSWORD_VALIDATORS = [ 94 | { 95 | "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator", 96 | }, 97 | { 98 | "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator", 99 | }, 100 | { 101 | "NAME": "django.contrib.auth.password_validation.CommonPasswordValidator", 102 | }, 103 | { 104 | "NAME": "django.contrib.auth.password_validation.NumericPasswordValidator", 105 | }, 106 | ] 107 | 108 | 109 | # Internationalization 110 | # https://docs.djangoproject.com/en/3.2/topics/i18n/ 111 | 112 | LANGUAGE_CODE = "en-us" 113 | 114 | TIME_ZONE = "UTC" 115 | 116 | USE_I18N = True 117 | 118 | USE_L10N = True 119 | 120 | USE_TZ = True 121 | 122 | 123 | # Static files (CSS, JavaScript, Images) 124 | # https://docs.djangoproject.com/en/3.2/howto/static-files/ 125 | 126 | STATICFILES_DIRS = [ 127 | BASE_DIR / "demo_project" / "site_static", 128 | ] 129 | STATIC_ROOT = BASE_DIR / "static" 130 | STATIC_URL = "/static/" 131 | 132 | # Default primary key field type 133 | # https://docs.djangoproject.com/en/3.2/ref/settings/#default-auto-field 134 | 135 | DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" 136 | 137 | COOKIE_CONSENT_SETTINGS = { 138 | # Base template will be used for the cookie management view. 139 | # It should contain {% block content %}{% endblock %} 140 | "base_template_name": "base.html", 141 | 142 | # Description will be used in the modal dialog and cookie management view. 143 | # Provide either a translatable string or a HTML template name. 144 | "description": "", 145 | "description_template_name": "gdpr_cookie_consent/descriptions/what_are_cookies.html", 146 | 147 | # Extra information will be used at the end of cookie management view. 148 | # Provide either a translatable string or a HTML template name. 149 | "extra_information": "", 150 | "extra_information_template_name": "gdpr_cookie_consent/descriptions/extra.html", 151 | 152 | # Button texts can be customized. 153 | "buttons": { 154 | "accept_all_dialog": _("Accept all recommended"), 155 | "reject_all_dialog": _("Reject non-required"), 156 | "manage_cookies": _("Manage cookies"), 157 | "accept_all_config": _("Accept all"), 158 | "reject_all_config": _("Reject all"), 159 | "save_preferences": _("Save preferences"), 160 | "save_and_close": _("Save and close"), 161 | "close": _("Close"), 162 | }, 163 | # Should it be possible to close the dialog without choosing a consent? 164 | "show_dialog_close_button": True, 165 | 166 | # Dialog position: center, top, left, right, bottom 167 | "dialog_position": "center", 168 | 169 | # All important elements will have CSS classes with `cc-` prefix, 170 | # by which you can target them and overwrite their styling. 171 | # But you can attach some CSS classes to certain elements too. 172 | "styling": { 173 | "primary_button_css_classes": "btn btn-primary rounded-pill px-3", 174 | "secondary_button_css_classes": "btn btn-secondary rounded-pill px-3", 175 | "close_button_css_classes": "btn", 176 | "provider_list_css_classes": "list-inline", 177 | "provider_item_css_classes": "badge bg-secondary font-normal", 178 | "link_css_classes": "", 179 | "section_anchor_css_classes": "", 180 | }, 181 | # Any variables that will be passed to conditional HTML snippets 182 | "extra_context": { 183 | "FUNCTIONALITY_COOKIE_VALUE": "🛠", 184 | "PERFORMANCE_COOKIE_VALUE": "📊", 185 | "MARKETING_COOKIE_VALUE": "📢", 186 | }, 187 | 188 | # Consent cookie max age say how many seconds to keep the cookie consent preferences. 189 | # For example, it can be approximately six months 190 | "consent_cookie_max_age": 60 * 60 * 24 * 30 * 6, 191 | 192 | # URL of the page where you get redirected after saving the cookie settings 193 | "redirect_url": "/", 194 | 195 | # Sections define the purposes of cookie groups. 196 | # For example: Essential, Functionality, Performance, and Marketing 197 | "sections": [ 198 | { 199 | # The slug will be used as a readable identifier of the section 200 | "slug": "essential", 201 | 202 | # Translatable title of the section 203 | "title": _("Essential Cookies"), 204 | 205 | # Required sections will be already selected and read-only 206 | "required": True, 207 | 208 | # Preselected sections will be selected in the configuration page before the consent is given. 209 | # (all required sections will be preselected too and this setting is ignored for those) 210 | # N.B. Laws of the United Kingdom, Germany, and France require that 211 | # the opt-in consent for cookies must not be pre-enabled, 212 | # so consult your lawyers before enabling this setting. 213 | "preselected": True, 214 | 215 | # Section summary will be shown in the modal dialog and preferences form. 216 | # Provide either a translatable string or an HTML template name. 217 | "summary": _( 218 | "These cookies are always on, as they’re essential for making this website work, and making it safe. Without these cookies, services you’ve asked for can’t be provided."), 219 | "summary_template_name": "", 220 | 221 | # Section description will be used at the extended cookie explanation in the cookie management view. 222 | # Provide either a translatable string or an HTML template name. 223 | "description": _( 224 | "These cookies are always on, as they’re essential for making this website work, and making it safe. Without these cookies, services you’ve asked for can’t be provided."), 225 | "description_template_name": "", 226 | 227 | # Cookie providers are websites that set the cookies on your website 228 | "providers": [ 229 | { 230 | # Translatable title of the provider 231 | "title": _("This website"), 232 | 233 | # Provider description will be used at the extended cookie explanation in the cookie management view. 234 | # Provide either a translatable string or an HTML template name. 235 | "description": "", 236 | "description_template_name": "", 237 | 238 | # A list of cookies set by the provider 239 | "cookies": [ 240 | { 241 | # Cookie name can include wildcard syntax like "abc_*" 242 | "cookie_name": "sessionid", 243 | 244 | # Human-readable translated duration of the cookie 245 | "duration": _("2 Weeks"), 246 | 247 | # Cookie description will be used at the extended cookie explanation in the cookie management view. 248 | # Provide either a translatable string or a HTML template name. 249 | "description": _( 250 | "Session ID used to authenticate you and give permissions to use the site."), 251 | "description_template_name": "", 252 | 253 | # Cookie domain will be used to delete cookies if a section is unchecked 254 | "domain": "127.0.0.1", 255 | }, 256 | { 257 | "cookie_name": "csrftoken", 258 | "duration": _("Session"), 259 | "description": _( 260 | "Security token used to ensure that no hackers are posting forms on your behalf."), 261 | "description_template_name": "", 262 | "domain": "127.0.0.1", 263 | }, 264 | { 265 | "cookie_name": "cookie_consent", 266 | "duration": _("6 Years"), 267 | "description": _("Settings of Cookie Consent preferences."), 268 | "description_template_name": "", 269 | "domain": "127.0.0.1", 270 | }, 271 | ] 272 | }, 273 | ], 274 | }, 275 | { 276 | "slug": "functionality", 277 | "title": _("Functionality Cookies"), 278 | 279 | # Conditional HTML snippet will be loaded or rendered if this section is selected 280 | "conditional_html_template_name": "conditional_html/functionality.html", 281 | "required": False, 282 | "preselected": False, 283 | "summary": _( 284 | "These cookies help us provide enhanced functionality and personalisation, and remember your settings. They may be set by us or by third party providers."), 285 | "summary_template_name": "", 286 | "description": _( 287 | "These cookies help us provide enhanced functionality and personalisation, and remember your settings. They may be set by us or by third party providers."), 288 | "description_template_name": "", 289 | "providers": [ 290 | { 291 | "title": _("This website"), 292 | "description": _(""), 293 | "description_template_name": "", 294 | "cookies": [ 295 | { 296 | "cookie_name": "functionality_cookie", 297 | "duration": _("Session"), 298 | "description": "", 299 | "description_template_name": "", 300 | "domain": "127.0.0.1", 301 | }, 302 | ] 303 | }, 304 | ], 305 | }, 306 | { 307 | "slug": "performance", 308 | "title": _("Performance Cookies"), 309 | "conditional_html_template_name": "conditional_html/performance.html", 310 | "required": False, 311 | "preselected": False, 312 | "summary": _( 313 | "These cookies help us analyse how many people are using this website, where they come from and how they're using it. If you opt out of these cookies, we can’t get feedback to make this website better for you and all our users."), 314 | "summary_template_name": "", 315 | "description": _( 316 | "These cookies help us analyse how many people are using this website, where they come from and how they're using it. If you opt out of these cookies, we can’t get feedback to make this website better for you and all our users."), 317 | "description_template_name": "", 318 | "providers": [ 319 | { 320 | "title": _("This website"), 321 | "description": _(""), 322 | "description_template_name": "", 323 | "cookies": [ 324 | { 325 | "cookie_name": "performance_cookie", 326 | "duration": _("Session"), 327 | "description": "", 328 | "description_template_name": "", 329 | "domain": "127.0.0.1", 330 | }, 331 | ] 332 | }, 333 | ], 334 | }, 335 | { 336 | "slug": "marketing", 337 | "title": _("Marketing Cookies"), 338 | "conditional_html_template_name": "conditional_html/marketing.html", 339 | "required": False, 340 | "preselected": False, 341 | "summary": _( 342 | "These cookies are set by our advertising partners to track your activity and show you relevant ads on other sites as you browse the internet."), 343 | "summary_template_name": "", 344 | "description": _( 345 | "These cookies are set by our advertising partners to track your activity and show you relevant ads on other sites as you browse the internet."), 346 | "description_template_name": "", 347 | "providers": [ 348 | { 349 | "title": _("This website"), 350 | "description": _(""), 351 | "description_template_name": "", 352 | "cookies": [ 353 | { 354 | "cookie_name": "marketing_cookie", 355 | "duration": _("Session"), 356 | "description": "", 357 | "description_template_name": "", 358 | "domain": "127.0.0.1", 359 | }, 360 | ] 361 | }, 362 | ], 363 | }, 364 | ] 365 | } 366 | 367 | TESTS_SHOW_BROWSER = True -------------------------------------------------------------------------------- /demo_project/site_static/css/style.css: -------------------------------------------------------------------------------- 1 | /********** Template CSS **********/ 2 | :root { 3 | --primary: #D98E16; /* #EAA636;*/ 4 | --secondary: #545454; 5 | --light: #FDF5EB; 6 | --dark: #1E1916; 7 | --cc-backdrop: rgba(0, 0, 0, 0.2); 8 | --cc-dialog-margin-correction: -19px; 9 | --cc-page-scroll-buffer: 111px; 10 | } 11 | html, body { 12 | margin: 0; 13 | padding: 0; 14 | min-height: 100vh; 15 | } 16 | .cc-modal-dialog::backdrop { 17 | backdrop-filter: blur(2px); 18 | } 19 | h4, 20 | h5, 21 | h6, 22 | .h4, 23 | .h5, 24 | .h6 { 25 | font-weight: 600 !important; 26 | } 27 | 28 | .py-6 { 29 | padding-top: 6rem; 30 | padding-bottom: 6rem; 31 | } 32 | 33 | .my-6 { 34 | margin-top: 6rem; 35 | margin-bottom: 6rem; 36 | } 37 | 38 | .back-to-top { 39 | position: fixed; 40 | display: none; 41 | right: 30px; 42 | bottom: 30px; 43 | z-index: 99; 44 | } 45 | 46 | 47 | /*** Spinner ***/ 48 | #spinner { 49 | opacity: 0; 50 | visibility: hidden; 51 | transition: opacity .5s ease-out, visibility 0s linear .5s; 52 | z-index: 99999; 53 | } 54 | 55 | #spinner.show { 56 | transition: opacity .5s ease-out, visibility 0s linear 0s; 57 | visibility: visible; 58 | opacity: 1; 59 | } 60 | 61 | 62 | /*** Button ***/ 63 | .btn { 64 | font-weight: 500; 65 | transition: .5s; 66 | } 67 | 68 | .btn.btn-primary { 69 | color: #FFFFFF; 70 | background: var(--primary); 71 | } 72 | 73 | .btn-square { 74 | width: 38px; 75 | height: 38px; 76 | } 77 | 78 | .btn-sm-square { 79 | width: 32px; 80 | height: 32px; 81 | } 82 | 83 | .btn-lg-square { 84 | width: 48px; 85 | height: 48px; 86 | } 87 | 88 | .btn-square, 89 | .btn-sm-square, 90 | .btn-lg-square { 91 | padding: 0; 92 | display: flex; 93 | align-items: center; 94 | justify-content: center; 95 | font-weight: normal; 96 | } 97 | 98 | 99 | /*** Navbar ***/ 100 | .navbar .dropdown-toggle::after { 101 | border: none; 102 | content: "\f107"; 103 | font-family: "Font Awesome 5 Free"; 104 | font-weight: 900; 105 | vertical-align: middle; 106 | margin-left: 8px; 107 | } 108 | 109 | .navbar .navbar-nav .nav-link { 110 | padding: 35px 15px; 111 | color: var(--light); 112 | outline: none; 113 | } 114 | 115 | .navbar .navbar-nav .nav-link:hover, 116 | .navbar .navbar-nav .nav-link.active { 117 | color: var(--primary); 118 | } 119 | 120 | .navbar.fixed-top { 121 | transition: .5s; 122 | } 123 | 124 | @media (max-width: 991.98px) { 125 | .navbar .navbar-nav { 126 | margin-top: 10px; 127 | border-top: 1px solid rgba(255, 255, 255, .3); 128 | background: var(--dark); 129 | } 130 | 131 | .navbar .navbar-nav .nav-link { 132 | padding: 10px 0; 133 | } 134 | } 135 | 136 | @media (min-width: 992px) { 137 | .navbar .nav-item .dropdown-menu { 138 | display: block; 139 | visibility: hidden; 140 | top: 100%; 141 | transform: rotateX(-75deg); 142 | transform-origin: 0% 0%; 143 | transition: .5s; 144 | opacity: 0; 145 | } 146 | 147 | .navbar .nav-item:hover .dropdown-menu { 148 | transform: rotateX(0deg); 149 | visibility: visible; 150 | transition: .5s; 151 | opacity: 1; 152 | } 153 | } 154 | 155 | 156 | /*** Header ***/ 157 | .header-carousel .owl-carousel-inner { 158 | position: absolute; 159 | width: 100%; 160 | height: 100%; 161 | top: 0; 162 | left: 0; 163 | display: flex; 164 | align-items: center; 165 | background: rgba(0, 0, 0, .5); 166 | } 167 | 168 | @media (max-width: 768px) { 169 | .header-carousel .owl-carousel-item { 170 | position: relative; 171 | min-height: 600px; 172 | } 173 | 174 | .header-carousel .owl-carousel-item img { 175 | position: absolute; 176 | width: 100%; 177 | height: 100%; 178 | object-fit: cover; 179 | } 180 | 181 | .header-carousel .owl-carousel-item p { 182 | font-size: 16px !important; 183 | } 184 | } 185 | 186 | .header-carousel .owl-nav { 187 | position: relative; 188 | width: 80px; 189 | height: 80px; 190 | margin: -40px auto 0 auto; 191 | display: flex; 192 | justify-content: center; 193 | align-items: center; 194 | } 195 | 196 | .header-carousel .owl-nav::before { 197 | position: absolute; 198 | content: ""; 199 | width: 100%; 200 | height: 100%; 201 | top: 0; 202 | left: 0; 203 | background: #FFFFFF; 204 | transform: rotate(45deg); 205 | } 206 | 207 | .header-carousel .owl-nav .owl-prev, 208 | .header-carousel .owl-nav .owl-next { 209 | position: relative; 210 | font-size: 40px; 211 | color: var(--primary); 212 | transition: .5s; 213 | z-index: 1; 214 | } 215 | 216 | .header-carousel .owl-nav .owl-prev:hover, 217 | .header-carousel .owl-nav .owl-next:hover { 218 | color: var(--dark); 219 | } 220 | 221 | .page-header { 222 | margin-bottom: 6rem; 223 | background: linear-gradient(rgba(0, 0, 0, .5), rgba(0, 0, 0, .5)), url(../img/carousel-1.jpg) center center no-repeat; 224 | background-size: cover; 225 | } 226 | 227 | .breadcrumb-item+.breadcrumb-item::before { 228 | color: var(--light); 229 | } 230 | 231 | 232 | /*** Facts ***/ 233 | .fact-item { 234 | transition: .5s; 235 | } 236 | 237 | .fact-item:hover { 238 | margin-top: -10px; 239 | background: #FFFFFF !important; 240 | box-shadow: 0 0 45px rgba(0, 0, 0, .07); 241 | } 242 | 243 | 244 | /*** About ***/ 245 | .img-twice::before { 246 | position: absolute; 247 | content: ""; 248 | width: 60%; 249 | height: 80%; 250 | top: 10%; 251 | left: 20%; 252 | background: var(--primary); 253 | border: 25px solid var(--light); 254 | border-radius: 6px; 255 | z-index: -1; 256 | } 257 | 258 | 259 | /*** Product ***/ 260 | .product-item { 261 | transition: .5s; 262 | } 263 | 264 | .product-item:hover { 265 | background: var(--primary) !important; 266 | } 267 | 268 | .product-item:hover * { 269 | color: var(--light); 270 | } 271 | 272 | .product-item:hover .border-primary { 273 | border-color: var(--light) !important; 274 | } 275 | 276 | .product-item .product-overlay { 277 | position: absolute; 278 | width: 100%; 279 | height: 0; 280 | top: 0; 281 | left: 0; 282 | display: flex; 283 | align-items: center; 284 | justify-content: center; 285 | background: rgba(0, 0, 0, .5); 286 | overflow: hidden; 287 | opacity: 0; 288 | transition: .5s; 289 | } 290 | 291 | .product-item:hover .product-overlay { 292 | height: 100%; 293 | opacity: 1; 294 | } 295 | 296 | 297 | /*** Team ***/ 298 | .team-item .team-text { 299 | position: relative; 300 | height: 100px; 301 | overflow: hidden; 302 | } 303 | 304 | .team-item .team-title { 305 | position: absolute; 306 | width: 100%; 307 | height: 100%; 308 | top: 0; 309 | left: 0; 310 | display: flex; 311 | flex-direction: column; 312 | align-items: center; 313 | justify-content: center; 314 | background: var(--light); 315 | transition: .5s; 316 | } 317 | 318 | .team-item:hover .team-title { 319 | top: -100px; 320 | } 321 | 322 | .team-item .team-social { 323 | position: absolute; 324 | width: 100%; 325 | height: 100%; 326 | top: 100px; 327 | left: 0; 328 | display: flex; 329 | align-items: center; 330 | justify-content: center; 331 | background: var(--primary); 332 | transition: .5s; 333 | } 334 | 335 | .team-item .team-social .btn { 336 | margin: 0 3px; 337 | } 338 | 339 | .team-item:hover .team-social { 340 | top: 0; 341 | } 342 | 343 | 344 | /*** Testimonial ***/ 345 | .testimonial-carousel .owl-item .testimonial-item img { 346 | width: 60px; 347 | height: 60px; 348 | } 349 | 350 | .testimonial-carousel .owl-item .testimonial-item, 351 | .testimonial-carousel .owl-item .testimonial-item * { 352 | transition: .5s; 353 | } 354 | 355 | .testimonial-carousel .owl-item.center .testimonial-item { 356 | background: var(--primary) !important; 357 | } 358 | 359 | .testimonial-carousel .owl-item.center .testimonial-item * { 360 | color: #FFFFFF !important; 361 | } 362 | 363 | .testimonial-carousel .owl-nav { 364 | margin-top: 30px; 365 | display: flex; 366 | justify-content: center; 367 | } 368 | 369 | .testimonial-carousel .owl-nav .owl-prev, 370 | .testimonial-carousel .owl-nav .owl-next { 371 | margin: 0 12px; 372 | width: 50px; 373 | height: 50px; 374 | display: flex; 375 | align-items: center; 376 | justify-content: center; 377 | border-radius: 50px; 378 | font-size: 22px; 379 | color: var(--light); 380 | background: var(--primary); 381 | transition: .5s; 382 | } 383 | 384 | .testimonial-carousel .owl-nav .owl-prev:hover, 385 | .testimonial-carousel .owl-nav .owl-next:hover { 386 | color: var(--primary); 387 | background: var(--dark); 388 | } 389 | 390 | 391 | /*** Footer ***/ 392 | .footer .btn.btn-link { 393 | display: block; 394 | margin-bottom: 5px; 395 | padding: 0; 396 | text-align: left; 397 | color: var(--light); 398 | font-weight: normal; 399 | text-transform: capitalize; 400 | transition: .3s; 401 | } 402 | 403 | .footer .btn.btn-link::before { 404 | position: relative; 405 | content: "\f105"; 406 | font-family: "Font Awesome 5 Free"; 407 | font-weight: 900; 408 | color: var(--light); 409 | margin-right: 10px; 410 | } 411 | 412 | .footer .btn.btn-link:hover { 413 | color: var(--primary); 414 | letter-spacing: 1px; 415 | box-shadow: none; 416 | } 417 | 418 | .copyright { 419 | background: #111111; 420 | } 421 | 422 | .copyright a { 423 | color: var(--primary); 424 | } 425 | 426 | .copyright a:hover { 427 | color: var(--light); 428 | } 429 | 430 | #cc_accept_all, #cc_reject_all, #cc_save_preferences { 431 | scroll-margin-top: var(--cc-page-scroll-buffer); 432 | } -------------------------------------------------------------------------------- /demo_project/site_static/img/about-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/archatas/django-gdpr-cookie-consent-demo-project/2b72a07799dbfba5e05687c4a656451017fca847/demo_project/site_static/img/about-1.jpg -------------------------------------------------------------------------------- /demo_project/site_static/img/about-2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/archatas/django-gdpr-cookie-consent-demo-project/2b72a07799dbfba5e05687c4a656451017fca847/demo_project/site_static/img/about-2.jpg -------------------------------------------------------------------------------- /demo_project/site_static/img/carousel-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/archatas/django-gdpr-cookie-consent-demo-project/2b72a07799dbfba5e05687c4a656451017fca847/demo_project/site_static/img/carousel-1.jpg -------------------------------------------------------------------------------- /demo_project/site_static/img/carousel-2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/archatas/django-gdpr-cookie-consent-demo-project/2b72a07799dbfba5e05687c4a656451017fca847/demo_project/site_static/img/carousel-2.jpg -------------------------------------------------------------------------------- /demo_project/site_static/img/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/archatas/django-gdpr-cookie-consent-demo-project/2b72a07799dbfba5e05687c4a656451017fca847/demo_project/site_static/img/favicon.ico -------------------------------------------------------------------------------- /demo_project/site_static/img/product-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/archatas/django-gdpr-cookie-consent-demo-project/2b72a07799dbfba5e05687c4a656451017fca847/demo_project/site_static/img/product-1.jpg -------------------------------------------------------------------------------- /demo_project/site_static/img/product-2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/archatas/django-gdpr-cookie-consent-demo-project/2b72a07799dbfba5e05687c4a656451017fca847/demo_project/site_static/img/product-2.jpg -------------------------------------------------------------------------------- /demo_project/site_static/img/product-3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/archatas/django-gdpr-cookie-consent-demo-project/2b72a07799dbfba5e05687c4a656451017fca847/demo_project/site_static/img/product-3.jpg -------------------------------------------------------------------------------- /demo_project/site_static/img/service-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/archatas/django-gdpr-cookie-consent-demo-project/2b72a07799dbfba5e05687c4a656451017fca847/demo_project/site_static/img/service-1.jpg -------------------------------------------------------------------------------- /demo_project/site_static/img/service-2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/archatas/django-gdpr-cookie-consent-demo-project/2b72a07799dbfba5e05687c4a656451017fca847/demo_project/site_static/img/service-2.jpg -------------------------------------------------------------------------------- /demo_project/site_static/img/team-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/archatas/django-gdpr-cookie-consent-demo-project/2b72a07799dbfba5e05687c4a656451017fca847/demo_project/site_static/img/team-1.jpg -------------------------------------------------------------------------------- /demo_project/site_static/img/team-2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/archatas/django-gdpr-cookie-consent-demo-project/2b72a07799dbfba5e05687c4a656451017fca847/demo_project/site_static/img/team-2.jpg -------------------------------------------------------------------------------- /demo_project/site_static/img/team-3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/archatas/django-gdpr-cookie-consent-demo-project/2b72a07799dbfba5e05687c4a656451017fca847/demo_project/site_static/img/team-3.jpg -------------------------------------------------------------------------------- /demo_project/site_static/img/team-4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/archatas/django-gdpr-cookie-consent-demo-project/2b72a07799dbfba5e05687c4a656451017fca847/demo_project/site_static/img/team-4.jpg -------------------------------------------------------------------------------- /demo_project/site_static/img/testimonial-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/archatas/django-gdpr-cookie-consent-demo-project/2b72a07799dbfba5e05687c4a656451017fca847/demo_project/site_static/img/testimonial-1.jpg -------------------------------------------------------------------------------- /demo_project/site_static/img/testimonial-2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/archatas/django-gdpr-cookie-consent-demo-project/2b72a07799dbfba5e05687c4a656451017fca847/demo_project/site_static/img/testimonial-2.jpg -------------------------------------------------------------------------------- /demo_project/site_static/img/testimonial-3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/archatas/django-gdpr-cookie-consent-demo-project/2b72a07799dbfba5e05687c4a656451017fca847/demo_project/site_static/img/testimonial-3.jpg -------------------------------------------------------------------------------- /demo_project/site_static/img/testimonial-4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/archatas/django-gdpr-cookie-consent-demo-project/2b72a07799dbfba5e05687c4a656451017fca847/demo_project/site_static/img/testimonial-4.jpg -------------------------------------------------------------------------------- /demo_project/site_static/js/main.js: -------------------------------------------------------------------------------- 1 | (function ($) { 2 | "use strict"; 3 | 4 | // Spinner 5 | var spinner = function () { 6 | setTimeout(function () { 7 | if ($('#spinner').length > 0) { 8 | $('#spinner').removeClass('show'); 9 | } 10 | }, 1); 11 | }; 12 | spinner(); 13 | 14 | 15 | // Initiate the wowjs 16 | new WOW().init(); 17 | 18 | 19 | // Fixed Navbar 20 | $('.fixed-top').css('top', $('.top-bar').height()); 21 | $(window).scroll(function () { 22 | if ($(this).scrollTop()) { 23 | $('.fixed-top').addClass('bg-dark').css('top', 0); 24 | } else { 25 | $('.fixed-top').removeClass('bg-dark').css('top', $('.top-bar').height()); 26 | } 27 | }); 28 | 29 | 30 | // Back to top button 31 | $(window).scroll(function () { 32 | if ($(this).scrollTop() > 300) { 33 | $('.back-to-top').fadeIn('slow'); 34 | } else { 35 | $('.back-to-top').fadeOut('slow'); 36 | } 37 | }); 38 | $('.back-to-top').click(function () { 39 | $('html, body').animate({scrollTop: 0}, 1500, 'easeInOutExpo'); 40 | return false; 41 | }); 42 | 43 | 44 | // Header carousel 45 | $(".header-carousel").owlCarousel({ 46 | autoplay: false, 47 | smartSpeed: 1500, 48 | loop: true, 49 | nav: true, 50 | dots: false, 51 | items: 1, 52 | navText : [ 53 | '', 54 | '' 55 | ] 56 | }); 57 | 58 | 59 | // Facts counter 60 | $('[data-toggle="counter-up"]').counterUp({ 61 | delay: 10, 62 | time: 2000 63 | }); 64 | 65 | 66 | // Testimonials carousel 67 | $(".testimonial-carousel").owlCarousel({ 68 | autoplay: false, 69 | smartSpeed: 1000, 70 | margin: 25, 71 | loop: true, 72 | center: true, 73 | dots: false, 74 | nav: true, 75 | navText : [ 76 | '', 77 | '' 78 | ], 79 | responsive: { 80 | 0:{ 81 | items:1 82 | }, 83 | 768:{ 84 | items:2 85 | }, 86 | 992:{ 87 | items:3 88 | } 89 | } 90 | }); 91 | 92 | 93 | })(jQuery); 94 | 95 | -------------------------------------------------------------------------------- /demo_project/templates/base.html: -------------------------------------------------------------------------------- 1 | {% load static %} 2 | 3 | 4 | 5 | 6 | 7 | {% block title %}{% endblock %} 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | {% block body %} 37 | 38 |
39 |
40 |
41 | 42 | 43 | 44 | 45 |
46 |
47 |
48 | 54 |
55 |
56 | Follow us: 57 |
58 | 59 | 60 | 61 | 62 |
63 |
64 |
65 |
66 | 67 | 68 | 69 | 70 | 104 | 105 | 106 | 107 | 108 | 110 | 111 | 112 | 113 | 114 |
115 |
116 |
117 |
118 | {% block content %} 119 | {% endblock content %} 120 |
121 |
122 |
123 |
124 | 125 | 126 | 127 | 184 | 185 | 186 | 187 | 188 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | {% endblock body %} 220 | 221 | 222 | {% include "gdpr_cookie_consent/includes/cookie_consent.html" %} 223 | 248 | 249 | 250 | -------------------------------------------------------------------------------- /demo_project/templates/conditional_html/functionality.html: -------------------------------------------------------------------------------- 1 | 17 | 18 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /demo_project/templates/conditional_html/marketing.html: -------------------------------------------------------------------------------- 1 | 17 | -------------------------------------------------------------------------------- /demo_project/templates/conditional_html/performance.html: -------------------------------------------------------------------------------- 1 | 17 | -------------------------------------------------------------------------------- /demo_project/templates/gdpr_cookie_consent/descriptions/extra.html: -------------------------------------------------------------------------------- 1 | {% load i18n %} 2 | 3 |
4 |
5 |

{% blocktranslate trimmed %}How to Manage Cookies in the Browser{% endblocktranslate %}

6 |
7 | 8 | {% url "feedback:feedback_message_form" as feedback_url %} 9 |

{% blocktranslate trimmed %} 10 | You can also control cookie usage in your browser: 11 | {% endblocktranslate %}

12 | 19 |
20 | 21 |
22 |
23 |

{% blocktranslate trimmed %}Django GDPR Cookie Consent{% endblocktranslate %}

24 |
25 | 26 |

{% blocktranslate trimmed with version=cookie_consent_controller.get_version %} 27 | Cookie management on this website is maintained using Django GDPR Cookie Consent v{{ version }}, a tool that helps us obtain and manage your consent for the use of cookies in accordance with the General Data Protection Regulation (GDPR). 28 | {% endblocktranslate %}

29 |
-------------------------------------------------------------------------------- /demo_project/templates/index.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% load i18n static %} 3 | 4 | {% block title %}Baker - Django GDPR Cookie Consent Demonstration{% endblock %} 5 | 6 | {% block body %} 7 | 8 |
9 |
10 |
11 | 12 | 13 | 14 | 15 |
16 |
17 |
18 | 24 |
25 |
26 | Follow us: 27 |
28 | 29 | 30 | 31 | 32 |
33 |
34 |
35 |
36 | 37 | 38 | 39 | 40 | 74 | 75 | 76 | 77 | 78 |
79 | 111 |
112 | 113 | 114 | 115 | 116 |
117 |
118 |
119 |
120 |
121 | 122 |

Years Experience

123 |

50

124 |
125 |
126 |
127 |
128 | 129 |

Skilled Professionals

130 |

175

131 |
132 |
133 |
134 |
135 | 136 |

Total Products

137 |

135

138 |
139 |
140 |
141 |
142 | 143 |

Order Everyday

144 |

9357

145 |
146 |
147 |
148 |
149 |
150 | 151 | 152 | 153 | 154 |
155 |
156 |
157 |
158 |
159 |
160 | 161 |
162 |
163 | 164 |
165 |
166 |
167 |
168 |
169 |

// About Us

170 |

We Bake Every Item From The Core Of Our Hearts

171 |

Tempor erat elitr rebum at clita. Diam dolor diam ipsum sit. Aliqu diam amet diam et eos. Clita erat ipsum et lorem et sit, sed stet lorem sit clita duo justo magna dolore erat amet

172 |

Tempor erat elitr rebum at clita. Diam dolor diam ipsum sit. Aliqu diam amet diam et eos. Clita erat ipsum et lorem et sit, sed stet lorem sit clita duo justo magna dolore erat amet

173 |
174 |
175 | Quality Products 176 |
177 |
178 | Custom Products 179 |
180 |
181 | Online Order 182 |
183 |
184 | Home Delivery 185 |
186 |
187 | Read More 188 |
189 |
190 |
191 |
192 |
193 | 194 | 195 | 196 | 197 |
198 |
199 |
200 |
201 |
202 |

The Best Bakery In Your City

203 |
204 |
205 |
206 | 207 |
208 |

Call Us

209 |

+012 345 6789

210 |
211 |
212 |
213 |
214 |
215 |
216 |

// Bakery Products

217 |

Explore The Categories Of Our Bakery Products

218 |
219 |
220 |
221 |
222 |
223 |
$11 - $99
224 |

Cake

225 | Tempor erat elitr rebum at clita dolor diam ipsum sit diam amet diam et eos 226 |
227 |
228 | 229 |
230 | 231 |
232 |
233 |
234 |
235 |
236 |
237 |
238 |
$11 - $99
239 |

Bread

240 | Tempor erat elitr rebum at clita dolor diam ipsum sit diam amet diam et eos 241 |
242 |
243 | 244 |
245 | 246 |
247 |
248 |
249 |
250 |
251 |
252 |
253 |
$11 - $99
254 |

Cookies

255 | Tempor erat elitr rebum at clita dolor diam ipsum sit diam amet diam et eos 256 |
257 |
258 | 259 |
260 | 261 |
262 |
263 |
264 |
265 |
266 |
267 |
268 | 269 | 270 | 271 | 272 |
273 |
274 |
275 |
276 |

// Our Services

277 |

What Do We Offer For You?

278 |

Tempor erat elitr rebum at clita. Diam dolor diam ipsum sit. Aliqu diam amet diam et eos. Clita erat ipsum et lorem et sit, sed stet lorem sit clita duo justo magna dolore erat amet

279 |
280 |
281 |
282 |
283 | 284 |
285 |
Quality Products
286 |
287 | Magna sea eos sit dolor, ipsum amet ipsum lorem diam eos 288 |
289 |
290 |
291 |
292 | 293 |
294 |
Custom Products
295 |
296 | Magna sea eos sit dolor, ipsum amet ipsum lorem diam eos 297 |
298 |
299 |
300 |
301 | 302 |
303 |
Online Order
304 |
305 | Magna sea eos sit dolor, ipsum amet ipsum lorem diam eos 306 |
307 |
308 |
309 |
310 | 311 |
312 |
Home Delivery
313 |
314 | Magna sea eos sit dolor, ipsum amet ipsum lorem diam eos 315 |
316 |
317 |
318 |
319 |
320 |
321 | 322 |
323 |
324 | 325 |
326 |
327 |
328 |
329 |
330 |
331 | 332 | 333 | 334 | 335 |
336 |
337 |
338 |

// Our Team

339 |

We're Super Professional At Our Skills

340 |
341 |
342 |
343 |
344 | 345 |
346 |
347 |
Full Name
348 | Designation 349 |
350 | 355 |
356 |
357 |
358 |
359 |
360 | 361 |
362 |
363 |
Full Name
364 | Designation 365 |
366 | 371 |
372 |
373 |
374 |
375 |
376 | 377 |
378 |
379 |
Full Name
380 | Designation 381 |
382 | 387 |
388 |
389 |
390 |
391 |
392 | 393 |
394 |
395 |
Full Name
396 | Designation 397 |
398 | 403 |
404 |
405 |
406 |
407 |
408 |
409 | 410 | 411 | 412 | 413 |
414 |
415 |
416 |

// Client's Review

417 |

More Than 20000+ Customers Trusted Us

418 |
419 | 461 |
462 |
463 |
464 |

Subscribe Our Newsletter

465 |
466 |
467 |
468 | 469 | 470 |
471 |
472 |
473 |
474 |
475 |
476 | 477 | 478 | 479 | 480 | 537 | 538 | 539 | 540 | 541 | 554 | 555 | 556 | 557 | 558 | 559 | 560 | 561 | 562 | 563 | 564 | 565 | 566 | 567 | 568 | 569 | 570 | 571 | 572 | {% endblock body %} 573 | -------------------------------------------------------------------------------- /demo_project/templates/test.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% load i18n static %} 3 | 4 | {% block title %}Baker - Django GDPR Cookie Consent Demonstration{% endblock %} 5 | 6 | {% block body %} 7 |
8 |

Hello, World!

9 |

10 | {% url "cookie_consent:cookies_management" as cookie_management_url %} 11 | 12 | 🥠 {% trans "Manage Cookies" %} 13 | 14 |

15 |
16 | {% endblock body %} 17 | -------------------------------------------------------------------------------- /demo_project/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/archatas/django-gdpr-cookie-consent-demo-project/2b72a07799dbfba5e05687c4a656451017fca847/demo_project/tests/__init__.py -------------------------------------------------------------------------------- /demo_project/tests/test_checks.py: -------------------------------------------------------------------------------- 1 | from io import StringIO 2 | 3 | from django.core.management import call_command 4 | from django.core.management.base import SystemCheckError 5 | from django.test import SimpleTestCase, override_settings 6 | 7 | 8 | class SystemCheckIntegrationTest(SimpleTestCase): 9 | """ 10 | Here's the overview of all gdpr_cookie_consent errors Django system checks to test: 11 | - gdpr_cookie_consent.E001: `COOKIE_CONSENT_SETTINGS` is not defined in the settings. 12 | - gdpr_cookie_consent.E002: `["base_template_name"]` is not defined in `COOKIE_CONSENT_SETTINGS`. 13 | - gdpr_cookie_consent.E003: Template defined in `["*_template_name"]` doesn't exist. 14 | - gdpr_cookie_consent.E004: You cannot set both, `["*"]` and `["*_template_name"]`. 15 | - gdpr_cookie_consent.E005: `["dialog_position"]` must be one of `"center"`, `"top"`, `"left"`, `"right"`, `"bottom"`. 16 | - gdpr_cookie_consent.E006: `["sections"]` must contain at least one section. 17 | - gdpr_cookie_consent.E007: Each section must have a `["slug"]` defined. 18 | - gdpr_cookie_consent.E008: Slugs for sections must be unique. 19 | - gdpr_cookie_consent.E009: Each section must have at least one provider defined. 20 | - gdpr_cookie_consent.E010: Each provider must have at least one cookie defined. 21 | """ 22 | 23 | @override_settings(COOKIE_CONSENT_SETTINGS=None) 24 | def test_configuration_missing(self): 25 | """ 26 | gdpr_cookie_consent.E001: `COOKIE_CONSENT_SETTINGS` is not defined in the settings. 27 | """ 28 | stderr = StringIO() 29 | try: 30 | call_command("check", "-t", "gdpr_cookie_consent", stderr=stderr) 31 | except SystemCheckError: 32 | pass 33 | else: 34 | self.assertIn("(gdpr_cookie_consent.E001)", stderr.getvalue()) 35 | 36 | @override_settings( 37 | COOKIE_CONSENT_SETTINGS={ 38 | # "base_template_name": "base.html", # <-- 39 | "description": "", 40 | "description_template_name": "gdpr_cookie_consent/descriptions/what_are_cookies.html", 41 | "extra_information": "", 42 | "extra_information_template_name": "gdpr_cookie_consent/descriptions/extra.html", 43 | "dialog_position": "center", 44 | "sections": [ 45 | { 46 | "slug": "essential", 47 | "title": "Essential Cookies", 48 | "required": True, 49 | "preselected": True, 50 | "summary": "These cookies are always on, as they’re essential for making this website work, and making it safe. Without these cookies, services you’ve asked for can’t be provided.", 51 | "summary_template_name": "", 52 | "description": "These cookies are always on, as they’re essential for making this website work, and making it safe. Without these cookies, services you’ve asked for can’t be provided.", 53 | "description_template_name": "", 54 | "providers": [ 55 | { 56 | "title": "This website", 57 | "description": "", 58 | "description_template_name": "", 59 | "cookies": [ 60 | { 61 | "cookie_name": "cookie_consent", 62 | "duration": "6 Years", 63 | "description": "Settings of Cookie Consent preferences.", 64 | "description_template_name": "", 65 | "domain": "127.0.0.1", 66 | }, 67 | ], 68 | }, 69 | ], 70 | }, 71 | ], 72 | } 73 | ) 74 | def test_base_template_name_not_defined(self): 75 | """ 76 | gdpr_cookie_consent.E002: `["base_template_name"]` is not defined in `COOKIE_CONSENT_SETTINGS`. 77 | """ 78 | stderr = StringIO() 79 | try: 80 | call_command("check", "-t", "gdpr_cookie_consent", stderr=stderr) 81 | except SystemCheckError: 82 | pass 83 | else: 84 | self.assertIn("(gdpr_cookie_consent.E002)", stderr.getvalue()) 85 | 86 | @override_settings( 87 | COOKIE_CONSENT_SETTINGS={ 88 | "base_template_name": "base.html", 89 | "description": "", 90 | "description_template_name": "gdpr_cookie_consent/descriptions/THIS_TEMPLATE_DOESNT_EXIST.html", # <-- 91 | "extra_information": "", 92 | "extra_information_template_name": "gdpr_cookie_consent/descriptions/extra.html", 93 | "dialog_position": "center", 94 | "sections": [ 95 | { 96 | "slug": "essential", 97 | "title": "Essential Cookies", 98 | "required": True, 99 | "preselected": True, 100 | "summary": "These cookies are always on, as they’re essential for making this website work, and making it safe. Without these cookies, services you’ve asked for can’t be provided.", 101 | "summary_template_name": "", 102 | "description": "These cookies are always on, as they’re essential for making this website work, and making it safe. Without these cookies, services you’ve asked for can’t be provided.", 103 | "description_template_name": "", 104 | "providers": [ 105 | { 106 | "title": "This website", 107 | "description": "", 108 | "description_template_name": "", 109 | "cookies": [ 110 | { 111 | "cookie_name": "cookie_consent", 112 | "duration": "6 Years", 113 | "description": "Settings of Cookie Consent preferences.", 114 | "description_template_name": "", 115 | "domain": "127.0.0.1", 116 | }, 117 | ], 118 | }, 119 | ], 120 | }, 121 | ], 122 | } 123 | ) 124 | def test_template_doesnt_exist(self): 125 | """ 126 | gdpr_cookie_consent.E003: Template defined in `["*_template_name"]` doesn't exist. 127 | """ 128 | stderr = StringIO() 129 | try: 130 | call_command("check", "-t", "gdpr_cookie_consent", stderr=stderr) 131 | except SystemCheckError: 132 | pass 133 | else: 134 | self.assertIn("(gdpr_cookie_consent.E003)", stderr.getvalue()) 135 | 136 | @override_settings( 137 | COOKIE_CONSENT_SETTINGS={ 138 | "base_template_name": "base.html", 139 | "description": "WHAT ARE COOKIES?", # <-- 140 | "description_template_name": "gdpr_cookie_consent/descriptions/what_are_cookies.html", # <-- 141 | "extra_information": "", 142 | "extra_information_template_name": "gdpr_cookie_consent/descriptions/extra.html", 143 | "dialog_position": "center", 144 | "sections": [ 145 | { 146 | "slug": "essential", 147 | "title": "Essential Cookies", 148 | "required": True, 149 | "preselected": True, 150 | "summary": "These cookies are always on, as they’re essential for making this website work, and making it safe. Without these cookies, services you’ve asked for can’t be provided.", 151 | "summary_template_name": "", 152 | "description": "These cookies are always on, as they’re essential for making this website work, and making it safe. Without these cookies, services you’ve asked for can’t be provided.", 153 | "description_template_name": "", 154 | "providers": [ 155 | { 156 | "title": "This website", 157 | "description": "", 158 | "description_template_name": "", 159 | "cookies": [ 160 | { 161 | "cookie_name": "cookie_consent", 162 | "duration": "6 Years", 163 | "description": "Settings of Cookie Consent preferences.", 164 | "description_template_name": "", 165 | "domain": "127.0.0.1", 166 | }, 167 | ], 168 | }, 169 | ], 170 | }, 171 | ], 172 | } 173 | ) 174 | def test_duplicate_description_definition(self): 175 | """ 176 | gdpr_cookie_consent.E004: You cannot set both, `["*"]` and `["*_template_name"]`. 177 | """ 178 | stderr = StringIO() 179 | try: 180 | call_command("check", "-t", "gdpr_cookie_consent", stderr=stderr) 181 | except SystemCheckError: 182 | pass 183 | else: 184 | self.assertIn("(gdpr_cookie_consent.E004)", stderr.getvalue()) 185 | 186 | @override_settings( 187 | COOKIE_CONSENT_SETTINGS={ 188 | "base_template_name": "base.html", 189 | "description": "", 190 | "description_template_name": "gdpr_cookie_consent/descriptions/what_are_cookies.html", 191 | "extra_information": "", 192 | "extra_information_template_name": "gdpr_cookie_consent/descriptions/extra.html", 193 | "dialog_position": "ABOVE", # <-- 194 | "sections": [ 195 | { 196 | "slug": "essential", 197 | "title": "Essential Cookies", 198 | "required": True, 199 | "preselected": True, 200 | "summary": "These cookies are always on, as they’re essential for making this website work, and making it safe. Without these cookies, services you’ve asked for can’t be provided.", 201 | "summary_template_name": "", 202 | "description": "These cookies are always on, as they’re essential for making this website work, and making it safe. Without these cookies, services you’ve asked for can’t be provided.", 203 | "description_template_name": "", 204 | "providers": [ 205 | { 206 | "title": "This website", 207 | "description": "", 208 | "description_template_name": "", 209 | "cookies": [ 210 | { 211 | "cookie_name": "cookie_consent", 212 | "duration": "6 Years", 213 | "description": "Settings of Cookie Consent preferences.", 214 | "description_template_name": "", 215 | "domain": "127.0.0.1", 216 | }, 217 | ], 218 | }, 219 | ], 220 | }, 221 | ], 222 | } 223 | ) 224 | def test_invalid_dialog_position(self): 225 | """ 226 | gdpr_cookie_consent.E005: `["dialog_position"]` must be one of `"center"`, `"top"`, `"left"`, `"right"`, `"bottom"`. 227 | """ 228 | stderr = StringIO() 229 | try: 230 | call_command("check", "-t", "gdpr_cookie_consent", stderr=stderr) 231 | except SystemCheckError: 232 | pass 233 | else: 234 | self.assertIn("(gdpr_cookie_consent.E005)", stderr.getvalue()) 235 | 236 | @override_settings( 237 | COOKIE_CONSENT_SETTINGS={ 238 | "base_template_name": "base.html", 239 | "description": "", 240 | "description_template_name": "gdpr_cookie_consent/descriptions/what_are_cookies.html", 241 | "extra_information": "", 242 | "extra_information_template_name": "gdpr_cookie_consent/descriptions/extra.html", 243 | "dialog_position": "center", 244 | "sections": [] # <-- 245 | } 246 | ) 247 | def test_empty_sections(self): 248 | """ 249 | gdpr_cookie_consent.E006: `["sections"]` must contain at least one section. 250 | """ 251 | stderr = StringIO() 252 | try: 253 | call_command("check", "-t", "gdpr_cookie_consent", stderr=stderr) 254 | except SystemCheckError: 255 | pass 256 | else: 257 | self.assertIn("(gdpr_cookie_consent.E006)", stderr.getvalue()) 258 | 259 | @override_settings( 260 | COOKIE_CONSENT_SETTINGS={ 261 | "base_template_name": "base.html", 262 | "description": "", 263 | "description_template_name": "gdpr_cookie_consent/descriptions/what_are_cookies.html", 264 | "extra_information": "", 265 | "extra_information_template_name": "gdpr_cookie_consent/descriptions/extra.html", 266 | "dialog_position": "center", 267 | "sections": [ 268 | { 269 | # "slug": "essential", # <-- 270 | "title": "Essential Cookies", 271 | "required": True, 272 | "preselected": True, 273 | "summary": "These cookies are always on, as they’re essential for making this website work, and making it safe. Without these cookies, services you’ve asked for can’t be provided.", 274 | "summary_template_name": "", 275 | "description": "These cookies are always on, as they’re essential for making this website work, and making it safe. Without these cookies, services you’ve asked for can’t be provided.", 276 | "description_template_name": "", 277 | "providers": [ 278 | { 279 | "title": "This website", 280 | "description": "", 281 | "description_template_name": "", 282 | "cookies": [ 283 | { 284 | "cookie_name": "cookie_consent", 285 | "duration": "6 Years", 286 | "description": "Settings of Cookie Consent preferences.", 287 | "description_template_name": "", 288 | "domain": "127.0.0.1", 289 | }, 290 | ], 291 | }, 292 | ], 293 | }, 294 | ], 295 | } 296 | ) 297 | def test_no_section_slug(self): 298 | """ 299 | gdpr_cookie_consent.E007: Each section must have a `["slug"]` defined. 300 | """ 301 | stderr = StringIO() 302 | try: 303 | call_command("check", "-t", "gdpr_cookie_consent", stderr=stderr) 304 | except SystemCheckError: 305 | pass 306 | else: 307 | self.assertIn("(gdpr_cookie_consent.E007)", stderr.getvalue()) 308 | 309 | @override_settings( 310 | COOKIE_CONSENT_SETTINGS={ 311 | "base_template_name": "base.html", 312 | "description": "", 313 | "description_template_name": "gdpr_cookie_consent/descriptions/what_are_cookies.html", 314 | "extra_information": "", 315 | "extra_information_template_name": "gdpr_cookie_consent/descriptions/extra.html", 316 | "dialog_position": "center", 317 | "sections": [ 318 | { 319 | "slug": "essential", 320 | "title": "Essential Cookies", 321 | "required": True, 322 | "preselected": True, 323 | "summary": "These cookies are always on, as they’re essential for making this website work, and making it safe. Without these cookies, services you’ve asked for can’t be provided.", 324 | "summary_template_name": "", 325 | "description": "These cookies are always on, as they’re essential for making this website work, and making it safe. Without these cookies, services you’ve asked for can’t be provided.", 326 | "description_template_name": "", 327 | "providers": [ 328 | { 329 | "title": "This website", 330 | "description": "", 331 | "description_template_name": "", 332 | "cookies": [ 333 | { 334 | "cookie_name": "cookie_consent", 335 | "duration": "6 Years", 336 | "description": "Settings of Cookie Consent preferences.", 337 | "description_template_name": "", 338 | "domain": "127.0.0.1", 339 | }, 340 | ], 341 | }, 342 | ], 343 | }, 344 | { 345 | "slug": "essential", # <-- 346 | "title": "Essential Cookies", 347 | "required": True, 348 | "preselected": True, 349 | "summary": "These cookies are always on, as they’re essential for making this website work, and making it safe. Without these cookies, services you’ve asked for can’t be provided.", 350 | "summary_template_name": "", 351 | "description": "These cookies are always on, as they’re essential for making this website work, and making it safe. Without these cookies, services you’ve asked for can’t be provided.", 352 | "description_template_name": "", 353 | "providers": [ 354 | { 355 | "title": "This website", 356 | "description": "", 357 | "description_template_name": "", 358 | "cookies": [ 359 | { 360 | "cookie_name": "cookie_consent", 361 | "duration": "6 Years", 362 | "description": "Settings of Cookie Consent preferences.", 363 | "description_template_name": "", 364 | "domain": "127.0.0.1", 365 | }, 366 | ], 367 | }, 368 | ], 369 | }, 370 | ], 371 | } 372 | ) 373 | def test_duplicate_section_slug(self): 374 | """ 375 | gdpr_cookie_consent.E008: Slugs for sections must be unique. 376 | """ 377 | stderr = StringIO() 378 | try: 379 | call_command("check", "-t", "gdpr_cookie_consent", stderr=stderr) 380 | except SystemCheckError: 381 | pass 382 | else: 383 | self.assertIn("(gdpr_cookie_consent.E008)", stderr.getvalue()) 384 | 385 | @override_settings( 386 | COOKIE_CONSENT_SETTINGS={ 387 | "base_template_name": "base.html", 388 | "description": "", 389 | "description_template_name": "gdpr_cookie_consent/descriptions/what_are_cookies.html", 390 | "extra_information": "", 391 | "extra_information_template_name": "gdpr_cookie_consent/descriptions/extra.html", 392 | "dialog_position": "center", 393 | "sections": [ 394 | { 395 | "slug": "essential", 396 | "title": "Essential Cookies", 397 | "required": True, 398 | "preselected": True, 399 | "summary": "These cookies are always on, as they’re essential for making this website work, and making it safe. Without these cookies, services you’ve asked for can’t be provided.", 400 | "summary_template_name": "", 401 | "description": "These cookies are always on, as they’re essential for making this website work, and making it safe. Without these cookies, services you’ve asked for can’t be provided.", 402 | "description_template_name": "", 403 | "providers": [] # <-- 404 | }, 405 | ], 406 | } 407 | ) 408 | def test_no_section_providers(self): 409 | """ 410 | gdpr_cookie_consent.E009: Each section must have at least one provider defined. 411 | """ 412 | stderr = StringIO() 413 | try: 414 | call_command("check", "-t", "gdpr_cookie_consent", stderr=stderr) 415 | except SystemCheckError: 416 | pass 417 | else: 418 | self.assertIn("(gdpr_cookie_consent.E009)", stderr.getvalue()) 419 | 420 | @override_settings( 421 | COOKIE_CONSENT_SETTINGS={ 422 | "base_template_name": "base.html", 423 | "description": "", 424 | "description_template_name": "gdpr_cookie_consent/descriptions/what_are_cookies.html", 425 | "extra_information": "", 426 | "extra_information_template_name": "gdpr_cookie_consent/descriptions/extra.html", 427 | "dialog_position": "center", 428 | "sections": [ 429 | { 430 | "slug": "essential", 431 | "title": "Essential Cookies", 432 | "required": True, 433 | "preselected": True, 434 | "summary": "These cookies are always on, as they’re essential for making this website work, and making it safe. Without these cookies, services you’ve asked for can’t be provided.", 435 | "summary_template_name": "", 436 | "description": "These cookies are always on, as they’re essential for making this website work, and making it safe. Without these cookies, services you’ve asked for can’t be provided.", 437 | "description_template_name": "", 438 | "providers": [ 439 | { 440 | "title": "This website", 441 | "description": "", 442 | "description_template_name": "", 443 | "cookies": [], # <-- 444 | }, 445 | ], 446 | }, 447 | ], 448 | } 449 | ) 450 | def test_no_provider_cookies(self): 451 | """ 452 | gdpr_cookie_consent.E010: Each provider must have at least one cookie defined. 453 | """ 454 | stderr = StringIO() 455 | try: 456 | call_command("check", "-t", "gdpr_cookie_consent", stderr=stderr) 457 | except SystemCheckError: 458 | pass 459 | else: 460 | self.assertIn("(gdpr_cookie_consent.E010)", stderr.getvalue()) 461 | -------------------------------------------------------------------------------- /demo_project/tests/test_frontend.py: -------------------------------------------------------------------------------- 1 | from copy import deepcopy 2 | from urllib.parse import unquote 3 | from time import sleep 4 | 5 | from django.conf import settings 6 | from django.test import LiveServerTestCase 7 | from django.test import override_settings 8 | 9 | from selenium import webdriver 10 | from selenium.webdriver.chrome.options import Options 11 | from selenium.webdriver.support.ui import WebDriverWait 12 | from selenium.webdriver.common.by import By 13 | from selenium.webdriver.support import expected_conditions as EC 14 | from selenium.webdriver.chrome.service import Service as ChromeService 15 | 16 | from gdpr_cookie_consent.models import CookieConsentRecord 17 | 18 | SHOW_BROWSER = getattr(settings, "TESTS_SHOW_BROWSER", False) 19 | COOKIE_CONSENT_SETTINGS = deepcopy(settings.COOKIE_CONSENT_SETTINGS) 20 | COOKIE_CONSENT_SETTINGS["redirect_url"] = "test" 21 | 22 | 23 | @override_settings(DEBUG=True, COOKIE_CONSENT_SETTINGS=COOKIE_CONSENT_SETTINGS) 24 | class CookieManagementTest(LiveServerTestCase): 25 | host = "127.0.0.1" 26 | port = 8001 27 | 28 | @classmethod 29 | def setUpClass(cls): 30 | super().setUpClass() 31 | 32 | driver_path = settings.BASE_DIR / "drivers" / "chromedriver" 33 | chrome_options = Options() 34 | if not SHOW_BROWSER: 35 | chrome_options.add_argument("--headless") 36 | chrome_options.add_argument("--window-size=1280,720") 37 | chrome_options.add_argument("--window-position=50,50") 38 | chrome_options.add_argument("--disable-search-engine-choice-screen") 39 | 40 | chrome_options.set_capability( 41 | "goog:loggingPrefs", 42 | {"browser": "ALL", "driver": "ALL", "performance": "ALL"}, 43 | ) 44 | 45 | cls.browser = webdriver.Chrome( 46 | service=ChromeService(executable_path=driver_path), 47 | options=chrome_options, 48 | ) 49 | 50 | @classmethod 51 | def tearDownClass(cls): 52 | super().tearDownClass() 53 | cls.browser.quit() 54 | 55 | def wait_until_element_found(self, css_selector): 56 | return WebDriverWait(self.browser, timeout=10).until( 57 | lambda x: self.browser.find_element(By.CSS_SELECTOR, css_selector) 58 | ) 59 | 60 | def wait_until_element_found_and_interactable(self, css_selector): 61 | return WebDriverWait(self.browser, timeout=10).until( 62 | EC.element_to_be_clickable((By.CSS_SELECTOR, css_selector)) 63 | ) 64 | 65 | def wait_a_little(self, seconds=2): 66 | if SHOW_BROWSER: 67 | sleep(seconds) 68 | 69 | def focus_element(self, css_selector): 70 | self.browser.execute_script( 71 | f""" 72 | window.scrollComplete = false; 73 | let element = document.querySelector('{css_selector}'); 74 | 75 | // Set up scroll end listener 76 | window.addEventListener('scrollend', function handleScrollEnd() {{ 77 | element.setAttribute('tabindex', '-1'); 78 | element.focus(); 79 | window.scrollComplete = true; 80 | window.removeEventListener('scrollend', handleScrollEnd); 81 | }}, {{ once: true }}); 82 | 83 | // Trigger scroll (or no-op if already in view) 84 | element.scrollIntoView({{ block: 'center', behavior: 'smooth' }}); 85 | 86 | // Fallback: if no scroll needed, complete immediately 87 | setTimeout(() => {{ 88 | if (!window.scrollComplete) {{ 89 | element.setAttribute('tabindex', '-1'); 90 | element.focus(); 91 | window.scrollComplete = true; 92 | }} 93 | }}, 100); 94 | """ 95 | ) 96 | 97 | WebDriverWait(self.browser, 10).until( 98 | lambda driver: driver.execute_script( 99 | "return window.scrollComplete === true" 100 | ) 101 | ) 102 | 103 | element = self.browser.find_element(By.CSS_SELECTOR, css_selector) 104 | return element 105 | 106 | def extract_log_sequence(self): 107 | """Extracts messages like "functionality cookies granted" or "marketing cookies denied" from console.log()""" 108 | import re 109 | 110 | granting_denying_pattern = re.compile(r"[a-z]+ cookies (granted|denied)") 111 | consent_preferences_pattern = re.compile(r"consentPreferences: (\{.+})") 112 | logs = self.browser.get_log("browser") 113 | sequence = [] 114 | for log in logs: 115 | if log["level"] == "INFO": 116 | if group := granting_denying_pattern.search(log["message"]): 117 | sequence.append(group[0]) 118 | elif group := consent_preferences_pattern.search(log["message"]): 119 | sequence.append(group[1].replace("\\", "")) 120 | return sequence 121 | 122 | def test_01_accept_all_cookies(self): 123 | """ 124 | Tries to accept all cookies in the modal dialog. 125 | """ 126 | self.browser.delete_all_cookies() 127 | self.browser.get(f"{self.live_server_url}/test/") 128 | # self.wait_a_little(30) # DEBUG: for the screen recording 129 | button = self.wait_until_element_found("#cc_accept_all_cookies") 130 | self.focus_element("#cc_accept_all_cookies") 131 | button.click() 132 | self.wait_a_little() 133 | 134 | self.assertEqual( 135 | unquote(self.browser.get_cookie("cookie_consent")["value"]), 136 | "functionality|performance|marketing", 137 | ) 138 | self.assertEqual( 139 | unquote(self.browser.get_cookie("functionality_cookie")["value"]), 140 | "🛠", 141 | ) 142 | self.assertEqual( 143 | unquote(self.browser.get_cookie("performance_cookie")["value"]), 144 | "📊", 145 | ) 146 | self.assertEqual( 147 | unquote(self.browser.get_cookie("marketing_cookie")["value"]), 148 | "📢", 149 | ) 150 | self.assertEqual(CookieConsentRecord.objects.count(), 1) 151 | 152 | sequence = self.extract_log_sequence() 153 | self.assertEqual( 154 | sequence, 155 | [ 156 | "functionality cookies granted", 157 | "performance cookies granted", 158 | "marketing cookies granted", 159 | '{"functionality":true,"performance":true,"marketing":true}', 160 | ], 161 | ) 162 | 163 | def test_02_reject_all_cookies(self): 164 | """ 165 | Tries to reject all cookies in the modal dialog. 166 | """ 167 | self.browser.delete_all_cookies() 168 | self.browser.get(f"{self.live_server_url}/test/") 169 | # self.wait_a_little(4) # DEBUG: for the screen recording 170 | button = self.wait_until_element_found("#cc_reject_all_cookies") 171 | self.focus_element("#cc_reject_all_cookies") 172 | button.click() 173 | self.wait_a_little() 174 | 175 | self.assertEqual( 176 | unquote(self.browser.get_cookie("cookie_consent")["value"]), 177 | '""', 178 | ) 179 | self.assertEqual( 180 | self.browser.get_cookie("functionality_cookie"), 181 | None, 182 | ) 183 | self.assertEqual( 184 | self.browser.get_cookie("performance_cookie"), 185 | None, 186 | ) 187 | self.assertEqual( 188 | self.browser.get_cookie("marketing_cookie"), 189 | None, 190 | ) 191 | self.assertEqual(CookieConsentRecord.objects.count(), 1) 192 | 193 | sequence = self.extract_log_sequence() 194 | self.assertEqual( 195 | sequence, 196 | [ 197 | "functionality cookies denied", 198 | "performance cookies denied", 199 | "marketing cookies denied", 200 | '{"functionality":false,"performance":false,"marketing":false}', 201 | ], 202 | ) 203 | 204 | def test_03_accept_only_functionality_cookies(self): 205 | """ 206 | Tries to manage cookies and accept only functionality cookies in the modal dialog. 207 | """ 208 | self.browser.delete_all_cookies() 209 | self.browser.get(f"{self.live_server_url}/test/") 210 | # self.wait_a_little(4) # DEBUG: for the screen recording 211 | button = self.wait_until_element_found("#cc_manage_cookies") 212 | self.focus_element("#cc_manage_cookies") 213 | button.click() 214 | switch = self.focus_element("#cc_switch_functionality") 215 | switch.click() 216 | button = self.wait_until_element_found_and_interactable("#cc_save_preferences") 217 | self.focus_element("#cc_save_preferences") 218 | button.click() 219 | self.wait_a_little() 220 | 221 | self.assertEqual( 222 | unquote(self.browser.get_cookie("cookie_consent")["value"]), 223 | "functionality", 224 | ) 225 | self.assertEqual( 226 | unquote(self.browser.get_cookie("functionality_cookie")["value"]), 227 | "🛠", 228 | ) 229 | self.assertEqual( 230 | self.browser.get_cookie("performance_cookie"), 231 | None, 232 | ) 233 | self.assertEqual( 234 | self.browser.get_cookie("marketing_cookie"), 235 | None, 236 | ) 237 | self.assertEqual(CookieConsentRecord.objects.count(), 1) 238 | 239 | sequence = self.extract_log_sequence() 240 | self.assertEqual( 241 | sequence, 242 | [ 243 | "functionality cookies granted", 244 | "performance cookies denied", 245 | "marketing cookies denied", 246 | '{"functionality":true,"performance":false,"marketing":false}', 247 | ], 248 | ) 249 | 250 | def test_04_manage_cookies(self): 251 | """ 252 | Tries to 253 | 1. close the modal dialog, 254 | 2. click on "manage cookies" link, 255 | 3. accept all cookies, 256 | 4. then click on "manage cookies" again, 257 | 5. and reject all cookies. 258 | """ 259 | self.browser.delete_all_cookies() 260 | self.browser.get(f"{self.live_server_url}/test/") 261 | # self.wait_a_little(4) # DEBUG: for the screen recording 262 | button = self.wait_until_element_found("#cc_modal_close") 263 | self.focus_element("#cc_modal_close") 264 | button.click() 265 | 266 | self.assertEqual( 267 | self.browser.get_cookie("cookie_consent"), 268 | None, 269 | ) 270 | self.assertEqual( 271 | self.browser.get_cookie("functionality_cookie"), 272 | None, 273 | ) 274 | self.assertEqual( 275 | self.browser.get_cookie("performance_cookie"), 276 | None, 277 | ) 278 | self.assertEqual( 279 | self.browser.get_cookie("marketing_cookie"), 280 | None, 281 | ) 282 | link = self.browser.find_element(By.CSS_SELECTOR, "#manage_cookies") 283 | self.focus_element("#manage_cookies") 284 | link.click() 285 | button = self.wait_until_element_found("#cc_accept_all") 286 | self.wait_a_little(3) # wait for JS animation to finish 287 | self.focus_element("#cc_accept_all") 288 | button.click() 289 | self.wait_a_little() 290 | button = self.browser.find_element(By.CSS_SELECTOR, "#cc_save_preferences") 291 | self.focus_element("#cc_save_preferences") 292 | self.wait_a_little() 293 | button.click() 294 | self.wait_a_little() 295 | 296 | link = self.wait_until_element_found_and_interactable("#cc_message") 297 | 298 | self.assertEqual( 299 | unquote(self.browser.get_cookie("cookie_consent")["value"]), 300 | "functionality|performance|marketing", 301 | ) 302 | self.assertEqual( 303 | unquote(self.browser.get_cookie("functionality_cookie")["value"]), 304 | "🛠", 305 | ) 306 | self.assertEqual( 307 | unquote(self.browser.get_cookie("performance_cookie")["value"]), 308 | "📊", 309 | ) 310 | self.assertEqual( 311 | unquote(self.browser.get_cookie("marketing_cookie")["value"]), 312 | "📢", 313 | ) 314 | 315 | button = self.wait_until_element_found("#cc_reject_all") 316 | self.focus_element("#cc_reject_all") 317 | button.click() 318 | self.wait_a_little() 319 | button = self.browser.find_element(By.CSS_SELECTOR, "#cc_save_preferences") 320 | self.focus_element("#cc_save_preferences") 321 | self.wait_a_little() 322 | button.click() 323 | self.wait_a_little() 324 | 325 | self.wait_until_element_found_and_interactable("#cc_message") 326 | self.wait_a_little() 327 | 328 | self.assertEqual( 329 | unquote(self.browser.get_cookie("cookie_consent")["value"]), 330 | '""', 331 | ) 332 | self.assertEqual( 333 | self.browser.get_cookie("functionality_cookie"), 334 | None, 335 | ) 336 | self.assertEqual( 337 | self.browser.get_cookie("performance_cookie"), 338 | None, 339 | ) 340 | self.assertEqual( 341 | self.browser.get_cookie("marketing_cookie"), 342 | None, 343 | ) 344 | self.assertEqual(CookieConsentRecord.objects.count(), 2) 345 | 346 | sequence = self.extract_log_sequence() 347 | self.assertEqual( 348 | sequence, 349 | [ 350 | "functionality cookies granted", 351 | "performance cookies granted", 352 | "marketing cookies granted", 353 | '{"functionality":true,"performance":true,"marketing":true}', 354 | "functionality cookies denied", 355 | "performance cookies denied", 356 | "marketing cookies denied", 357 | '{"functionality":false,"performance":false,"marketing":false}', 358 | ], 359 | ) 360 | -------------------------------------------------------------------------------- /demo_project/urls.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from django.urls import path, include 3 | from django.views.generic import TemplateView 4 | 5 | urlpatterns = [ 6 | path("", TemplateView.as_view(template_name="index.html"), name="home"), 7 | path("test/", TemplateView.as_view(template_name="test.html"), name="test"), 8 | path( 9 | "cookies/", 10 | include("gdpr_cookie_consent.urls", namespace="cookie_consent"), 11 | ), 12 | path("admin/", admin.site.urls), 13 | ] 14 | -------------------------------------------------------------------------------- /demo_project/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for demo_project project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/3.2/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'demo_project.settings') 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """Django's command-line utility for administrative tasks.""" 3 | import os 4 | import sys 5 | 6 | 7 | def main(): 8 | """Run administrative tasks.""" 9 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'demo_project.settings') 10 | try: 11 | from django.core.management import execute_from_command_line 12 | except ImportError as exc: 13 | raise ImportError( 14 | "Couldn't import Django. Are you sure it's installed and " 15 | "available on your PYTHONPATH environment variable? Did you " 16 | "forget to activate a virtual environment?" 17 | ) from exc 18 | execute_from_command_line(sys.argv) 19 | 20 | 21 | if __name__ == '__main__': 22 | main() 23 | -------------------------------------------------------------------------------- /media/.gitignore: -------------------------------------------------------------------------------- 1 | * -------------------------------------------------------------------------------- /private_wheels/.gitignore: -------------------------------------------------------------------------------- 1 | *.whl -------------------------------------------------------------------------------- /private_wheels/README.md: -------------------------------------------------------------------------------- 1 | # Django GDPR Cookie Consent app 2 | 3 | [Get the Django GDPR Cookie Consent app](https://websightful.gumroad.com/l/django-gdpr-cookie-consent) and put the `*.whl` file to this directory. 4 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | Django==5.2.3 2 | requests==2.32.3 3 | selenium==4.10.0 4 | file:./private_wheels/django_gdpr_cookie_consent-4.1.2-py2.py3-none-any.whl 5 | -------------------------------------------------------------------------------- /tmp/.gitignore: -------------------------------------------------------------------------------- 1 | * --------------------------------------------------------------------------------