Skip to content

Commit bb359d7

Browse files
copilot suggestions and public decals
1 parent 797b3b9 commit bb359d7

12 files changed

Lines changed: 73 additions & 33 deletions

File tree

app/eventyay/agenda/apps.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
1+
import logging
12
from contextlib import suppress
23

34
from django.apps import AppConfig
45

6+
LOGGER = logging.getLogger(__name__)
7+
58

69
class AgendaConfig(AppConfig):
710
name = 'eventyay.agenda'
@@ -10,7 +13,6 @@ def ready(self):
1013
from .phrases import AgendaPhrases # noqa
1114
from eventyay.schedule.signals import schedule_release
1215

13-
@schedule_release.connect
1416
def on_schedule_release(sender, schedule, **kwargs):
1517
from django.core.cache import cache
1618

@@ -31,7 +33,9 @@ def on_schedule_release(sender, schedule, **kwargs):
3133
from eventyay.agenda.tasks import warm_schedule_caches
3234
warm_schedule_caches.apply_async(kwargs={'schedule_pk': schedule.pk}, countdown=3)
3335
except Exception:
34-
pass
36+
LOGGER.exception('Failed to enqueue warm_schedule_caches for schedule pk=%s', schedule.pk)
37+
38+
schedule_release.connect(on_schedule_release, dispatch_uid='agenda.on_schedule_release')
3539

3640

3741
with suppress(ImportError):

app/eventyay/agenda/tasks.py

Lines changed: 34 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import json
22
import logging
33

4+
from django.core.cache import cache
45
from django_scopes import scope, scopes_disabled
56
from i18nfield.utils import I18nJSONEncoder
67

@@ -9,6 +10,8 @@
910

1011
LOGGER = logging.getLogger(__name__)
1112

13+
_CACHE_TTL = 600
14+
1215

1316
@app.task(name='pretalx.agenda.export_schedule_html')
1417
def export_schedule_html(*, event_id: int, make_zip=True):
@@ -33,15 +36,20 @@ def export_schedule_html(*, event_id: int, make_zip=True):
3336

3437
@app.task(name='eventyay.agenda.warm_schedule_caches')
3538
def warm_schedule_caches(*, schedule_pk: int):
36-
"""Pre-build and cache non-enriched schedule JSON for the newly released schedule.
39+
"""Pre-build and cache schedule JSON payloads for the newly released schedule.
3740
38-
Called shortly after schedule_release so the first real user request hits
39-
a warm cache instead of paying the full build_data cost.
40-
"""
41-
from django.core.cache import cache
41+
Warms non-enriched, enriched, and exporters caches so the first real user
42+
request after a schedule publish hits a warm cache instead of paying the
43+
full build_data cost.
4244
45+
Note: Schedule and agenda.views.utils are imported locally to break the
46+
circular import chain: base.models.schedule → agenda.tasks → base.models.
47+
"""
48+
# Local imports required to break circular dependency:
49+
# base.models.schedule imports export_schedule_html from this module,
50+
# so module-level imports of Schedule or agenda.views.utils create a cycle.
51+
from eventyay.agenda.views.utils import build_public_schedule_exporters, escape_json_for_script
4352
from eventyay.base.models import Schedule
44-
from eventyay.agenda.views.utils import escape_json_for_script
4553

4654
with scopes_disabled():
4755
schedule = (
@@ -55,14 +63,26 @@ def warm_schedule_caches(*, schedule_pk: int):
5563
with scope(event=schedule.event):
5664
for featured in (True, False):
5765
try:
58-
data = schedule.build_data(
59-
all_talks=False,
60-
enrich=False,
61-
include_featured_speaker_metadata=featured,
62-
)
63-
result = escape_json_for_script(json.dumps(data, cls=I18nJSONEncoder))
64-
cache.set(f'eagenda:schedule:{schedule.pk}:{int(featured)}', result, 300)
66+
non_enriched = escape_json_for_script(json.dumps(
67+
schedule.build_data(all_talks=False, enrich=False, include_featured_speaker_metadata=featured),
68+
cls=I18nJSONEncoder,
69+
))
70+
cache.set(f'eagenda:schedule:{schedule.pk}:{int(featured)}', non_enriched, _CACHE_TTL)
6571
except Exception:
66-
LOGGER.exception('Failed to warm non-enriched cache for schedule %s', schedule.pk)
72+
LOGGER.exception('Failed to warm non-enriched cache for schedule %s (featured=%s)', schedule.pk, featured)
73+
74+
try:
75+
enriched = escape_json_for_script(json.dumps(
76+
schedule.build_data(all_talks=False, enrich=True, include_featured_speaker_metadata=featured),
77+
cls=I18nJSONEncoder,
78+
))
79+
cache.set(f'eagenda:enriched:{schedule.pk}:{int(featured)}', enriched, _CACHE_TTL)
80+
except Exception:
81+
LOGGER.exception('Failed to warm enriched cache for schedule %s (featured=%s)', schedule.pk, featured)
82+
83+
try:
84+
build_public_schedule_exporters(schedule.event, version=schedule.version)
85+
except Exception:
86+
LOGGER.exception('Failed to warm exporters cache for schedule %s', schedule.pk)
6787

6888
LOGGER.info('Pre-warmed schedule caches for schedule pk=%s version=%s', schedule.pk, schedule.version)

app/eventyay/agenda/views/schedule.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -352,7 +352,7 @@ def get_context_data(self, **kwargs):
352352
meta_json = escape_json_for_script(json.dumps(meta))
353353
ctx['schedule_meta_json'] = meta_json
354354
if version:
355-
cache.set(meta_cache_key, meta_json, 300)
355+
cache.set(meta_cache_key, meta_json, 600)
356356
return ctx
357357

358358

app/eventyay/agenda/views/utils.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ def build_enriched_schedule_json(request: HttpRequest) -> str:
6161
result = escape_json_for_script(json.dumps(data, cls=I18nJSONEncoder))
6262

6363
if schedule.version:
64-
cache.set(cache_key, result, 300)
64+
cache.set(cache_key, result, 600)
6565
return result
6666

6767

@@ -92,7 +92,7 @@ def build_schedule_json(request: HttpRequest, schedule=None) -> str:
9292
result = escape_json_for_script(json.dumps(data, cls=I18nJSONEncoder))
9393

9494
if schedule.version:
95-
cache.set(cache_key, result, 300)
95+
cache.set(cache_key, result, 600)
9696
return result
9797

9898

@@ -122,7 +122,7 @@ def build_talk_schedule_json(request: HttpRequest, submission_code: str) -> str:
122122
result = escape_json_for_script(json.dumps(data, cls=I18nJSONEncoder))
123123

124124
if schedule.version:
125-
cache.set(cache_key, result, 300)
125+
cache.set(cache_key, result, 600)
126126
return result
127127

128128

@@ -186,7 +186,7 @@ def build_speaker_schedule_json(request: HttpRequest, speaker_code: str) -> str:
186186
result = escape_json_for_script(json.dumps(data, cls=I18nJSONEncoder))
187187

188188
if schedule.version:
189-
cache.set(cache_key, result, 300)
189+
cache.set(cache_key, result, 600)
190190
return result
191191

192192

@@ -361,7 +361,7 @@ def build_public_schedule_exporters(event, version=None):
361361
'qrcode_svg': str(exporter.get_qrcode()) if getattr(exporter, 'show_qrcode', False) else '',
362362
}
363363
)
364-
cache.set(cache_key, result, 300)
364+
cache.set(cache_key, result, 600)
365365
return result
366366

367367

app/eventyay/api/auth/permission.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -53,16 +53,16 @@ def _set_eventpermset(request, perm_holder):
5353
)
5454

5555
def _has_event_permission(
56-
self, request, perm_holder, required_permission, event_slug, organizer_slug=None
56+
self, request, perm_holder, required_permission, event_slug, organizer_slug=None, *, allow_public_read=False
5757
):
5858
request.event = self._resolve_event(event_slug, organizer_slug=organizer_slug)
5959
if not request.event:
6060
return False
6161

6262
request.organizer = request.event.organizer
6363

64-
# Allow public access for safe methods if no specific permission is required
65-
if request.method in SAFE_METHODS and not required_permission:
64+
# Allow public read-only access only for endpoints that explicitly opt in.
65+
if allow_public_read and request.method in SAFE_METHODS and not required_permission:
6666
request.eventpermset = set()
6767
return True
6868

@@ -76,9 +76,10 @@ def _has_event_permission(
7676

7777
def has_permission(self, request, view):
7878
required_permission = self._get_required_permission(request, view)
79+
allow_public_read = getattr(view, 'allow_public_read', False)
7980

8081
if not request.user.is_authenticated and not isinstance(request.auth, (Device, TeamAPIToken)):
81-
if request.method not in SAFE_METHODS or required_permission:
82+
if request.method not in SAFE_METHODS or required_permission or not allow_public_read:
8283
return False
8384

8485
if request.user.is_authenticated:
@@ -99,9 +100,12 @@ def has_permission(self, request, view):
99100
required_permission,
100101
event_slug=kwargs['event'],
101102
organizer_slug=kwargs.get('organizer'),
103+
allow_public_read=allow_public_read,
102104
):
103105
return False
104106
elif 'organizer' in kwargs:
107+
if not request.user.is_authenticated and not isinstance(request.auth, (Device, TeamAPIToken)):
108+
return False
105109
if not request.organizer or not perm_holder.has_organizer_permission(request.organizer, request=request):
106110
return False
107111
if isinstance(perm_holder, User) and perm_holder.has_active_staff_session(request.session.session_key):

app/eventyay/api/views/speaker.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,7 @@ class SpeakerViewSet(
125125
lookup_field = 'user__code__iexact'
126126
search_fields = ('user__fullname', 'user__email')
127127
endpoint = 'speakers'
128+
allow_public_read = True
128129
filter_backends = (SpeakerSearchFilter, DjangoFilterBackend)
129130

130131
def get_legacy_serializer_class(self): # pragma: no cover

app/eventyay/api/views/submission.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,7 @@ class SubmissionViewSet(PretalxViewSetMixin, viewsets.ModelViewSet):
259259
lookup_field = 'code__iexact'
260260
search_fields = ('title', 'speakers__fullname')
261261
filterset_class = SubmissionFilter
262+
allow_public_read = True
262263
permission_map = {
263264
'make_submitted': 'submission.state_change_submission',
264265
'add_speaker': 'submission.update_submission',

app/eventyay/webapp/schedule-editor/vite.config.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ export default {
4141
emptyOutDir: true,
4242
manifest: 'schedule-editor-manifest.json',
4343
assetsDir: '',
44-
sourcemap: true,
44+
sourcemap: false,
4545
rollupOptions: {
4646
input: 'src/main.ts',
4747
output: {

app/eventyay/webapp/schedule/src/components/SessionModal.vue

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,19 @@ dialog.pretalx-modal#session-modal(ref="modal", @click.stop="close()")
4343
img(v-if="answer.question.icon && remoteApiUrl", :src="`${remoteApiUrl}questions/${answer.question.id}/icon/`", :alt="getLocalizedString(answer.question.question)", width="16", height="16")
4444
span(v-else) {{ getLocalizedString(answer.question.question) }}
4545
.inline-answer(v-for="answer in shortAnswers", :key="answer.id")
46-
span.question
47-
strong {{ getLocalizedString(answer.question.question) }}:
48-
span.answer(v-if="answer.question.variant === 'file'")
46+
template(v-if="answer.question.variant === 'url' && answer.answer")
47+
strong.question
48+
a(:href="answer.answer", target="_blank", rel="noopener noreferrer") {{ getLocalizedString(answer.question.question) }}
49+
template(v-else)
50+
span.question
51+
strong {{ getLocalizedString(answer.question.question) }}:
52+
span.answer(v-if="answer.question.variant === 'file'")
53+
i.fa.fa-file-o
54+
a(v-if="answer.answer_file", :href="answer.answer_file.url") {{ answer.answer_file }}
55+
span(v-else) {{ t.no_file_provided }}
56+
span.answer(v-else-if="answer.question.variant === 'boolean'") {{ answer.answer ? t.yes : t.no }}
57+
span.answer(v-else-if="answer.answer", v-html="renderRichText(answer.answer)")
58+
span.answer(v-else) {{ t.no_response }}
4959
.downloads(v-if="displayResources.length > 0")
5060
hr
5161
h4 {{ t.downloads }}

app/eventyay/webapp/schedule/src/components/TalkDetail.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -274,7 +274,7 @@ export default {
274274
if (this.remoteApiUrl) return this.remoteApiUrl
275275
if (!this.baseUrl) return null
276276
try {
277-
const url = new URL(this.baseUrl)
277+
const url = new URL(this.baseUrl, window.location.origin)
278278
const segments = url.pathname.split('/').filter(s => s.length > 0)
279279
const slug = segments[segments.length - 1] || ''
280280
return `${url.origin}/api/v1/events/${slug}/`

0 commit comments

Comments
 (0)