|
1 | 1 | # django-cf
|
2 |
| -Django database engine for Cloudflare D1 and Durable Objects |
| 2 | +django-cf is a package that integrates Django with Cloudflare products |
| 3 | + |
| 4 | +Integrations: |
| 5 | + - Cloudflare D1 |
| 6 | + - Cloudflare Workers |
3 | 7 |
|
4 | 8 | ## Installation
|
5 | 9 |
|
6 | 10 | ```bash
|
7 | 11 | pip install django-cf
|
8 | 12 | ```
|
9 | 13 |
|
10 |
| -## Using with D1 |
| 14 | +## Cloudflare D1 |
11 | 15 |
|
12 |
| -The D1 engine uses the HTTP api directly from Cloudflare, meaning you only need to create a new D1 database, then |
13 |
| -create an API token with `D1 read` permission, and you are good to go! |
| 16 | +Cloudflare D1 doesn't support transactions, meaning all execute queries are final and rollbacks are not available. |
| 17 | + |
| 18 | +A simple tutorial is [available here](https://massadas.com/posts/django-meets-cloudflare-d1/) for you to read. |
14 | 19 |
|
15 |
| -But using an HTTP endpoint for executing queries one by one is very slow, and currently there is no way to accelerate |
16 |
| -it, until a websocket endpoint is available. |
17 | 20 |
|
18 |
| -The HTTP api doesn't support transactions, meaning all execute queries are final and rollbacks are not available. |
| 21 | +### D1 Binding |
| 22 | + |
| 23 | +You can now deploy Django into a Cloudflare Python Worker, and in that environment, D1 is available as a Binding for |
| 24 | +faster queries. |
19 | 25 |
|
20 | 26 | ```python
|
21 | 27 | DATABASES = {
|
22 | 28 | 'default': {
|
23 |
| - 'ENGINE': 'django_d1', |
| 29 | + 'ENGINE': 'django_cf.d1_binding', |
| 30 | + 'CLOUDFLARE_BINDING': 'DB', |
| 31 | + } |
| 32 | +} |
| 33 | +``` |
| 34 | + |
| 35 | +### D1 API |
| 36 | + |
| 37 | +The D1 engine uses the HTTP api directly from Cloudflare, meaning you only need to create a new D1 database, then |
| 38 | +create an API token with `D1 read` and `D1 write` permission, and you are good to go! |
| 39 | + |
| 40 | +But using an HTTP endpoint for executing queries one by one is very slow, and currently there is no way to speed up |
| 41 | +it. |
| 42 | + |
| 43 | +```python |
| 44 | +DATABASES = { |
| 45 | + 'default': { |
| 46 | + 'ENGINE': 'django_cf.d1_api', |
24 | 47 | 'CLOUDFLARE_DATABASE_ID': '<database_id>',
|
25 | 48 | 'CLOUDFLARE_ACCOUNT_ID': '<account_id>',
|
26 | 49 | 'CLOUDFLARE_TOKEN': '<token>',
|
27 | 50 | }
|
28 | 51 | }
|
29 | 52 | ```
|
30 | 53 |
|
31 |
| -The Cloudflare token requires D1 Edit permissions. |
| 54 | +## Cloudflare Workers |
32 | 55 |
|
33 |
| -A simple tutorial is [available here](https://massadas.com/posts/django-meets-cloudflare-d1/) for you to read. |
| 56 | +django-cf includes an adapter that allows you to run Django inside Cloudflare Workers named `DjangoCFAdapter` |
| 57 | + |
| 58 | +Suggested project structure |
| 59 | +``` |
| 60 | +root |
| 61 | + |-> src/ |
| 62 | + |-> src/manage.py |
| 63 | + |-> src/worker.py <-- Wrangler entrypoint |
| 64 | + |-> src/your-apps-here/ |
| 65 | + |-> src/vendor/... <-- Project dependencies, details bellow |
| 66 | + |-> vendor.txt |
| 67 | + |-> wrangler.jsonc |
| 68 | +``` |
| 69 | + |
| 70 | +`vendor.txt` |
| 71 | +```txt |
| 72 | +django==5.1.2 |
| 73 | +django-cf |
| 74 | +tzdata |
| 75 | +``` |
| 76 | + |
| 77 | +`wrangler.jsonc` |
| 78 | +```jsonc |
| 79 | +{ |
| 80 | + "name": "django-on-workers", |
| 81 | + "main": "src/worker.py", |
| 82 | + "compatibility_flags": [ |
| 83 | + "python_workers_20250116", |
| 84 | + "python_workers" |
| 85 | + ], |
| 86 | + "compatibility_date": "2025-04-10", |
| 87 | + "assets": { |
| 88 | + "directory": "./staticfiles/" |
| 89 | + }, |
| 90 | + "rules": [ |
| 91 | + { |
| 92 | + "globs": [ |
| 93 | + "vendor/**/*.py", |
| 94 | + "vendor/**/*.mo", |
| 95 | + "vendor/tzdata/**/", |
| 96 | + ], |
| 97 | + "type": "Data", |
| 98 | + "fallthrough": true |
| 99 | + } |
| 100 | + ], |
| 101 | + "d1_databases": [ |
| 102 | + { |
| 103 | + "binding": "DB", |
| 104 | + "database_name": "my-django-db", |
| 105 | + "database_id": "924e612f-6293-4a3f-be66-cce441957b03", |
| 106 | + } |
| 107 | + ], |
| 108 | + "observability": { |
| 109 | + "enabled": true |
| 110 | + } |
| 111 | +} |
| 112 | +``` |
34 | 113 |
|
35 |
| -## Using with Durable Objects |
| 114 | +`src/worker.py` |
| 115 | +```python |
| 116 | +from django_cf import DjangoCFAdapter |
36 | 117 |
|
37 |
| -The DO engine, requires you to deploy [workers-dbms](https://github.com/G4brym/workers-dbms) on your cloudflare account. |
38 |
| -This engine uses a websocket endpoint to keep the connection alive, meaning sql queries are executed way faster. |
| 118 | +async def on_fetch(request, env): |
| 119 | + from app.wsgi import application # Update acording to your project structure |
| 120 | + adapter = DjangoCFAdapter(application) |
39 | 121 |
|
40 |
| -workers-dbms have an experimental transaction support, everything should be working out of the box, but you should keep |
41 |
| -an eye out for weird issues and report them back. |
| 122 | + return adapter.handle_request(request) |
42 | 123 |
|
| 124 | +``` |
43 | 125 |
|
| 126 | +Then run this command to vendor your dependencies: |
| 127 | +```bash |
| 128 | +pip install -t src/vendor -r vendor.txt |
| 129 | +``` |
| 130 | + |
| 131 | +To bundle static assets with your worker, add this line to your `settings.py`, this will place the assets outside the src folder |
44 | 132 | ```python
|
45 |
| -DATABASES = { |
46 |
| - 'default': { |
47 |
| - 'ENGINE': 'django_dbms', |
48 |
| - 'WORKERS_DBMS_ENDPOINT': '<websocket_endpoint>', # This should start with wss:// |
49 |
| - 'WORKERS_DBMS_ACCESS_ID': '<access_id>', # Optional, but highly recommended! |
50 |
| - 'WORKERS_DBMS_ACCESS_SECRET': '<access_secret>', # Optional, but highly recommended! |
51 |
| - } |
52 |
| -} |
| 133 | +STATIC_URL = 'static/' |
| 134 | +STATIC_ROOT = BASE_DIR.parent.joinpath('staticfiles').joinpath('static') |
53 | 135 | ```
|
54 | 136 |
|
| 137 | +And this command generate the static assets: |
| 138 | +```bash |
| 139 | +python src/manage.py collectstatic |
| 140 | +``` |
| 141 | + |
| 142 | +Now deploy your worker |
| 143 | +```bash |
| 144 | +npx wrangler deploy |
| 145 | +``` |
| 146 | + |
| 147 | +### Running migrations and other commands |
| 148 | +In the ideal setup, your application will have two settings, one for production and another for development. |
| 149 | + |
| 150 | +- The production one, will connect to D1 via using the binding, as this is way faster. |
| 151 | +- The development one, will connect using the D1 API. |
| 152 | + |
| 153 | +Using this setup, you can apply the migrations from your local machine. |
| 154 | + |
| 155 | +In case that is not enought for you, here is a snippet that allows you to apply D1 migrations using a deployed worker: |
| 156 | + |
| 157 | +Just add these new routes to your `urls.py`: |
| 158 | + |
| 159 | +```python |
| 160 | +from django.contrib import admin |
| 161 | +from django.contrib.auth import get_user_model; |
| 162 | +from django.http import JsonResponse |
| 163 | +from django.urls import path |
| 164 | + |
| 165 | +def create_admin(request): |
| 166 | + User = get_user_model(); |
| 167 | + User.objects.create_superuser( 'admin', '[email protected]', 'password') |
| 168 | + return JsonResponse({"user": "ok"}) |
| 169 | + |
| 170 | +def migrate(request): |
| 171 | + from django.core.management import execute_from_command_line |
| 172 | + execute_from_command_line(["manage.py", "migrate"]) |
| 173 | + |
| 174 | + return JsonResponse({"migrations": "ok"}) |
| 175 | + |
| 176 | +urlpatterns = [ |
| 177 | + path('create-admin', create_admin), |
| 178 | + path('migrate', migrate), |
| 179 | + path('admin/', admin.site.urls), |
| 180 | +] |
| 181 | +``` |
| 182 | + |
| 183 | +You may now call your worker to apply all missing migrations, ex: `https://django-on-workers.{username}.workers.dev/migrate` |
55 | 184 |
|
56 | 185 | ## Limitations
|
57 | 186 |
|
58 | 187 | When using D1 engine, queries are expected to be slow, and transactions are disabled.
|
59 | 188 |
|
| 189 | +A lot of query features are additionally disabled, for example inline sql functions, used extensively inside Django Admin |
| 190 | + |
60 | 191 | Read all Django limitations for SQLite [databases here](https://docs.djangoproject.com/en/5.0/ref/databases/#sqlite-notes).
|
0 commit comments