From 5f1ab9d2cddb802e27b782c4f6bcb45a914dbb4a Mon Sep 17 00:00:00 2001 From: Jacob Nesbitt Date: Mon, 27 Oct 2025 10:35:18 -0400 Subject: [PATCH 01/11] Supply a default name in createsuperuser --- dandiapi/api/management/commands/createsuperuser.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/dandiapi/api/management/commands/createsuperuser.py b/dandiapi/api/management/commands/createsuperuser.py index ebc281267..2bf5d644b 100644 --- a/dandiapi/api/management/commands/createsuperuser.py +++ b/dandiapi/api/management/commands/createsuperuser.py @@ -2,7 +2,7 @@ from typing import TYPE_CHECKING -from django.db.models.signals import post_save +from django.db.models.signals import post_init, post_save from resonant_settings.allauth_support.management.commands import createsuperuser from dandiapi.api.models.user import UserMetadata @@ -11,16 +11,22 @@ from resonant_settings.allauth_support.createsuperuser import EmailAsUsernameProxyUser +def populate_name(instance: EmailAsUsernameProxyUser, *args, **kwargs): + instance.first_name = 'Super' + instance.last_name = 'User' + + def create_usermetadata(instance: EmailAsUsernameProxyUser, *args, **kwargs): UserMetadata.objects.create(user=instance, status=UserMetadata.Status.APPROVED) class Command(createsuperuser.Command): def handle(self, *args, **kwargs) -> str | None: - # Temporarily connect a post_save signal handler so that we can catch the creation of + # Temporarily connect post_* signal handlers so that we can catch the creation of # this superuser. Note, we do this in the handle() method to ensure this only happens # when this management command is actually run. post_save.connect(create_usermetadata, sender=createsuperuser.user_model) + post_init.connect(populate_name, sender=createsuperuser.user_model) # Save the return value of the parent class function so we can return it later return_value = super().handle(*args, **kwargs) @@ -29,5 +35,6 @@ def handle(self, *args, **kwargs) -> str | None: # unexpected behavior if, for example, someone extends this command and doesn't # realize there's a signal handler attached dynamically. post_save.disconnect(create_usermetadata, sender=createsuperuser.user_model) + post_init.disconnect(populate_name, sender=createsuperuser.user_model) return return_value From 751c34bc2020cf9646d24a3e37264fcd4b155197 Mon Sep 17 00:00:00 2001 From: Isaac To Date: Thu, 30 Oct 2025 14:49:47 -0700 Subject: [PATCH 02/11] feat: expose allowed schema versions at `/info/` endpoint --- dandiapi/api/views/info.py | 2 ++ dandiapi/settings/base.py | 2 ++ 2 files changed, 4 insertions(+) diff --git a/dandiapi/api/views/info.py b/dandiapi/api/views/info.py index 561006a0c..fa2c410ac 100644 --- a/dandiapi/api/views/info.py +++ b/dandiapi/api/views/info.py @@ -53,6 +53,7 @@ def __init__(self, *args, **kwargs): # Schema schema_version = serializers.CharField() schema_url = serializers.URLField() + allowed_schema_versions = serializers.ListField(child=serializers.CharField()) # Versions version = serializers.CharField() @@ -73,6 +74,7 @@ def info_view(request): data={ 'schema_version': settings.DANDI_SCHEMA_VERSION, 'schema_url': get_schema_url(), + 'allowed_schema_versions': settings.ALLOWED_DANDI_SCHEMA_VERSIONS, 'version': importlib.metadata.version('dandiapi'), 'cli-minimal-version': '0.60.0', 'cli-bad-versions': [], diff --git a/dandiapi/settings/base.py b/dandiapi/settings/base.py index ec32d4395..7af8db68b 100644 --- a/dandiapi/settings/base.py +++ b/dandiapi/settings/base.py @@ -7,6 +7,7 @@ from urllib.parse import urlunparse from corsheaders.defaults import default_headers +from dandischema.consts import ALLOWED_INPUT_SCHEMAS from dandischema.consts import DANDI_SCHEMA_VERSION as _DEFAULT_DANDI_SCHEMA_VERSION import django_stubs_ext from environ import Env @@ -175,6 +176,7 @@ DANDI_SCHEMA_VERSION: str = env.str( 'DJANGO_DANDI_SCHEMA_VERSION', default=_DEFAULT_DANDI_SCHEMA_VERSION ) +ALLOWED_DANDI_SCHEMA_VERSIONS: list[str] = ALLOWED_INPUT_SCHEMAS DANDI_ZARR_PREFIX_NAME: str = env.str('DJANGO_DANDI_ZARR_PREFIX_NAME', default='zarr') From 622ee67a359c77497d1b10ad85a5e043bd5a9766 Mon Sep 17 00:00:00 2001 From: Jacob Nesbitt Date: Wed, 5 Nov 2025 16:17:59 -0500 Subject: [PATCH 03/11] Update DOI settings check Update `doi_configured()` to use `all` instead of `any`, and remove the `check_doi_settings` system check, as it's no longer necessary. --- dandiapi/api/apps.py | 1 - dandiapi/api/checks.py | 24 ------------------------ dandiapi/api/doi.py | 2 +- 3 files changed, 1 insertion(+), 26 deletions(-) delete mode 100644 dandiapi/api/checks.py diff --git a/dandiapi/api/apps.py b/dandiapi/api/apps.py index 51ced7070..c51cfac52 100644 --- a/dandiapi/api/apps.py +++ b/dandiapi/api/apps.py @@ -9,5 +9,4 @@ class ApiConfig(AppConfig): def ready(self): # RUF100 is caused by https://github.com/astral-sh/ruff/issues/60 - import dandiapi.api.checks # noqa: F401, RUF100 import dandiapi.api.signals # noqa: F401 diff --git a/dandiapi/api/checks.py b/dandiapi/api/checks.py deleted file mode 100644 index 8149ed49c..000000000 --- a/dandiapi/api/checks.py +++ /dev/null @@ -1,24 +0,0 @@ -from __future__ import annotations - -from django.conf import settings -from django.core.checks import Error, register - -from dandiapi.api.doi import DANDI_DOI_SETTINGS, doi_configured - - -@register() -def check_doi_settings(app_configs, **kwargs): - if not doi_configured(): - # If no DOI settings are defined, DOIs will not be created on publish. - return [] - errors = [] - for setting, name in DANDI_DOI_SETTINGS: - if setting is None: - errors.append( - Error( - f'Setting {name} is not specified, but other DOI settings are.', - hint=f'Define {name} as an environment variable.', - obj=settings, - ) - ) - return errors diff --git a/dandiapi/api/doi.py b/dandiapi/api/doi.py index 9ff298973..5cd18cbd9 100644 --- a/dandiapi/api/doi.py +++ b/dandiapi/api/doi.py @@ -21,7 +21,7 @@ def doi_configured() -> bool: - return any(setting is not None for setting, _ in DANDI_DOI_SETTINGS) + return all(setting is not None for setting, _ in DANDI_DOI_SETTINGS) def _generate_doi_data(version: Version): From e93b525fc1e2bb898c6a8a5bdbd10a13bfb4ff39 Mon Sep 17 00:00:00 2001 From: Jacob Nesbitt Date: Thu, 6 Nov 2025 11:33:02 -0500 Subject: [PATCH 04/11] Remove test_rest_info test --- dandiapi/api/tests/test_info.py | 29 ----------------------------- 1 file changed, 29 deletions(-) delete mode 100644 dandiapi/api/tests/test_info.py diff --git a/dandiapi/api/tests/test_info.py b/dandiapi/api/tests/test_info.py deleted file mode 100644 index a56938ba7..000000000 --- a/dandiapi/api/tests/test_info.py +++ /dev/null @@ -1,29 +0,0 @@ -from __future__ import annotations - -from django.conf import settings - -from dandiapi.api.views.info import get_schema_url - -from .fuzzy import Re - - -def test_rest_info(api_client): - resp = api_client.get('/api/info/') - assert resp.status_code == 200 - - # Get the expected schema URL - schema_url = get_schema_url() - - assert resp.json() == { - 'schema_version': settings.DANDI_SCHEMA_VERSION, - 'schema_url': schema_url, - # Matches setuptools_scm's versioning scheme "no-guess-dev" - 'version': Re(r'\d+\.\d+\.\d+(\.post1\.dev\d+\+g[0-9a-f]{7,}(\.d\d{8})?)?'), - 'cli-minimal-version': '0.60.0', - 'cli-bad-versions': [], - 'services': { - 'api': {'url': f'{settings.DANDI_API_URL}/api'}, - 'webui': {'url': settings.DANDI_WEB_APP_URL}, - 'jupyterhub': {'url': settings.DANDI_JUPYTERHUB_URL}, - }, - } From 7d3854d800db324ae7beaff1955dd97d54e43dde Mon Sep 17 00:00:00 2001 From: Mike VanDenburgh Date: Mon, 10 Nov 2025 16:54:05 +0000 Subject: [PATCH 05/11] Regression test for publish metadata bug --- dandiapi/api/tests/test_version.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/dandiapi/api/tests/test_version.py b/dandiapi/api/tests/test_version.py index 8d3d5057c..9aea61ded 100644 --- a/dandiapi/api/tests/test_version.py +++ b/dandiapi/api/tests/test_version.py @@ -357,20 +357,27 @@ def test_version_aggregate_assets_summary(draft_asset_factory): @pytest.mark.django_db -def test_version_publish_updates_draft_version_assets_summary(draft_asset_factory): +def test_version_publish_draft_version_metadata_updates(draft_asset_factory): user = UserFactory.create() version = DraftVersionFactory.create(status=Version.Status.PUBLISHING) asset = draft_asset_factory(status=Asset.Status.VALID) version.assets.add(asset) tasks.publish_dandiset_task(version.dandiset.id, user.id) + published_version = Version.objects.latest('created') + # Ensure draft assets summary has been updated version.refresh_from_db() draft_asset_summary = version.metadata['assetsSummary'] - published_asset_summary = Version.objects.latest('created').metadata['assetsSummary'] - + published_asset_summary = published_version.metadata['assetsSummary'] assert published_asset_summary == draft_asset_summary + # Ensure published version contains expected new fields, and draft version does not + assert 'publishedBy' in published_version.metadata + assert 'datePublished' in published_version.metadata + assert 'publishedBy' not in version.metadata + assert 'datePublished' not in version.metadata + @pytest.mark.django_db def test_version_aggregate_assets_summary_metadata_modified(draft_asset_factory): From 5d895356640d5dc9449810266c5e07c7b49bae37 Mon Sep 17 00:00:00 2001 From: Mike VanDenburgh Date: Mon, 10 Nov 2025 16:57:14 +0000 Subject: [PATCH 06/11] Fix bug in publishing process There is a bug during the publishing process where a shallow reference to the draft `Version.metadata` dict is erroneously used for the newly created published `Version`. This means that when we insert new fields into the published metadata during the publishing process, we are also unknowingly adding them into the draft `Version` object that lives in memory. Then, during the `_publish_dandiset` service function, we call `.save()` on the draft version, which writes those new fields to the DB. --- dandiapi/api/services/publish/__init__.py | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/dandiapi/api/services/publish/__init__.py b/dandiapi/api/services/publish/__init__.py index 59042d9a5..216b32c4e 100644 --- a/dandiapi/api/services/publish/__init__.py +++ b/dandiapi/api/services/publish/__init__.py @@ -1,5 +1,6 @@ from __future__ import annotations +import copy import datetime from typing import TYPE_CHECKING @@ -86,24 +87,25 @@ def _lock_dandiset_for_publishing(*, user: User, dandiset: Dandiset) -> None: # def _build_publishable_version_from_draft(draft_version: Version) -> Version: - publishable_version = Version( - dandiset=draft_version.dandiset, - name=draft_version.name, - metadata=draft_version.metadata, - status=Version.Status.VALID, - version=Version.next_published_version(draft_version.dandiset), - ) + # Make a deep copy of the dict to avoid mutating the draft version's metadata. + publishable_version_metadata = copy.deepcopy(draft_version.metadata) now = datetime.datetime.now(datetime.UTC) # inject the publishedBy and datePublished fields - publishable_version.metadata.update( + publishable_version_metadata.update( { 'publishedBy': draft_version.published_by(now), 'datePublished': now.isoformat(), } ) - return publishable_version + return Version( + dandiset=draft_version.dandiset, + name=draft_version.name, + metadata=publishable_version_metadata, + status=Version.Status.VALID, + version=Version.next_published_version(draft_version.dandiset), + ) def _publish_dandiset(dandiset_id: int, user_id: int) -> None: From f2ea29c497b06440ee684d772c62259bfb641373 Mon Sep 17 00:00:00 2001 From: dandibot Date: Wed, 12 Nov 2025 02:34:15 +0000 Subject: [PATCH 07/11] auto shipit - CHANGELOG.md etc --- CHANGELOG.md | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index dd2933c99..ace7125ea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,25 @@ +# v0.18.1 (Wed Nov 12 2025) + +#### ๐Ÿ› Bug Fix + +- Fix bug in publishing process [#2636](https://github.com/dandi/dandi-archive/pull/2636) ([@mvandenburgh](https://github.com/mvandenburgh)) +- Supply a default name in createsuperuser [#2616](https://github.com/dandi/dandi-archive/pull/2616) ([@jjnesbitt](https://github.com/jjnesbitt)) + +#### ๐Ÿ  Internal + +- Update how DOI settings are checked for configuration [#2634](https://github.com/dandi/dandi-archive/pull/2634) ([@jjnesbitt](https://github.com/jjnesbitt)) + +#### ๐Ÿงช Tests + +- Remove the `test_rest_info` test [#2635](https://github.com/dandi/dandi-archive/pull/2635) ([@jjnesbitt](https://github.com/jjnesbitt)) + +#### Authors: 2 + +- Jacob Nesbitt ([@jjnesbitt](https://github.com/jjnesbitt)) +- Mike VanDenburgh ([@mvandenburgh](https://github.com/mvandenburgh)) + +--- + # v0.18.0 (Tue Nov 04 2025) #### ๐Ÿš€ Enhancement From e7b09c9c4a78c11b49633f769f6b19d311af9137 Mon Sep 17 00:00:00 2001 From: Mike VanDenburgh Date: Wed, 12 Nov 2025 16:45:23 +0000 Subject: [PATCH 08/11] Add celery/frontend commands to dev container docs --- DEVELOPMENT.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md index 0773122d8..a110f2685 100644 --- a/DEVELOPMENT.md +++ b/DEVELOPMENT.md @@ -71,7 +71,10 @@ but allows developers to run Python code on their native system. to create a dummy dandiset to start working with. ### Run Application -1. Run `./manage.py runserver_plus 0.0.0.0:8000` from the VSCode built-in-terminal. +1. Run the following commands in three seperate VSCode built-in-terminals: + 1. `./manage.py runserver_plus 0.0.0.0:8000` + 1. `uv run celery --app dandiapi.celery worker --loglevel INFO --without-heartbeat -Q celery,calculate_sha256,ingest_zarr_archive,manifest-worker -B` + 1. `cd web/ && npm install && npm run dev` 1. Access the site, starting at http://localhost:8000/admin/ 1. When finished, use `Ctrl+C` From 0d67796441e7c4d494cff5960d70eae96f4c1632 Mon Sep 17 00:00:00 2001 From: Mike VanDenburgh Date: Wed, 12 Nov 2025 16:52:44 +0000 Subject: [PATCH 09/11] Make devcontainers the "recommended quickstart" --- DEVELOPMENT.md | 41 ++++++++++++++++++++--------------------- 1 file changed, 20 insertions(+), 21 deletions(-) diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md index a110f2685..80c81c4e2 100644 --- a/DEVELOPMENT.md +++ b/DEVELOPMENT.md @@ -8,9 +8,28 @@ You would need a local clone of the `dandi-archive` repository to develop on it. 1. Run `cd dandi-archive` 1. Make sure your PostgreSQL port (5432) is available. -## Develop with Docker (recommended quickstart) +## Develop with VSCode Dev Containers (recommended quickstart) This is the simplest configuration for developers to start with. +### Initial Setup +1. Follow the steps for [setting up Dev Containers](https://code.visualstudio.com/docs/devcontainers/containers#_installation) if necessary. +1. From VSCode, use `Ctrl-Shift-p` and run the command `Dev Containers: Reopen in Container`. +1. From the VSCode built-in terminal, run `./manage.py migrate`. +1. From the VSCode built-in terminal, run `./manage.py createsuperuser --email $(git config user.email)` and follow the prompts. +1. From the VSCode built-in terminal, run `./manage.py create_dev_dandiset --owner $(git config user.email)` + to create a dummy dandiset to start working with. + +### Run Application +1. Run the following commands in three seperate VSCode built-in-terminals: + 1. `./manage.py runserver_plus 0.0.0.0:8000` + 1. `uv run celery --app dandiapi.celery worker --loglevel INFO --without-heartbeat -Q celery,calculate_sha256,ingest_zarr_archive,manifest-worker -B` + 1. `cd web/ && npm install && npm run dev` +1. Access the site, starting at http://localhost:8000/admin/ +1. When finished, use `Ctrl+C` + +## Develop with Docker +This configuration also uses containers, but with Docker Compose instead of VScode Dev Containers. + ### Initial Setup 1. Install [Docker Compose](https://docs.docker.com/compose/install/) 1. Run `docker compose run --rm django ./manage.py migrate` @@ -59,26 +78,6 @@ but allows developers to run Python code on their native system. 1. `uv run celery --app dandiapi.celery worker --loglevel INFO --without-heartbeat -Q celery,calculate_sha256,ingest_zarr_archive,manifest-worker -B` 1. When finished, run `docker compose stop` - -## Develop with VSCode Dev Containers - -### Initial Setup -1. Follow the steps for [setting up Dev Containers](https://code.visualstudio.com/docs/devcontainers/containers#_installation) if necessary. -1. From VSCode, use `Ctrl-Shift-p` and run the command `Dev Containers: Reopen in Container`. -1. From the VSCode built-in terminal, run `./manage.py migrate`. -1. From the VSCode built-in terminal, run `./manage.py createsuperuser --email $(git config user.email)` and follow the prompts. -1. From the VSCode built-in terminal, run `./manage.py create_dev_dandiset --owner $(git config user.email)` - to create a dummy dandiset to start working with. - -### Run Application -1. Run the following commands in three seperate VSCode built-in-terminals: - 1. `./manage.py runserver_plus 0.0.0.0:8000` - 1. `uv run celery --app dandiapi.celery worker --loglevel INFO --without-heartbeat -Q celery,calculate_sha256,ingest_zarr_archive,manifest-worker -B` - 1. `cd web/ && npm install && npm run dev` -1. Access the site, starting at http://localhost:8000/admin/ -1. When finished, use `Ctrl+C` - - ## Testing ### Initial Setup tox is used to manage the execution of all tests. To set up to run the tests: From 87f7327f0097babd8357e5050f7f359f83276707 Mon Sep 17 00:00:00 2001 From: Mike VanDenburgh Date: Wed, 12 Nov 2025 17:01:19 +0000 Subject: [PATCH 10/11] Add note about uv cache volume --- DEVELOPMENT.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md index 80c81c4e2..8645f271c 100644 --- a/DEVELOPMENT.md +++ b/DEVELOPMENT.md @@ -13,6 +13,10 @@ This is the simplest configuration for developers to start with. ### Initial Setup 1. Follow the steps for [setting up Dev Containers](https://code.visualstudio.com/docs/devcontainers/containers#_installation) if necessary. +1. If you have previously used the "Develop with Docker" workflow described below, follow the below steps. Otherwise, skip to the next step. + 1. Run `docker compose volumes` + 1. If one of the listed volumes ends with `_uv_cache`, delete it by running `docker volume rm ` (this operation is safe and simply deletes your cached `uv` dependencies to avoid potential permission issues between the devcontainer and "Develop with Docker" configurations). + 1. From VSCode, use `Ctrl-Shift-p` and run the command `Dev Containers: Reopen in Container`. 1. From the VSCode built-in terminal, run `./manage.py migrate`. 1. From the VSCode built-in terminal, run `./manage.py createsuperuser --email $(git config user.email)` and follow the prompts. @@ -20,7 +24,7 @@ This is the simplest configuration for developers to start with. to create a dummy dandiset to start working with. ### Run Application -1. Run the following commands in three seperate VSCode built-in-terminals: +1. Run the following commands in three separate VSCode built-in-terminals: 1. `./manage.py runserver_plus 0.0.0.0:8000` 1. `uv run celery --app dandiapi.celery worker --loglevel INFO --without-heartbeat -Q celery,calculate_sha256,ingest_zarr_archive,manifest-worker -B` 1. `cd web/ && npm install && npm run dev` From 0a868aeb977c094bba9def4fcdebfe92ed72b2da Mon Sep 17 00:00:00 2001 From: dandibot Date: Thu, 13 Nov 2025 19:29:36 +0000 Subject: [PATCH 11/11] auto shipit - CHANGELOG.md etc --- CHANGELOG.md | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ace7125ea..f87676285 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,28 @@ +# v0.19.0 (Thu Nov 13 2025) + +### Release Notes + +#### Expose allowed schema versions at `/info/` endpoint ([#2625](https://github.com/dandi/dandi-archive/pull/2625)) + +The `/info/` endpoint now exposes the allowed list of DANDI schema version through `allowed_schema_versions` key value. + +--- + +#### ๐Ÿ› Bug Fix + +- Expose allowed schema versions at `/info/` endpoint [#2625](https://github.com/dandi/dandi-archive/pull/2625) ([@candleindark](https://github.com/candleindark)) + +#### ๐Ÿ“ Documentation + +- Update devcontainer section in readme [#2639](https://github.com/dandi/dandi-archive/pull/2639) ([@mvandenburgh](https://github.com/mvandenburgh)) + +#### Authors: 2 + +- Isaac To ([@candleindark](https://github.com/candleindark)) +- Mike VanDenburgh ([@mvandenburgh](https://github.com/mvandenburgh)) + +--- + # v0.18.1 (Wed Nov 12 2025) #### ๐Ÿ› Bug Fix