django-sqlitetrigger 0.1.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_sqlitetrigger-0.1.0/.github/workflows/python-publish.yml +32 -0
- django_sqlitetrigger-0.1.0/.gitignore +350 -0
- django_sqlitetrigger-0.1.0/LICENSE +27 -0
- django_sqlitetrigger-0.1.0/PKG-INFO +69 -0
- django_sqlitetrigger-0.1.0/README.md +55 -0
- django_sqlitetrigger-0.1.0/example/example/__init__.py +0 -0
- django_sqlitetrigger-0.1.0/example/example/admin.py +13 -0
- django_sqlitetrigger-0.1.0/example/example/apps.py +5 -0
- django_sqlitetrigger-0.1.0/example/example/asgi.py +16 -0
- django_sqlitetrigger-0.1.0/example/example/migrations/0001_initial.py +23 -0
- django_sqlitetrigger-0.1.0/example/example/migrations/0002_book_sheets_update.py +18 -0
- django_sqlitetrigger-0.1.0/example/example/migrations/0003_alter_book_title.py +18 -0
- django_sqlitetrigger-0.1.0/example/example/migrations/0004_remove_book_sheets_update_book_sheets_update.py +22 -0
- django_sqlitetrigger-0.1.0/example/example/migrations/__init__.py +0 -0
- django_sqlitetrigger-0.1.0/example/example/models.py +21 -0
- django_sqlitetrigger-0.1.0/example/example/settings.py +119 -0
- django_sqlitetrigger-0.1.0/example/example/tests.py +3 -0
- django_sqlitetrigger-0.1.0/example/example/urls.py +22 -0
- django_sqlitetrigger-0.1.0/example/example/views.py +3 -0
- django_sqlitetrigger-0.1.0/example/example/wsgi.py +16 -0
- django_sqlitetrigger-0.1.0/example/manage.py +22 -0
- django_sqlitetrigger-0.1.0/pyproject.toml +33 -0
- django_sqlitetrigger-0.1.0/sqlitetrigger/__init__.py +45 -0
- django_sqlitetrigger-0.1.0/sqlitetrigger/apps.py +81 -0
- django_sqlitetrigger-0.1.0/sqlitetrigger/conditions.py +178 -0
- django_sqlitetrigger-0.1.0/sqlitetrigger/contrib.py +200 -0
- django_sqlitetrigger-0.1.0/sqlitetrigger/core.py +316 -0
- django_sqlitetrigger-0.1.0/sqlitetrigger/installation.py +130 -0
- django_sqlitetrigger-0.1.0/sqlitetrigger/management/__init__.py +0 -0
- django_sqlitetrigger-0.1.0/sqlitetrigger/management/commands/__init__.py +0 -0
- django_sqlitetrigger-0.1.0/sqlitetrigger/management/commands/sqlitetrigger.py +66 -0
- django_sqlitetrigger-0.1.0/sqlitetrigger/migrations.py +384 -0
- django_sqlitetrigger-0.1.0/sqlitetrigger/registry.py +85 -0
- django_sqlitetrigger-0.1.0/tests/__init__.py +0 -0
- django_sqlitetrigger-0.1.0/tests/models.py +84 -0
- django_sqlitetrigger-0.1.0/tests/settings.py +18 -0
- django_sqlitetrigger-0.1.0/tests/test_commands.py +35 -0
- django_sqlitetrigger-0.1.0/tests/test_conditions.py +139 -0
- django_sqlitetrigger-0.1.0/tests/test_contrib.py +184 -0
- django_sqlitetrigger-0.1.0/tests/test_core.py +160 -0
- django_sqlitetrigger-0.1.0/tests/test_migrations.py +201 -0
- django_sqlitetrigger-0.1.0/tests/test_registry.py +72 -0
- django_sqlitetrigger-0.1.0/uv.lock +150 -0
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
name: "Publish"
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags:
|
|
6
|
+
# Publish on any tag starting with a `v`, e.g., v0.1.0
|
|
7
|
+
- v*
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
run:
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
environment:
|
|
13
|
+
name: pypi
|
|
14
|
+
permissions:
|
|
15
|
+
id-token: write
|
|
16
|
+
contents: read
|
|
17
|
+
steps:
|
|
18
|
+
- name: Checkout
|
|
19
|
+
uses: actions/checkout@v6
|
|
20
|
+
- name: Install uv
|
|
21
|
+
uses: astral-sh/setup-uv@v7
|
|
22
|
+
- name: Install Python
|
|
23
|
+
run: uv python install 3.14
|
|
24
|
+
- name: Build
|
|
25
|
+
run: uv build
|
|
26
|
+
# Check that basic features work and we didn't miss to include crucial files
|
|
27
|
+
# - name: Smoke test (wheel)
|
|
28
|
+
# run: uv run --isolated --no-project --with dist/*.whl tests/smoke_test.py
|
|
29
|
+
# - name: Smoke test (source distribution)
|
|
30
|
+
# run: uv run --isolated --no-project --with dist/*.tar.gz tests/smoke_test.py
|
|
31
|
+
- name: Publish
|
|
32
|
+
run: uv publish
|
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
|
|
2
|
+
# Created by https://www.gitignore.io/api/vim,osx,python,django,pycharm,komodoedit,elasticbeanstalk,visualstudiocode
|
|
3
|
+
# Edit at https://www.gitignore.io/?templates=vim,osx,python,django,pycharm,komodoedit,elasticbeanstalk,visualstudiocode
|
|
4
|
+
|
|
5
|
+
### Django ###
|
|
6
|
+
*.log
|
|
7
|
+
*.pot
|
|
8
|
+
*.pyc
|
|
9
|
+
__pycache__/
|
|
10
|
+
local_settings.py
|
|
11
|
+
db.sqlite3
|
|
12
|
+
media
|
|
13
|
+
|
|
14
|
+
# If your build process includes running collectstatic, then you probably don't need or want to include staticfiles/
|
|
15
|
+
# in your Git repository. Update and uncomment the following line accordingly.
|
|
16
|
+
# <django-project-name>/staticfiles/
|
|
17
|
+
|
|
18
|
+
### Django.Python Stack ###
|
|
19
|
+
# Byte-compiled / optimized / DLL files
|
|
20
|
+
*.py[cod]
|
|
21
|
+
*$py.class
|
|
22
|
+
|
|
23
|
+
# C extensions
|
|
24
|
+
*.so
|
|
25
|
+
|
|
26
|
+
# Distribution / packaging
|
|
27
|
+
.Python
|
|
28
|
+
build/
|
|
29
|
+
develop-eggs/
|
|
30
|
+
dist/
|
|
31
|
+
downloads/
|
|
32
|
+
eggs/
|
|
33
|
+
.eggs/
|
|
34
|
+
lib/
|
|
35
|
+
lib64/
|
|
36
|
+
parts/
|
|
37
|
+
sdist/
|
|
38
|
+
var/
|
|
39
|
+
wheels/
|
|
40
|
+
pip-wheel-metadata/
|
|
41
|
+
share/python-wheels/
|
|
42
|
+
*.egg-info/
|
|
43
|
+
.installed.cfg
|
|
44
|
+
*.egg
|
|
45
|
+
MANIFEST
|
|
46
|
+
|
|
47
|
+
# PyInstaller
|
|
48
|
+
# Usually these files are written by a python script from a template
|
|
49
|
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
|
50
|
+
*.manifest
|
|
51
|
+
*.spec
|
|
52
|
+
|
|
53
|
+
# Installer logs
|
|
54
|
+
pip-log.txt
|
|
55
|
+
pip-delete-this-directory.txt
|
|
56
|
+
|
|
57
|
+
# Unit test / coverage reports
|
|
58
|
+
htmlcov/
|
|
59
|
+
.tox/
|
|
60
|
+
.nox/
|
|
61
|
+
.coverage
|
|
62
|
+
.coverage.*
|
|
63
|
+
.cache
|
|
64
|
+
nosetests.xml
|
|
65
|
+
coverage.xml
|
|
66
|
+
*.cover
|
|
67
|
+
.hypothesis/
|
|
68
|
+
.pytest_cache/
|
|
69
|
+
|
|
70
|
+
# Translations
|
|
71
|
+
*.mo
|
|
72
|
+
|
|
73
|
+
# Django stuff:
|
|
74
|
+
db.sqlite3-journal
|
|
75
|
+
|
|
76
|
+
# Flask stuff:
|
|
77
|
+
instance/
|
|
78
|
+
.webassets-cache
|
|
79
|
+
|
|
80
|
+
# Scrapy stuff:
|
|
81
|
+
.scrapy
|
|
82
|
+
|
|
83
|
+
# PyBuilder
|
|
84
|
+
target/
|
|
85
|
+
|
|
86
|
+
# Jupyter Notebook
|
|
87
|
+
.ipynb_checkpoints
|
|
88
|
+
|
|
89
|
+
# IPython
|
|
90
|
+
profile_default/
|
|
91
|
+
ipython_config.py
|
|
92
|
+
|
|
93
|
+
# pyenv
|
|
94
|
+
.python-version
|
|
95
|
+
|
|
96
|
+
# pipenv
|
|
97
|
+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
|
98
|
+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
|
99
|
+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
|
100
|
+
# install all needed dependencies.
|
|
101
|
+
#Pipfile.lock
|
|
102
|
+
|
|
103
|
+
# celery beat schedule file
|
|
104
|
+
celerybeat-schedule
|
|
105
|
+
|
|
106
|
+
# SageMath parsed files
|
|
107
|
+
*.sage.py
|
|
108
|
+
|
|
109
|
+
# Environments
|
|
110
|
+
.env
|
|
111
|
+
.venv
|
|
112
|
+
env/
|
|
113
|
+
venv/
|
|
114
|
+
ENV/
|
|
115
|
+
env.bak/
|
|
116
|
+
venv.bak/
|
|
117
|
+
|
|
118
|
+
# Spyder project settings
|
|
119
|
+
.spyderproject
|
|
120
|
+
.spyproject
|
|
121
|
+
|
|
122
|
+
# Rope project settings
|
|
123
|
+
.ropeproject
|
|
124
|
+
|
|
125
|
+
# mkdocs documentation
|
|
126
|
+
/site
|
|
127
|
+
|
|
128
|
+
# mypy
|
|
129
|
+
.mypy_cache/
|
|
130
|
+
.dmypy.json
|
|
131
|
+
dmypy.json
|
|
132
|
+
|
|
133
|
+
# Pyre type checker
|
|
134
|
+
.pyre/
|
|
135
|
+
|
|
136
|
+
### ElasticBeanstalk ###
|
|
137
|
+
.elasticbeanstalk/
|
|
138
|
+
|
|
139
|
+
### KomodoEdit ###
|
|
140
|
+
*.komodoproject
|
|
141
|
+
.komodotools
|
|
142
|
+
|
|
143
|
+
### OSX ###
|
|
144
|
+
# General
|
|
145
|
+
.DS_Store
|
|
146
|
+
.AppleDouble
|
|
147
|
+
.LSOverride
|
|
148
|
+
|
|
149
|
+
# Icon must end with two \r
|
|
150
|
+
Icon
|
|
151
|
+
|
|
152
|
+
# Thumbnails
|
|
153
|
+
._*
|
|
154
|
+
|
|
155
|
+
# Files that might appear in the root of a volume
|
|
156
|
+
.DocumentRevisions-V100
|
|
157
|
+
.fseventsd
|
|
158
|
+
.Spotlight-V100
|
|
159
|
+
.TemporaryItems
|
|
160
|
+
.Trashes
|
|
161
|
+
.VolumeIcon.icns
|
|
162
|
+
.com.apple.timemachine.donotpresent
|
|
163
|
+
|
|
164
|
+
# Directories potentially created on remote AFP share
|
|
165
|
+
.AppleDB
|
|
166
|
+
.AppleDesktop
|
|
167
|
+
Network Trash Folder
|
|
168
|
+
Temporary Items
|
|
169
|
+
.apdisk
|
|
170
|
+
|
|
171
|
+
### PyCharm ###
|
|
172
|
+
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm
|
|
173
|
+
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
|
174
|
+
|
|
175
|
+
# User-specific stuff
|
|
176
|
+
.idea/**/workspace.xml
|
|
177
|
+
.idea/**/tasks.xml
|
|
178
|
+
.idea/**/usage.statistics.xml
|
|
179
|
+
.idea/**/dictionaries
|
|
180
|
+
.idea/**/shelf
|
|
181
|
+
|
|
182
|
+
# Generated files
|
|
183
|
+
.idea/**/contentModel.xml
|
|
184
|
+
|
|
185
|
+
# Sensitive or high-churn files
|
|
186
|
+
.idea/**/dataSources/
|
|
187
|
+
.idea/**/dataSources.ids
|
|
188
|
+
.idea/**/dataSources.local.xml
|
|
189
|
+
.idea/**/sqlDataSources.xml
|
|
190
|
+
.idea/**/dynamic.xml
|
|
191
|
+
.idea/**/uiDesigner.xml
|
|
192
|
+
.idea/**/dbnavigator.xml
|
|
193
|
+
|
|
194
|
+
# Gradle
|
|
195
|
+
.idea/**/gradle.xml
|
|
196
|
+
.idea/**/libraries
|
|
197
|
+
|
|
198
|
+
# Gradle and Maven with auto-import
|
|
199
|
+
# When using Gradle or Maven with auto-import, you should exclude module files,
|
|
200
|
+
# since they will be recreated, and may cause churn. Uncomment if using
|
|
201
|
+
# auto-import.
|
|
202
|
+
# .idea/modules.xml
|
|
203
|
+
# .idea/*.iml
|
|
204
|
+
# .idea/modules
|
|
205
|
+
# *.iml
|
|
206
|
+
# *.ipr
|
|
207
|
+
|
|
208
|
+
# CMake
|
|
209
|
+
cmake-build-*/
|
|
210
|
+
|
|
211
|
+
# Mongo Explorer plugin
|
|
212
|
+
.idea/**/mongoSettings.xml
|
|
213
|
+
|
|
214
|
+
# File-based project format
|
|
215
|
+
*.iws
|
|
216
|
+
|
|
217
|
+
# IntelliJ
|
|
218
|
+
out/
|
|
219
|
+
|
|
220
|
+
# mpeltonen/sbt-idea plugin
|
|
221
|
+
.idea_modules/
|
|
222
|
+
|
|
223
|
+
# JIRA plugin
|
|
224
|
+
atlassian-ide-plugin.xml
|
|
225
|
+
|
|
226
|
+
# Cursive Clojure plugin
|
|
227
|
+
.idea/replstate.xml
|
|
228
|
+
|
|
229
|
+
# Crashlytics plugin (for Android Studio and IntelliJ)
|
|
230
|
+
com_crashlytics_export_strings.xml
|
|
231
|
+
crashlytics.properties
|
|
232
|
+
crashlytics-build.properties
|
|
233
|
+
fabric.properties
|
|
234
|
+
|
|
235
|
+
# Editor-based Rest Client
|
|
236
|
+
.idea/httpRequests
|
|
237
|
+
|
|
238
|
+
# Android studio 3.1+ serialized cache file
|
|
239
|
+
.idea/caches/build_file_checksums.ser
|
|
240
|
+
|
|
241
|
+
### PyCharm Patch ###
|
|
242
|
+
# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721
|
|
243
|
+
|
|
244
|
+
# *.iml
|
|
245
|
+
# modules.xml
|
|
246
|
+
# .idea/misc.xml
|
|
247
|
+
# *.ipr
|
|
248
|
+
|
|
249
|
+
# Sonarlint plugin
|
|
250
|
+
.idea/sonarlint
|
|
251
|
+
|
|
252
|
+
### Python ###
|
|
253
|
+
# Byte-compiled / optimized / DLL files
|
|
254
|
+
|
|
255
|
+
# C extensions
|
|
256
|
+
|
|
257
|
+
# Distribution / packaging
|
|
258
|
+
|
|
259
|
+
# PyInstaller
|
|
260
|
+
# Usually these files are written by a python script from a template
|
|
261
|
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
|
262
|
+
|
|
263
|
+
# Installer logs
|
|
264
|
+
|
|
265
|
+
# Unit test / coverage reports
|
|
266
|
+
|
|
267
|
+
# Translations
|
|
268
|
+
|
|
269
|
+
# Django stuff:
|
|
270
|
+
|
|
271
|
+
# Flask stuff:
|
|
272
|
+
|
|
273
|
+
# Scrapy stuff:
|
|
274
|
+
|
|
275
|
+
# Sphinx documentation
|
|
276
|
+
|
|
277
|
+
# PyBuilder
|
|
278
|
+
|
|
279
|
+
# Jupyter Notebook
|
|
280
|
+
|
|
281
|
+
# IPython
|
|
282
|
+
|
|
283
|
+
# pyenv
|
|
284
|
+
|
|
285
|
+
# pipenv
|
|
286
|
+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
|
287
|
+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
|
288
|
+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
|
289
|
+
# install all needed dependencies.
|
|
290
|
+
|
|
291
|
+
# celery beat schedule file
|
|
292
|
+
|
|
293
|
+
# SageMath parsed files
|
|
294
|
+
|
|
295
|
+
# Environments
|
|
296
|
+
|
|
297
|
+
# Spyder project settings
|
|
298
|
+
|
|
299
|
+
# Rope project settings
|
|
300
|
+
|
|
301
|
+
# mkdocs documentation
|
|
302
|
+
|
|
303
|
+
# mypy
|
|
304
|
+
|
|
305
|
+
# Pyre type checker
|
|
306
|
+
|
|
307
|
+
### Vim ###
|
|
308
|
+
# Swap
|
|
309
|
+
[._]*.s[a-v][a-z]
|
|
310
|
+
[._]*.sw[a-p]
|
|
311
|
+
[._]s[a-rt-v][a-z]
|
|
312
|
+
[._]ss[a-gi-z]
|
|
313
|
+
[._]sw[a-p]
|
|
314
|
+
|
|
315
|
+
# Session
|
|
316
|
+
Session.vim
|
|
317
|
+
Sessionx.vim
|
|
318
|
+
|
|
319
|
+
# Temporary
|
|
320
|
+
.netrwhist
|
|
321
|
+
*~
|
|
322
|
+
# Auto-generated tag files
|
|
323
|
+
tags
|
|
324
|
+
# Persistent undo
|
|
325
|
+
[._]*.un~
|
|
326
|
+
|
|
327
|
+
### VisualStudioCode ###
|
|
328
|
+
.vscode/*
|
|
329
|
+
!.vscode/settings.json
|
|
330
|
+
!.vscode/tasks.json
|
|
331
|
+
!.vscode/launch.json
|
|
332
|
+
!.vscode/extensions.json
|
|
333
|
+
|
|
334
|
+
### VisualStudioCode Patch ###
|
|
335
|
+
# Ignore all local history of files
|
|
336
|
+
.history
|
|
337
|
+
|
|
338
|
+
# End of https://www.gitignore.io/api/vim,osx,python,django,pycharm,komodoedit,elasticbeanstalk,visualstudiocode
|
|
339
|
+
|
|
340
|
+
# Ignore custom Docker compose DB data
|
|
341
|
+
.db
|
|
342
|
+
|
|
343
|
+
# Ignore PyCharm
|
|
344
|
+
.idea/
|
|
345
|
+
|
|
346
|
+
# Ignore local poetry settings
|
|
347
|
+
poetry.toml
|
|
348
|
+
|
|
349
|
+
# Ignore PyCharm idea folder
|
|
350
|
+
.idea
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
Copyright (c) 2026, django-sqlitetrigger authors and community
|
|
2
|
+
All rights reserved.
|
|
3
|
+
|
|
4
|
+
Redistribution and use in source and binary forms, with or without
|
|
5
|
+
modification, are permitted provided that the following conditions are met:
|
|
6
|
+
|
|
7
|
+
* Redistributions of source code must retain the above copyright
|
|
8
|
+
notice, this list of conditions and the following disclaimer.
|
|
9
|
+
|
|
10
|
+
* Redistributions in binary form must reproduce the above copyright
|
|
11
|
+
notice, this list of conditions and the following disclaimer in the
|
|
12
|
+
documentation and/or other materials provided with the distribution.
|
|
13
|
+
|
|
14
|
+
* Neither the name of the copyright holder nor the names of its
|
|
15
|
+
contributors may be used to endorse or promote products derived from
|
|
16
|
+
this software without specific prior written permission.
|
|
17
|
+
|
|
18
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
|
19
|
+
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
20
|
+
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
21
|
+
DISCLAIMED. IN NO EVENT SHALL AMBITION BE LIABLE FOR ANY
|
|
22
|
+
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
23
|
+
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
24
|
+
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
|
25
|
+
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
26
|
+
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
27
|
+
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: django-sqlitetrigger
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: SQLite trigger support integrated with Django models.
|
|
5
|
+
License-Expression: BSD-3-Clause
|
|
6
|
+
License-File: LICENSE
|
|
7
|
+
Requires-Python: >=3.14
|
|
8
|
+
Requires-Dist: django>=6.0
|
|
9
|
+
Provides-Extra: dev
|
|
10
|
+
Requires-Dist: django-dynamic-fixture>=4.0; extra == 'dev'
|
|
11
|
+
Requires-Dist: pytest-django>=4.11; extra == 'dev'
|
|
12
|
+
Requires-Dist: pytest>=9.0; extra == 'dev'
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
|
|
15
|
+
# django-sqlitetrigger
|
|
16
|
+
|
|
17
|
+
SQLite trigger support integrated with Django models.
|
|
18
|
+
|
|
19
|
+
Inspired by [django-pgtrigger](https://github.com/AmbitionEng/django-pgtrigger), this package provides declarative trigger support for SQLite-backed Django projects. I'm very grateful for the existence of `django-pgtrigger`, without which I don't think this package could exist.
|
|
20
|
+
|
|
21
|
+
## AI disclosure
|
|
22
|
+
|
|
23
|
+
To be super clear: this project was built by prompting GitHub Copilot to modify `django-pgtrigger` for use with SQLite. I was in the process of doing something very similar manually, when it dawned on me that this is the ideal use for today's agentic AI coding systems.
|
|
24
|
+
|
|
25
|
+
The resulting package is usable by me for my purposes. As the license notes, it comes with no warranty or expectation that it'll solve your problems.
|
|
26
|
+
|
|
27
|
+
## Quick start
|
|
28
|
+
|
|
29
|
+
Install and add `sqlitetrigger` to `INSTALLED_APPS`:
|
|
30
|
+
|
|
31
|
+
```python
|
|
32
|
+
INSTALLED_APPS = [
|
|
33
|
+
"sqlitetrigger",
|
|
34
|
+
# ...
|
|
35
|
+
]
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Add triggers to your models:
|
|
39
|
+
|
|
40
|
+
```python
|
|
41
|
+
import sqlitetrigger
|
|
42
|
+
|
|
43
|
+
class ProtectedModel(models.Model):
|
|
44
|
+
"""This model cannot be deleted!"""
|
|
45
|
+
|
|
46
|
+
class Meta:
|
|
47
|
+
triggers = [
|
|
48
|
+
sqlitetrigger.Protect(name="protect_deletes", operation=sqlitetrigger.Delete)
|
|
49
|
+
]
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Install triggers:
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
python manage.py sqlitetrigger install
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Built-in triggers
|
|
59
|
+
|
|
60
|
+
- **Protect** — prevent insert, update, or delete operations
|
|
61
|
+
- **ReadOnly** — prevent changes to specific fields
|
|
62
|
+
- **SoftDelete** — intercept deletes and set a field instead
|
|
63
|
+
- **FSM** — enforce valid field state transitions
|
|
64
|
+
|
|
65
|
+
## Running tests
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
uv run pytest
|
|
69
|
+
```
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# django-sqlitetrigger
|
|
2
|
+
|
|
3
|
+
SQLite trigger support integrated with Django models.
|
|
4
|
+
|
|
5
|
+
Inspired by [django-pgtrigger](https://github.com/AmbitionEng/django-pgtrigger), this package provides declarative trigger support for SQLite-backed Django projects. I'm very grateful for the existence of `django-pgtrigger`, without which I don't think this package could exist.
|
|
6
|
+
|
|
7
|
+
## AI disclosure
|
|
8
|
+
|
|
9
|
+
To be super clear: this project was built by prompting GitHub Copilot to modify `django-pgtrigger` for use with SQLite. I was in the process of doing something very similar manually, when it dawned on me that this is the ideal use for today's agentic AI coding systems.
|
|
10
|
+
|
|
11
|
+
The resulting package is usable by me for my purposes. As the license notes, it comes with no warranty or expectation that it'll solve your problems.
|
|
12
|
+
|
|
13
|
+
## Quick start
|
|
14
|
+
|
|
15
|
+
Install and add `sqlitetrigger` to `INSTALLED_APPS`:
|
|
16
|
+
|
|
17
|
+
```python
|
|
18
|
+
INSTALLED_APPS = [
|
|
19
|
+
"sqlitetrigger",
|
|
20
|
+
# ...
|
|
21
|
+
]
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Add triggers to your models:
|
|
25
|
+
|
|
26
|
+
```python
|
|
27
|
+
import sqlitetrigger
|
|
28
|
+
|
|
29
|
+
class ProtectedModel(models.Model):
|
|
30
|
+
"""This model cannot be deleted!"""
|
|
31
|
+
|
|
32
|
+
class Meta:
|
|
33
|
+
triggers = [
|
|
34
|
+
sqlitetrigger.Protect(name="protect_deletes", operation=sqlitetrigger.Delete)
|
|
35
|
+
]
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Install triggers:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
python manage.py sqlitetrigger install
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Built-in triggers
|
|
45
|
+
|
|
46
|
+
- **Protect** — prevent insert, update, or delete operations
|
|
47
|
+
- **ReadOnly** — prevent changes to specific fields
|
|
48
|
+
- **SoftDelete** — intercept deletes and set a field instead
|
|
49
|
+
- **FSM** — enforce valid field state transitions
|
|
50
|
+
|
|
51
|
+
## Running tests
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
uv run pytest
|
|
55
|
+
```
|
|
File without changes
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from django.contrib import admin
|
|
2
|
+
|
|
3
|
+
from example.models import Book
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class BookAdmin(admin.ModelAdmin):
|
|
7
|
+
model = Book
|
|
8
|
+
fields = ('title', 'pages', 'sheets')
|
|
9
|
+
readonly_fields = ('sheets',)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
# Register your models here.
|
|
13
|
+
admin.site.register(Book, BookAdmin)
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""
|
|
2
|
+
ASGI config for example 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/6.0/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', 'example.settings')
|
|
15
|
+
|
|
16
|
+
application = get_asgi_application()
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# Generated by Django 6.0.2 on 2026-02-09 20:47
|
|
2
|
+
|
|
3
|
+
from django.db import migrations, models
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Migration(migrations.Migration):
|
|
7
|
+
|
|
8
|
+
initial = True
|
|
9
|
+
|
|
10
|
+
dependencies = [
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
operations = [
|
|
14
|
+
migrations.CreateModel(
|
|
15
|
+
name='Book',
|
|
16
|
+
fields=[
|
|
17
|
+
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
18
|
+
('title', models.CharField(max_length=100)),
|
|
19
|
+
('pages', models.PositiveIntegerField()),
|
|
20
|
+
('sheets', models.PositiveIntegerField(editable=False)),
|
|
21
|
+
],
|
|
22
|
+
),
|
|
23
|
+
]
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Generated by Django 6.0.2 on 2026-02-10 14:57
|
|
2
|
+
|
|
3
|
+
import sqlitetrigger.migrations
|
|
4
|
+
from django.db import migrations
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Migration(migrations.Migration):
|
|
8
|
+
|
|
9
|
+
dependencies = [
|
|
10
|
+
('example', '0001_initial'),
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
operations = [
|
|
14
|
+
sqlitetrigger.migrations.AddTrigger(
|
|
15
|
+
model_name='book',
|
|
16
|
+
trigger=sqlitetrigger.migrations.CompiledTrigger(drop_sql=['DROP TRIGGER IF EXISTS sqlitetrigger_example_book_sheets_update;'], install_sql=['CREATE TRIGGER sqlitetrigger_example_book_sheets_update\n AFTER UPDATE ON example_book\n FOR EACH ROW\nBEGIN\n UPDATE example_book SET sheets = new.pages / 2 WHERE id = new.id;\nEND;'], name='sheets_update'),
|
|
17
|
+
),
|
|
18
|
+
]
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Generated by Django 6.0.2 on 2026-02-10 15:06
|
|
2
|
+
|
|
3
|
+
from django.db import migrations, models
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Migration(migrations.Migration):
|
|
7
|
+
|
|
8
|
+
dependencies = [
|
|
9
|
+
('example', '0002_book_sheets_update'),
|
|
10
|
+
]
|
|
11
|
+
|
|
12
|
+
operations = [
|
|
13
|
+
migrations.AlterField(
|
|
14
|
+
model_name='book',
|
|
15
|
+
name='title',
|
|
16
|
+
field=models.CharField(max_length=200),
|
|
17
|
+
),
|
|
18
|
+
]
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Generated by Django 6.0.2 on 2026-02-10 15:21
|
|
2
|
+
|
|
3
|
+
import sqlitetrigger.migrations
|
|
4
|
+
from django.db import migrations
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Migration(migrations.Migration):
|
|
8
|
+
|
|
9
|
+
dependencies = [
|
|
10
|
+
('example', '0003_alter_book_title'),
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
operations = [
|
|
14
|
+
sqlitetrigger.migrations.RemoveTrigger(
|
|
15
|
+
model_name='book',
|
|
16
|
+
name='sheets_update',
|
|
17
|
+
),
|
|
18
|
+
sqlitetrigger.migrations.AddTrigger(
|
|
19
|
+
model_name='book',
|
|
20
|
+
trigger=sqlitetrigger.migrations.CompiledTrigger(drop_sql=['DROP TRIGGER IF EXISTS sqlitetrigger_example_book_sheets_update;'], install_sql=['CREATE TRIGGER sqlitetrigger_example_book_sheets_update\n AFTER UPDATE ON example_book\n FOR EACH ROW\nBEGIN\n UPDATE example_book SET sheets = ceil(new.pages / 2.0) WHERE id = new.id;\nEND;'], name='sheets_update'),
|
|
21
|
+
),
|
|
22
|
+
]
|
|
File without changes
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
from django.db import models
|
|
2
|
+
|
|
3
|
+
from sqlitetrigger import Trigger, After, Update, Func
|
|
4
|
+
|
|
5
|
+
# Create your models here.
|
|
6
|
+
class Book(models.Model):
|
|
7
|
+
class Meta:
|
|
8
|
+
triggers = [
|
|
9
|
+
Trigger(
|
|
10
|
+
name='sheets_update',
|
|
11
|
+
when=After,
|
|
12
|
+
operation=Update,
|
|
13
|
+
func=Func("UPDATE {meta.db_table} SET {columns.sheets} = ceil(new.{columns.pages} / 2.0) WHERE {columns.id} = new.{columns.id};"),
|
|
14
|
+
)
|
|
15
|
+
]
|
|
16
|
+
title = models.CharField(max_length=200)
|
|
17
|
+
pages = models.PositiveIntegerField()
|
|
18
|
+
sheets = models.PositiveIntegerField(editable=False)
|
|
19
|
+
|
|
20
|
+
def __str__(self):
|
|
21
|
+
return self.title
|