├── .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 | 
24 |
25 | The modal dialog at the bottom:
26 |
27 | 
28 |
29 | The modal dialog on the right:
30 |
31 | 
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 |
41 |
42 |
43 |
44 |
45 |
66 |
67 |
68 |
69 |
70 |
71 |
72 | Baker
73 |
74 |
75 |
76 |
77 |
78 |
93 |
94 |
95 |
96 |
97 |
98 |
Call Us
99 |
+012 345 6789
100 |
101 |
102 |
103 |
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 |
189 |
190 |
191 |
192 | ©
Your Site Name , All Right Reserved.
193 |
194 |
195 |
196 | Website Template Designed By
HTML Codex
197 |
198 |
199 |
200 |
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 |
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 |
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 |
11 |
12 |
13 |
14 |
15 |
36 |
37 |
38 |
39 |
40 |
41 |
42 | Baker
43 |
44 |
45 |
46 |
47 |
48 |
63 |
64 |
65 |
66 |
67 |
68 |
Call Us
69 |
+012 345 6789
70 |
71 |
72 |
73 |
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 |
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 |
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 |
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 |
420 |
421 |
422 |
423 |
424 |
Client Name
425 | Profession
426 |
427 |
428 |
Tempor erat elitr rebum at clita. Diam dolor diam ipsum sit diam amet diam et eos. Clita erat ipsum et lorem et sit.
429 |
430 |
431 |
432 |
433 |
434 |
Client Name
435 | Profession
436 |
437 |
438 |
Tempor erat elitr rebum at clita. Diam dolor diam ipsum sit diam amet diam et eos. Clita erat ipsum et lorem et sit.
439 |
440 |
441 |
442 |
443 |
444 |
Client Name
445 | Profession
446 |
447 |
448 |
Tempor erat elitr rebum at clita. Diam dolor diam ipsum sit diam amet diam et eos. Clita erat ipsum et lorem et sit.
449 |
450 |
451 |
452 |
453 |
454 |
Client Name
455 | Profession
456 |
457 |
458 |
Tempor erat elitr rebum at clita. Diam dolor diam ipsum sit diam amet diam et eos. Clita erat ipsum et lorem et sit.
459 |
460 |
461 |
462 |
463 |
464 |
Subscribe Our Newsletter
465 |
466 |
467 |
468 |
469 | SignUp
470 |
471 |
472 |
473 |
474 |
475 |
476 |
477 |
478 |
479 |
480 |
537 |
538 |
539 |
540 |
541 |
542 |
543 |
544 |
545 | ©
Your Site Name , All Right Reserved.
546 |
547 |
548 |
549 | Website Template Designed By
HTML Codex
550 |
551 |
552 |
553 |
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 | *
--------------------------------------------------------------------------------