Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
5f1ab9d
Supply a default name in createsuperuser
jjnesbitt Oct 27, 2025
751c34b
feat: expose allowed schema versions at `/info/` endpoint
candleindark Oct 30, 2025
de06943
Merge pull request #2616 from dandi/fix-superuser-missing-name
jjnesbitt Nov 4, 2025
622ee67
Update DOI settings check
jjnesbitt Nov 5, 2025
26bbd0b
Merge pull request #2634 from dandi/update-doi-configured-check
jjnesbitt Nov 6, 2025
e93b525
Remove test_rest_info test
jjnesbitt Nov 6, 2025
79cbdbc
Merge pull request #2635 from dandi/remove-test-rest-info
jjnesbitt Nov 6, 2025
7d3854d
Regression test for publish metadata bug
mvandenburgh Nov 10, 2025
5d89535
Fix bug in publishing process
mvandenburgh Nov 10, 2025
c825384
Merge pull request #2636 from dandi/fix-publish-bug
mvandenburgh Nov 12, 2025
f2ea29c
auto shipit - CHANGELOG.md etc
dandibot Nov 12, 2025
e7b09c9
Add celery/frontend commands to dev container docs
mvandenburgh Nov 12, 2025
0d67796
Make devcontainers the "recommended quickstart"
mvandenburgh Nov 12, 2025
87f7327
Add note about uv cache volume
mvandenburgh Nov 12, 2025
aee123b
Merge pull request #2639 from dandi/update-devcontainers-readme
mvandenburgh Nov 12, 2025
5c9bf25
Merge pull request #2625 from candleindark/add-allowed_schema_versions
jjnesbitt Nov 13, 2025
0a868ae
auto shipit - CHANGELOG.md etc
dandibot Nov 13, 2025
5e431b9
Merge pull request #140 from aplbrain/apl-setup
laurendiaz Nov 13, 2025
2b600ae
Merge commit '0a868aeb977c094bba9def4fcdebfe92ed72b2da' into 141-sync…
laurendiaz Nov 26, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,50 @@
# 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

- 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
Expand Down
42 changes: 24 additions & 18 deletions DEVELOPMENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,32 @@ 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. 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 <volume_name>` (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.
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 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`
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`
Expand Down Expand Up @@ -59,23 +82,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 `./manage.py runserver_plus 0.0.0.0:8000` from the VSCode built-in-terminal.
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:
Expand Down
1 change: 0 additions & 1 deletion dandiapi/api/apps.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
24 changes: 0 additions & 24 deletions dandiapi/api/checks.py

This file was deleted.

2 changes: 1 addition & 1 deletion dandiapi/api/doi.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
11 changes: 9 additions & 2 deletions dandiapi/api/management/commands/createsuperuser.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)
Expand All @@ -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
20 changes: 11 additions & 9 deletions dandiapi/api/services/publish/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from __future__ import annotations

import copy
import datetime
from typing import TYPE_CHECKING

Expand Down Expand Up @@ -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:
Expand Down
13 changes: 10 additions & 3 deletions dandiapi/api/tests/test_version.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
2 changes: 2 additions & 0 deletions dandiapi/api/views/info.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -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': [],
Expand Down
2 changes: 2 additions & 0 deletions dandiapi/settings/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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')

Expand Down
Loading