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
5 changes: 5 additions & 0 deletions apps/api/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,11 @@ def save_lab_answers(lab_inst_id):
lab_answers = LabAnswers(user_id=current_user.id, lab_id=lab_inst.lab_id)
db.session.add(lab_answers)

current_app.logger.info(
f"Save lab_answers id={lab_answers.id} user={current_user.username} "
f"lab={lab_inst.lab_id} request-answers={content}"
)

# Merge into the stored answers rather than replacing them: keep keys not
# present in this payload, and don't let an empty incoming value wipe an
# answer that was already saved (guards against a partial/early auto-save
Expand Down
13 changes: 13 additions & 0 deletions apps/templates/pages/lab_instance_view.html
Original file line number Diff line number Diff line change
Expand Up @@ -410,6 +410,16 @@ <h3 class="card-title">Lab Guide</h3>
if (!answers || Object.keys(answers).length === 0) {
return;
}
// Never overwrite stored answers with an all-blank form. Firefox's
// form-state restoration re-blanks fields after load and fires change
// events; without this guard that would auto-save an empty payload and
// wipe the user's answers.
var hasValue = Object.keys(answers).some(function(k){
return answers[k] !== "" && answers[k] != null;
});
if (!hasValue) {
return;
}
var request = $.ajax({
url: "/api/lab_answers/{{ lab['lab_instance_id'] }}",
type: "POST",
Expand Down Expand Up @@ -441,6 +451,9 @@ <h3 class="card-title">Lab Guide</h3>
$('.save-answers').click(function () {
saveAnswers();
});
// stop Firefox from restoring/re-blanking these fields after load, which
// otherwise fights the answer loader and triggers empty auto-saves
$("#lab-guide input, #lab-guide textarea, #lab-guide select").attr('autocomplete', 'off');
var timeoutSave;
$("#lab-guide input, #lab-guide textarea, #lab-guide select").on('input propertychange change', function() {
// don't auto-save until the initial load has settled, otherwise a
Expand Down
17 changes: 15 additions & 2 deletions tests/js/labAnswers.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -147,11 +147,24 @@ const { $ } = env;
eq('save: first radio not clobbered by later unchecked siblings', post.q_radio, 'A');
}

// === saveAnswers: empty / unchecked groups serialize as "" =================
// === saveAnswers: a fully-blank form is NOT posted (never wipe answers) =====
// Firefox re-blanks the guide inputs after load and fires change events; the
// guard in saveAnswers must skip the POST entirely so an all-empty payload
// can't overwrite previously saved answers.
{
env.setForm(FORM);
const post = env.save();
eq('save: empty text', post.q_text, '');
eq('save: blank form suppresses POST', post, undefined);
}

// === saveAnswers: blank fields still serialize as "" in a partial save ======
// When at least one field has a value the POST goes through, and the empty
// fields must still serialize as "" so they round-trip via applyAnswers.
{
env.setForm(FORM);
$("input[name=q_text]").val('answered');
const post = env.save();
eq('save: partial POST happens', post && post.q_text, 'answered');
eq('save: unchecked checkbox group', post.q_cb, '');
eq('save: no radio selected', post.q_radio, '');
}
Expand Down
Loading