django-deacon 1.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- django_deacon-1.0.0/LICENSE +21 -0
- django_deacon-1.0.0/PKG-INFO +122 -0
- django_deacon-1.0.0/README.md +92 -0
- django_deacon-1.0.0/pyproject.toml +65 -0
- django_deacon-1.0.0/pyproject.toml.orig +62 -0
- django_deacon-1.0.0/src/django_deacon/__init__.py +0 -0
- django_deacon-1.0.0/src/django_deacon/apps.py +6 -0
- django_deacon-1.0.0/src/django_deacon/templatetags/__init__.py +0 -0
- django_deacon-1.0.0/src/django_deacon/templatetags/deacon.py +96 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Deacon
|
|
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.
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: django-deacon
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Django app that adds Deacon, an AI support chat that answers from your docs, to every page.
|
|
5
|
+
Keywords: django,deacon,chat widget,support,ai,chatbot,ai-chatbot,ask-ai,support-chat
|
|
6
|
+
Author: Deacon
|
|
7
|
+
Author-email: Deacon <hello@heydeacon.com>
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
11
|
+
Classifier: Environment :: Web Environment
|
|
12
|
+
Classifier: Framework :: Django
|
|
13
|
+
Classifier: Framework :: Django :: 5.2
|
|
14
|
+
Classifier: Framework :: Django :: 6.0
|
|
15
|
+
Classifier: Framework :: Django :: 6.1
|
|
16
|
+
Classifier: Intended Audience :: Developers
|
|
17
|
+
Classifier: Operating System :: OS Independent
|
|
18
|
+
Classifier: Programming Language :: Python :: 3
|
|
19
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
24
|
+
Requires-Dist: django>=5.2
|
|
25
|
+
Requires-Python: >=3.11
|
|
26
|
+
Project-URL: Homepage, https://heydeacon.com
|
|
27
|
+
Project-URL: Repository, https://github.com/heydeacon/django-deacon
|
|
28
|
+
Project-URL: Issues, https://github.com/heydeacon/django-deacon/issues
|
|
29
|
+
Description-Content-Type: text/markdown
|
|
30
|
+
|
|
31
|
+
# django-deacon
|
|
32
|
+
|
|
33
|
+
[Deacon](https://heydeacon.com) is an AI support agent that answers questions from your own docs and content, and says when it doesn't know. This app adds its chat widget to every page of a Django site.
|
|
34
|
+
|
|
35
|
+
You need a Deacon account, and there's a free plan.
|
|
36
|
+
|
|
37
|
+
## Install
|
|
38
|
+
|
|
39
|
+
```sh
|
|
40
|
+
pip install django-deacon
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Set it up
|
|
44
|
+
|
|
45
|
+
Add the app and your key to `settings.py`:
|
|
46
|
+
|
|
47
|
+
```python
|
|
48
|
+
INSTALLED_APPS = [
|
|
49
|
+
# ...the rest of your apps
|
|
50
|
+
"django_deacon",
|
|
51
|
+
]
|
|
52
|
+
|
|
53
|
+
DEACON_KEY = "pk_your_widget_key"
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Then load the tag in the `<head>` of your base template:
|
|
57
|
+
|
|
58
|
+
```django
|
|
59
|
+
{% load deacon %}
|
|
60
|
+
<head>
|
|
61
|
+
{# ...the rest of your head #}
|
|
62
|
+
{% deacon_widget %}
|
|
63
|
+
</head>
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
To find your key, open Install from the button at the top of the Deacon dashboard. The snippet there has it in `data-key`, and it starts with `pk_`. It isn't a secret, since every visitor's browser reads it, but you can read it from the environment like any setting: `DEACON_KEY = os.environ.get("DEACON_KEY")`. If the key is missing, the tag prints nothing and logs a warning that says why, so your pages still render. A setting of the wrong type raises `ImproperlyConfigured`, which says why.
|
|
67
|
+
|
|
68
|
+
Deacon only runs on the sites listed on Install. A listed domain covers its subdomains, so `example.com` covers `app.example.com`. If your site lives somewhere else, such as `example.fly.dev`, add that address on Install too.
|
|
69
|
+
|
|
70
|
+
When you run `python manage.py runserver`, Install notices the widget on `127.0.0.1:8000` and lets you add it in one click. Chats on a development address don't count towards your plan.
|
|
71
|
+
|
|
72
|
+
If you tell Deacon who is signed in, with the `window.deaconUser` script from [Deacon's install guide](https://heydeacon.com/agent/install.md), put that script above `{% deacon_widget %}` in the head, because the widget reads it as it starts.
|
|
73
|
+
|
|
74
|
+
## Options
|
|
75
|
+
|
|
76
|
+
| Setting | Type | Default | What it does |
|
|
77
|
+
| ------------------- | ------- | -------- | ------------ |
|
|
78
|
+
| `DEACON_KEY` | str | none | Your Deacon key. Without it the tag prints nothing and logs a warning. |
|
|
79
|
+
| `DEACON_CONTEXT` | dict | none | Details Deacon takes into account when it answers, the same on every page, such as `{"product": "api", "docs_version": "3.2"}`. Up to 12 keys, each value a string, number or boolean. Anyone can read them in the page's HTML, so never put a secret here. |
|
|
80
|
+
| `DEACON_PAGE_VIEWS` | bool | `True` | The same script counts visits for Deacon's website analytics. Set this to `False` to send no page views or JavaScript errors from your site. Chat still works. |
|
|
81
|
+
|
|
82
|
+
```python
|
|
83
|
+
DEACON_KEY = "pk_your_widget_key"
|
|
84
|
+
DEACON_CONTEXT = {"product": "api", "docs_version": "3.2"}
|
|
85
|
+
DEACON_PAGE_VIEWS = False
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## Content Security Policy
|
|
89
|
+
|
|
90
|
+
If your site sends a Content Security Policy with a nonce, the tag carries the request's nonce. It reads it from Django's own CSP support (Django 6.0 and later), which needs the `django.template.context_processors.csp` context processor, or from [django-csp](https://pypi.org/project/django-csp/)'s `request.csp_nonce`, which needs `django.template.context_processors.request`.
|
|
91
|
+
|
|
92
|
+
The policy also needs Deacon's address, `https://heydeacon.com`, in `script-src`, `connect-src` and `frame-src`, and `'unsafe-inline'` in `style-src`, because the widget writes its styles where a nonce can't reach. Keep the nonce out of `style-src`, since a nonce there makes browsers ignore `'unsafe-inline'`:
|
|
93
|
+
|
|
94
|
+
```python
|
|
95
|
+
from django.utils.csp import CSP
|
|
96
|
+
|
|
97
|
+
SECURE_CSP = {
|
|
98
|
+
"default-src": [CSP.SELF],
|
|
99
|
+
"script-src": [CSP.SELF, CSP.NONCE, "https://heydeacon.com"],
|
|
100
|
+
"style-src": [CSP.SELF, CSP.UNSAFE_INLINE],
|
|
101
|
+
"connect-src": [CSP.SELF, "https://heydeacon.com"],
|
|
102
|
+
"frame-src": ["https://heydeacon.com"],
|
|
103
|
+
}
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## Opening the chat from your own button
|
|
107
|
+
|
|
108
|
+
The widget comes with its own button. To use one of yours instead, hide Deacon's in Appearance in the dashboard and call `window.Deacon.open()` from your button or link. `Deacon.close()` and `Deacon.toggle()` work too.
|
|
109
|
+
|
|
110
|
+
## What it adds
|
|
111
|
+
|
|
112
|
+
The tag prints the same tag Install gives you:
|
|
113
|
+
|
|
114
|
+
```html
|
|
115
|
+
<script src="https://heydeacon.com/widget/v1/loader.js" data-key="pk_your_widget_key" async></script>
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
It loads in the background without holding up the page. Works with Django 5.2 and later, on Python 3.11 and later.
|
|
119
|
+
|
|
120
|
+
## License
|
|
121
|
+
|
|
122
|
+
MIT
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# django-deacon
|
|
2
|
+
|
|
3
|
+
[Deacon](https://heydeacon.com) is an AI support agent that answers questions from your own docs and content, and says when it doesn't know. This app adds its chat widget to every page of a Django site.
|
|
4
|
+
|
|
5
|
+
You need a Deacon account, and there's a free plan.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
pip install django-deacon
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Set it up
|
|
14
|
+
|
|
15
|
+
Add the app and your key to `settings.py`:
|
|
16
|
+
|
|
17
|
+
```python
|
|
18
|
+
INSTALLED_APPS = [
|
|
19
|
+
# ...the rest of your apps
|
|
20
|
+
"django_deacon",
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
DEACON_KEY = "pk_your_widget_key"
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Then load the tag in the `<head>` of your base template:
|
|
27
|
+
|
|
28
|
+
```django
|
|
29
|
+
{% load deacon %}
|
|
30
|
+
<head>
|
|
31
|
+
{# ...the rest of your head #}
|
|
32
|
+
{% deacon_widget %}
|
|
33
|
+
</head>
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
To find your key, open Install from the button at the top of the Deacon dashboard. The snippet there has it in `data-key`, and it starts with `pk_`. It isn't a secret, since every visitor's browser reads it, but you can read it from the environment like any setting: `DEACON_KEY = os.environ.get("DEACON_KEY")`. If the key is missing, the tag prints nothing and logs a warning that says why, so your pages still render. A setting of the wrong type raises `ImproperlyConfigured`, which says why.
|
|
37
|
+
|
|
38
|
+
Deacon only runs on the sites listed on Install. A listed domain covers its subdomains, so `example.com` covers `app.example.com`. If your site lives somewhere else, such as `example.fly.dev`, add that address on Install too.
|
|
39
|
+
|
|
40
|
+
When you run `python manage.py runserver`, Install notices the widget on `127.0.0.1:8000` and lets you add it in one click. Chats on a development address don't count towards your plan.
|
|
41
|
+
|
|
42
|
+
If you tell Deacon who is signed in, with the `window.deaconUser` script from [Deacon's install guide](https://heydeacon.com/agent/install.md), put that script above `{% deacon_widget %}` in the head, because the widget reads it as it starts.
|
|
43
|
+
|
|
44
|
+
## Options
|
|
45
|
+
|
|
46
|
+
| Setting | Type | Default | What it does |
|
|
47
|
+
| ------------------- | ------- | -------- | ------------ |
|
|
48
|
+
| `DEACON_KEY` | str | none | Your Deacon key. Without it the tag prints nothing and logs a warning. |
|
|
49
|
+
| `DEACON_CONTEXT` | dict | none | Details Deacon takes into account when it answers, the same on every page, such as `{"product": "api", "docs_version": "3.2"}`. Up to 12 keys, each value a string, number or boolean. Anyone can read them in the page's HTML, so never put a secret here. |
|
|
50
|
+
| `DEACON_PAGE_VIEWS` | bool | `True` | The same script counts visits for Deacon's website analytics. Set this to `False` to send no page views or JavaScript errors from your site. Chat still works. |
|
|
51
|
+
|
|
52
|
+
```python
|
|
53
|
+
DEACON_KEY = "pk_your_widget_key"
|
|
54
|
+
DEACON_CONTEXT = {"product": "api", "docs_version": "3.2"}
|
|
55
|
+
DEACON_PAGE_VIEWS = False
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Content Security Policy
|
|
59
|
+
|
|
60
|
+
If your site sends a Content Security Policy with a nonce, the tag carries the request's nonce. It reads it from Django's own CSP support (Django 6.0 and later), which needs the `django.template.context_processors.csp` context processor, or from [django-csp](https://pypi.org/project/django-csp/)'s `request.csp_nonce`, which needs `django.template.context_processors.request`.
|
|
61
|
+
|
|
62
|
+
The policy also needs Deacon's address, `https://heydeacon.com`, in `script-src`, `connect-src` and `frame-src`, and `'unsafe-inline'` in `style-src`, because the widget writes its styles where a nonce can't reach. Keep the nonce out of `style-src`, since a nonce there makes browsers ignore `'unsafe-inline'`:
|
|
63
|
+
|
|
64
|
+
```python
|
|
65
|
+
from django.utils.csp import CSP
|
|
66
|
+
|
|
67
|
+
SECURE_CSP = {
|
|
68
|
+
"default-src": [CSP.SELF],
|
|
69
|
+
"script-src": [CSP.SELF, CSP.NONCE, "https://heydeacon.com"],
|
|
70
|
+
"style-src": [CSP.SELF, CSP.UNSAFE_INLINE],
|
|
71
|
+
"connect-src": [CSP.SELF, "https://heydeacon.com"],
|
|
72
|
+
"frame-src": ["https://heydeacon.com"],
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Opening the chat from your own button
|
|
77
|
+
|
|
78
|
+
The widget comes with its own button. To use one of yours instead, hide Deacon's in Appearance in the dashboard and call `window.Deacon.open()` from your button or link. `Deacon.close()` and `Deacon.toggle()` work too.
|
|
79
|
+
|
|
80
|
+
## What it adds
|
|
81
|
+
|
|
82
|
+
The tag prints the same tag Install gives you:
|
|
83
|
+
|
|
84
|
+
```html
|
|
85
|
+
<script src="https://heydeacon.com/widget/v1/loader.js" data-key="pk_your_widget_key" async></script>
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
It loads in the background without holding up the page. Works with Django 5.2 and later, on Python 3.11 and later.
|
|
89
|
+
|
|
90
|
+
## License
|
|
91
|
+
|
|
92
|
+
MIT
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "django-deacon"
|
|
3
|
+
version = "1.0.0"
|
|
4
|
+
description = "Django app that adds Deacon, an AI support chat that answers from your docs, to every page."
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
license = "MIT"
|
|
7
|
+
license-files = ["LICENSE"]
|
|
8
|
+
requires-python = ">=3.11"
|
|
9
|
+
dependencies = ["Django>=5.2"]
|
|
10
|
+
keywords = [
|
|
11
|
+
"django",
|
|
12
|
+
"deacon",
|
|
13
|
+
"chat widget",
|
|
14
|
+
"support",
|
|
15
|
+
"ai",
|
|
16
|
+
"chatbot",
|
|
17
|
+
"ai-chatbot",
|
|
18
|
+
"ask-ai",
|
|
19
|
+
"support-chat",
|
|
20
|
+
]
|
|
21
|
+
classifiers = [
|
|
22
|
+
"Development Status :: 5 - Production/Stable",
|
|
23
|
+
"Environment :: Web Environment",
|
|
24
|
+
"Framework :: Django",
|
|
25
|
+
"Framework :: Django :: 5.2",
|
|
26
|
+
"Framework :: Django :: 6.0",
|
|
27
|
+
"Framework :: Django :: 6.1",
|
|
28
|
+
"Intended Audience :: Developers",
|
|
29
|
+
"Operating System :: OS Independent",
|
|
30
|
+
"Programming Language :: Python :: 3",
|
|
31
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
32
|
+
"Programming Language :: Python :: 3.11",
|
|
33
|
+
"Programming Language :: Python :: 3.12",
|
|
34
|
+
"Programming Language :: Python :: 3.13",
|
|
35
|
+
"Programming Language :: Python :: 3.14",
|
|
36
|
+
]
|
|
37
|
+
|
|
38
|
+
[[project.authors]]
|
|
39
|
+
name = "Deacon"
|
|
40
|
+
email = "hello@heydeacon.com"
|
|
41
|
+
|
|
42
|
+
[project.urls]
|
|
43
|
+
Homepage = "https://heydeacon.com"
|
|
44
|
+
Repository = "https://github.com/heydeacon/django-deacon"
|
|
45
|
+
Issues = "https://github.com/heydeacon/django-deacon/issues"
|
|
46
|
+
|
|
47
|
+
[dependency-groups]
|
|
48
|
+
dev = [
|
|
49
|
+
"django-csp>=4.0",
|
|
50
|
+
"pytest>=9.1",
|
|
51
|
+
"pytest-django>=4.14",
|
|
52
|
+
"ruff>=0.17.2",
|
|
53
|
+
]
|
|
54
|
+
|
|
55
|
+
[build-system]
|
|
56
|
+
requires = ["uv_build>=0.13.5,<0.14"]
|
|
57
|
+
build-backend = "uv_build"
|
|
58
|
+
|
|
59
|
+
[tool.pytest.ini_options]
|
|
60
|
+
DJANGO_SETTINGS_MODULE = "tests.settings"
|
|
61
|
+
pythonpath = ["."]
|
|
62
|
+
testpaths = ["tests"]
|
|
63
|
+
|
|
64
|
+
[tool.ruff]
|
|
65
|
+
line-length = 100
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "django-deacon"
|
|
3
|
+
version = "1.0.0"
|
|
4
|
+
description = "Django app that adds Deacon, an AI support chat that answers from your docs, to every page."
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
license = "MIT"
|
|
7
|
+
license-files = ["LICENSE"]
|
|
8
|
+
authors = [{ name = "Deacon", email = "hello@heydeacon.com" }]
|
|
9
|
+
requires-python = ">=3.11"
|
|
10
|
+
dependencies = ["Django>=5.2"]
|
|
11
|
+
keywords = [
|
|
12
|
+
"django",
|
|
13
|
+
"deacon",
|
|
14
|
+
"chat widget",
|
|
15
|
+
"support",
|
|
16
|
+
"ai",
|
|
17
|
+
"chatbot",
|
|
18
|
+
"ai-chatbot",
|
|
19
|
+
"ask-ai",
|
|
20
|
+
"support-chat",
|
|
21
|
+
]
|
|
22
|
+
classifiers = [
|
|
23
|
+
"Development Status :: 5 - Production/Stable",
|
|
24
|
+
"Environment :: Web Environment",
|
|
25
|
+
"Framework :: Django",
|
|
26
|
+
"Framework :: Django :: 5.2",
|
|
27
|
+
"Framework :: Django :: 6.0",
|
|
28
|
+
"Framework :: Django :: 6.1",
|
|
29
|
+
"Intended Audience :: Developers",
|
|
30
|
+
"Operating System :: OS Independent",
|
|
31
|
+
"Programming Language :: Python :: 3",
|
|
32
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
33
|
+
"Programming Language :: Python :: 3.11",
|
|
34
|
+
"Programming Language :: Python :: 3.12",
|
|
35
|
+
"Programming Language :: Python :: 3.13",
|
|
36
|
+
"Programming Language :: Python :: 3.14",
|
|
37
|
+
]
|
|
38
|
+
|
|
39
|
+
[project.urls]
|
|
40
|
+
Homepage = "https://heydeacon.com"
|
|
41
|
+
Repository = "https://github.com/heydeacon/django-deacon"
|
|
42
|
+
Issues = "https://github.com/heydeacon/django-deacon/issues"
|
|
43
|
+
|
|
44
|
+
[dependency-groups]
|
|
45
|
+
dev = [
|
|
46
|
+
"django-csp>=4.0",
|
|
47
|
+
"pytest>=9.1",
|
|
48
|
+
"pytest-django>=4.14",
|
|
49
|
+
"ruff>=0.17.2",
|
|
50
|
+
]
|
|
51
|
+
|
|
52
|
+
[build-system]
|
|
53
|
+
requires = ["uv_build>=0.13.5,<0.14"]
|
|
54
|
+
build-backend = "uv_build"
|
|
55
|
+
|
|
56
|
+
[tool.pytest.ini_options]
|
|
57
|
+
DJANGO_SETTINGS_MODULE = "tests.settings"
|
|
58
|
+
pythonpath = ["."]
|
|
59
|
+
testpaths = ["tests"]
|
|
60
|
+
|
|
61
|
+
[tool.ruff]
|
|
62
|
+
line-length = 100
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import logging
|
|
3
|
+
|
|
4
|
+
from django import template
|
|
5
|
+
from django.conf import settings
|
|
6
|
+
from django.core.exceptions import ImproperlyConfigured
|
|
7
|
+
from django.utils.html import escape
|
|
8
|
+
from django.utils.safestring import mark_safe
|
|
9
|
+
|
|
10
|
+
register = template.Library()
|
|
11
|
+
logger = logging.getLogger("django_deacon")
|
|
12
|
+
|
|
13
|
+
LOADER_SRC = "https://heydeacon.com/widget/v1/loader.js"
|
|
14
|
+
|
|
15
|
+
MISSING_KEY = (
|
|
16
|
+
"django-deacon: the DEACON_KEY setting is missing, so the page has no Deacon widget. In "
|
|
17
|
+
"Deacon, open Install (the button at the top of the dashboard) and copy the data-key value "
|
|
18
|
+
"from the snippet. It starts with pk_."
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@register.simple_tag(takes_context=True)
|
|
23
|
+
def deacon_widget(context):
|
|
24
|
+
"""
|
|
25
|
+
The tag Install gives, with the key from the DEACON_KEY setting. With no key it prints
|
|
26
|
+
nothing and logs a warning, so a missing setting never takes a page down.
|
|
27
|
+
"""
|
|
28
|
+
key = str(getattr(settings, "DEACON_KEY", None) or "").strip()
|
|
29
|
+
|
|
30
|
+
if key == "":
|
|
31
|
+
logger.warning(MISSING_KEY)
|
|
32
|
+
return ""
|
|
33
|
+
|
|
34
|
+
deacon_context = getattr(settings, "DEACON_CONTEXT", None)
|
|
35
|
+
page_views = getattr(settings, "DEACON_PAGE_VIEWS", True)
|
|
36
|
+
_check_context(deacon_context)
|
|
37
|
+
_check_page_views(page_views)
|
|
38
|
+
|
|
39
|
+
attributes = {"data-key": key}
|
|
40
|
+
if deacon_context is not None:
|
|
41
|
+
attributes["data-context"] = json.dumps(
|
|
42
|
+
deacon_context, separators=(",", ":"), ensure_ascii=False
|
|
43
|
+
)
|
|
44
|
+
if page_views is False:
|
|
45
|
+
attributes["data-no-page-views"] = True
|
|
46
|
+
|
|
47
|
+
return _loader_tag(attributes, _csp_nonce(context))
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _csp_nonce(context):
|
|
51
|
+
"""
|
|
52
|
+
The request's Content Security Policy nonce: from Django's own CSP support (6.0 and
|
|
53
|
+
later, through its csp context processor), else from django-csp's request.csp_nonce, or
|
|
54
|
+
None when the project has no nonce.
|
|
55
|
+
"""
|
|
56
|
+
nonce = context.get("csp_nonce")
|
|
57
|
+
if nonce is None:
|
|
58
|
+
nonce = getattr(context.get("request"), "csp_nonce", None)
|
|
59
|
+
|
|
60
|
+
return None if nonce is None else str(nonce)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _check_context(deacon_context):
|
|
64
|
+
"""A wrong type is a mistake in the project's settings, so it raises."""
|
|
65
|
+
if deacon_context is None:
|
|
66
|
+
return
|
|
67
|
+
|
|
68
|
+
if not isinstance(deacon_context, dict) or not all(
|
|
69
|
+
isinstance(value, str | int | float | bool) for value in deacon_context.values()
|
|
70
|
+
):
|
|
71
|
+
raise ImproperlyConfigured(
|
|
72
|
+
"django-deacon: the DEACON_CONTEXT setting must be a dict whose values are strings, "
|
|
73
|
+
"numbers or booleans."
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _check_page_views(page_views):
|
|
78
|
+
if not isinstance(page_views, bool):
|
|
79
|
+
raise ImproperlyConfigured(
|
|
80
|
+
"django-deacon: the DEACON_PAGE_VIEWS setting must be True or False."
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def _loader_tag(attributes, nonce):
|
|
85
|
+
"""
|
|
86
|
+
The tag Install gives, with any of its other attributes between the key and async, and
|
|
87
|
+
the nonce after them. An attribute that is True is written with no value, as async is.
|
|
88
|
+
"""
|
|
89
|
+
rendered = "".join(
|
|
90
|
+
f" {name}" if value is True else f' {name}="{escape(value)}"'
|
|
91
|
+
for name, value in attributes.items()
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
nonce_attribute = "" if nonce is None else f' nonce="{escape(nonce)}"'
|
|
95
|
+
|
|
96
|
+
return mark_safe(f'<script src="{LOADER_SRC}"{rendered} async{nonce_attribute}></script>')
|