Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 3 additions & 0 deletions .github/workflows/frontend-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,9 @@ jobs:
- name: Apply migrations to API server
run: ./manage.py migrate

- name: Create any cache tables
run: ./manage.py createcachetable

- name: Install test data
run: ./manage.py loaddata playwright

Expand Down
3 changes: 3 additions & 0 deletions DEVELOPMENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ This is the simplest configuration for developers to start with.

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. Run `docker compose run --rm django ./manage.py createcachetable`
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.
Expand All @@ -37,6 +38,7 @@ This configuration also uses containers, but with Docker Compose instead of VSco
### Initial Setup
1. Install [Docker Compose](https://docs.docker.com/compose/install/)
1. Run `docker compose run --rm django ./manage.py migrate`
1. Run `docker compose run --rm django ./manage.py createcachetable`
1. Run `docker compose run --rm django ./manage.py createsuperuser --email $(git config user.email)`
and follow the prompts to create your own user.
This sets your username to your git email to ensure parity with how GitHub logins work. You can also replace the command substitution expression with a literal email address, or omit the `--email` option entirely to run the command in interactive mode.
Expand Down Expand Up @@ -67,6 +69,7 @@ but allows developers to run Python code on their native system.
1. [Install `uv`](https://docs.astral.sh/uv/getting-started/installation/)
1. Run `export UV_ENV_FILE=./dev/.env.docker-compose-native`
1. Run `./manage.py migrate`
1. Run `docker compose run --rm django ./manage.py createcachetable`
1. Run `./manage.py createsuperuser --email $(git config user.email)` and follow the prompts.
1. Run `./manage.py create_dev_dandiset --owner $(git config user.email)`
to create a dummy dandiset to start working with.
Expand Down
2 changes: 2 additions & 0 deletions dandiapi/api/tests/test_info.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
from __future__ import annotations

from dandischema.conf import get_instance_config
import pytest


@pytest.mark.django_db
def test_rest_info_instance_config_include_none(api_client):
resp = api_client.get('/api/info/')
assert resp.status_code == 200
Expand Down
2 changes: 2 additions & 0 deletions dandiapi/api/tests/test_schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
PublishedAsset,
],
)
@pytest.mark.django_db
def test_schema_latest(api_client, model: CommonModel):
"""Test that the schema endpoints return valid schemas."""
resp = api_client.get('/api/schemas/', {'model': model.__name__})
Expand All @@ -30,6 +31,7 @@ def test_schema_latest(api_client, model: CommonModel):
assert schema == expected_schema


@pytest.mark.django_db
def test_schema_unsupported_model(api_client):
"""Test that the schema endpoint returns an error when passed invalid choice."""
resp = api_client.get('/api/schemas/', {'model': 'NotAValidModel'})
Expand Down
14 changes: 14 additions & 0 deletions dandiapi/api/throttling.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
from __future__ import annotations

from rest_framework.throttling import UserRateThrottle


# This is not currently used, but if we ever choose to rate limit logged-in users,
# this is how we can accomplish that, without applying it to admins.
class DandiUserRateThrottle(UserRateThrottle):
def get_cache_key(self, request, view):
# Don't rate limit admin users
if request.user and (request.user.is_staff or request.user.is_superuser):
return None

return super().get_cache_key(request, view)
19 changes: 19 additions & 0 deletions dandiapi/settings/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from datetime import timedelta
import logging
from pathlib import Path
import sys
from typing import TYPE_CHECKING, cast
from urllib.parse import urlunparse

Expand Down Expand Up @@ -147,6 +148,24 @@
REST_FRAMEWORK['DEFAULT_PAGINATION_CLASS'] = 'dandiapi.api.views.pagination.DandiPagination'
REST_FRAMEWORK['EXCEPTION_HANDLER'] = 'dandiapi.drf_utils.rewrap_django_core_exceptions'

# Throttling configuration
REST_FRAMEWORK['DEFAULT_THROTTLE_CLASSES'] = [
'rest_framework.throttling.AnonRateThrottle',
]
# By default, set request rate limit to a very high number, effectively disabling it.
# This is done to preserve the rate limiting behavior between dev and prod,
# without actually impeding developer experience.
REST_FRAMEWORK['DEFAULT_THROTTLE_RATES'] = {
'anon': f'{sys.maxsize}/minute',
}

CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.db.DatabaseCache',
'LOCATION': 'dandi_cache_table',
}
}

REST_FRAMEWORK_EXTENSIONS = {'DEFAULT_PARENT_LOOKUP_KWARG_NAME_PREFIX': ''}

# Clearing out the stock `SWAGGER_SETTINGS` variable causes a Django login
Expand Down
6 changes: 6 additions & 0 deletions dandiapi/settings/production.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,12 @@
},
}

# In production, enable rate limiting for unauthenticated users
REST_FRAMEWORK['DEFAULT_THROTTLE_RATES'] = {
'anon': '500/minute',
}


DANDI_DEV_EMAIL: str = env.str('DJANGO_DANDI_DEV_EMAIL')
DANDI_ADMIN_EMAIL: str = env.str('DJANGO_DANDI_ADMIN_EMAIL')

Expand Down
Loading