diff --git a/app/eventyay/cfp/flow.py b/app/eventyay/cfp/flow.py index 551cc31297..6bb3b2a7bb 100644 --- a/app/eventyay/cfp/flow.py +++ b/app/eventyay/cfp/flow.py @@ -13,6 +13,7 @@ from django.db.models import Q from django.forms import ValidationError from django.http import HttpResponseNotAllowed +from django.utils.datastructures import MultiValueDict from django.shortcuts import redirect from django.urls import reverse from django.utils.functional import Promise, cached_property @@ -219,14 +220,40 @@ def get_form_initial(self): return copy.deepcopy({**initial_data, **previous_data}) def get_form(self, from_storage=False): - if self.request.method == 'GET' or from_storage: + # Cache form initial data to avoid repeated work + form_initial = self.get_form_initial() + + if self.request.method == 'GET': + # For initial GET requests, use an unbound form populated from session data + # This shows saved values but intentionally does not display validation errors + return self.form_class( + data=None, + initial=form_initial, + files=None, + **self.get_form_kwargs(), + ) + if from_storage: + # For validation checks, create a bound form with session data return self.form_class( - data=self.get_form_initial() if from_storage else None, - initial=self.get_form_initial(), + data=form_initial, + initial=form_initial, files=self.get_files(), **self.get_form_kwargs(), ) - return self.form_class(data=self.request.POST, files=self.request.FILES, **self.get_form_kwargs()) + # For POST requests, merge new uploads with existing session files + # This allows users to navigate back without losing previously uploaded files + session_files = self.get_files() or {} + + # Preserve MultiValueDict semantics for proper multi-file field support + files = MultiValueDict() + # Add session files first + for field, file_obj in session_files.items(): + files[field] = file_obj + # For each field, new uploads completely replace any existing session files + for field, file_list in self.request.FILES.lists(): + files.setlist(field, file_list) + + return self.form_class(data=self.request.POST, files=files, **self.get_form_kwargs()) def is_completed(self, request): self.request = request @@ -237,11 +264,29 @@ def get_context_data(self, **kwargs): result['form'] = self.get_form() previous_data = self.cfp_session.get('data') result['submission_title'] = previous_data.get('info', {}).get('title') + # Add information about uploaded files for display in templates + saved_files = self.cfp_session.get('files', {}).get(self.identifier, {}) or {} + result['uploaded_files'] = { + field: file_dict.get('name') for field, file_dict in saved_files.items() + } return result def post(self, request): self.request = request form = self.get_form() + action = request.POST.get('action', 'submit') + + # For "back" action, only save data if form is valid + if action == 'back': + if form.is_valid(): + self.set_data(form.cleaned_data) + # Always save files if present + if form.files: + self.set_files(form.files) + prev_url = self.get_prev_url(request) + return redirect(prev_url) if prev_url else redirect(request.path) + + # For "submit" and "draft" actions, validate as before if not form.is_valid(): error_message = '\n\n'.join( (f'{form.fields[key].label}: ' if key != '__all__' else '') + ' '.join(values) diff --git a/app/eventyay/cfp/templates/cfp/event/submission_base.html b/app/eventyay/cfp/templates/cfp/event/submission_base.html index 5284913e1d..810fd20650 100644 --- a/app/eventyay/cfp/templates/cfp/event/submission_base.html +++ b/app/eventyay/cfp/templates/cfp/event/submission_base.html @@ -13,7 +13,8 @@ {% block cfp_header %} {% include "cfp/includes/forms_header.html" %} {% compress js %} - + + {% endcompress %} {% block cfp_submission_header %}{% endblock cfp_submission_header %} {% endblock cfp_header %} @@ -39,7 +40,7 @@ {% block cfp_form %} {% if request.user.is_authenticated %} -