reactpy_django 5.0.0__py3-none-any.whl
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.
- js/node_modules/@pyscript/core/src/stdlib/pyscript/__init__.py +61 -0
- js/node_modules/@pyscript/core/src/stdlib/pyscript/display.py +177 -0
- js/node_modules/@pyscript/core/src/stdlib/pyscript/event_handling.py +76 -0
- js/node_modules/@pyscript/core/src/stdlib/pyscript/fetch.py +87 -0
- js/node_modules/@pyscript/core/src/stdlib/pyscript/ffi.py +18 -0
- js/node_modules/@pyscript/core/src/stdlib/pyscript/flatted.py +148 -0
- js/node_modules/@pyscript/core/src/stdlib/pyscript/magic_js.py +79 -0
- js/node_modules/@pyscript/core/src/stdlib/pyscript/storage.py +60 -0
- js/node_modules/@pyscript/core/src/stdlib/pyscript/util.py +33 -0
- js/node_modules/@pyscript/core/src/stdlib/pyscript/web.py +1176 -0
- js/node_modules/@pyscript/core/src/stdlib/pyscript/websocket.py +71 -0
- js/node_modules/@pyscript/core/src/stdlib/pyscript/workers.py +43 -0
- js/node_modules/flatted/python/flatted.py +149 -0
- js/node_modules/flatted/python/test.py +63 -0
- js/node_modules/polyscript/python/mip.py +288 -0
- reactpy_django/__init__.py +32 -0
- reactpy_django/apps.py +11 -0
- reactpy_django/checks.py +558 -0
- reactpy_django/clean.py +141 -0
- reactpy_django/components.py +284 -0
- reactpy_django/config.py +128 -0
- reactpy_django/database.py +31 -0
- reactpy_django/decorators.py +65 -0
- reactpy_django/exceptions.py +34 -0
- reactpy_django/hooks.py +469 -0
- reactpy_django/html.py +3 -0
- reactpy_django/http/__init__.py +0 -0
- reactpy_django/http/urls.py +18 -0
- reactpy_django/http/views.py +62 -0
- reactpy_django/management/__init__.py +0 -0
- reactpy_django/management/commands/__init__.py +0 -0
- reactpy_django/management/commands/clean_reactpy.py +37 -0
- reactpy_django/migrations/0001_initial.py +25 -0
- reactpy_django/migrations/0002_rename_created_at_componentparams_last_accessed.py +17 -0
- reactpy_django/migrations/0003_componentsession_delete_componentparams.py +28 -0
- reactpy_django/migrations/0004_config.py +27 -0
- reactpy_django/migrations/0005_alter_componentsession_last_accessed.py +17 -0
- reactpy_django/migrations/0006_userdatamodel.py +28 -0
- reactpy_django/migrations/__init__.py +0 -0
- reactpy_django/models.py +48 -0
- reactpy_django/py.typed +1 -0
- reactpy_django/pyscript/__init__.py +0 -0
- reactpy_django/pyscript/component_template.py +26 -0
- reactpy_django/pyscript/layout_handler.py +139 -0
- reactpy_django/router/__init__.py +5 -0
- reactpy_django/router/converters.py +8 -0
- reactpy_django/router/resolvers.py +22 -0
- reactpy_django/static/reactpy_django/client.js +1 -0
- reactpy_django/static/reactpy_django/morphdom/morphdom-esm.js +769 -0
- reactpy_django/static/reactpy_django/morphdom/morphdom-factory.js +705 -0
- reactpy_django/static/reactpy_django/morphdom/morphdom-umd.js +777 -0
- reactpy_django/static/reactpy_django/morphdom/morphdom-umd.min.js +1 -0
- reactpy_django/static/reactpy_django/morphdom/morphdom.js +771 -0
- reactpy_django/static/reactpy_django/pyscript/codemirror-BEtcgaoQ.js +2 -0
- reactpy_django/static/reactpy_django/pyscript/codemirror-BEtcgaoQ.js.map +1 -0
- reactpy_django/static/reactpy_django/pyscript/codemirror_commands-DDxffOmd.js +2 -0
- reactpy_django/static/reactpy_django/pyscript/codemirror_commands-DDxffOmd.js.map +1 -0
- reactpy_django/static/reactpy_django/pyscript/codemirror_lang-python-CnWnFqxD.js +2 -0
- reactpy_django/static/reactpy_django/pyscript/codemirror_lang-python-CnWnFqxD.js.map +1 -0
- reactpy_django/static/reactpy_django/pyscript/codemirror_language-CjmvX4ix.js +2 -0
- reactpy_django/static/reactpy_django/pyscript/codemirror_language-CjmvX4ix.js.map +1 -0
- reactpy_django/static/reactpy_django/pyscript/codemirror_state-D1qTXrff.js +2 -0
- reactpy_django/static/reactpy_django/pyscript/codemirror_state-D1qTXrff.js.map +1 -0
- reactpy_django/static/reactpy_django/pyscript/codemirror_view-DVb8uYMr.js +2 -0
- reactpy_django/static/reactpy_django/pyscript/codemirror_view-DVb8uYMr.js.map +1 -0
- reactpy_django/static/reactpy_django/pyscript/core-CjO3FOKB.js +2 -0
- reactpy_django/static/reactpy_django/pyscript/core-CjO3FOKB.js.map +1 -0
- reactpy_django/static/reactpy_django/pyscript/core.css +1 -0
- reactpy_django/static/reactpy_django/pyscript/core.js +2 -0
- reactpy_django/static/reactpy_django/pyscript/core.js.map +1 -0
- reactpy_django/static/reactpy_django/pyscript/deprecations-manager-pFtn19mE.js +2 -0
- reactpy_django/static/reactpy_django/pyscript/deprecations-manager-pFtn19mE.js.map +1 -0
- reactpy_django/static/reactpy_django/pyscript/error-tq-z48YI.js +2 -0
- reactpy_django/static/reactpy_django/pyscript/error-tq-z48YI.js.map +1 -0
- reactpy_django/static/reactpy_django/pyscript/index-S1Do43bx.js +2 -0
- reactpy_django/static/reactpy_django/pyscript/index-S1Do43bx.js.map +1 -0
- reactpy_django/static/reactpy_django/pyscript/mpy-DovD7Qjy.js +2 -0
- reactpy_django/static/reactpy_django/pyscript/mpy-DovD7Qjy.js.map +1 -0
- reactpy_django/static/reactpy_django/pyscript/py-BUsUWVJg.js +2 -0
- reactpy_django/static/reactpy_django/pyscript/py-BUsUWVJg.js.map +1 -0
- reactpy_django/static/reactpy_django/pyscript/py-editor-CeySmmer.js +2 -0
- reactpy_django/static/reactpy_django/pyscript/py-editor-CeySmmer.js.map +1 -0
- reactpy_django/static/reactpy_django/pyscript/py-terminal-CH_wV7wQ.js +2 -0
- reactpy_django/static/reactpy_django/pyscript/py-terminal-CH_wV7wQ.js.map +1 -0
- reactpy_django/static/reactpy_django/pyscript/storage.js +2 -0
- reactpy_django/static/reactpy_django/pyscript/storage.js.map +1 -0
- reactpy_django/static/reactpy_django/pyscript/toml-CvAfdf9_.js +3 -0
- reactpy_django/static/reactpy_django/pyscript/toml-CvAfdf9_.js.map +1 -0
- reactpy_django/static/reactpy_django/pyscript/toml-DiUM0_qs.js +3 -0
- reactpy_django/static/reactpy_django/pyscript/toml-DiUM0_qs.js.map +1 -0
- reactpy_django/static/reactpy_django/pyscript/xterm-BY7uk_OU.js +2 -0
- reactpy_django/static/reactpy_django/pyscript/xterm-BY7uk_OU.js.map +1 -0
- reactpy_django/static/reactpy_django/pyscript/xterm-readline-CZfBw7ic.js +2 -0
- reactpy_django/static/reactpy_django/pyscript/xterm-readline-CZfBw7ic.js.map +1 -0
- reactpy_django/static/reactpy_django/pyscript/xterm.css +7 -0
- reactpy_django/static/reactpy_django/pyscript/xterm_addon-fit--gyF3PcZ.js +2 -0
- reactpy_django/static/reactpy_django/pyscript/xterm_addon-fit--gyF3PcZ.js.map +1 -0
- reactpy_django/static/reactpy_django/pyscript/xterm_addon-web-links-Cnej-nJ6.js +2 -0
- reactpy_django/static/reactpy_django/pyscript/xterm_addon-web-links-Cnej-nJ6.js.map +1 -0
- reactpy_django/static/reactpy_django/pyscript/zip-DrwYHuF9.js +2 -0
- reactpy_django/static/reactpy_django/pyscript/zip-DrwYHuF9.js.map +1 -0
- reactpy_django/static/reactpy_django/pyscript-custom.css +3 -0
- reactpy_django/static/reactpy_django/pyscript-hide-debug.css +3 -0
- reactpy_django/templates/reactpy/component.html +27 -0
- reactpy_django/templates/reactpy/pyscript_component.html +2 -0
- reactpy_django/templates/reactpy/pyscript_setup.html +8 -0
- reactpy_django/templatetags/__init__.py +0 -0
- reactpy_django/templatetags/reactpy.py +250 -0
- reactpy_django/types.py +81 -0
- reactpy_django/utils.py +560 -0
- reactpy_django/websocket/__init__.py +0 -0
- reactpy_django/websocket/consumer.py +227 -0
- reactpy_django/websocket/paths.py +15 -0
- reactpy_django-5.0.0.dist-info/LICENSE.md +9 -0
- reactpy_django-5.0.0.dist-info/METADATA +152 -0
- reactpy_django-5.0.0.dist-info/RECORD +118 -0
- reactpy_django-5.0.0.dist-info/WHEEL +5 -0
- reactpy_django-5.0.0.dist-info/top_level.txt +2 -0
reactpy_django/checks.py
ADDED
|
@@ -0,0 +1,558 @@
|
|
|
1
|
+
import contextlib
|
|
2
|
+
import math
|
|
3
|
+
import sys
|
|
4
|
+
|
|
5
|
+
from django.contrib.staticfiles.finders import find
|
|
6
|
+
from django.core.checks import Error, Tags, Warning, register
|
|
7
|
+
from django.template import loader
|
|
8
|
+
from django.urls import NoReverseMatch
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@register(Tags.compatibility)
|
|
12
|
+
def reactpy_warnings(app_configs, **kwargs):
|
|
13
|
+
from django.conf import settings
|
|
14
|
+
from django.urls import reverse
|
|
15
|
+
|
|
16
|
+
from reactpy_django import config
|
|
17
|
+
from reactpy_django.config import REACTPY_FAILED_COMPONENTS
|
|
18
|
+
|
|
19
|
+
warnings = []
|
|
20
|
+
INSTALLED_APPS: list[str] = getattr(settings, "INSTALLED_APPS", [])
|
|
21
|
+
|
|
22
|
+
# Check if REACTPY_DATABASE is not an in-memory database.
|
|
23
|
+
if (
|
|
24
|
+
getattr(settings, "DATABASES", {})
|
|
25
|
+
.get(getattr(settings, "REACTPY_DATABASE", "default"), {})
|
|
26
|
+
.get("NAME", None)
|
|
27
|
+
== ":memory:"
|
|
28
|
+
):
|
|
29
|
+
warnings.append(
|
|
30
|
+
Warning(
|
|
31
|
+
"Using ReactPy with an in-memory database can cause unexpected "
|
|
32
|
+
"behaviors.",
|
|
33
|
+
hint="Configure settings.py:DATABASES[REACTPY_DATABASE], to use a "
|
|
34
|
+
"multiprocessing and thread safe database.",
|
|
35
|
+
id="reactpy_django.W001",
|
|
36
|
+
)
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
# Check if ReactPy URLs are reachable
|
|
40
|
+
try:
|
|
41
|
+
reverse("reactpy:web_modules", kwargs={"file": "example"})
|
|
42
|
+
reverse("reactpy:view_to_iframe", kwargs={"dotted_path": "example"})
|
|
43
|
+
except Exception:
|
|
44
|
+
warnings.append(
|
|
45
|
+
Warning(
|
|
46
|
+
"ReactPy URLs have not been registered.",
|
|
47
|
+
hint="""Add 'path("reactpy/", include("reactpy_django.http.urls"))' """
|
|
48
|
+
"to your application's urlpatterns. If this application does not need "
|
|
49
|
+
"to render ReactPy components, you add this warning to SILENCED_SYSTEM_CHECKS.",
|
|
50
|
+
id="reactpy_django.W002",
|
|
51
|
+
)
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
# Warn if REACTPY_BACKHAUL_THREAD is set to True with Daphne
|
|
55
|
+
if (
|
|
56
|
+
sys.argv[0].endswith("daphne")
|
|
57
|
+
or ("runserver" in sys.argv and "daphne" in INSTALLED_APPS)
|
|
58
|
+
) and getattr(settings, "REACTPY_BACKHAUL_THREAD", False):
|
|
59
|
+
warnings.append(
|
|
60
|
+
Warning(
|
|
61
|
+
"Unstable configuration detected. REACTPY_BACKHAUL_THREAD is enabled "
|
|
62
|
+
"and you running with Daphne.",
|
|
63
|
+
hint="Set settings.py:REACTPY_BACKHAUL_THREAD to False or use a different web server.",
|
|
64
|
+
id="reactpy_django.W003",
|
|
65
|
+
)
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
# Check if reactpy_django/client.js is available
|
|
69
|
+
if not find("reactpy_django/client.js"):
|
|
70
|
+
warnings.append(
|
|
71
|
+
Warning(
|
|
72
|
+
"ReactPy client.js could not be found within Django static files!",
|
|
73
|
+
hint="Check all static files related Django settings and INSTALLED_APPS.",
|
|
74
|
+
id="reactpy_django.W004",
|
|
75
|
+
)
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
# Check if any components failed to be registered
|
|
79
|
+
if REACTPY_FAILED_COMPONENTS:
|
|
80
|
+
warnings.append(
|
|
81
|
+
Warning(
|
|
82
|
+
"ReactPy failed to register the following components:\n\t+ "
|
|
83
|
+
+ "\n\t+ ".join(REACTPY_FAILED_COMPONENTS),
|
|
84
|
+
hint="Check if these paths are valid, or if an exception is being "
|
|
85
|
+
"raised during import.",
|
|
86
|
+
id="reactpy_django.W005",
|
|
87
|
+
)
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
# Check if the reactpy/component.html template exists
|
|
91
|
+
try:
|
|
92
|
+
loader.get_template("reactpy/component.html")
|
|
93
|
+
except Exception:
|
|
94
|
+
warnings.append(
|
|
95
|
+
Warning(
|
|
96
|
+
"ReactPy HTML templates could not be found!",
|
|
97
|
+
hint="Check your settings.py:TEMPLATES configuration and make sure "
|
|
98
|
+
"ReactPy-Django is installed properly.",
|
|
99
|
+
id="reactpy_django.W006",
|
|
100
|
+
)
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
# DELETED W007: Check if REACTPY_WEBSOCKET_URL doesn't end with a slash
|
|
104
|
+
# DELETED W008: Check if REACTPY_WEBSOCKET_URL doesn't start with an alphanumeric character
|
|
105
|
+
# DELETED W009: Check if deprecated value REACTPY_WEBSOCKET_URL exists
|
|
106
|
+
|
|
107
|
+
# Check if REACTPY_URL_PREFIX is being used properly in our HTTP URLs
|
|
108
|
+
with contextlib.suppress(NoReverseMatch):
|
|
109
|
+
full_path = reverse("reactpy:web_modules", kwargs={"file": "example"}).strip(
|
|
110
|
+
"/"
|
|
111
|
+
)
|
|
112
|
+
reactpy_http_prefix = f'{full_path[: full_path.find("web_module/")].strip("/")}'
|
|
113
|
+
if reactpy_http_prefix != config.REACTPY_URL_PREFIX:
|
|
114
|
+
warnings.append(
|
|
115
|
+
Warning(
|
|
116
|
+
"HTTP paths are not prefixed with REACTPY_URL_PREFIX. "
|
|
117
|
+
"Some ReactPy features may not work as expected.",
|
|
118
|
+
hint="Use one of the following solutions.\n"
|
|
119
|
+
"\t1) Utilize REACTPY_URL_PREFIX within your urls.py:\n"
|
|
120
|
+
f'\t path("{config.REACTPY_URL_PREFIX}/", include("reactpy_django.http.urls"))\n'
|
|
121
|
+
"\t2) Modify settings.py:REACTPY_URL_PREFIX to match your existing HTTP path:\n"
|
|
122
|
+
f'\t REACTPY_URL_PREFIX = "{reactpy_http_prefix}/"\n'
|
|
123
|
+
"\t3) If you not rendering components by this ASGI application, then remove "
|
|
124
|
+
"ReactPy HTTP and websocket routing. This is common for configurations that "
|
|
125
|
+
"rely entirely on `host` configuration in your template tag.",
|
|
126
|
+
id="reactpy_django.W010",
|
|
127
|
+
)
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
# Check if REACTPY_URL_PREFIX is empty
|
|
131
|
+
if not getattr(settings, "REACTPY_URL_PREFIX", "reactpy/"):
|
|
132
|
+
warnings.append(
|
|
133
|
+
Warning(
|
|
134
|
+
"REACTPY_URL_PREFIX should not be empty!",
|
|
135
|
+
hint="Change your REACTPY_URL_PREFIX to be written in the following format: '/example_url/'",
|
|
136
|
+
id="reactpy_django.W011",
|
|
137
|
+
)
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
# Check if `daphne` is not in installed apps when using `runserver`
|
|
141
|
+
if "runserver" in sys.argv and "daphne" not in getattr(
|
|
142
|
+
settings, "INSTALLED_APPS", []
|
|
143
|
+
):
|
|
144
|
+
warnings.append(
|
|
145
|
+
Warning(
|
|
146
|
+
"You have not configured the `runserver` command to use ASGI. "
|
|
147
|
+
"ReactPy will work properly in this configuration.",
|
|
148
|
+
hint="Add daphne to settings.py:INSTALLED_APPS.",
|
|
149
|
+
id="reactpy_django.W012",
|
|
150
|
+
)
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
# DELETED W013: Check if deprecated value REACTPY_RECONNECT_MAX exists
|
|
154
|
+
|
|
155
|
+
# Check if REACTPY_RECONNECT_INTERVAL is set to a large value
|
|
156
|
+
if (
|
|
157
|
+
isinstance(config.REACTPY_RECONNECT_INTERVAL, int)
|
|
158
|
+
and config.REACTPY_RECONNECT_INTERVAL > 30000
|
|
159
|
+
):
|
|
160
|
+
warnings.append(
|
|
161
|
+
Warning(
|
|
162
|
+
"REACTPY_RECONNECT_INTERVAL is set to >30 seconds. Are you sure this is intentional? "
|
|
163
|
+
"This may cause unexpected delays between reconnection.",
|
|
164
|
+
hint="Check your value for REACTPY_RECONNECT_INTERVAL or suppress this warning.",
|
|
165
|
+
id="reactpy_django.W014",
|
|
166
|
+
)
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
# Check if REACTPY_RECONNECT_MAX_RETRIES is set to a large value
|
|
170
|
+
if (
|
|
171
|
+
isinstance(config.REACTPY_RECONNECT_MAX_RETRIES, int)
|
|
172
|
+
and config.REACTPY_RECONNECT_MAX_RETRIES > 5000
|
|
173
|
+
):
|
|
174
|
+
warnings.append(
|
|
175
|
+
Warning(
|
|
176
|
+
"REACTPY_RECONNECT_MAX_RETRIES is set to a very large value "
|
|
177
|
+
f"{config.REACTPY_RECONNECT_MAX_RETRIES}. Are you sure this is intentional? "
|
|
178
|
+
"This may leave your clients attempting reconnections for a long time.",
|
|
179
|
+
hint="Check your value for REACTPY_RECONNECT_MAX_RETRIES or suppress this warning.",
|
|
180
|
+
id="reactpy_django.W015",
|
|
181
|
+
)
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
# Check if the REACTPY_RECONNECT_BACKOFF_MULTIPLIER is set to a large value
|
|
185
|
+
if (
|
|
186
|
+
isinstance(config.REACTPY_RECONNECT_BACKOFF_MULTIPLIER, (int, float))
|
|
187
|
+
and config.REACTPY_RECONNECT_BACKOFF_MULTIPLIER > 100
|
|
188
|
+
):
|
|
189
|
+
warnings.append(
|
|
190
|
+
Warning(
|
|
191
|
+
"REACTPY_RECONNECT_BACKOFF_MULTIPLIER is set to a very large value. Are you sure this is intentional?",
|
|
192
|
+
hint="Check your value for REACTPY_RECONNECT_BACKOFF_MULTIPLIER or suppress this warning.",
|
|
193
|
+
id="reactpy_django.W016",
|
|
194
|
+
)
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
# Check if REACTPY_RECONNECT_MAX_INTERVAL is reachable
|
|
198
|
+
if (
|
|
199
|
+
isinstance(config.REACTPY_RECONNECT_MAX_INTERVAL, int)
|
|
200
|
+
and isinstance(config.REACTPY_RECONNECT_INTERVAL, int)
|
|
201
|
+
and isinstance(config.REACTPY_RECONNECT_MAX_RETRIES, int)
|
|
202
|
+
and isinstance(config.REACTPY_RECONNECT_BACKOFF_MULTIPLIER, (int, float))
|
|
203
|
+
and config.REACTPY_RECONNECT_INTERVAL > 0
|
|
204
|
+
and config.REACTPY_RECONNECT_MAX_INTERVAL > 0
|
|
205
|
+
and config.REACTPY_RECONNECT_MAX_RETRIES > 0
|
|
206
|
+
and config.REACTPY_RECONNECT_BACKOFF_MULTIPLIER > 1
|
|
207
|
+
and (
|
|
208
|
+
config.REACTPY_RECONNECT_BACKOFF_MULTIPLIER
|
|
209
|
+
** config.REACTPY_RECONNECT_MAX_RETRIES
|
|
210
|
+
)
|
|
211
|
+
* config.REACTPY_RECONNECT_INTERVAL
|
|
212
|
+
< config.REACTPY_RECONNECT_MAX_INTERVAL
|
|
213
|
+
):
|
|
214
|
+
max_value = math.floor(
|
|
215
|
+
(
|
|
216
|
+
config.REACTPY_RECONNECT_BACKOFF_MULTIPLIER
|
|
217
|
+
** config.REACTPY_RECONNECT_MAX_RETRIES
|
|
218
|
+
)
|
|
219
|
+
* config.REACTPY_RECONNECT_INTERVAL
|
|
220
|
+
)
|
|
221
|
+
warnings.append(
|
|
222
|
+
Warning(
|
|
223
|
+
"Your current ReactPy configuration can never reach REACTPY_RECONNECT_MAX_INTERVAL. At most you will reach "
|
|
224
|
+
f"{max_value} miliseconds, which is less than {config.REACTPY_RECONNECT_MAX_INTERVAL} (REACTPY_RECONNECT_MAX_INTERVAL).",
|
|
225
|
+
hint="Check your ReactPy REACTPY_RECONNECT_* settings.",
|
|
226
|
+
id="reactpy_django.W017",
|
|
227
|
+
)
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
# Check if 'reactpy_django' is in the correct position in INSTALLED_APPS
|
|
231
|
+
position_to_beat = 0
|
|
232
|
+
for app in INSTALLED_APPS:
|
|
233
|
+
if app.startswith("django.contrib."):
|
|
234
|
+
position_to_beat = INSTALLED_APPS.index(app)
|
|
235
|
+
if (
|
|
236
|
+
"reactpy_django" in INSTALLED_APPS
|
|
237
|
+
and INSTALLED_APPS.index("reactpy_django") < position_to_beat
|
|
238
|
+
):
|
|
239
|
+
warnings.append(
|
|
240
|
+
Warning(
|
|
241
|
+
"The position of 'reactpy_django' in INSTALLED_APPS is suspicious.",
|
|
242
|
+
hint="Move 'reactpy_django' below all 'django.contrib.*' apps, or suppress this warning.",
|
|
243
|
+
id="reactpy_django.W018",
|
|
244
|
+
)
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
# Check if REACTPY_CLEAN_SESSION is not a valid property
|
|
248
|
+
if getattr(settings, "REACTPY_CLEAN_SESSION", None):
|
|
249
|
+
warnings.append(
|
|
250
|
+
Warning(
|
|
251
|
+
"REACTPY_CLEAN_SESSION is not a valid property value.",
|
|
252
|
+
hint="Did you mean to use REACTPY_CLEAN_SESSIONS instead?",
|
|
253
|
+
id="reactpy_django.W019",
|
|
254
|
+
)
|
|
255
|
+
)
|
|
256
|
+
|
|
257
|
+
return warnings
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
@register(Tags.compatibility)
|
|
261
|
+
def reactpy_errors(app_configs, **kwargs):
|
|
262
|
+
from django.conf import settings
|
|
263
|
+
|
|
264
|
+
from reactpy_django import config
|
|
265
|
+
|
|
266
|
+
errors = []
|
|
267
|
+
|
|
268
|
+
# Make sure ASGI is enabled
|
|
269
|
+
if not getattr(settings, "ASGI_APPLICATION", None):
|
|
270
|
+
errors.append(
|
|
271
|
+
Error(
|
|
272
|
+
"ASGI_APPLICATION is not defined, but ReactPy requires ASGI.",
|
|
273
|
+
hint="Add ASGI_APPLICATION to settings.py.",
|
|
274
|
+
id="reactpy_django.E001",
|
|
275
|
+
)
|
|
276
|
+
)
|
|
277
|
+
|
|
278
|
+
# DATABASE_ROUTERS is properly configured when REACTPY_DATABASE is defined
|
|
279
|
+
if getattr(
|
|
280
|
+
settings, "REACTPY_DATABASE", None
|
|
281
|
+
) and "reactpy_django.database.Router" not in getattr(
|
|
282
|
+
settings, "DATABASE_ROUTERS", []
|
|
283
|
+
):
|
|
284
|
+
errors.append(
|
|
285
|
+
Error(
|
|
286
|
+
"ReactPy database has been changed but the database router is "
|
|
287
|
+
"not configured.",
|
|
288
|
+
hint="Set settings.py:DATABASE_ROUTERS to "
|
|
289
|
+
"['reactpy_django.database.Router', ...]",
|
|
290
|
+
id="reactpy_django.E002",
|
|
291
|
+
)
|
|
292
|
+
)
|
|
293
|
+
|
|
294
|
+
# Check if REACTPY_URL_PREFIX is a valid data type
|
|
295
|
+
if not isinstance(getattr(settings, "REACTPY_URL_PREFIX", ""), str):
|
|
296
|
+
errors.append(
|
|
297
|
+
Error(
|
|
298
|
+
"Invalid type for REACTPY_URL_PREFIX.",
|
|
299
|
+
hint="REACTPY_URL_PREFIX should be a string.",
|
|
300
|
+
obj=settings.REACTPY_URL_PREFIX,
|
|
301
|
+
id="reactpy_django.E003",
|
|
302
|
+
)
|
|
303
|
+
)
|
|
304
|
+
|
|
305
|
+
# Check if REACTPY_SESSION_MAX_AGE is a valid data type
|
|
306
|
+
if not isinstance(getattr(settings, "REACTPY_SESSION_MAX_AGE", 0), int):
|
|
307
|
+
errors.append(
|
|
308
|
+
Error(
|
|
309
|
+
"Invalid type for REACTPY_SESSION_MAX_AGE.",
|
|
310
|
+
hint="REACTPY_SESSION_MAX_AGE should be an integer.",
|
|
311
|
+
obj=settings.REACTPY_SESSION_MAX_AGE,
|
|
312
|
+
id="reactpy_django.E004",
|
|
313
|
+
)
|
|
314
|
+
)
|
|
315
|
+
|
|
316
|
+
# Check if REACTPY_CACHE is a valid data type
|
|
317
|
+
if not isinstance(getattr(settings, "REACTPY_CACHE", ""), str):
|
|
318
|
+
errors.append(
|
|
319
|
+
Error(
|
|
320
|
+
"Invalid type for REACTPY_CACHE.",
|
|
321
|
+
hint="REACTPY_CACHE should be a string.",
|
|
322
|
+
obj=settings.REACTPY_CACHE,
|
|
323
|
+
id="reactpy_django.E005",
|
|
324
|
+
)
|
|
325
|
+
)
|
|
326
|
+
|
|
327
|
+
# Check if REACTPY_DATABASE is a valid data type
|
|
328
|
+
if not isinstance(getattr(settings, "REACTPY_DATABASE", ""), str):
|
|
329
|
+
errors.append(
|
|
330
|
+
Error(
|
|
331
|
+
"Invalid type for REACTPY_DATABASE.",
|
|
332
|
+
hint="REACTPY_DATABASE should be a string.",
|
|
333
|
+
obj=settings.REACTPY_DATABASE,
|
|
334
|
+
id="reactpy_django.E006",
|
|
335
|
+
)
|
|
336
|
+
)
|
|
337
|
+
|
|
338
|
+
# Check if REACTPY_DEFAULT_QUERY_POSTPROCESSOR is a valid data type
|
|
339
|
+
if not isinstance(
|
|
340
|
+
getattr(settings, "REACTPY_DEFAULT_QUERY_POSTPROCESSOR", ""), (str, type(None))
|
|
341
|
+
):
|
|
342
|
+
errors.append(
|
|
343
|
+
Error(
|
|
344
|
+
"Invalid type for REACTPY_DEFAULT_QUERY_POSTPROCESSOR.",
|
|
345
|
+
hint="REACTPY_DEFAULT_QUERY_POSTPROCESSOR should be a string or None.",
|
|
346
|
+
obj=settings.REACTPY_DEFAULT_QUERY_POSTPROCESSOR,
|
|
347
|
+
id="reactpy_django.E007",
|
|
348
|
+
)
|
|
349
|
+
)
|
|
350
|
+
|
|
351
|
+
# Check if REACTPY_AUTH_BACKEND is a valid data type
|
|
352
|
+
if not isinstance(getattr(settings, "REACTPY_AUTH_BACKEND", ""), str):
|
|
353
|
+
errors.append(
|
|
354
|
+
Error(
|
|
355
|
+
"Invalid type for REACTPY_AUTH_BACKEND.",
|
|
356
|
+
hint="REACTPY_AUTH_BACKEND should be a string.",
|
|
357
|
+
obj=settings.REACTPY_AUTH_BACKEND,
|
|
358
|
+
id="reactpy_django.E008",
|
|
359
|
+
)
|
|
360
|
+
)
|
|
361
|
+
|
|
362
|
+
# DELETED E009: Check if `channels` is in INSTALLED_APPS
|
|
363
|
+
|
|
364
|
+
# Check if REACTPY_DEFAULT_HOSTS is a valid data type
|
|
365
|
+
if not isinstance(getattr(settings, "REACTPY_DEFAULT_HOSTS", []), list):
|
|
366
|
+
errors.append(
|
|
367
|
+
Error(
|
|
368
|
+
"Invalid type for REACTPY_DEFAULT_HOSTS.",
|
|
369
|
+
hint="REACTPY_DEFAULT_HOSTS should be a list.",
|
|
370
|
+
obj=settings.REACTPY_DEFAULT_HOSTS,
|
|
371
|
+
id="reactpy_django.E010",
|
|
372
|
+
)
|
|
373
|
+
)
|
|
374
|
+
|
|
375
|
+
# Check of all values in the REACTPY_DEFAULT_HOSTS are strings
|
|
376
|
+
if isinstance(getattr(settings, "REACTPY_DEFAULT_HOSTS", None), list):
|
|
377
|
+
for host in settings.REACTPY_DEFAULT_HOSTS:
|
|
378
|
+
if not isinstance(host, str):
|
|
379
|
+
errors.append(
|
|
380
|
+
Error(
|
|
381
|
+
f"Invalid type {type(host)} within REACTPY_DEFAULT_HOSTS.",
|
|
382
|
+
hint="REACTPY_DEFAULT_HOSTS should be a list of strings.",
|
|
383
|
+
obj=settings.REACTPY_DEFAULT_HOSTS,
|
|
384
|
+
id="reactpy_django.E011",
|
|
385
|
+
)
|
|
386
|
+
)
|
|
387
|
+
break
|
|
388
|
+
|
|
389
|
+
# Check if REACTPY_RECONNECT_INTERVAL is a valid data type
|
|
390
|
+
if not isinstance(config.REACTPY_RECONNECT_INTERVAL, int):
|
|
391
|
+
errors.append(
|
|
392
|
+
Error(
|
|
393
|
+
"Invalid type for REACTPY_RECONNECT_INTERVAL.",
|
|
394
|
+
hint="REACTPY_RECONNECT_INTERVAL should be an integer.",
|
|
395
|
+
id="reactpy_django.E012",
|
|
396
|
+
)
|
|
397
|
+
)
|
|
398
|
+
|
|
399
|
+
# Check if REACTPY_RECONNECT_INTERVAL is a positive integer
|
|
400
|
+
if (
|
|
401
|
+
isinstance(config.REACTPY_RECONNECT_INTERVAL, int)
|
|
402
|
+
and config.REACTPY_RECONNECT_INTERVAL < 0
|
|
403
|
+
):
|
|
404
|
+
errors.append(
|
|
405
|
+
Error(
|
|
406
|
+
"Invalid value for REACTPY_RECONNECT_INTERVAL.",
|
|
407
|
+
hint="REACTPY_RECONNECT_INTERVAL should be a positive integer.",
|
|
408
|
+
id="reactpy_django.E013",
|
|
409
|
+
)
|
|
410
|
+
)
|
|
411
|
+
|
|
412
|
+
# Check if REACTPY_RECONNECT_MAX_INTERVAL is a valid data type
|
|
413
|
+
if not isinstance(config.REACTPY_RECONNECT_MAX_INTERVAL, int):
|
|
414
|
+
errors.append(
|
|
415
|
+
Error(
|
|
416
|
+
"Invalid type for REACTPY_RECONNECT_MAX_INTERVAL.",
|
|
417
|
+
hint="REACTPY_RECONNECT_MAX_INTERVAL should be an integer.",
|
|
418
|
+
id="reactpy_django.E014",
|
|
419
|
+
)
|
|
420
|
+
)
|
|
421
|
+
|
|
422
|
+
# Check if REACTPY_RECONNECT_MAX_INTERVAL is a positive integer
|
|
423
|
+
if (
|
|
424
|
+
isinstance(config.REACTPY_RECONNECT_MAX_INTERVAL, int)
|
|
425
|
+
and config.REACTPY_RECONNECT_MAX_INTERVAL < 0
|
|
426
|
+
):
|
|
427
|
+
errors.append(
|
|
428
|
+
Error(
|
|
429
|
+
"Invalid value for REACTPY_RECONNECT_MAX_INTERVAL.",
|
|
430
|
+
hint="REACTPY_RECONNECT_MAX_INTERVAL should be a positive integer.",
|
|
431
|
+
id="reactpy_django.E015",
|
|
432
|
+
)
|
|
433
|
+
)
|
|
434
|
+
|
|
435
|
+
# Check if REACTPY_RECONNECT_MAX_INTERVAL is greater than REACTPY_RECONNECT_INTERVAL
|
|
436
|
+
if (
|
|
437
|
+
isinstance(config.REACTPY_RECONNECT_MAX_INTERVAL, int)
|
|
438
|
+
and isinstance(config.REACTPY_RECONNECT_INTERVAL, int)
|
|
439
|
+
and config.REACTPY_RECONNECT_MAX_INTERVAL < config.REACTPY_RECONNECT_INTERVAL
|
|
440
|
+
):
|
|
441
|
+
errors.append(
|
|
442
|
+
Error(
|
|
443
|
+
"REACTPY_RECONNECT_MAX_INTERVAL is less than REACTPY_RECONNECT_INTERVAL.",
|
|
444
|
+
hint="REACTPY_RECONNECT_MAX_INTERVAL should be greater than or equal to REACTPY_RECONNECT_INTERVAL.",
|
|
445
|
+
id="reactpy_django.E016",
|
|
446
|
+
)
|
|
447
|
+
)
|
|
448
|
+
|
|
449
|
+
# Check if REACTPY_RECONNECT_MAX_RETRIES is a valid data type
|
|
450
|
+
if not isinstance(config.REACTPY_RECONNECT_MAX_RETRIES, int):
|
|
451
|
+
errors.append(
|
|
452
|
+
Error(
|
|
453
|
+
"Invalid type for REACTPY_RECONNECT_MAX_RETRIES.",
|
|
454
|
+
hint="REACTPY_RECONNECT_MAX_RETRIES should be an integer.",
|
|
455
|
+
id="reactpy_django.E017",
|
|
456
|
+
)
|
|
457
|
+
)
|
|
458
|
+
|
|
459
|
+
# Check if REACTPY_RECONNECT_MAX_RETRIES is a positive integer
|
|
460
|
+
if (
|
|
461
|
+
isinstance(config.REACTPY_RECONNECT_MAX_RETRIES, int)
|
|
462
|
+
and config.REACTPY_RECONNECT_MAX_RETRIES < 0
|
|
463
|
+
):
|
|
464
|
+
errors.append(
|
|
465
|
+
Error(
|
|
466
|
+
"Invalid value for REACTPY_RECONNECT_MAX_RETRIES.",
|
|
467
|
+
hint="REACTPY_RECONNECT_MAX_RETRIES should be a positive integer.",
|
|
468
|
+
id="reactpy_django.E018",
|
|
469
|
+
)
|
|
470
|
+
)
|
|
471
|
+
|
|
472
|
+
# Check if REACTPY_RECONNECT_BACKOFF_MULTIPLIER is a valid data type
|
|
473
|
+
if not isinstance(config.REACTPY_RECONNECT_BACKOFF_MULTIPLIER, (int, float)):
|
|
474
|
+
errors.append(
|
|
475
|
+
Error(
|
|
476
|
+
"Invalid type for REACTPY_RECONNECT_BACKOFF_MULTIPLIER.",
|
|
477
|
+
hint="REACTPY_RECONNECT_BACKOFF_MULTIPLIER should be an integer or float.",
|
|
478
|
+
id="reactpy_django.E019",
|
|
479
|
+
)
|
|
480
|
+
)
|
|
481
|
+
|
|
482
|
+
# Check if REACTPY_RECONNECT_BACKOFF_MULTIPLIER is greater than or equal to 1
|
|
483
|
+
if (
|
|
484
|
+
isinstance(config.REACTPY_RECONNECT_BACKOFF_MULTIPLIER, (int, float))
|
|
485
|
+
and config.REACTPY_RECONNECT_BACKOFF_MULTIPLIER < 1
|
|
486
|
+
):
|
|
487
|
+
errors.append(
|
|
488
|
+
Error(
|
|
489
|
+
"Invalid value for REACTPY_RECONNECT_BACKOFF_MULTIPLIER.",
|
|
490
|
+
hint="REACTPY_RECONNECT_BACKOFF_MULTIPLIER should be greater than or equal to 1.",
|
|
491
|
+
id="reactpy_django.E020",
|
|
492
|
+
)
|
|
493
|
+
)
|
|
494
|
+
|
|
495
|
+
# Check if REACTPY_PRERENDER is a valid data type
|
|
496
|
+
if not isinstance(config.REACTPY_PRERENDER, bool):
|
|
497
|
+
errors.append(
|
|
498
|
+
Error(
|
|
499
|
+
"Invalid type for REACTPY_PRERENDER.",
|
|
500
|
+
hint="REACTPY_PRERENDER should be a boolean.",
|
|
501
|
+
id="reactpy_django.E021",
|
|
502
|
+
)
|
|
503
|
+
)
|
|
504
|
+
|
|
505
|
+
# Check if REACTPY_AUTO_RELOGIN is a valid data type
|
|
506
|
+
if not isinstance(config.REACTPY_AUTO_RELOGIN, bool):
|
|
507
|
+
errors.append(
|
|
508
|
+
Error(
|
|
509
|
+
"Invalid type for REACTPY_AUTO_RELOGIN.",
|
|
510
|
+
hint="REACTPY_AUTO_RELOGIN should be a boolean.",
|
|
511
|
+
id="reactpy_django.E022",
|
|
512
|
+
)
|
|
513
|
+
)
|
|
514
|
+
|
|
515
|
+
# Check if REACTPY_CLEAN_INTERVAL is a valid data type
|
|
516
|
+
if not isinstance(config.REACTPY_CLEAN_INTERVAL, (int, type(None))):
|
|
517
|
+
errors.append(
|
|
518
|
+
Error(
|
|
519
|
+
"Invalid type for REACTPY_CLEAN_INTERVAL.",
|
|
520
|
+
hint="REACTPY_CLEAN_INTERVAL should be an integer or None.",
|
|
521
|
+
id="reactpy_django.E023",
|
|
522
|
+
)
|
|
523
|
+
)
|
|
524
|
+
|
|
525
|
+
# Check if REACTPY_CLEAN_INTERVAL is a positive integer
|
|
526
|
+
if (
|
|
527
|
+
isinstance(config.REACTPY_CLEAN_INTERVAL, int)
|
|
528
|
+
and config.REACTPY_CLEAN_INTERVAL < 0
|
|
529
|
+
):
|
|
530
|
+
errors.append(
|
|
531
|
+
Error(
|
|
532
|
+
"Invalid value for REACTPY_CLEAN_INTERVAL.",
|
|
533
|
+
hint="REACTPY_CLEAN_INTERVAL should be a positive integer or None.",
|
|
534
|
+
id="reactpy_django.E024",
|
|
535
|
+
)
|
|
536
|
+
)
|
|
537
|
+
|
|
538
|
+
# Check if REACTPY_CLEAN_SESSIONS is a valid data type
|
|
539
|
+
if not isinstance(config.REACTPY_CLEAN_SESSIONS, bool):
|
|
540
|
+
errors.append(
|
|
541
|
+
Error(
|
|
542
|
+
"Invalid type for REACTPY_CLEAN_SESSIONS.",
|
|
543
|
+
hint="REACTPY_CLEAN_SESSIONS should be a boolean.",
|
|
544
|
+
id="reactpy_django.E025",
|
|
545
|
+
)
|
|
546
|
+
)
|
|
547
|
+
|
|
548
|
+
# Check if REACTPY_CLEAN_USER_DATA is a valid data type
|
|
549
|
+
if not isinstance(config.REACTPY_CLEAN_USER_DATA, bool):
|
|
550
|
+
errors.append(
|
|
551
|
+
Error(
|
|
552
|
+
"Invalid type for REACTPY_CLEAN_USER_DATA.",
|
|
553
|
+
hint="REACTPY_CLEAN_USER_DATA should be a boolean.",
|
|
554
|
+
id="reactpy_django.E026",
|
|
555
|
+
)
|
|
556
|
+
)
|
|
557
|
+
|
|
558
|
+
return errors
|
reactpy_django/clean.py
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
from datetime import datetime, timedelta
|
|
5
|
+
from typing import TYPE_CHECKING, Literal
|
|
6
|
+
|
|
7
|
+
from django.contrib.auth import get_user_model
|
|
8
|
+
from django.utils import timezone
|
|
9
|
+
|
|
10
|
+
_logger = logging.getLogger(__name__)
|
|
11
|
+
|
|
12
|
+
if TYPE_CHECKING:
|
|
13
|
+
from reactpy_django.models import Config
|
|
14
|
+
|
|
15
|
+
CLEAN_NEEDED_BY: datetime = datetime(
|
|
16
|
+
year=1, month=1, day=1, tzinfo=timezone.now().tzinfo
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def clean(
|
|
21
|
+
*args: Literal["all", "sessions", "user_data"],
|
|
22
|
+
immediate: bool = False,
|
|
23
|
+
verbosity: int = 1,
|
|
24
|
+
):
|
|
25
|
+
from reactpy_django.config import (
|
|
26
|
+
REACTPY_CLEAN_SESSIONS,
|
|
27
|
+
REACTPY_CLEAN_USER_DATA,
|
|
28
|
+
)
|
|
29
|
+
from reactpy_django.models import Config
|
|
30
|
+
|
|
31
|
+
config = Config.load()
|
|
32
|
+
if immediate or is_clean_needed(config):
|
|
33
|
+
config.cleaned_at = timezone.now()
|
|
34
|
+
config.save()
|
|
35
|
+
sessions = REACTPY_CLEAN_SESSIONS
|
|
36
|
+
user_data = REACTPY_CLEAN_USER_DATA
|
|
37
|
+
|
|
38
|
+
if args:
|
|
39
|
+
sessions = any(value in args for value in {"sessions", "all"})
|
|
40
|
+
user_data = any(value in args for value in {"user_data", "all"})
|
|
41
|
+
|
|
42
|
+
if sessions:
|
|
43
|
+
clean_sessions(verbosity)
|
|
44
|
+
if user_data:
|
|
45
|
+
clean_user_data(verbosity)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def clean_sessions(verbosity: int = 1):
|
|
49
|
+
"""Deletes expired component sessions from the database.
|
|
50
|
+
As a performance optimization, this is only run once every REACTPY_SESSION_MAX_AGE seconds.
|
|
51
|
+
"""
|
|
52
|
+
from reactpy_django.config import REACTPY_DEBUG_MODE, REACTPY_SESSION_MAX_AGE
|
|
53
|
+
from reactpy_django.models import ComponentSession
|
|
54
|
+
|
|
55
|
+
if verbosity >= 2:
|
|
56
|
+
print("Cleaning ReactPy component sessions...")
|
|
57
|
+
|
|
58
|
+
start_time = timezone.now()
|
|
59
|
+
expiration_date = timezone.now() - timedelta(seconds=REACTPY_SESSION_MAX_AGE)
|
|
60
|
+
session_objects = ComponentSession.objects.filter(
|
|
61
|
+
last_accessed__lte=expiration_date
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
if verbosity >= 2:
|
|
65
|
+
print(f"Deleting {session_objects.count()} expired component sessions...")
|
|
66
|
+
|
|
67
|
+
session_objects.delete()
|
|
68
|
+
|
|
69
|
+
if REACTPY_DEBUG_MODE or verbosity >= 2:
|
|
70
|
+
inspect_clean_duration(start_time, "component sessions", verbosity)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def clean_user_data(verbosity: int = 1):
|
|
74
|
+
"""Delete any user data that is not associated with an existing `User`.
|
|
75
|
+
This is a safety measure to ensure that we don't have any orphaned data in the database.
|
|
76
|
+
|
|
77
|
+
Our `UserDataModel` is supposed to automatically get deleted on every `User` delete signal.
|
|
78
|
+
However, we can't use Django to enforce this relationship since ReactPy can be configured to
|
|
79
|
+
use any database.
|
|
80
|
+
"""
|
|
81
|
+
from reactpy_django.config import REACTPY_DEBUG_MODE
|
|
82
|
+
from reactpy_django.models import UserDataModel
|
|
83
|
+
|
|
84
|
+
if verbosity >= 2:
|
|
85
|
+
print("Cleaning ReactPy user data...")
|
|
86
|
+
|
|
87
|
+
start_time = timezone.now()
|
|
88
|
+
user_model = get_user_model()
|
|
89
|
+
all_users = user_model.objects.all()
|
|
90
|
+
all_user_pks = all_users.values_list(user_model._meta.pk.name, flat=True)
|
|
91
|
+
|
|
92
|
+
# Django doesn't support using QuerySets as an argument with cross-database relations.
|
|
93
|
+
if user_model.objects.db != UserDataModel.objects.db:
|
|
94
|
+
all_user_pks = list(all_user_pks) # type: ignore
|
|
95
|
+
|
|
96
|
+
user_data_objects = UserDataModel.objects.exclude(user_pk__in=all_user_pks)
|
|
97
|
+
|
|
98
|
+
if verbosity >= 2:
|
|
99
|
+
print(
|
|
100
|
+
f"Deleting {user_data_objects.count()} user data objects not associated with an existing user..."
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
user_data_objects.delete()
|
|
104
|
+
|
|
105
|
+
if REACTPY_DEBUG_MODE or verbosity >= 2:
|
|
106
|
+
inspect_clean_duration(start_time, "user data", verbosity)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def is_clean_needed(config: Config | None = None) -> bool:
|
|
110
|
+
"""Check if a clean is needed. This function avoids unnecessary database reads by caching the
|
|
111
|
+
CLEAN_NEEDED_BY date."""
|
|
112
|
+
from reactpy_django.config import REACTPY_CLEAN_INTERVAL
|
|
113
|
+
from reactpy_django.models import Config
|
|
114
|
+
|
|
115
|
+
global CLEAN_NEEDED_BY
|
|
116
|
+
|
|
117
|
+
if REACTPY_CLEAN_INTERVAL is None:
|
|
118
|
+
return False
|
|
119
|
+
|
|
120
|
+
if timezone.now() >= CLEAN_NEEDED_BY:
|
|
121
|
+
config = config or Config.load()
|
|
122
|
+
CLEAN_NEEDED_BY = config.cleaned_at + timedelta(seconds=REACTPY_CLEAN_INTERVAL)
|
|
123
|
+
|
|
124
|
+
return timezone.now() >= CLEAN_NEEDED_BY
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def inspect_clean_duration(start_time: datetime, task_name: str, verbosity: int):
|
|
128
|
+
clean_duration = timezone.now() - start_time
|
|
129
|
+
|
|
130
|
+
if verbosity >= 3:
|
|
131
|
+
print(
|
|
132
|
+
f"Cleaned ReactPy {task_name} in {clean_duration.total_seconds()} seconds."
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
if clean_duration.total_seconds() > 1:
|
|
136
|
+
_logger.warning(
|
|
137
|
+
"ReactPy has taken %s seconds to clean %s. "
|
|
138
|
+
"This may indicate a performance issue with your system, cache, or database.",
|
|
139
|
+
clean_duration.total_seconds(),
|
|
140
|
+
task_name,
|
|
141
|
+
)
|