diff --git a/CLAUDE.md b/CLAUDE.md index ba6d50d266..ca80c161b4 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -79,3 +79,21 @@ The project uses Docker Compose for local development with services: - Test configuration uses separate settings (`pycon.settings.test`) - Ruff handles both linting and formatting for Python code - Biome handles linting and formatting for JavaScript/TypeScript + +## Local Development + +**IMPORTANT**: When running locally, all Python/Django commands must run inside Docker. The local virtual environment will not work. + +Use `docker exec pycon-backend-1` (without `-t` flag for non-interactive/script usage, with `-it` for interactive terminal). + +- **Start services**: `docker-compose up` (starts all services) +- **Run tests**: `docker exec pycon-backend-1 uv run pytest -l -s -vvv` +- **Single test**: `docker exec pycon-backend-1 uv run pytest path/to/test_file.py::test_function -l -s -vvv` +- **Lint/format**: `docker exec pycon-backend-1 uv run ruff check` and `docker exec pycon-backend-1 uv run ruff format` +- **Type checking**: `docker exec pycon-backend-1 uv run mypy .` +- **Django management**: `docker exec pycon-backend-1 uv run python manage.py ` +- **Migrations**: `docker exec pycon-backend-1 uv run python manage.py makemigrations` and `docker exec pycon-backend-1 uv run python manage.py migrate` + +**Troubleshooting**: If the backend container is not working: +1. Restart container: `docker restart pycon-backend-1` +2. If dependencies changed: Remove `backend/.venv` and rebuild with `docker-compose build --no-cache && docker-compose up` diff --git a/backend/conferences/migrations/0057_add_grants_waiting_list_update_deadline_type.py b/backend/conferences/migrations/0057_add_grants_waiting_list_update_deadline_type.py new file mode 100644 index 0000000000..89974c011a --- /dev/null +++ b/backend/conferences/migrations/0057_add_grants_waiting_list_update_deadline_type.py @@ -0,0 +1,56 @@ +# Generated by Django 5.1.4 on 2026-01-28 + +from django.db import migrations, models + + +def convert_custom_deadlines_to_grants_waiting_list_update(apps, schema_editor): + """ + Convert existing custom deadlines with name containing + 'Update Grants in Waiting List' to the new grants_waiting_list_update type. + """ + Deadline = apps.get_model("conferences", "Deadline") + + # Update custom deadlines that have "Update Grants in Waiting List" in their name + Deadline.objects.filter( + type="custom", name__contains={"en": "Update Grants in Waiting List"} + ).update(type="grants_waiting_list_update") + + +def revert_grants_waiting_list_update_to_custom(apps, schema_editor): + """ + Revert grants_waiting_list_update deadlines back to custom type. + """ + Deadline = apps.get_model("conferences", "Deadline") + Deadline.objects.filter(type="grants_waiting_list_update").update(type="custom") + + +class Migration(migrations.Migration): + + dependencies = [ + ("conferences", "0056_conference_max_proposals"), + ] + + operations = [ + migrations.AlterField( + model_name="deadline", + name="type", + field=models.CharField( + choices=[ + ("cfp", "Call for proposal"), + ("voting", "Voting"), + ("refund", "Ticket refund"), + ("grants", "Grants"), + ("badge_preview", "Badge preview"), + ("invitation_letter_request", "Invitation letter request"), + ("grants_waiting_list_update", "Grants waiting list update"), + ("custom", "Custom deadline"), + ], + max_length=256, + verbose_name="type", + ), + ), + migrations.RunPython( + convert_custom_deadlines_to_grants_waiting_list_update, + revert_grants_waiting_list_update_to_custom, + ), + ] diff --git a/backend/conferences/models/deadline.py b/backend/conferences/models/deadline.py index 69b7816144..c2e09b244c 100644 --- a/backend/conferences/models/deadline.py +++ b/backend/conferences/models/deadline.py @@ -23,6 +23,7 @@ class Deadline(TimeFramedModel): ("grants", _("Grants")), ("badge_preview", _("Badge preview")), ("invitation_letter_request", _("Invitation letter request")), + ("grants_waiting_list_update", _("Grants waiting list update")), ("custom", _("Custom deadline")), ) diff --git a/backend/grants/tasks.py b/backend/grants/tasks.py index b05f310cd8..9d79947c72 100644 --- a/backend/grants/tasks.py +++ b/backend/grants/tasks.py @@ -137,9 +137,17 @@ def _send_grant_waiting_list_email(grant_id, template_identifier): reply_url = urljoin(settings.FRONTEND_URL, "/grants/reply/") deadline = grant.conference.deadlines.filter( - type="custom", name__contains={"en": "Update Grants in Waiting List"} + type="grants_waiting_list_update" ).first() + if not deadline: + logger.error( + f"No grants_waiting_list_update deadline found for conference {grant.conference}" + ) + raise ValueError( + f"Conference {grant.conference.code} missing grants_waiting_list_update deadline" + ) + _new_send_grant_email( template_identifier=template_identifier, grant=grant, diff --git a/backend/grants/tests/test_tasks.py b/backend/grants/tests/test_tasks.py index fa01b67171..e70bfc0a84 100644 --- a/backend/grants/tests/test_tasks.py +++ b/backend/grants/tests/test_tasks.py @@ -70,11 +70,7 @@ def test_send_grant_reply_waiting_list_email(settings, sent_emails): DeadlineFactory( start=datetime(2023, 3, 1, 23, 59, tzinfo=timezone.utc), conference=conference, - type="custom", - name={ - "en": "Update Grants in Waiting List", - "it": "Update Grants in Waiting List", - }, + type="grants_waiting_list_update", ) grant = GrantFactory(conference=conference, user=user) @@ -432,11 +428,7 @@ def test_send_grant_reply_waiting_list_update_email(settings, sent_emails): DeadlineFactory( conference=grant.conference, start=datetime(2023, 3, 1, 23, 59, tzinfo=timezone.utc), - type="custom", - name={ - "en": "Update Grants in Waiting List", - "it": "Update Grants in Waiting List", - }, + type="grants_waiting_list_update", ) conference_name = grant.conference.name.localize("en") @@ -466,3 +458,11 @@ def test_send_grant_reply_waiting_list_update_email(settings, sent_emails): assert sent_email.placeholders["conference_name"] == conference_name assert sent_email.placeholders["grants_update_deadline"] == "1 March 2023" assert sent_email.placeholders["reply_url"] == "https://pycon.it/grants/reply/" + + +def test_send_grant_waiting_list_email_missing_deadline(): + grant = GrantFactory() + # No deadline created + + with pytest.raises(ValueError, match="missing grants_waiting_list_update deadline"): + send_grant_reply_waiting_list_email(grant_id=grant.id)