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
2 changes: 1 addition & 1 deletion .github/workflows/backend-checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ jobs:

steps:
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
- uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7.1.0
with:
python-version: "3.14.7"
- name: Install uv
Expand Down
4 changes: 3 additions & 1 deletion .github/workflows/backend-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ jobs:

steps:
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
- uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7.1.0
with:
python-version: "3.14.7"
- name: Install uv
Expand All @@ -46,6 +46,8 @@ jobs:
version: "0.12.5"
enable-cache: true
- run: pip install codecov
- name: Install system dependencies
run: sudo apt-get update && sudo apt-get install -y --no-install-recommends libharfbuzz-subset0

- name: Install python dependencies
if: steps.setup-uv.outputs.cache-hit != 'true'
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/claude.yml
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ jobs:
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
with:
fetch-depth: 1
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
- uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7.1.0
with:
python-version: "3.14.7"
- name: Install uv
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/graphql-schema.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ jobs:
with:
persist-credentials: false

- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
- uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7.1.0
with:
python-version: "3.14.7"

Expand Down
3 changes: 3 additions & 0 deletions backend/badge_scanner/admin.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
from django.contrib import admin

from custom_admin.admin import HTTPS_URL_FORMFIELD_OVERRIDES


from .models import BadgeScan


@admin.register(BadgeScan)
class BadgeScanAdmin(admin.ModelAdmin):
formfield_overrides = HTTPS_URL_FORMFIELD_OVERRIDES
autocomplete_fields = (
"scanned_by",
"scanned_user",
Expand Down
2 changes: 2 additions & 0 deletions backend/conferences/admin/conference.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
Topic,
)
from conferences.admin.views import grants_summary
from custom_admin.admin import HTTPS_URL_FORMFIELD_OVERRIDES
from .actions import (
clone_conference,
create_conference_vouchers_on_pretix,
Expand Down Expand Up @@ -110,6 +111,7 @@ class ConferenceAdmin(
OrderedInlineModelAdminMixin,
admin.ModelAdmin,
):
formfield_overrides = HTTPS_URL_FORMFIELD_OVERRIDES
list_display = (
"name",
"code",
Expand Down
11 changes: 11 additions & 0 deletions backend/custom_admin/admin.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from django.db import models
from django.db.models import F
from django.contrib import messages
from functools import wraps
Expand All @@ -13,6 +14,16 @@
admin.site.site_header = SITE_NAME
admin.site.site_title = SITE_NAME

HTTPS_URL_FORMFIELD_OVERRIDES = {models.URLField: {"assume_scheme": "https"}}


def formfield_with_https_urls(db_field, **kwargs):
# ModelAdmin replaces a form's formfield_callback with its own, so admins
# also need HTTPS_URL_FORMFIELD_OVERRIDES
if isinstance(db_field, models.URLField):
kwargs.setdefault("assume_scheme", "https")
return db_field.formfield(**kwargs)


class CustomIndexLinks(admin.ModelAdmin):
def get_index_links(self) -> list:
Expand Down
31 changes: 31 additions & 0 deletions backend/custom_admin/tests/test_admin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import pytest
from django import forms
from django.contrib import admin

pytestmark = pytest.mark.django_db


def test_admin_url_fields_default_to_https(rf, admin_superuser):
request = rf.get("/")
request.user = admin_superuser
form_classes = {}
for model_admin in admin.site._registry.values():
form_classes[type(model_admin).__name__] = model_admin.get_form(
request, fields=None
)
for inline in model_admin.get_inline_instances(request):
form_classes[type(inline).__name__] = inline.get_formset(
request, fields=None
).form

cleaned_urls = {
f"{admin_name}.{field_name}": field.clean("example.com")
for admin_name, form_class in form_classes.items()
for field_name, field in form_class.base_fields.items()
if isinstance(field, forms.URLField)
}

assert cleaned_urls
assert {
name: url for name, url in cleaned_urls.items() if url != "https://example.com"
} == {}
3 changes: 3 additions & 0 deletions backend/events/admin.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
from django.contrib import admin

from custom_admin.admin import HTTPS_URL_FORMFIELD_OVERRIDES

from .models import Event


@admin.register(Event)
class EventAdmin(admin.ModelAdmin):
formfield_overrides = HTTPS_URL_FORMFIELD_OVERRIDES
list_display = ("title", "start", "end", "conference")
fieldsets = (
(
Expand Down
3 changes: 3 additions & 0 deletions backend/google_api/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from django.shortcuts import redirect
from django.urls import path, reverse
from import_export.admin import ImportExportModelAdmin
from custom_admin.admin import HTTPS_URL_FORMFIELD_OVERRIDES
from google_api.models import GoogleCloudOAuthCredential, GoogleCloudToken
import google_auth_oauthlib.flow
from django.utils.safestring import mark_safe
Expand All @@ -14,6 +15,7 @@


class GoogleCloudTokenInline(admin.StackedInline):
formfield_overrides = HTTPS_URL_FORMFIELD_OVERRIDES
model = GoogleCloudToken
autocomplete_fields = ("admin_user",)
extra = 0
Expand All @@ -24,6 +26,7 @@ def has_add_permission(self, request, obj=None):

@admin.register(GoogleCloudOAuthCredential)
class GoogleCloudOAuthCredentialAdmin(ImportExportModelAdmin):
formfield_overrides = HTTPS_URL_FORMFIELD_OVERRIDES
inlines = [GoogleCloudTokenInline]

def get_list_display(self, request):
Expand Down
11 changes: 11 additions & 0 deletions backend/grants/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
from countries import countries
from countries.filters import CountryFilter
from custom_admin.admin import (
HTTPS_URL_FORMFIELD_OVERRIDES,
confirm_pending_status,
reset_pending_status_back_to_status,
validate_single_conference_selection,
Expand Down Expand Up @@ -71,6 +72,14 @@
"departure_country",
"conference__code",
"created",
"age_group",
"email",
"has_sent_submission",
"submission_title",
"submission_tags",
"submission_admin_link",
"submission_pycon_link",
"grant_admin_link",
)


Expand Down Expand Up @@ -510,6 +519,7 @@ def delete_model(self, request, obj):

@admin.register(Grant)
class GrantAdmin(ExportMixin, ConferencePermissionMixin, admin.ModelAdmin):
formfield_overrides = HTTPS_URL_FORMFIELD_OVERRIDES
change_list_template = "admin/grants/grant/change_list.html"
resource_classes = [GrantResource]
list_display = (
Expand Down Expand Up @@ -799,6 +809,7 @@ class Media:

@admin.register(GrantConfirmPendingStatusProxy)
class GrantConfirmPendingStatusProxyAdmin(admin.ModelAdmin):
formfield_overrides = HTTPS_URL_FORMFIELD_OVERRIDES
list_display = (
"id",
"full_name",
Expand Down
18 changes: 18 additions & 0 deletions backend/grants/tests/test_admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@
GrantReimbursementFactory,
)
from grants.models import Grant, GrantReimbursement
from i18n.strings import LazyI18nString
from submissions.tests.factories import SubmissionFactory

pytestmark = pytest.mark.django_db

Expand Down Expand Up @@ -887,6 +889,22 @@ def test_export_includes_one_column_per_form_question():
assert rows[1]["First time?"] == ""


def test_export_includes_applicant_and_submission_columns():
conference = ConferenceFactory()
grant = GrantFactory(conference=conference)
SubmissionFactory(
conference=conference,
speaker=grant.user,
title=LazyI18nString({"en": "My talk"}),
)

row = _export_grants(conference).dict[0]

assert row["email"] == grant.user.email
assert row["has_sent_submission"] == "TRUE"
assert row["submission_title"] == "My talk"


def test_export_without_a_form_has_no_extra_columns():
conference = ConferenceFactory()
GrantFactory(conference=conference)
Expand Down
6 changes: 6 additions & 0 deletions backend/participants/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,23 @@
from django.contrib import admin
from django.utils.safestring import mark_safe

from custom_admin.admin import (
HTTPS_URL_FORMFIELD_OVERRIDES,
formfield_with_https_urls,
)
from participants.models import Participant


class ParticipantForm(forms.ModelForm):
class Meta:
model = Participant
formfield_callback = formfield_with_https_urls
fields = "__all__"


@admin.register(Participant)
class ParticipantAdmin(admin.ModelAdmin):
formfield_overrides = HTTPS_URL_FORMFIELD_OVERRIDES
form = ParticipantForm
search_fields = ("user__email", "user__full_name")
list_display = (
Expand Down
4 changes: 2 additions & 2 deletions backend/pretix/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -568,8 +568,8 @@ def create_order(conference: Conference, order_data: CreateOrderInput) -> Order:
)

if response.status_code != 200:
with sentry_sdk.push_scope() as scope:
scope.user = {"email": order_data.email}
with sentry_sdk.new_scope() as scope:
scope.set_user({"email": order_data.email})
scope.set_extra("order-code", data["code"])
scope.set_extra("pec", order_data.invoice_information.pec)
scope.set_extra(
Expand Down
3 changes: 3 additions & 0 deletions backend/pycon/settings/test.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@
if "debug_toolbar.middleware.DebugToolbarMiddleware" in MIDDLEWARE:
MIDDLEWARE.remove("debug_toolbar.middleware.DebugToolbarMiddleware")

# collectstatic never runs in tests, so STATIC_ROOT does not exist
MIDDLEWARE.remove("whitenoise.middleware.WhiteNoiseMiddleware")

SECRET_KEY = "this-key-should-only-be-used-for-tests"
HASHID_DEFAULT_SECRET_SALT = "only-for-tests"

Expand Down
2 changes: 1 addition & 1 deletion backend/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ text = "MIT"
dev = [
"mypy<2.0.0,>=1.2.0",
"pytest==9.0.3",
"pytest-cov==4.1.0",
"pytest-cov==7.1.0",
"pytest-django==4.6.0",
"factory-boy==3.3.3",
"pdbpp<1.0.0,>=0.10.3",
Expand Down
10 changes: 9 additions & 1 deletion backend/schedule/admin.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
from django.db import transaction
from custom_admin.admin import validate_single_conference_selection
from custom_admin.admin import (
HTTPS_URL_FORMFIELD_OVERRIDES,
formfield_with_https_urls,
validate_single_conference_selection,
)
from import_export.resources import ModelResource
from django.db.models import Count, Prefetch
from typing import Dict
Expand Down Expand Up @@ -281,6 +285,7 @@ def __init__(self, *args, **kwargs):

class Meta:
model = ScheduleItem
formfield_callback = formfield_with_https_urls
fields = (
"conference",
"type",
Expand Down Expand Up @@ -308,6 +313,7 @@ class Meta:

@admin.register(ScheduleItem)
class ScheduleItemAdmin(ConferencePermissionMixin, admin.ModelAdmin):
formfield_overrides = HTTPS_URL_FORMFIELD_OVERRIDES
list_display = (
"title",
"conference",
Expand Down Expand Up @@ -618,6 +624,7 @@ class Meta:

@admin.register(ScheduleItemInvitation)
class ScheduleItemInvitationAdmin(ExportMixin, admin.ModelAdmin):
formfield_overrides = HTTPS_URL_FORMFIELD_OVERRIDES
resource_classes = [ScheduleItemInvitationResource]
list_display = (
"slot",
Expand Down Expand Up @@ -711,6 +718,7 @@ class RoomAdmin(OrderedModelAdmin):


class DayRoomThroughModelInline(OrderedTabularInline):
formfield_overrides = HTTPS_URL_FORMFIELD_OVERRIDES
model = DayRoomThroughModel
fields = ("room", "order", "move_up_down_links", "streaming_url", "slido_url")
readonly_fields = (
Expand Down
3 changes: 3 additions & 0 deletions backend/sponsors/admin.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
from django.contrib import admin
from ordered_model.admin import OrderedModelAdmin

from custom_admin.admin import HTTPS_URL_FORMFIELD_OVERRIDES

from .models import (
Sponsor,
SponsorLead,
Expand All @@ -13,6 +15,7 @@

@admin.register(Sponsor)
class SponsorAdmin(OrderedModelAdmin):
formfield_overrides = HTTPS_URL_FORMFIELD_OVERRIDES
list_display = ("name", "move_up_down_links")
list_filter = (
"levels__conference",
Expand Down
6 changes: 6 additions & 0 deletions backend/submissions/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
from grants.tasks import get_name
from notifications.models import EmailTemplate, EmailTemplateIdentifier
from custom_admin.admin import (
HTTPS_URL_FORMFIELD_OVERRIDES,
confirm_pending_status,
formfield_with_https_urls,
reset_pending_status_back_to_status,
validate_single_conference_selection,
)
Expand Down Expand Up @@ -139,6 +141,7 @@ class Meta:
class SubmissionAdminForm(forms.ModelForm):
class Meta:
model = Submission
formfield_callback = formfield_with_https_urls
fields = [
"title",
"slug",
Expand Down Expand Up @@ -208,13 +211,15 @@ def send_proposal_in_waiting_list_email_action(modeladmin, request, queryset):


class ProposalMaterialInline(admin.TabularInline):
formfield_overrides = HTTPS_URL_FORMFIELD_OVERRIDES
model = ProposalMaterial
extra = 0
autocomplete_fields = ("file",)


@admin.register(Submission)
class SubmissionAdmin(ExportMixin, ConferencePermissionMixin, admin.ModelAdmin):
formfield_overrides = HTTPS_URL_FORMFIELD_OVERRIDES
resource_classes = [SubmissionResource]
form = SubmissionAdminForm
list_display = (
Expand Down Expand Up @@ -387,6 +392,7 @@ def apply_and_notify_status_change(modeladmin, request, queryset):

@admin.register(SubmissionConfirmPendingStatusProxy)
class SubmissionConfirmPendingStatusProxyAdmin(admin.ModelAdmin):
formfield_overrides = HTTPS_URL_FORMFIELD_OVERRIDES
list_display = (
"id",
"title",
Expand Down
Loading
Loading