Feat: Import schedule and speakers #2629
Conversation
Reviewer's GuideAdds a two-step CSV-based import flow for speakers and sessions in the organizer UI, wiring new upload & mapping forms to Celery-backed import tasks that upsert users, speaker profiles, submissions, links, and metadata based on configurable column mappings stored in event settings. Sequence diagram for the new speaker CSV import flowsequenceDiagram
actor Organizer
participant Browser
participant SpeakerImportView
participant CachedFile
participant SpeakerImportProcessView
participant CeleryBroker
participant import_speakers
participant Database
Organizer->>Browser: Open speakers_import URL
Browser->>SpeakerImportView: GET speakers/import/
SpeakerImportView-->>Browser: Render CSVImportForm
Organizer->>Browser: Select CSV and submit form
Browser->>SpeakerImportView: POST speakers/import/ (file)
SpeakerImportView->>CachedFile: create(expires, file, filename speaker_import.csv)
SpeakerImportView-->>Browser: Redirect to speakers/import/<file_id>/
Organizer->>Browser: Follow redirect
Browser->>SpeakerImportProcessView: GET speakers/import/<file_id>/
SpeakerImportProcessView->>CachedFile: get(file_id, filename speaker_import.csv)
SpeakerImportProcessView->>Database: parse_csv(file)
SpeakerImportProcessView-->>Browser: Render SpeakerImportProcessForm + preview
Organizer->>Browser: Submit column mapping
Browser->>SpeakerImportProcessView: POST speakers/import/<file_id>/ (mapping)
SpeakerImportProcessView->>Database: Save event.settings.speaker_import_settings
SpeakerImportProcessView->>CeleryBroker: Enqueue task import_speakers(event_id, file_id, settings, locale, user_id)
SpeakerImportProcessView-->>Browser: Render AsyncAction polling page
loop Until task finished
Browser->>SpeakerImportProcessView: GET speakers/import/<file_id>/?async_id=...
SpeakerImportProcessView->>CeleryBroker: Check task state
CeleryBroker-->>SpeakerImportProcessView: Task pending or finished
SpeakerImportProcessView-->>Browser: Progress or redirect
end
CeleryBroker->>import_speakers: Run task
import_speakers->>CachedFile: get(file_id)
import_speakers->>Database: parse_csv(file)
loop For each CSV row
import_speakers->>Database: Upsert User, SpeakerProfile, SpeakerRole
import_speakers->>Database: event.log_action eventyay.speaker.imported
end
import_speakers->>CachedFile: delete(file)
import_speakers-->>CeleryBroker: Task success
SpeakerImportProcessView-->>Browser: Redirect to speakers list on success
Sequence diagram for the new session CSV import flowsequenceDiagram
actor Organizer
participant Browser
participant SubmissionImportView
participant CachedFile
participant SubmissionImportProcessView
participant CeleryBroker
participant import_submissions
participant Database
Organizer->>Browser: Open submissions_import URL
Browser->>SubmissionImportView: GET submissions/import/
SubmissionImportView-->>Browser: Render CSVImportForm
Organizer->>Browser: Select CSV and submit form
Browser->>SubmissionImportView: POST submissions/import/ (file)
SubmissionImportView->>CachedFile: create(expires, file, filename session_import.csv)
SubmissionImportView-->>Browser: Redirect to submissions/import/<file_id>/
Organizer->>Browser: Follow redirect
Browser->>SubmissionImportProcessView: GET submissions/import/<file_id>/
SubmissionImportProcessView->>CachedFile: get(file_id, filename session_import.csv)
SubmissionImportProcessView->>Database: parse_csv(file)
SubmissionImportProcessView-->>Browser: Render SessionImportProcessForm + preview
Organizer->>Browser: Submit column mapping
Browser->>SubmissionImportProcessView: POST submissions/import/<file_id>/ (mapping)
SubmissionImportProcessView->>Database: Save event.settings.submission_import_settings
SubmissionImportProcessView->>CeleryBroker: Enqueue task import_submissions(event_id, file_id, settings, locale, user_id)
SubmissionImportProcessView-->>Browser: Render AsyncAction polling page
loop Until task finished
Browser->>SubmissionImportProcessView: GET submissions/import/<file_id>/?async_id=...
SubmissionImportProcessView->>CeleryBroker: Check task state
CeleryBroker-->>SubmissionImportProcessView: Task pending or finished
SubmissionImportProcessView-->>Browser: Progress or redirect
end
CeleryBroker->>import_submissions: Run task
import_submissions->>CachedFile: get(file_id)
import_submissions->>Database: parse_csv(file)
loop For each CSV row
import_submissions->>Database: Upsert Submission, SpeakerRole, SpeakerProfile, Room assignment, Tags, Answers
import_submissions->>Database: event.log_action eventyay.submission.imported
end
import_submissions->>CachedFile: delete(file)
import_submissions-->>CeleryBroker: Task success
SubmissionImportProcessView-->>Browser: Redirect to submissions list on success
Class diagram for new CSV import forms, helpers, and viewsclassDiagram
class CSVImportForm {
+FileField file
+clean_file()
}
class ImportField {
+str identifier
+str label
+bool required
+str help_text
+list~str~ suggestions
+Iterable static_choices
}
class SpeakerImportProcessForm {
-list~str~ headers
-Event event
-dict~str,str~ _initial_data
+__init__(*args, headers, event, initial, **kwargs)
-_find_suggestion(field_spec)
+clean()
}
class SessionImportProcessForm {
-list~str~ headers
-Event event
-dict~str,str~ _initial_data
+__init__(*args, headers, event, initial, **kwargs)
-_find_suggestion(field_spec)
-_add_question_fields()
+clean()
}
class SpeakerImportView {
+permission_required
+template_name
+form_class
+form_valid(form)
}
class SpeakerImportProcessView {
+permission_required
+template_name
+form_class
+task
+known_errortypes
+file
+parsed
+get_form_kwargs()
+preview_rows()
+headers()
+get(request, *args, **kwargs)
+form_valid(form)
+get_success_url(value)
+get_error_url()
+get_success_message(value)
}
class SubmissionImportView {
+permission_required
+template_name
+form_class
+form_valid(form)
}
class SubmissionImportProcessView {
+permission_required
+template_name
+form_class
+task
+known_errortypes
+file
+parsed
+get_form_kwargs()
+preview_rows()
+headers()
+get(request, *args, **kwargs)
+form_valid(form)
+get_success_url(value)
+get_error_url()
+get_success_message(value)
}
class ImportExecutionError {
}
class ProfiledEventTask {
}
class ImportTasksModule {
+import_speakers(event, fileid, settings, locale, user)
+import_submissions(event, fileid, settings, locale, user)
+_import_speaker_row(event, settings, record, acting_user)
+_import_submission_row(event, settings, record, acting_user)
+_resolve_csv(mapping_value, record)
+_truthy(value)
+_find_submission_by_ref(event, ref)
+_find_user_for_speaker(event, ref)
+_safe_int(value)
+_set_question_answer(submission, question_id, answer_text)
}
class DjangoForm {
}
class EventPermissionRequired {
}
class FormView {
}
class AsyncAction {
}
CSVImportForm --|> DjangoForm
SpeakerImportProcessForm --|> DjangoForm
SessionImportProcessForm --|> DjangoForm
SpeakerImportView --|> EventPermissionRequired
SpeakerImportView --|> FormView
SubmissionImportView --|> EventPermissionRequired
SubmissionImportView --|> FormView
SpeakerImportProcessView --|> EventPermissionRequired
SpeakerImportProcessView --|> AsyncAction
SpeakerImportProcessView --|> FormView
SubmissionImportProcessView --|> EventPermissionRequired
SubmissionImportProcessView --|> AsyncAction
SubmissionImportProcessView --|> FormView
ImportField <.. SpeakerImportProcessForm
ImportField <.. SessionImportProcessForm
ImportExecutionError <.. ImportTasksModule
ProfiledEventTask <.. ImportTasksModule
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
There was a problem hiding this comment.
Hey - I've found 1 issue, and left some high level feedback:
- The speaker avatar-related mapping fields (
avatar_url,avatar_source,avatar_license) are exposed inSpeakerImportProcessFormbut never consumed in_import_speaker_row, so either wire these through toSpeakerProfile/metadata or drop them from the form to avoid confusing organizers. - The helper
_find_user_for_speaker(and the identifier lookup in_import_speaker_row) iterate over allSpeakerProfilerows twice in Python; consider replacing these loops with directSpeakerProfile.objects.filter(...).select_related('user')queries (byuser__code/user__fullname) to keep imports efficient for events with many speakers. - In both
import_speakersandimport_submissions, theerrorslist is populated but never used; either surface these row-level errors to callers/logs or remove the accumulation to keep the task code simpler.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- The speaker avatar-related mapping fields (`avatar_url`, `avatar_source`, `avatar_license`) are exposed in `SpeakerImportProcessForm` but never consumed in `_import_speaker_row`, so either wire these through to `SpeakerProfile`/metadata or drop them from the form to avoid confusing organizers.
- The helper `_find_user_for_speaker` (and the identifier lookup in `_import_speaker_row`) iterate over all `SpeakerProfile` rows twice in Python; consider replacing these loops with direct `SpeakerProfile.objects.filter(...).select_related('user')` queries (by `user__code` / `user__fullname`) to keep imports efficient for events with many speakers.
- In both `import_speakers` and `import_submissions`, the `errors` list is populated but never used; either surface these row-level errors to callers/logs or remove the accumulation to keep the task code simpler.
## Individual Comments
### Comment 1
<location path="app/eventyay/base/services/talkimport.py" line_range="114-123" />
<code_context>
+def _import_speaker_row(event, settings, record, acting_user):
</code_context>
<issue_to_address>
**issue (bug_risk):** Speaker import ignores avatar-related mappings defined in the mapping form.
`SPEAKER_IMPORT_FIELDS` includes `avatar_url`, `avatar_source`, and `avatar_license`, but `_import_speaker_row` never reads or persists these values. Users can map these fields with no effect. Please either fully support importing and storing these fields, or remove them from `SPEAKER_IMPORT_FIELDS` so we don’t expose non-functional mappings.
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
There was a problem hiding this comment.
Pull request overview
Adds organizer-facing CSV import workflows for speakers and sessions, integrating with existing async task infrastructure and persisting per-event column mappings to streamline repeated bulk onboarding.
Changes:
- Added Celery-backed import tasks for speakers and sessions, including upsert/linking logic.
- Added CSV upload + column-mapping forms/views/templates for organizer dashboard import flows.
- Wired new routes, sidebar navigation links, event URL helpers, and event settings for stored mappings.
Reviewed changes
Copilot reviewed 12 out of 12 changed files in this pull request and generated 10 comments.
Show a summary per file
| File | Description |
|---|---|
| app/eventyay/base/services/talkimport.py | Implements the asynchronous speaker/session import tasks and row processing logic. |
| app/eventyay/orga/forms/importers.py | Adds CSV upload + column mapping forms, including header auto-suggestions and dynamic question fields. |
| app/eventyay/orga/views/speaker.py | Adds speaker import upload + mapping/process views and task dispatch. |
| app/eventyay/orga/views/submission.py | Adds session import upload + mapping/process views and task dispatch. |
| app/eventyay/orga/urls.py | Registers new organizer URLs for speaker/session import flows. |
| app/eventyay/base/models/event.py | Adds new orga_urls helpers for import endpoints. |
| app/eventyay/base/configurations/default_setting.py | Registers new event settings keys for persisting import mappings. |
| app/eventyay/orga/templates/orga/base.html | Adds organizer sidebar navigation links for the new import pages. |
| app/eventyay/orga/templates/orga/speaker/import.html | New speaker CSV upload template. |
| app/eventyay/orga/templates/orga/speaker/import_process.html | New speaker column-mapping + preview template. |
| app/eventyay/orga/templates/orga/submission/import.html | New session CSV upload template. |
| app/eventyay/orga/templates/orga/submission/import_process.html | New session column-mapping + preview template. |
e763536 to
e602b83
Compare
e602b83 to
fd677c0
Compare
|
@codex review |
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
fd677c0 to
be94670
Compare
|
@shivam-pawar-7217, removing your comment as this is irrelevant |
be94670 to
77603ab
Compare
77603ab to
05377c1
Compare
05377c1 to
dc57ec6
Compare
app/eventyay/orga/templates/orga/submission/import_process.html
Outdated
Show resolved
Hide resolved
9b3e44c to
a0793a3
Compare
a0793a3 to
9f616e7
Compare
9f616e7 to
d75a577
Compare
d75a577 to
7cc7a9d
Compare
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Saksham-Sirohi
left a comment
There was a problem hiding this comment.
Looks Good! Works
| cf = CachedFile.objects.get(id=fileid) | ||
| try: |
There was a problem hiding this comment.
import_submissions fetches the CachedFile with CachedFile.objects.get(id=fileid) without handling CachedFile.DoesNotExist. If the cached upload expires or is deleted before the Celery task runs, this will raise an unexpected exception (not in known_errortypes), leading to a generic error/redirect and making the UX less recoverable. Mirror import_speakers by catching DoesNotExist and raising ImportExecutionError with a user-facing message.
| cf = CachedFile.objects.get(id=fileid) | |
| try: | |
| try: | |
| cf = CachedFile.objects.get(id=fileid) | |
| except CachedFile.DoesNotExist: | |
| raise ImportExecutionError(_('The uploaded file could not be found. Please upload it again.')) | |
| try: |
End-to-End Speaker/Session CSV Import + Organizer Schedule Editor Fixes
This PR adds a complete organizer-side import flow for speakers and sessions from CSV, including mapping UI, async processing, data upsert logic, and schedule assignment support.
It also fixes organizer-only rendering issues in the schedule editor that could make sessions appear in public/frontend schedule views but not in the organizer editor.
What was happening before
What this PR changes
1) Organizer CSV import flows (new end-to-end UX)
2) New import mapping forms
question_<id>fields).3) Async import task plumbing
talkimporttask module.created,updated,skipped,errors) for user feedback.4) Speaker import behavior
SpeakerProfileas needed.5) Session import behavior
6) Organizer schedule editor rendering fixes
7) Async waiting-page resilience
8) Config and platform updates
What happens when (runtime flow)
Speaker import
Session import
Schedule editor
Edge cases covered
Summary
Adds organizer CSV import for speakers and sessions with async processing and event-scoped mapping settings.
Key Behavior
Stability Fixes
base/tasks.py).Config
MAX_SIZE_CONFIG[UPLOAD_SIZE_CSV](default currently 10 MB).Files Updated in Final Patch