Enhance import preview functionality with editable fields and overlay logic for payload updates
This commit is contained in:
@@ -2,9 +2,14 @@
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="session_key" value="{{ session_key }}">
|
||||
{% for item in items %}
|
||||
<div class="card mb-2">
|
||||
<div class="card mb-2" id="import-item-{{ item.index }}">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Question {{ item.index|add:1 }}</h5>
|
||||
<div class="d-flex justify-content-between align-items-start">
|
||||
<h5 class="card-title mb-0">Question {{ item.index|add:1 }}</h5>
|
||||
<div>
|
||||
<button type="button" class="btn btn-sm btn-outline-secondary ms-2" data-item-index="{{ item.index }}" onclick="toggleEdit(this)">Edit</button>
|
||||
</div>
|
||||
</div>
|
||||
{% if item.errors %}
|
||||
<div class="alert alert-danger" role="alert">
|
||||
<strong>Validation errors:</strong>
|
||||
@@ -25,161 +30,168 @@
|
||||
<strong>Note:</strong> Numeric references were removed from fields: <em>{{ item.stripped_fields|join:", " }}</em>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if item.payload %}
|
||||
<p><strong>Title:</strong> {{ item.payload.title|default:'(No title)'|safe }}</p>
|
||||
<p><strong>Stem:</strong> {{ item.payload.stem|default:''|safe }}</p>
|
||||
<p><strong>Answers:</strong>
|
||||
<ul>
|
||||
<li>A: {{ item.payload.a_answer|default:''|safe }}</li>
|
||||
{% if item.payload.a_feedback %}
|
||||
<p><strong>A Feedback:</strong> {{ item.payload.a_feedback|default:''|safe }}</p>
|
||||
{% endif %}
|
||||
<li>B: {{ item.payload.b_answer|default:''|safe }}</li>
|
||||
{% if item.payload.b_feedback %}
|
||||
<p><strong>B Feedback:</strong> {{ item.payload.b_feedback|default:''|safe }}</p>
|
||||
{% endif %}
|
||||
<li>C: {{ item.payload.c_answer|default:''|safe }}</li>
|
||||
{% if item.payload.c_feedback %}
|
||||
<p><strong>C Feedback:</strong> {{ item.payload.c_feedback|default:''|safe }}</p>
|
||||
{% endif %}
|
||||
<li>D: {{ item.payload.d_answer|default:''|safe }}</li>
|
||||
{% if item.payload.d_feedback %}
|
||||
<p><strong>D Feedback:</strong> {{ item.payload.d_feedback|default:''|safe }}</p>
|
||||
{% endif %}
|
||||
<li>E: {{ item.payload.e_answer|default:''|safe }}</li>
|
||||
{% if item.payload.e_feedback %}
|
||||
<p><strong>E Feedback:</strong> {{ item.payload.e_feedback|default:''|safe }}</p>
|
||||
{% endif %}
|
||||
|
||||
</ul>
|
||||
<p><strong>Best Answer:</strong> {{ item.payload.best_answer|default:'' }}
|
||||
</p>
|
||||
<p><strong>Feedback:</strong> {{ item.payload.feedback|default:''|safe }}</p>
|
||||
<p><strong>Sources:</strong>
|
||||
{% if item.payload.sources %}
|
||||
<ul>
|
||||
{% for source in item.payload.sources %}
|
||||
<li>{{ source }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% else %}
|
||||
None
|
||||
{% endif %}
|
||||
</p>
|
||||
{% endif %}
|
||||
<p><strong>M2M fields (click to exclude)</strong></p>
|
||||
<p class="small text-muted">Green = will be created/attached. Click a value to exclude it (grey).</p>
|
||||
<div class="mb-2">
|
||||
<label class="form-label"><strong>Title</strong></label>
|
||||
<input class="form-control form-control-sm" name="payload_{{ item.index }}_title" value="{{ item.payload.title|default_if_none:'' }}" disabled>
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<label class="form-label"><strong>Stem</strong></label>
|
||||
<textarea class="form-control form-control-sm" name="payload_{{ item.index }}_stem" rows="3" disabled>{{ item.payload.stem|default_if_none:'' }}</textarea>
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<label class="form-label"><strong>Answers</strong></label>
|
||||
<div class="mb-1">
|
||||
<label class="form-label small">A</label>
|
||||
<input class="form-control form-control-sm" name="payload_{{ item.index }}_a_answer" value="{{ item.payload.a_answer|default_if_none:'' }}" disabled>
|
||||
<label class="form-label small mt-1">A Feedback</label>
|
||||
<textarea class="form-control form-control-sm" name="payload_{{ item.index }}_a_feedback" rows="2" disabled>{{ item.payload.a_feedback|default_if_none:'' }}</textarea>
|
||||
</div>
|
||||
<div class="mb-1">
|
||||
<label class="form-label small">B</label>
|
||||
<input class="form-control form-control-sm" name="payload_{{ item.index }}_b_answer" value="{{ item.payload.b_answer|default_if_none:'' }}" disabled>
|
||||
<label class="form-label small mt-1">B Feedback</label>
|
||||
<textarea class="form-control form-control-sm" name="payload_{{ item.index }}_b_feedback" rows="2" disabled>{{ item.payload.b_feedback|default_if_none:'' }}</textarea>
|
||||
</div>
|
||||
<div class="mb-1">
|
||||
<label class="form-label small">C</label>
|
||||
<input class="form-control form-control-sm" name="payload_{{ item.index }}_c_answer" value="{{ item.payload.c_answer|default_if_none:'' }}" disabled>
|
||||
<label class="form-label small mt-1">C Feedback</label>
|
||||
<textarea class="form-control form-control-sm" name="payload_{{ item.index }}_c_feedback" rows="2" disabled>{{ item.payload.c_feedback|default_if_none:'' }}</textarea>
|
||||
</div>
|
||||
<div class="mb-1">
|
||||
<label class="form-label small">D</label>
|
||||
<input class="form-control form-control-sm" name="payload_{{ item.index }}_d_answer" value="{{ item.payload.d_answer|default_if_none:'' }}" disabled>
|
||||
<label class="form-label small mt-1">D Feedback</label>
|
||||
<textarea class="form-control form-control-sm" name="payload_{{ item.index }}_d_feedback" rows="2" disabled>{{ item.payload.d_feedback|default_if_none:'' }}</textarea>
|
||||
</div>
|
||||
<div class="mb-1">
|
||||
<label class="form-label small">E</label>
|
||||
<input class="form-control form-control-sm" name="payload_{{ item.index }}_e_answer" value="{{ item.payload.e_answer|default_if_none:'' }}" disabled>
|
||||
<label class="form-label small mt-1">E Feedback</label>
|
||||
<textarea class="form-control form-control-sm" name="payload_{{ item.index }}_e_feedback" rows="2" disabled>{{ item.payload.e_feedback|default_if_none:'' }}</textarea>
|
||||
</div>
|
||||
<div class="mb-1">
|
||||
<label class="form-label small">Best Answer</label>
|
||||
<input class="form-control form-control-sm" name="payload_{{ item.index }}_best_answer" value="{{ item.payload.best_answer|default_if_none:'' }}" disabled>
|
||||
</div>
|
||||
<div class="mb-1">
|
||||
<label class="form-label small">Feedback</label>
|
||||
<textarea class="form-control form-control-sm" name="payload_{{ item.index }}_feedback" rows="2" disabled>{{ item.payload.feedback|default_if_none:'' }}</textarea>
|
||||
</div>
|
||||
<div class="mb-1 small text-muted">Sources: {% if item.payload.sources %}{{ item.payload.sources|join:", " }}{% else %}None{% endif %}</div>
|
||||
<p><strong>M2M fields (click to exclude)</strong></p>
|
||||
<p class="small text-muted">Green = will be created/attached. Click a value to exclude it (grey).</p>
|
||||
<div class="mb-2">
|
||||
<!-- Finding -->
|
||||
<div class="m2m-group" data-model="Finding"><strong>Finding:</strong>
|
||||
{% if item.resolved_m2m.finding %}
|
||||
{% for val in item.resolved_m2m.finding %}
|
||||
<button type="button" class="m2m-toggle btn btn-sm btn-success ms-1" data-idx="{{ item.index }}" data-model="Finding" data-value="{{ val }}">{{ val }}</button>
|
||||
<div class="m2m-group" data-model="Finding"><strong>Finding:</strong>
|
||||
{% if item.resolved_m2m.finding %}
|
||||
{% for val in item.resolved_m2m.finding %}
|
||||
<button type="button" class="m2m-toggle btn btn-sm btn-success ms-1" data-idx="{{ item.index }}" data-model="Finding" data-value="{{ val }}">{{ val }}</button>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% if item.missing_map.finding %}
|
||||
{% for val in item.missing_map.finding %}
|
||||
<button type="button" class="m2m-toggle btn btn-sm btn-success ms-1" data-idx="{{ item.index }}" data-model="Finding" data-value="{{ val }}">{{ val }}</button>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
<span class="text-muted m2m-none" style="display:none">(none)</span>
|
||||
{% if item.missing_map.finding %}
|
||||
<div class="small text-info">Would create: {{ item.missing_map.finding|join:", " }}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Structure -->
|
||||
<div class="m2m-group" data-model="Structure"><strong>Structure:</strong>
|
||||
{% if item.resolved_m2m.structure %}
|
||||
{% for val in item.resolved_m2m.structure %}
|
||||
<button type="button" class="m2m-toggle btn btn-sm btn-success ms-1" data-idx="{{ item.index }}" data-model="Structure" data-value="{{ val }}">{{ val }}</button>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% if item.missing_map.finding %}
|
||||
{% for val in item.missing_map.finding %}
|
||||
<button type="button" class="m2m-toggle btn btn-sm btn-success ms-1" data-idx="{{ item.index }}" data-model="Finding" data-value="{{ val }}">{{ val }}</button>
|
||||
{% if item.missing_map.structure %}
|
||||
{% for val in item.missing_map.structure %}
|
||||
<button type="button" class="m2m-toggle btn btn-sm btn-success ms-1" data-idx="{{ item.index }}" data-model="Structure" data-value="{{ val }}">{{ val }}</button>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
<span class="text-muted m2m-none" style="display:none">(none)</span>
|
||||
{% if item.missing_map.finding %}
|
||||
<div class="small text-info">Would create: {{ item.missing_map.finding|join:", " }}</div>
|
||||
{% if item.missing_map.structure %}
|
||||
<div class="small text-info">Would create: {{ item.missing_map.structure|join:", " }}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Structure -->
|
||||
<div class="m2m-group" data-model="Structure"><strong>Structure:</strong>
|
||||
{% if item.resolved_m2m.structure %}
|
||||
{% for val in item.resolved_m2m.structure %}
|
||||
<button type="button" class="m2m-toggle btn btn-sm btn-success ms-1" data-idx="{{ item.index }}" data-model="Structure" data-value="{{ val }}">{{ val }}</button>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% if item.missing_map.structure %}
|
||||
{% for val in item.missing_map.structure %}
|
||||
<button type="button" class="m2m-toggle btn btn-sm btn-success ms-1" data-idx="{{ item.index }}" data-model="Structure" data-value="{{ val }}">{{ val }}</button>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
<span class="text-muted m2m-none" style="display:none">(none)</span>
|
||||
{% if item.missing_map.structure %}
|
||||
<div class="small text-info">Would create: {{ item.missing_map.structure|join:", " }}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<!-- Condition -->
|
||||
<div class="m2m-group" data-model="Condition"><strong>Condition:</strong>
|
||||
{% if item.resolved_m2m.condition %}
|
||||
{% for val in item.resolved_m2m.condition %}
|
||||
<button type="button" class="m2m-toggle btn btn-sm btn-success ms-1" data-idx="{{ item.index }}" data-model="Condition" data-value="{{ val }}">{{ val }}</button>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% if item.missing_map.condition %}
|
||||
{% for val in item.missing_map.condition %}
|
||||
<button type="button" class="m2m-toggle btn btn-sm btn-success ms-1" data-idx="{{ item.index }}" data-model="Condition" data-value="{{ val }}">{{ val }}</button>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
<span class="text-muted m2m-none" style="display:none">(none)</span>
|
||||
{% if item.missing_map.condition %}
|
||||
<div class="small text-info">Would create: {{ item.missing_map.condition|join:", " }}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="m2m-group" data-model="Condition"><strong>Condition:</strong>
|
||||
{% if item.resolved_m2m.condition %}
|
||||
{% for val in item.resolved_m2m.condition %}
|
||||
<button type="button" class="m2m-toggle btn btn-sm btn-success ms-1" data-idx="{{ item.index }}" data-model="Condition" data-value="{{ val }}">{{ val }}</button>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% if item.missing_map.condition %}
|
||||
{% for val in item.missing_map.condition %}
|
||||
<button type="button" class="m2m-toggle btn btn-sm btn-success ms-1" data-idx="{{ item.index }}" data-model="Condition" data-value="{{ val }}">{{ val }}</button>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
<span class="text-muted m2m-none" style="display:none">(none)</span>
|
||||
{% if item.missing_map.condition %}
|
||||
<div class="small text-info">Would create: {{ item.missing_map.condition|join:", " }}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<!-- Presentation -->
|
||||
<div class="m2m-group" data-model="Presentation"><strong>Presentation:</strong>
|
||||
{% if item.resolved_m2m.presentation %}
|
||||
{% for val in item.resolved_m2m.presentation %}
|
||||
<button type="button" class="m2m-toggle btn btn-sm btn-success ms-1" data-idx="{{ item.index }}" data-model="Presentation" data-value="{{ val }}">{{ val }}</button>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% if item.missing_map.presentation %}
|
||||
{% for val in item.missing_map.presentation %}
|
||||
<button type="button" class="m2m-toggle btn btn-sm btn-success ms-1" data-idx="{{ item.index }}" data-model="Presentation" data-value="{{ val }}">{{ val }}</button>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
<span class="text-muted m2m-none" style="display:none">(none)</span>
|
||||
{% if item.missing_map.presentation %}
|
||||
<div class="small text-info">Would create: {{ item.missing_map.presentation|join:", " }}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="m2m-group" data-model="Presentation"><strong>Presentation:</strong>
|
||||
{% if item.resolved_m2m.presentation %}
|
||||
{% for val in item.resolved_m2m.presentation %}
|
||||
<button type="button" class="m2m-toggle btn btn-sm btn-success ms-1" data-idx="{{ item.index }}" data-model="Presentation" data-value="{{ val }}">{{ val }}</button>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% if item.missing_map.presentation %}
|
||||
{% for val in item.missing_map.presentation %}
|
||||
<button type="button" class="m2m-toggle btn btn-sm btn-success ms-1" data-idx="{{ item.index }}" data-model="Presentation" data-value="{{ val }}">{{ val }}</button>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
<span class="text-muted m2m-none" style="display:none">(none)</span>
|
||||
{% if item.missing_map.presentation %}
|
||||
<div class="small text-info">Would create: {{ item.missing_map.presentation|join:", " }}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<!-- Subspecialty -->
|
||||
<div class="m2m-group" data-model="Subspecialty"><strong>Subspecialty:</strong>
|
||||
{% if item.resolved_m2m.subspecialty %}
|
||||
{% for val in item.resolved_m2m.subspecialty %}
|
||||
<button type="button" class="m2m-toggle btn btn-sm btn-success ms-1" data-idx="{{ item.index }}" data-model="Subspecialty" data-value="{{ val }}">{{ val }}</button>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% if item.missing_map.subspecialty %}
|
||||
{% for val in item.missing_map.subspecialty %}
|
||||
<button type="button" class="m2m-toggle btn btn-sm btn-success ms-1" data-idx="{{ item.index }}" data-model="Subspecialty" data-value="{{ val }}">{{ val }}</button>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
<span class="text-muted m2m-none" style="display:none">(none)</span>
|
||||
{% if item.missing_map.subspecialty %}
|
||||
<div class="small text-info">Would create: {{ item.missing_map.subspecialty|join:", " }}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<! -- Category -->
|
||||
<div class="m2m-group" data-model="Category"><strong>Category:</strong>
|
||||
{{ item.payload.category|default:'(none)' }}
|
||||
<div class="m2m-group" data-model="Subspecialty"><strong>Subspecialty:</strong>
|
||||
{% if item.resolved_m2m.subspecialty %}
|
||||
{% for val in item.resolved_m2m.subspecialty %}
|
||||
<button type="button" class="m2m-toggle btn btn-sm btn-success ms-1" data-idx="{{ item.index }}" data-model="Subspecialty" data-value="{{ val }}">{{ val }}</button>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% if item.missing_map.subspecialty %}
|
||||
{% for val in item.missing_map.subspecialty %}
|
||||
<button type="button" class="m2m-toggle btn btn-sm btn-success ms-1" data-idx="{{ item.index }}" data-model="Subspecialty" data-value="{{ val }}">{{ val }}</button>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
<span class="text-muted m2m-none" style="display:none">(none)</span>
|
||||
{% if item.missing_map.subspecialty %}
|
||||
<div class="small text-info">Would create: {{ item.missing_map.subspecialty|join:", " }}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<! -- Category -->
|
||||
<div class="m2m-group" data-model="Category"><strong>Category:</strong>
|
||||
{{ item.payload.category|default:'(none)' }}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<!-- DEBUG: show raw resolved and missing maps for troubleshooting -->
|
||||
<div class="card-footer text-monospace small">
|
||||
<details>
|
||||
<summary>Debug: resolved_m2m / missing_map (click to expand)</summary>
|
||||
<div class="card-footer text-monospace small">
|
||||
<details>
|
||||
<summary>Debug: resolved_m2m / missing_map (click to expand)</summary>
|
||||
<pre style="white-space:pre-wrap;">resolved_m2m: {{ item.resolved_m2m|default:"{}" }}
|
||||
missing_map: {{ item.missing_map|default:"{}" }}</pre>
|
||||
</details>
|
||||
</div>
|
||||
<div>
|
||||
<label><input type="checkbox" name="selected" value="{{ item.index }}" checked> Include</label>
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
<div>
|
||||
<label><input type="checkbox" name="selected" value="{{ item.index }}" checked> Include</label>
|
||||
</div>
|
||||
<!-- container for hidden exclude inputs for this item -->
|
||||
<div id="exclude-container-{{ item.index }}"></div>
|
||||
<div id="exclude-container-{{ item.index }}"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
<div class="d-flex gap-2">
|
||||
@@ -255,3 +267,22 @@ missing_map: {{ item.missing_map|default:"{}" }}</pre>
|
||||
</script>
|
||||
|
||||
<!-- Bottom controls removed: submit and cancel are inside the form above -->
|
||||
|
||||
<script>
|
||||
// Toggle editing of payload inputs for a specific item (disabled by default)
|
||||
function toggleEdit(btn){
|
||||
const idx = btn.getAttribute('data-item-index');
|
||||
if(!idx) return;
|
||||
const card = document.getElementById('import-item-' + idx);
|
||||
if(!card) return;
|
||||
const selector = 'input[name^="payload_' + idx + '_"], textarea[name^="payload_' + idx + '_"]';
|
||||
const inputs = card.querySelectorAll(selector);
|
||||
if(inputs.length === 0) return;
|
||||
// check current state from first input
|
||||
const currentlyDisabled = inputs[0].disabled === true;
|
||||
// toggle
|
||||
inputs.forEach(function(i){ i.disabled = currentlyDisabled ? false : true; });
|
||||
// update button text
|
||||
btn.textContent = currentlyDisabled ? 'Lock' : 'Edit';
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -950,6 +950,20 @@ def import_llm_confirm(request):
|
||||
except Exception:
|
||||
errors.append({"index": idx, "errors": ["Missing candidate"]})
|
||||
continue
|
||||
# Overlay any edited fields posted from the preview form (names like payload_<idx>_stem)
|
||||
overlay = {}
|
||||
prefix = f"payload_{idx}_"
|
||||
for k, v in request.POST.items():
|
||||
if k.startswith(prefix):
|
||||
field = k[len(prefix):]
|
||||
overlay[field] = v
|
||||
if overlay:
|
||||
# apply overlay onto a shallow copy
|
||||
new_payload = dict(payload)
|
||||
for fk, fv in overlay.items():
|
||||
# convert list-like fields and numeric conversions are not performed here; keep simple strings/lists
|
||||
new_payload[fk] = fv
|
||||
payload = new_payload
|
||||
# Strip numeric bracket references here as well, before confirm import
|
||||
payload, stripped = _strip_numeric_refs_in_payload(payload)
|
||||
if stripped:
|
||||
|
||||
Reference in New Issue
Block a user