Skip to content

Commit 63d4ac7

Browse files
committed
Merge branch 'remove-async'
2 parents 5970fe4 + b2c5c4e commit 63d4ac7

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

59 files changed

+816
-917
lines changed

.pre-commit-config.yaml

+2-2
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ exclude: "^docs/|.idea|devcontainer.json|tests/fixtures|tests/.*snapshots/.*\\.(
22
default_stages: [pre-commit]
33

44
default_language_version:
5-
python: python3.12
5+
python: python3.13
66

77
repos:
88
- repo: https://github.com/pre-commit/pre-commit-hooks
@@ -64,7 +64,7 @@ repos:
6464

6565
- repo: https://github.com/astral-sh/ruff-pre-commit
6666
# Ruff version.
67-
rev: v0.9.10
67+
rev: v0.11.2
6868
hooks:
6969
- id: ruff
7070
args: [--fix, --preview]

CHANGELOG.md

+12
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,18 @@
22

33
## Unreleased
44

5+
## 25.04.1
6+
7+
- Remove async functions to simplify the code.
8+
- We now run with `gunicorn` instead of `daphne` in production. We use 4 `gunicorn` workers.
9+
- Switch to Python 3.13
10+
- Correct title for feeds without sections in feeds admin.
11+
12+
## 25.03.2
13+
14+
- Improve user Django admin page.
15+
- Prevent errors with empty slugs.
16+
517
## 25.03.1
618

719
- Can filter tags in tags admin.

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ You will have to start Django with the provided run target.
2828

2929
### Using Pycharm
3030

31-
By default, everything is set up to develop locally with Pycharm. So you will need docker (for the database), [uv](https://docs.astral.sh/uv/), Python 3.12 and nodeJS 20+ installed for this to work.
31+
By default, everything is set up to develop locally with Pycharm. So you will need docker (for the database), [uv](https://docs.astral.sh/uv/), Python 3.13 and nodeJS 20+ installed for this to work.
3232
Django will be started automatically.
3333
On the first run, you must run `npm install` to install a few JS deps and `uv run pre-commit install --hook-type pre-commit --hook-type pre-push` to configure `pre-commit`.
3434

config/settings.py

+30-31
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import asgiref
99
import django
1010
import environ
11+
from csp.constants import NONCE, NONE, SELF, STRICT_DYNAMIC, UNSAFE_INLINE
1112
from django.contrib.messages import constants as messages
1213
from django.utils.translation import gettext_lazy as _
1314
from template_partials.apps import wrap_loaders
@@ -101,7 +102,6 @@
101102
# APPS
102103
# ------------------------------------------------------------------------------
103104
DJANGO_APPS = [
104-
"daphne",
105105
"django.contrib.auth",
106106
"django.contrib.contenttypes",
107107
"django.contrib.sessions",
@@ -332,30 +332,31 @@
332332
# https://django-csp.readthedocs.io/en/latest/configuration.html
333333
# https://content-security-policy.com/
334334
# https://csp-evaluator.withgoogle.com/
335-
CSP_DEFAULT_SRC = ("'self'",)
336-
CSP_SCRIPT_SRC = ("'strict-dynamic'", "'unsafe-inline'", "https:")
337-
CSP_SCRIPT_SRC_ATTR = None
338-
CSP_SCRIPT_SRC_ELEM = None
339-
CSP_IMG_SRC = ("'self'", "data:")
340-
CSP_OBJECT_SRC = ("'none'",)
341-
CSP_MEDIA_SRC = ("'self'",)
342-
CSP_FRAME_SRC = ("'none'",)
343-
CSP_FONT_SRC = ("'self'",)
344-
CSP_CONNECT_SRC = env.tuple("CSP_CONNECT_SRC", default=("'self'",))
345-
CSP_STYLE_SRC = ("'strict-dynamic'", "'unsafe-inline'", "https:")
346-
CSP_STYLE_SRC_ATTR = None
347-
CSP_STYLE_SRC_ELEM = None
348-
CSP_BASE_URI = ("'none'",)
349-
CSP_FRAME_ANCESTORS = ("'none'",)
350-
CSP_FORM_ACTION = ("'self'",)
351-
CSP_MANIFEST_SRC = ("'self'",)
352-
CSP_WORKER_SRC = ("'self'",)
353-
CSP_PLUGIN_TYPES = None
354-
CSP_REQUIRE_SRI_FOR = None
355-
CSP_INCLUDE_NONCE_IN = ("script-src", "style-src")
356-
# Those are forced to true in production
357-
CSP_UPGRADE_INSECURE_REQUESTS = IS_PRODUCTION
358-
CSP_BLOCK_ALL_MIXED_CONTENT = IS_PRODUCTION
335+
CONTENT_SECURITY_POLICY = {
336+
"DIRECTIVES": {
337+
"default-src": (SELF,),
338+
"script-src": (STRICT_DYNAMIC, UNSAFE_INLINE, "https:", NONCE),
339+
"script-src-attr": None,
340+
"script-src-elem": None,
341+
"img-src": (SELF, "data:"),
342+
"object-src": (NONE,),
343+
"media-src": (SELF,),
344+
"frame-src": (NONE,),
345+
"font-src": (SELF,),
346+
"connect-src": env.tuple("CSP_CONNECT_SRC", default=(SELF,)),
347+
"style-src": (STRICT_DYNAMIC, UNSAFE_INLINE, "https:", NONCE),
348+
"style-src-attr": None,
349+
"style-src-elem": None,
350+
"base-uri": (NONE,),
351+
"child-src": (NONE,),
352+
"frame-ancestors": (NONE,),
353+
"form-action": (SELF,),
354+
"manifest-src": (SELF,),
355+
"worker-src": (SELF,),
356+
"require-sri-for": (NONE,),
357+
"upgrade-insecure-requests": IS_PRODUCTION,
358+
},
359+
}
359360

360361

361362
# CORS
@@ -490,11 +491,9 @@
490491
# ------------------------------------------------------------------------------
491492
ACCOUNT_ALLOW_REGISTRATION = env.bool("DJANGO_ACCOUNT_ALLOW_REGISTRATION", True)
492493
# https://django-allauth.readthedocs.io/en/latest/configuration.html
493-
ACCOUNT_AUTHENTICATION_METHOD = "email"
494-
# https://django-allauth.readthedocs.io/en/latest/configuration.html
495-
ACCOUNT_EMAIL_REQUIRED = True
494+
ACCOUNT_LOGIN_METHODS = {"email"}
496495
# https://django-allauth.readthedocs.io/en/latest/configuration.html
497-
ACCOUNT_USERNAME_REQUIRED = False
496+
ACCOUNT_SIGNUP_FIELDS = ["email*", "password1*", "password2*"]
498497
# https://django-allauth.readthedocs.io/en/latest/configuration.html
499498
ACCOUNT_USER_MODEL_USERNAME_FIELD = None
500499
# https://django-allauth.readthedocs.io/en/latest/configuration.html
@@ -515,7 +514,7 @@
515514
# django-version-checks (https://pypi.org/project/django-version-checks/)
516515
# ------------------------------------------------------------------------------
517516
VERSION_CHECKS = {
518-
"python": "==3.12.*",
517+
"python": "==3.13.*",
519518
"postgresql": ">=16",
520519
}
521520

@@ -623,7 +622,7 @@ def before_send_to_sentry(event, hint):
623622
# ------------------------------------------------------------------------------
624623
# See https://django-ninja.dev/reference/settings/
625624
NINJA_PAGINATION_MAX_LIMIT = 500
626-
NINJA_PAGINATION_CLASS = "legadilo.utils.pagination.LimitOffsetPagination"
625+
NINJA_PAGINATION_CLASS = "ninja.pagination.LimitOffsetPagination"
627626

628627

629628
# Legadilo's specific stuff...

devops/compose/local/django/Dockerfile

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
FROM python:3.12-slim-bookworm
1+
FROM python:3.13-slim-bookworm
22

33
ARG BUILD_ENVIRONMENT=local
44
ARG APP_HOME=/app

devops/compose/local/docs/Dockerfile

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
FROM python:3.12-slim-bookworm
1+
FROM python:3.13-slim-bookworm
22

33
ARG BUILD_ENVIRONMENT
44
ENV PYTHONUNBUFFERED 1

devops/compose/production/django/Dockerfile

+2-2
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ COPY ./package-lock.json /
55

66
RUN cd / && npm install
77

8-
FROM python:3.12-slim-bookworm AS python-builder
8+
FROM python:3.13-slim-bookworm AS python-builder
99

1010
ARG BUILD_ENVIRONMENT=production
1111
ARG APP_HOME=/app
@@ -46,7 +46,7 @@ RUN chmod +x /setup-django.sh
4646
RUN /setup-django.sh
4747

4848

49-
FROM python:3.12-slim-bookworm
49+
FROM python:3.13-slim-bookworm
5050

5151
ARG BUILD_ENVIRONMENT=production
5252
ARG APP_HOME=/app

devops/compose/production/django/start.sh

+6-3
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,22 @@ set -o errexit
44
set -o pipefail
55
set -o nounset
66

7+
readonly SERVER_PORT=8000
8+
readonly NB_GUNICORN_WORKERS=4
9+
readonly GUNICORN_REQUEST_TIMEOUT=60
10+
711
python manage.py createcachetable
812
python manage.py migrate
913

1014
cmd="${1:-server}"
1115

1216
case "${cmd}" in
1317
server)
14-
SERVER_PORT=8000
1518
echo "Starting server on port ${SERVER_PORT}"
1619
if [[ "${BUILD_ENV}" == "local" ]]; then
17-
exec python manage.py runserver 0.0.0.0:${SERVER_PORT}
20+
exec python manage.py runserver "0.0.0.0:${SERVER_PORT}"
1821
else
19-
exec daphne config.asgi:application --bind 0.0.0.0 --port ${SERVER_PORT} --no-server-name --verbosity 1
22+
exec gunicorn config.wsgi:application --bind "0.0.0.0:${SERVER_PORT}" --timeout ${GUNICORN_REQUEST_TIMEOUT} --workers ${NB_GUNICORN_WORKERS}
2023
fi
2124
;;
2225
cron)
+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# 9 - Remove async for now
2+
3+
* **Date:** 2025-04-01
4+
* **Status:** Accepted
5+
* Partially supersedes <project:./0000-project-setup.md> and <project:./0005-deployment.md>
6+
7+
## Context
8+
9+
After using async, it brings complexity for performance improvements we need yet.
10+
The main issue being that we cannot yet use transactions in an async context (see [this issue](https://code.djangoproject.com/ticket/33882)).
11+
This results in extra complexities with usages of `sync_to_async` and `async_to_sync` to switch from one context to another.
12+
13+
Since we do multiple database operations, I think transaction must be used to always be in a valid state.
14+
Or to state it differently: since we are far from performance problems yet, it’s best to just use transactions for consistency than to remove them to have cleaner async support or have the complexity that comes with sync to async conversions.
15+
16+
Since we do lots of async requests, I still think async makes sense.
17+
Just not now.
18+
19+
20+
## Decisions
21+
22+
Simply the code as much as we can and remove async support for now.
23+
It will impact many files, but it should be relatively straightforward.
24+
25+
26+
## Consequences
27+
28+
- We may have to re-add async later on.
29+
- Lots of code changes.
30+
- Must have an export command to export big files.
31+
- Use `gunicorn` to run the application in production:
32+
- It’s kind of a default choice for this, and I’ve used it in the past with great success.
33+
- Create an incoming HTTP request timeout. Set it to 60s to allow for slow search.
34+
- Use 4 workers: from my experience, it’s a good number.
35+
- We may need to allow for incoming request timeout and number of workers to be configured.

justfile

+1-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ release:
2222
echo "Creating version ${new_tag} Press enter to accept."
2323
read -r
2424

25-
{{docker-cmd}} pull python:3.12-slim-bookworm
25+
{{docker-cmd}} pull python:3.13-slim-bookworm
2626
{{docker-compose-cmd}} -f production.yml build --build-arg "VERSION=${new_tag}" django
2727
{{docker-cmd}} image tag legadilo_production_django:latest "rg.fr-par.scw.cloud/legadilo/legadilo-django:${new_tag}"
2828
{{docker-cmd}} image tag legadilo_production_django:latest rg.fr-par.scw.cloud/legadilo/legadilo-django:latest

legadilo/constants.py

+1
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,4 @@
1515
# along with this program. If not, see <http://www.gnu.org/licenses/>.
1616

1717
SEARCHED_TEXT_MIN_LENGTH = 3
18+
MAX_PARALLEL_CONNECTIONS = 10

0 commit comments

Comments
 (0)