glitchtip: 5.2.1 -> 6.0.9 -> 6.1.4 (#497264)
This commit is contained in:
@@ -45,19 +45,6 @@ in
|
||||
default = "glitchtip";
|
||||
};
|
||||
|
||||
listenAddress = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
description = "The address to listen on.";
|
||||
default = "127.0.0.1";
|
||||
example = "0.0.0.0";
|
||||
};
|
||||
|
||||
port = lib.mkOption {
|
||||
type = lib.types.port;
|
||||
description = "The port to listen on.";
|
||||
default = 8000;
|
||||
};
|
||||
|
||||
stateDir = lib.mkOption {
|
||||
type = lib.types.path;
|
||||
description = "State directory of glitchtip.";
|
||||
@@ -66,7 +53,7 @@ in
|
||||
|
||||
settings = lib.mkOption {
|
||||
description = ''
|
||||
Configuration of GlitchTip. See <https://glitchtip.com/documentation/install#configuration> for more information.
|
||||
Configuration of GlitchTip. See <https://glitchtip.com/documentation/install#configuration> for more information and required settings.
|
||||
'';
|
||||
default = { };
|
||||
defaultText = lib.literalExpression ''
|
||||
@@ -74,8 +61,14 @@ in
|
||||
DEBUG = 0;
|
||||
DEBUG_TOOLBAR = 0;
|
||||
DATABASE_URL = lib.mkIf config.services.glitchtip.database.createLocally "postgresql://@/glitchtip";
|
||||
GLITCHTIP_DOMAIN = lib.mkIf config.services.glitchtip.nginx.createLocally "https://''${config.services.glitchtip.nginx.domain}";
|
||||
GLITCHTIP_VERSION = config.services.glitchtip.package.version;
|
||||
GRANIAN_HOST = "127.0.0.1";
|
||||
GRANIAN_PORT = 8000;
|
||||
GRANIAN_STATIC_PATH_MOUNT = "''${config.services.glitchtip.package}/lib/glitchtip/static";
|
||||
GRANIAN_WORKERS = 1;
|
||||
PYTHONUNBUFFERED = 1;
|
||||
REDIS_URL = lib.mkIf config.services.glitchtip.redis.createLocally "unix://''${config.services.redis.servers.glitchtip.unixSocket}";
|
||||
CELERY_BROKER_URL = lib.mkIf config.services.glitchtip.redis.createLocally "redis+socket://''${config.services.redis.servers.glitchtip.unixSocket}";
|
||||
}
|
||||
'';
|
||||
example = {
|
||||
@@ -94,9 +87,28 @@ in
|
||||
|
||||
options = {
|
||||
GLITCHTIP_DOMAIN = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
type = lib.types.nullOr lib.types.str;
|
||||
description = "The URL under which GlitchTip is externally reachable.";
|
||||
example = "https://glitchtip.example.com";
|
||||
default = null;
|
||||
};
|
||||
|
||||
GLITCHTIP_ENABLE_MCP = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
description = "Whether to enable the MCP api.";
|
||||
default = false;
|
||||
};
|
||||
|
||||
GRANIAN_WORKERS = lib.mkOption {
|
||||
type = lib.types.ints.positive;
|
||||
description = "Number of granian workers to start";
|
||||
default = 1;
|
||||
};
|
||||
|
||||
ENABLE_OBSERVABILITY_API = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
description = "Whether to enable the Prometheus metrics endpoint.";
|
||||
default = false;
|
||||
};
|
||||
|
||||
ENABLE_USER_REGISTRATION = lib.mkOption {
|
||||
@@ -132,49 +144,74 @@ in
|
||||
database.createLocally = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = true;
|
||||
description = ''
|
||||
Whether to enable and configure a local PostgreSQL database server.
|
||||
'';
|
||||
description = "Whether to enable and configure a local PostgreSQL database server.";
|
||||
};
|
||||
|
||||
nginx = {
|
||||
createLocally = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = false;
|
||||
description = "Whether to enable and configure a local Nginx server.";
|
||||
};
|
||||
|
||||
domain = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
example = "glitchtip.example.com";
|
||||
description = ''
|
||||
Domain under which GlitchTip will be reachable.
|
||||
In contrast to `settings.GLITCHTIP_DOMAIN` this option has no protocol.
|
||||
It will also set `settings.GLITCHTIP_DOMAIN` with the `https://` protocol.
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
redis.createLocally = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = true;
|
||||
description = ''
|
||||
Whether to enable and configure a local Redis instance.
|
||||
'';
|
||||
};
|
||||
|
||||
gunicorn.extraArgs = lib.mkOption {
|
||||
type = lib.types.listOf lib.types.str;
|
||||
default = [ ];
|
||||
description = "Extra arguments for gunicorn.";
|
||||
};
|
||||
|
||||
celery.extraArgs = lib.mkOption {
|
||||
type = lib.types.listOf lib.types.str;
|
||||
default = [ ];
|
||||
description = "Extra arguments for celery.";
|
||||
description = "Whether to enable and configure a local Redis instance.";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
imports = [
|
||||
(lib.mkRenamedOptionModule
|
||||
[ "services" "glitchtip" "listenAddress" ]
|
||||
[ "services" "glitchtip" "settings" "GRANIAN_HOST" ]
|
||||
)
|
||||
(lib.mkRenamedOptionModule
|
||||
[ "services" "glitchtip" "port" ]
|
||||
[ "services" "glitchtip" "settings" "GRANIAN_PORT" ]
|
||||
)
|
||||
(lib.mkRemovedOptionModule [ "services" "glitchtip" "celery" "extraArgs" ]
|
||||
"GlitchTip 6 migrated away from celery. Please check the upstream docs how to handle your usecase now."
|
||||
)
|
||||
(lib.mkRemovedOptionModule [ "services" "glitchtip" "gunicorn" "extraArgs" ]
|
||||
"GlitchTip 6 migrated away from gunicorn. Please check the upstream docs how to handle your usecase now."
|
||||
)
|
||||
];
|
||||
|
||||
config = lib.mkIf cfg.enable {
|
||||
services.glitchtip.settings = {
|
||||
DEBUG = lib.mkDefault 0;
|
||||
DEBUG_TOOLBAR = lib.mkDefault 0;
|
||||
PYTHONPATH = "${python.pkgs.makePythonPath pkg.propagatedBuildInputs}:${pkg}/lib/glitchtip";
|
||||
DATABASE_URL = lib.mkIf cfg.database.createLocally "postgresql://@/glitchtip";
|
||||
REDIS_URL = lib.mkIf cfg.redis.createLocally "unix://${config.services.redis.servers.glitchtip.unixSocket}";
|
||||
CELERY_BROKER_URL = lib.mkIf cfg.redis.createLocally "redis+socket://${config.services.redis.servers.glitchtip.unixSocket}";
|
||||
GLITCHTIP_VERSION = pkg.version;
|
||||
GRANIAN_HOST = lib.mkDefault "127.0.0.1";
|
||||
GRANIAN_PORT = lib.mkDefault 8000;
|
||||
GRANIAN_STATIC_PATH_MOUNT = "${pkg}/lib/glitchtip/static";
|
||||
GRANIAN_WORKERS = lib.mkDefault 1;
|
||||
PYTHONPATH = "${python.pkgs.makePythonPath pkg.propagatedBuildInputs}:${pkg}/lib/glitchtip";
|
||||
PYTHONUNBUFFERED = lib.mkDefault 1;
|
||||
}
|
||||
// lib.optionalAttrs cfg.database.createLocally { DATABASE_URL = "postgresql://@/glitchtip"; }
|
||||
// lib.optionalAttrs cfg.nginx.createLocally { GLITCHTIP_DOMAIN = "https://${cfg.nginx.domain}"; }
|
||||
// lib.optionalAttrs cfg.redis.createLocally {
|
||||
REDIS_URL = "unix://${config.services.redis.servers.glitchtip.unixSocket}";
|
||||
};
|
||||
|
||||
systemd.services =
|
||||
let
|
||||
commonService = {
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
|
||||
wants = [ "network-online.target" ];
|
||||
requires =
|
||||
lib.optional cfg.database.createLocally "postgresql.target"
|
||||
@@ -235,35 +272,55 @@ in
|
||||
{
|
||||
glitchtip = commonService // {
|
||||
description = "GlitchTip";
|
||||
environment =
|
||||
environment
|
||||
// lib.optionalAttrs (cfg.settings.ENABLE_OBSERVABILITY_API && cfg.settings.WORKERS > 1) {
|
||||
PROMETHEUS_MULTIPROC_DIR = "/tmp/prometheus_multiproc";
|
||||
};
|
||||
bindsTo = [ "glitchtip-worker.service" ];
|
||||
before = [ "glitchtip-worker.service" ];
|
||||
|
||||
preStart = ''
|
||||
${lib.getExe pkg} migrate
|
||||
${lib.getExe pkg} createcachetable
|
||||
${lib.getExe pkg} maintain_partitions
|
||||
'';
|
||||
|
||||
serviceConfig = commonServiceConfig // {
|
||||
ExecStart = ''
|
||||
${lib.getExe python.pkgs.gunicorn} \
|
||||
--bind=${cfg.listenAddress}:${toString cfg.port} \
|
||||
${lib.concatStringsSep " " cfg.gunicorn.extraArgs} \
|
||||
glitchtip.wsgi
|
||||
${lib.getExe python.pkgs.granian} \
|
||||
--interface ${if cfg.settings.GLITCHTIP_ENABLE_MCP then "asgi" else "asginl"} \
|
||||
glitchtip.asgi:application \
|
||||
--host ${cfg.settings.GRANIAN_HOST} \
|
||||
--port ${toString cfg.settings.GRANIAN_PORT} \
|
||||
--workers ${toString cfg.settings.GRANIAN_WORKERS} \
|
||||
--no-ws
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
glitchtip-worker = commonService // {
|
||||
description = "GlitchTip Job Runner";
|
||||
|
||||
environment = environment // {
|
||||
IS_WORKER = "1";
|
||||
};
|
||||
serviceConfig = commonServiceConfig // {
|
||||
ExecStart = ''
|
||||
${lib.getExe python.pkgs.celery} \
|
||||
-A glitchtip worker \
|
||||
-B -s /run/glitchtip/celerybeat-schedule \
|
||||
${lib.concatStringsSep " " cfg.celery.extraArgs}
|
||||
'';
|
||||
ExecStart = "${lib.getExe pkg} runworker --scheduler";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
services.nginx = lib.mkIf cfg.nginx.createLocally {
|
||||
enable = true;
|
||||
virtualHosts.${cfg.nginx.domain} = {
|
||||
forceSSL = lib.mkDefault true;
|
||||
locations = {
|
||||
"/".proxyPass = "http://${cfg.settings.GRANIAN_HOST}:${toString cfg.settings.GRANIAN_PORT}";
|
||||
"/static/".root = "${pkg}/lib/glitchtip";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
services.postgresql = lib.mkIf cfg.database.createLocally {
|
||||
enable = true;
|
||||
ensureDatabases = [ "glitchtip" ];
|
||||
@@ -289,15 +346,13 @@ in
|
||||
|
||||
systemd.tmpfiles.settings.glitchtip."${cfg.stateDir}/uploads".d = { inherit (cfg) user group; };
|
||||
|
||||
environment.systemPackages =
|
||||
let
|
||||
glitchtip-manage = pkgs.writeShellScriptBin "glitchtip-manage" ''
|
||||
set -o allexport
|
||||
${lib.toShellVars environment}
|
||||
${lib.concatMapStringsSep "\n" (f: "source ${f}") cfg.environmentFiles}
|
||||
${config.security.wrapperDir}/sudo -E -u ${cfg.user} ${lib.getExe pkg} "$@"
|
||||
'';
|
||||
in
|
||||
[ glitchtip-manage ];
|
||||
environment.systemPackages = [
|
||||
(pkgs.writeShellScriptBin "glitchtip-manage" ''
|
||||
set -o allexport
|
||||
${lib.toShellVars environment}
|
||||
${lib.concatMapStringsSep "\n" (f: "source ${f}") cfg.environmentFiles}
|
||||
${config.security.wrapperDir}/sudo -E -u ${cfg.user} ${lib.getExe pkg} "$@"
|
||||
'')
|
||||
];
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
{ lib, ... }:
|
||||
|
||||
let
|
||||
domain = "http://glitchtip.local:8000";
|
||||
domain = "glitchtip.local";
|
||||
url = "http://${domain}";
|
||||
in
|
||||
|
||||
{
|
||||
@@ -16,8 +17,10 @@ in
|
||||
{
|
||||
services.glitchtip = {
|
||||
enable = true;
|
||||
port = 8000;
|
||||
settings.GLITCHTIP_DOMAIN = domain;
|
||||
nginx.createLocally = true;
|
||||
nginx.domain = domain;
|
||||
settings.GLITCHTIP_DOMAIN = lib.mkForce url;
|
||||
settings.CSRF_TRUSTED_ORIGINS = "${url},http://localhost:8000";
|
||||
environmentFiles = [
|
||||
(builtins.toFile "glitchtip.env" ''
|
||||
SECRET_KEY=8Hz7YCGzo7fiicHb8Qr22ZqwoIB7lSRx
|
||||
@@ -25,19 +28,23 @@ in
|
||||
];
|
||||
};
|
||||
|
||||
services.nginx.virtualHosts.${domain}.forceSSL = false;
|
||||
|
||||
environment.systemPackages = [ pkgs.sentry-cli ];
|
||||
|
||||
networking.hosts."127.0.0.1" = [ "glitchtip.local" ];
|
||||
networking.hosts."127.0.0.1" = [ domain ];
|
||||
};
|
||||
|
||||
interactive.sshBackdoor.enable = true;
|
||||
interactive.defaults.virtualisation.graphics = false;
|
||||
|
||||
interactive.nodes.machine = {
|
||||
services.glitchtip.listenAddress = "0.0.0.0";
|
||||
networking.firewall.allowedTCPPorts = [ 8000 ];
|
||||
networking.firewall.allowedTCPPorts = [ 80 ];
|
||||
virtualisation.forwardPorts = [
|
||||
{
|
||||
from = "host";
|
||||
host.port = 8000;
|
||||
guest.port = 8000;
|
||||
guest.port = 80;
|
||||
}
|
||||
];
|
||||
};
|
||||
@@ -53,7 +60,7 @@ in
|
||||
machine.wait_for_unit("glitchtip-worker.service")
|
||||
machine.wait_for_open_port(8000)
|
||||
|
||||
origin_url = "${domain}"
|
||||
origin_url = "${url}"
|
||||
cookie_jar_path = "/tmp/cookies.txt"
|
||||
curl = f"curl -b {cookie_jar_path} -c {cookie_jar_path} -fS -H 'Origin: {origin_url}'"
|
||||
|
||||
@@ -89,7 +96,7 @@ in
|
||||
# fetch dsn
|
||||
resp = json.loads(machine.succeed(f"{curl} {origin_url}/api/0/projects/main/test/keys/"))
|
||||
assert len(resp) == 1
|
||||
assert re.match(r"^http://[\da-f]+@glitchtip\.local:8000/\d+$", dsn := resp[0]["dsn"]["public"])
|
||||
assert re.match(r"^http://[\da-f]+@glitchtip\.local/\d+$", dsn := resp[0]["dsn"]["public"])
|
||||
|
||||
# send event
|
||||
machine.succeed(f"SENTRY_DSN={dsn} sentry-cli send-event -m 'hello world'")
|
||||
|
||||
@@ -5,22 +5,27 @@
|
||||
fetchNpmDeps,
|
||||
jq,
|
||||
moreutils,
|
||||
nodejs_22,
|
||||
}:
|
||||
|
||||
buildNpmPackage (finalAttrs: {
|
||||
pname = "glitchtip-frontend";
|
||||
version = "5.2.1";
|
||||
version = "6.1.5";
|
||||
|
||||
src = fetchFromGitLab {
|
||||
owner = "glitchtip";
|
||||
repo = "glitchtip-frontend";
|
||||
tag = "v${finalAttrs.version}";
|
||||
hash = "sha256-aqGgaVjJogG3mDooQVpR59SR0HDuMPfKuB1i0Z/AMs8=";
|
||||
hash = "sha256-YngWs12wJLi3NgRT3RhGYy38dhOqBATYPMsXEbeoycU=";
|
||||
};
|
||||
|
||||
nodejs = nodejs_22;
|
||||
|
||||
npmDeps = fetchNpmDeps {
|
||||
name = "${finalAttrs.pname}-${finalAttrs.version}-npm-deps";
|
||||
inherit (finalAttrs) src;
|
||||
hash = "sha256-urho5XwUJL7m8/xxv9EvH0MxQIW5TG7nOBSIa77RhJc=";
|
||||
npmDepsFetcherVersion = 3;
|
||||
hash = "sha256-8T2Ci8S0YHyzY1jjgcNXt5mxUy/4toJrD2edUxtJz3M=";
|
||||
};
|
||||
|
||||
postPatch = ''
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
{
|
||||
lib,
|
||||
python313,
|
||||
python314,
|
||||
fetchFromGitLab,
|
||||
fetchPypi,
|
||||
callPackage,
|
||||
stdenv,
|
||||
makeWrapper,
|
||||
@@ -10,22 +9,10 @@
|
||||
}:
|
||||
|
||||
let
|
||||
python = python313.override {
|
||||
python = python314.override {
|
||||
self = python;
|
||||
packageOverrides = final: prev: {
|
||||
django = final.django_5;
|
||||
django-csp = prev.django-csp.overridePythonAttrs rec {
|
||||
version = "4.0";
|
||||
src = fetchPypi {
|
||||
inherit version;
|
||||
pname = "django_csp";
|
||||
hash = "sha256-snAQu3Ausgo9rTKReN8rYaK4LTOLcPvcE8OjvShxKDM=";
|
||||
};
|
||||
};
|
||||
django-ninja-cursor-pagination = prev.django-ninja-cursor-pagination.overridePythonAttrs {
|
||||
# checks are failing with django 5
|
||||
doCheck = false;
|
||||
};
|
||||
django = final.django_6;
|
||||
};
|
||||
};
|
||||
|
||||
@@ -34,16 +21,15 @@ let
|
||||
[
|
||||
aiohttp
|
||||
anonymizeip
|
||||
arro3-core
|
||||
arro3-io
|
||||
boto3
|
||||
brotli
|
||||
celery
|
||||
celery-batches
|
||||
cxxfilt
|
||||
django
|
||||
django-allauth
|
||||
django-anymail
|
||||
django-cors-headers
|
||||
django-csp
|
||||
django-environ
|
||||
django-extensions
|
||||
django-import-export
|
||||
@@ -54,32 +40,33 @@ let
|
||||
django-postgres-partition
|
||||
django-prometheus
|
||||
django-storages
|
||||
django-valkey
|
||||
django-vcache
|
||||
django-vtasks
|
||||
duckdb
|
||||
google-cloud-logging
|
||||
granian
|
||||
gunicorn
|
||||
mcp
|
||||
minidump
|
||||
orjson
|
||||
psycopg
|
||||
pydantic
|
||||
sentry-sdk
|
||||
symbolic
|
||||
user-agents
|
||||
uvicorn
|
||||
uuid6
|
||||
uwsgi-chunked
|
||||
whitenoise
|
||||
]
|
||||
++ celery.optional-dependencies.redis
|
||||
++ celery.optional-dependencies.sqlalchemy
|
||||
++ django-allauth.optional-dependencies.headless-spec
|
||||
++ django-allauth.optional-dependencies.mfa
|
||||
++ django-allauth.optional-dependencies.socialaccount
|
||||
++ django-storages.optional-dependencies.boto3
|
||||
++ django-storages.optional-dependencies.azure
|
||||
++ django-storages.optional-dependencies.google
|
||||
++ django-valkey.optional-dependencies.libvalkey
|
||||
++ django-valkey.optional-dependencies.lz4
|
||||
++ django-vtasks.optional-dependencies.valkey
|
||||
++ granian.optional-dependencies.reload
|
||||
++ granian.optional-dependencies.uvloop
|
||||
++ mcp.optional-dependencies.cli
|
||||
++ psycopg.optional-dependencies.c
|
||||
++ psycopg.optional-dependencies.pool
|
||||
++ pydantic.optional-dependencies.email;
|
||||
@@ -89,16 +76,24 @@ in
|
||||
|
||||
stdenv.mkDerivation (finalAttrs: {
|
||||
pname = "glitchtip";
|
||||
version = "5.2.1";
|
||||
version = "6.1.4";
|
||||
pyproject = true;
|
||||
|
||||
src = fetchFromGitLab {
|
||||
owner = "glitchtip";
|
||||
repo = "glitchtip-backend";
|
||||
tag = "v${finalAttrs.version}";
|
||||
hash = "sha256-FxkIbSrIyvLnD9n/Cb2udOhsnoC/bwGAfCRB1L/fhwo=";
|
||||
hash = "sha256-wju/QbIwdtNYQmRppCfjoaqb++spFZbqAsvBwwZyeUM=";
|
||||
};
|
||||
|
||||
postPatch = ''
|
||||
echo 'import os
|
||||
ALLAUTH_TRUSTED_CLIENT_IP_HEADER = os.getenv(
|
||||
"ALLAUTH_TRUSTED_CLIENT_IP_HEADER",
|
||||
None
|
||||
)' >> glitchtip/settings.py
|
||||
'';
|
||||
|
||||
propagatedBuildInputs = pythonPackages;
|
||||
|
||||
nativeBuildInputs = [
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
buildPythonPackage,
|
||||
fetchFromGitHub,
|
||||
setuptools,
|
||||
wheel,
|
||||
django,
|
||||
django-modeltranslation,
|
||||
}:
|
||||
@@ -20,9 +19,15 @@ buildPythonPackage rec {
|
||||
hash = "sha256-IRLY4VaKYXVkSgU/zdY+PSmGrcFB2FlE5L7j0FqisRM=";
|
||||
};
|
||||
|
||||
postPatch = lib.optionalString (lib.versionAtLeast django.version "6.0") ''
|
||||
# sadly upstream does not use pytest, so we must patch the one failing test or we could not run any tests at all
|
||||
# see https://github.com/justinmayer/django-autoslug/issues/87
|
||||
substituteInPlace autoslug/tests/tests.py \
|
||||
--replace-fail "assert b.slug == 'hello_world-2'" "assert b.slug == 'hello_world_2'"
|
||||
'';
|
||||
|
||||
build-system = [
|
||||
setuptools
|
||||
wheel
|
||||
];
|
||||
|
||||
dependencies = [ django ];
|
||||
|
||||
@@ -36,6 +36,12 @@ buildPythonPackage rec {
|
||||
hash = "sha256-WgO/bDe4anQCc1q2Gdq3W70yDqDgmsvn39Qf9ZNVXuE=";
|
||||
};
|
||||
|
||||
patches = lib.optionals (lib.versionAtLeast django.version "6.0") [
|
||||
# Fix some tests when run with Django 6
|
||||
# see https://github.com/django-extensions/django-extensions/pull/1979
|
||||
./django_6-compat.diff
|
||||
];
|
||||
|
||||
build-system = [ setuptools ];
|
||||
|
||||
dependencies = [
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
From 5c096ad7a59a3e9edee66e27fbae46c30e4abd6b Mon Sep 17 00:00:00 2001
|
||||
From: asherzod1 <sh.abdumutalov1@gmail.com>
|
||||
Date: Wed, 11 Mar 2026 00:53:19 +0500
|
||||
Subject: [PATCH] fix: add Django 6 compatibility for signals, constraints, and
|
||||
random char field
|
||||
|
||||
---
|
||||
django_extensions/db/fields/__init__.py | 18 +++++++++++++-----
|
||||
.../management/commands/list_signals.py | 4 +++-
|
||||
tests/test_management_command.py | 6 +++++-
|
||||
tests/testapp/models.py | 7 ++++++-
|
||||
4 files changed, 27 insertions(+), 8 deletions(-)
|
||||
|
||||
diff --git a/django_extensions/db/fields/__init__.py b/django_extensions/db/fields/__init__.py
|
||||
index 938254e1f..c233cee84 100644
|
||||
--- a/django_extensions/db/fields/__init__.py
|
||||
+++ b/django_extensions/db/fields/__init__.py
|
||||
@@ -86,7 +86,11 @@ def find_unique(self, model_instance, field, iterator, *args):
|
||||
|
||||
new = next(iterator)
|
||||
kwargs[self.attname] = new
|
||||
- while not new or queryset.filter(query, **kwargs):
|
||||
+ while True:
|
||||
+ matching = queryset.filter(query, **kwargs)
|
||||
+ has_match = matching.exists() if hasattr(matching, "exists") else bool(matching)
|
||||
+ if new and not has_match:
|
||||
+ break
|
||||
new = next(iterator)
|
||||
kwargs[self.attname] = new
|
||||
setattr(model_instance, self.attname, new)
|
||||
@@ -388,10 +392,14 @@ def in_unique_together(self, model_instance):
|
||||
return False
|
||||
|
||||
def pre_save(self, model_instance, add):
|
||||
- if (not add or self.keep_default) and getattr(
|
||||
- model_instance, self.attname
|
||||
- ) != "":
|
||||
- return getattr(model_instance, self.attname)
|
||||
+ current_value = getattr(model_instance, self.attname)
|
||||
+ # Django 6 may call pre_save multiple times for inserts; if we've already
|
||||
+ # populated the field value, reuse it instead of regenerating.
|
||||
+ if current_value not in ("", None):
|
||||
+ return current_value
|
||||
+
|
||||
+ if (not add or self.keep_default) and current_value != "":
|
||||
+ return current_value
|
||||
|
||||
population = ""
|
||||
if self.include_alpha:
|
||||
diff --git a/django_extensions/management/commands/list_signals.py b/django_extensions/management/commands/list_signals.py
|
||||
index 9cadc4f85..e7bc9d7f6 100644
|
||||
--- a/django_extensions/management/commands/list_signals.py
|
||||
+++ b/django_extensions/management/commands/list_signals.py
|
||||
@@ -52,7 +52,9 @@ def handle(self, *args, **options):
|
||||
for signal in signals:
|
||||
signal_name = SIGNAL_NAMES.get(signal, "unknown")
|
||||
for receiver in signal.receivers:
|
||||
- if django.VERSION >= (5, 0):
|
||||
+ if django.VERSION >= (6, 0):
|
||||
+ lookup, receiver, _sender, is_async = receiver
|
||||
+ elif django.VERSION >= (5, 0):
|
||||
lookup, receiver, is_async = receiver
|
||||
else:
|
||||
lookup, receiver = receiver
|
||||
diff --git a/tests/test_management_command.py b/tests/test_management_command.py
|
||||
index ea205ed72..0fb9dc27b 100644
|
||||
--- a/tests/test_management_command.py
|
||||
+++ b/tests/test_management_command.py
|
||||
@@ -3,6 +3,7 @@
|
||||
import logging
|
||||
import importlib
|
||||
|
||||
+import django
|
||||
from django.core.management import (
|
||||
call_command,
|
||||
find_commands,
|
||||
@@ -421,7 +422,10 @@ def test_field_class(self):
|
||||
stdout=out,
|
||||
)
|
||||
self.output = out.getvalue()
|
||||
- self.assertIn("id - AutoField", self.output)
|
||||
+ if django.VERSION >= (6, 0):
|
||||
+ self.assertIn("id - BigAutoField", self.output)
|
||||
+ else:
|
||||
+ self.assertIn("id - AutoField", self.output)
|
||||
self.assertIn("char_field - CharField", self.output)
|
||||
self.assertIn("integer_field - IntegerField", self.output)
|
||||
self.assertIn("foreign_key_field - ForeignKey", self.output)
|
||||
diff --git a/tests/testapp/models.py b/tests/testapp/models.py
|
||||
index 278402f51..3f31efcda 100644
|
||||
--- a/tests/testapp/models.py
|
||||
+++ b/tests/testapp/models.py
|
||||
@@ -1,4 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
+import django
|
||||
from django.db import models
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.db.models import UniqueConstraint
|
||||
@@ -178,7 +179,11 @@ class Meta:
|
||||
fields=("common_field", "uniq_field"), name="unique_common_uniq_pair"
|
||||
),
|
||||
models.CheckConstraint(
|
||||
- check=~models.Q(common_field=models.F("another_common_field")),
|
||||
+ **(
|
||||
+ {"condition": ~models.Q(common_field=models.F("another_common_field"))}
|
||||
+ if django.VERSION >= (5, 2)
|
||||
+ else {"check": ~models.Q(common_field=models.F("another_common_field"))}
|
||||
+ ),
|
||||
name="common_and_another_common_differ",
|
||||
),
|
||||
]
|
||||
@@ -6,6 +6,7 @@
|
||||
django,
|
||||
pytestCheckHook,
|
||||
pytest-django,
|
||||
pytest-xdist,
|
||||
setuptools,
|
||||
}:
|
||||
|
||||
@@ -29,6 +30,7 @@ buildPythonPackage rec {
|
||||
django-environ
|
||||
pytestCheckHook
|
||||
pytest-django
|
||||
pytest-xdist
|
||||
];
|
||||
|
||||
pythonImportsCheck = [ "guardian" ];
|
||||
|
||||
@@ -28,6 +28,10 @@ buildPythonPackage rec {
|
||||
--replace-fail '"pytest-runner"' ""
|
||||
'';
|
||||
|
||||
pythonRelaxDeps = [
|
||||
"django"
|
||||
];
|
||||
|
||||
build-system = [ setuptools ];
|
||||
|
||||
dependencies = [ prometheus-client ];
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
{
|
||||
lib,
|
||||
buildPythonPackage,
|
||||
croniter,
|
||||
django,
|
||||
fetchFromGitLab,
|
||||
hatchling,
|
||||
ormsgpack,
|
||||
prometheus-client,
|
||||
pythonOlder,
|
||||
valkey,
|
||||
zstd,
|
||||
}:
|
||||
|
||||
buildPythonPackage rec {
|
||||
pname = "django-vcache";
|
||||
version = "1.0.0";
|
||||
pyproject = true;
|
||||
|
||||
src = fetchFromGitLab {
|
||||
owner = "glitchtip";
|
||||
repo = "django-vcache";
|
||||
tag = "v${version}";
|
||||
hash = "sha256-bOHEw4nl82tFjHiJdmyW0LleKMpjUh8uu4crGp6IsWY=";
|
||||
};
|
||||
|
||||
build-system = [ hatchling ];
|
||||
|
||||
dependencies = [
|
||||
django
|
||||
ormsgpack
|
||||
croniter
|
||||
valkey
|
||||
]
|
||||
++ valkey.optional-dependencies.libvalkey
|
||||
++ lib.optional (pythonOlder "3.14") zstd;
|
||||
|
||||
optional-dependencies = {
|
||||
metrics = [ prometheus-client ];
|
||||
valkey = [ valkey ] ++ valkey.optional-dependencies.libvalkey;
|
||||
};
|
||||
|
||||
pythonImportsCheck = [ "django_vcache" ];
|
||||
|
||||
# requires valkey sentinel cluster
|
||||
doCheck = false;
|
||||
|
||||
meta = {
|
||||
description = "Specialized, lightweight Django cache backend for Valkey";
|
||||
homepage = "https://gitlab.com/glitchtip/django-vcache/";
|
||||
changelog = "https://gitlab.com/glitchtip/django-vcache/-/blob/main/CHANGELOG.md#${
|
||||
lib.replaceString "." "" version
|
||||
}";
|
||||
license = lib.licenses.mit;
|
||||
maintainers = with lib.maintainers; [
|
||||
defelo
|
||||
felbinger
|
||||
];
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
{
|
||||
lib,
|
||||
buildPythonPackage,
|
||||
croniter,
|
||||
dj-database-url,
|
||||
django-valkey,
|
||||
django,
|
||||
fetchFromGitLab,
|
||||
hatchling,
|
||||
orjson,
|
||||
postgresql,
|
||||
postgresqlTestHook,
|
||||
prometheus-client,
|
||||
psycopg,
|
||||
pytest-asyncio,
|
||||
pytest-django,
|
||||
pytestCheckHook,
|
||||
pythonOlder,
|
||||
redisTestHook,
|
||||
valkey,
|
||||
zstandard,
|
||||
}:
|
||||
|
||||
buildPythonPackage rec {
|
||||
pname = "django-vtasks";
|
||||
version = "1.0.3";
|
||||
pyproject = true;
|
||||
|
||||
src = fetchFromGitLab {
|
||||
owner = "glitchtip";
|
||||
repo = "django-vtasks";
|
||||
tag = "v${version}";
|
||||
hash = "sha256-75W63HsLBT4EPQCiAXjd9qr6n07/2e5GCUNWeDzXUq0=";
|
||||
};
|
||||
|
||||
postPatch = ''
|
||||
# upstream does not use pytest to run the tests, but we just need to patch one async test to do so
|
||||
substituteInPlace scripts/test_stale_conn.py \
|
||||
--replace-fail "import asyncio" "import asyncio; import pytest" \
|
||||
--replace-fail "async def test_async_stale_connection():" "@pytest.mark.asyncio
|
||||
async def test_async_stale_connection():"
|
||||
'';
|
||||
|
||||
build-system = [ hatchling ];
|
||||
|
||||
dependencies = [
|
||||
django
|
||||
orjson
|
||||
croniter
|
||||
]
|
||||
++ lib.optional (pythonOlder "3.14") zstandard;
|
||||
|
||||
optional-dependencies = {
|
||||
metrics = [ prometheus-client ];
|
||||
valkey = [ valkey ] ++ valkey.optional-dependencies.libvalkey;
|
||||
};
|
||||
|
||||
pythonImportsCheck = [ "django_vtasks" ];
|
||||
|
||||
env = {
|
||||
DATABASE_URL = "postgresql://postgres@%2Fbuild%2Frun%2Fpostgresql";
|
||||
VALKEY_URL = "redis://127.0.0.1:6379";
|
||||
};
|
||||
|
||||
nativeCheckInputs = [
|
||||
django # must come first as vtasks only works with django 6
|
||||
|
||||
dj-database-url
|
||||
django-valkey
|
||||
postgresql
|
||||
postgresqlTestHook
|
||||
psycopg
|
||||
pytest-asyncio
|
||||
pytest-django
|
||||
pytestCheckHook
|
||||
redisTestHook # contains valkey
|
||||
];
|
||||
|
||||
meta = {
|
||||
description = "Very fast valkey/postgres django tasks backend";
|
||||
homepage = "https://gitlab.com/glitchtip/django-vtasks";
|
||||
changelog = "https://gitlab.com/glitchtip/django-vtasks/-/releases/${src.tag}";
|
||||
license = lib.licenses.mit;
|
||||
maintainers = with lib.maintainers; [
|
||||
defelo
|
||||
felbinger
|
||||
];
|
||||
broken = lib.versionOlder (lib.versions.major django.version) "6";
|
||||
};
|
||||
}
|
||||
@@ -4412,12 +4412,16 @@ self: super: with self; {
|
||||
|
||||
django-valkey = callPackage ../development/python-modules/django-valkey { };
|
||||
|
||||
django-vcache = callPackage ../development/python-modules/django-vcache { };
|
||||
|
||||
django-versatileimagefield =
|
||||
callPackage ../development/python-modules/django-versatileimagefield
|
||||
{ };
|
||||
|
||||
django-vite = callPackage ../development/python-modules/django-vite { };
|
||||
|
||||
django-vtasks = callPackage ../development/python-modules/django-vtasks { };
|
||||
|
||||
django-waffle = callPackage ../development/python-modules/django-waffle { };
|
||||
|
||||
django-weasyprint = callPackage ../development/python-modules/django-weasyprint { };
|
||||
|
||||
Reference in New Issue
Block a user