.
This commit is contained in:
@@ -72,29 +72,6 @@
|
|||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
|
|
||||||
{% crispy form form.helper %}
|
{% crispy form form.helper %}
|
||||||
{% comment %} <h3>Cases:</h3>
|
|
||||||
Add cases here. These can only be added once created (they can also be added to cases on creation). Click and drag to change order.
|
|
||||||
<input type="button" value="Add More Cases" id="add_more_case">
|
|
||||||
<input type=button id="case-order-button" title="click and drag to update case order" value="Update case order" />
|
|
||||||
<div id="case_formset" class="sortable list_formset">
|
|
||||||
<ol>
|
|
||||||
{% for form in case_formset %}
|
|
||||||
<li class="no-error case-formset">
|
|
||||||
{{form.non_field_errors}}
|
|
||||||
{{form.errors}}
|
|
||||||
{{ form }}
|
|
||||||
</li>
|
|
||||||
{% endfor %}
|
|
||||||
</ol>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
{{ case_formset.management_form }}
|
|
||||||
</form>
|
|
||||||
<div id="empty_case_form" style="display:none">
|
|
||||||
<li class='no_error case-formset'>
|
|
||||||
{{ case_formset.empty_form }}
|
|
||||||
</li>
|
|
||||||
</div> {% endcomment %}
|
|
||||||
{% comment %} <input type="submit" class="submit-button" value="Submit" name="submit"> {% endcomment %}
|
{% comment %} <input type="submit" class="submit-button" value="Submit" name="submit"> {% endcomment %}
|
||||||
</form>
|
</form>
|
||||||
<script>
|
<script>
|
||||||
|
|||||||
+10
-3
@@ -1568,8 +1568,10 @@ class CaseCollectionCreate(RevisionMixin, LoginRequiredMixin, CreateView):
|
|||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
context = super(CaseCollectionCreate, self).get_context_data(**kwargs)
|
context = super(CaseCollectionCreate, self).get_context_data(**kwargs)
|
||||||
|
# Only bind the case formset when the POST actually contains formset data.
|
||||||
if self.request.POST:
|
# We don't require adding cases on create, so if the template doesn't
|
||||||
|
# include the casedetail management fields we should skip validation.
|
||||||
|
if self.request.POST and "casedetail_set-TOTAL_FORMS" in self.request.POST:
|
||||||
context["case_formset"] = CaseCollectionCaseFormSet(
|
context["case_formset"] = CaseCollectionCaseFormSet(
|
||||||
self.request.POST,
|
self.request.POST,
|
||||||
self.request.FILES,
|
self.request.FILES,
|
||||||
@@ -1589,14 +1591,19 @@ class CaseCollectionCreate(RevisionMixin, LoginRequiredMixin, CreateView):
|
|||||||
|
|
||||||
context = self.get_context_data(form=form)
|
context = self.get_context_data(form=form)
|
||||||
case_formset = context["case_formset"]
|
case_formset = context["case_formset"]
|
||||||
|
# If the POST included case formset data, validate and save it. Otherwise
|
||||||
|
# skip formset processing so creating a collection without cases redirects
|
||||||
|
# normally to the detail view.
|
||||||
|
if self.request.POST and "casedetail_set-TOTAL_FORMS" in self.request.POST:
|
||||||
if case_formset.is_valid():
|
if case_formset.is_valid():
|
||||||
response = super().form_valid(form)
|
response = super().form_valid(form)
|
||||||
case_formset.instance = self.object
|
case_formset.instance = self.object
|
||||||
case_formset.save()
|
case_formset.save()
|
||||||
return response
|
return response
|
||||||
|
|
||||||
else:
|
else:
|
||||||
return super().form_invalid(form)
|
return super().form_invalid(form)
|
||||||
|
# No formset data present: proceed with normal create redirect.
|
||||||
|
return super().form_valid(form)
|
||||||
|
|
||||||
def get_success_url(self):
|
def get_success_url(self):
|
||||||
"""Redirect to the collection detail page after successful creation."""
|
"""Redirect to the collection detail page after successful creation."""
|
||||||
|
|||||||
+11
-19
@@ -623,36 +623,32 @@ class UserGroupExamForm(ModelForm):
|
|||||||
self.fields["anatomy_user_user_groups"] = ModelMultipleChoiceField(
|
self.fields["anatomy_user_user_groups"] = ModelMultipleChoiceField(
|
||||||
required=False,
|
required=False,
|
||||||
queryset=AnatomyExam.objects.filter(archive=False),
|
queryset=AnatomyExam.objects.filter(archive=False),
|
||||||
widget=FilteredSelectMultiple(
|
widget=ExamSearchWidget(exam_model=AnatomyExam),
|
||||||
verbose_name="Anatomy Exams", is_stacked=False
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
self.fields["rapid_user_user_groups"] = ModelMultipleChoiceField(
|
self.fields["rapid_user_user_groups"] = ModelMultipleChoiceField(
|
||||||
required=False,
|
required=False,
|
||||||
queryset=RapidsExam.objects.filter(archive=False),
|
queryset=RapidsExam.objects.filter(archive=False),
|
||||||
widget=FilteredSelectMultiple(verbose_name="Rapid Exams", is_stacked=False),
|
widget=ExamSearchWidget(exam_model=RapidsExam),
|
||||||
)
|
)
|
||||||
self.fields["shorts_user_user_groups"] = ModelMultipleChoiceField(
|
self.fields["shorts_user_user_groups"] = ModelMultipleChoiceField(
|
||||||
required=False,
|
required=False,
|
||||||
queryset=ShortsExam.objects.filter(archive=False),
|
queryset=ShortsExam.objects.filter(archive=False),
|
||||||
widget=FilteredSelectMultiple(verbose_name="Shorts Exams", is_stacked=False),
|
widget=ExamSearchWidget(exam_model=ShortsExam),
|
||||||
)
|
)
|
||||||
self.fields["longs_user_user_groups"] = ModelMultipleChoiceField(
|
self.fields["longs_user_user_groups"] = ModelMultipleChoiceField(
|
||||||
required=False,
|
required=False,
|
||||||
queryset=LongsExam.objects.filter(archive=False),
|
queryset=LongsExam.objects.filter(archive=False),
|
||||||
widget=FilteredSelectMultiple(verbose_name="Longs Exams", is_stacked=False),
|
widget=ExamSearchWidget(exam_model=LongsExam),
|
||||||
)
|
)
|
||||||
self.fields["physics_user_user_groups"] = ModelMultipleChoiceField(
|
self.fields["physics_user_user_groups"] = ModelMultipleChoiceField(
|
||||||
required=False,
|
required=False,
|
||||||
queryset=PhysicsExam.objects.filter(archive=False),
|
queryset=PhysicsExam.objects.filter(archive=False),
|
||||||
widget=FilteredSelectMultiple(
|
widget=ExamSearchWidget(exam_model=PhysicsExam),
|
||||||
verbose_name="Physics Exams", is_stacked=False
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
self.fields["sba_user_user_groups"] = ModelMultipleChoiceField(
|
self.fields["sba_user_user_groups"] = ModelMultipleChoiceField(
|
||||||
required=False,
|
required=False,
|
||||||
queryset=SbasExam.objects.filter(archive=False),
|
queryset=SbasExam.objects.filter(archive=False),
|
||||||
widget=FilteredSelectMultiple(verbose_name="Sbas Exams", is_stacked=False),
|
widget=ExamSearchWidget(exam_model=SbasExam),
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@@ -720,31 +716,27 @@ class CidGroupExamForm(ModelForm):
|
|||||||
self.fields["anatomy_cid_user_groups"] = ModelMultipleChoiceField(
|
self.fields["anatomy_cid_user_groups"] = ModelMultipleChoiceField(
|
||||||
required=False,
|
required=False,
|
||||||
queryset=AnatomyExam.objects.filter(archive=False),
|
queryset=AnatomyExam.objects.filter(archive=False),
|
||||||
widget=FilteredSelectMultiple(
|
widget=ExamSearchWidget(exam_model=AnatomyExam),
|
||||||
verbose_name="Anatomy Exams", is_stacked=False
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
self.fields["rapid_cid_user_groups"] = ModelMultipleChoiceField(
|
self.fields["rapid_cid_user_groups"] = ModelMultipleChoiceField(
|
||||||
required=False,
|
required=False,
|
||||||
queryset=RapidsExam.objects.filter(archive=False),
|
queryset=RapidsExam.objects.filter(archive=False),
|
||||||
widget=FilteredSelectMultiple(verbose_name="Rapid Exams", is_stacked=False),
|
widget=ExamSearchWidget(exam_model=RapidsExam),
|
||||||
)
|
)
|
||||||
self.fields["longs_cid_user_groups"] = ModelMultipleChoiceField(
|
self.fields["longs_cid_user_groups"] = ModelMultipleChoiceField(
|
||||||
required=False,
|
required=False,
|
||||||
queryset=LongsExam.objects.filter(archive=False),
|
queryset=LongsExam.objects.filter(archive=False),
|
||||||
widget=FilteredSelectMultiple(verbose_name="Longs Exams", is_stacked=False),
|
widget=ExamSearchWidget(exam_model=LongsExam),
|
||||||
)
|
)
|
||||||
self.fields["physics_cid_user_groups"] = ModelMultipleChoiceField(
|
self.fields["physics_cid_user_groups"] = ModelMultipleChoiceField(
|
||||||
required=False,
|
required=False,
|
||||||
queryset=PhysicsExam.objects.filter(archive=False),
|
queryset=PhysicsExam.objects.filter(archive=False),
|
||||||
widget=FilteredSelectMultiple(
|
widget=ExamSearchWidget(exam_model=PhysicsExam),
|
||||||
verbose_name="Physics Exams", is_stacked=False
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
self.fields["sba_cid_user_groups"] = ModelMultipleChoiceField(
|
self.fields["sba_cid_user_groups"] = ModelMultipleChoiceField(
|
||||||
required=False,
|
required=False,
|
||||||
queryset=SbasExam.objects.filter(archive=False),
|
queryset=SbasExam.objects.filter(archive=False),
|
||||||
widget=FilteredSelectMultiple(verbose_name="Sbas Exams", is_stacked=False),
|
widget=ExamSearchWidget(exam_model=SbasExam),
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
|||||||
@@ -483,6 +483,9 @@ class ExamSearchSelectMultipleWidget:
|
|||||||
<ul id="{selected_list_id}" class="list-group list-group-flush">
|
<ul id="{selected_list_id}" class="list-group list-group-flush">
|
||||||
{selected_html}
|
{selected_html}
|
||||||
</ul>
|
</ul>
|
||||||
|
<div class="mt-2 text-end">
|
||||||
|
<button type="button" id="exam_remove_all_{self.name}" class="btn btn-sm btn-outline-danger exam-remove-all-btn d-none" data-field="{self.name}">Remove all</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
@@ -583,6 +586,15 @@ class ExamSearchSelectMultipleWidget:
|
|||||||
}});
|
}});
|
||||||
}}
|
}}
|
||||||
|
|
||||||
|
// Wire up "Remove all" button for this widget instance
|
||||||
|
const removeAllBtn = document.getElementById('exam_remove_all_' + widgetFieldName);
|
||||||
|
if (removeAllBtn) {{
|
||||||
|
removeAllBtn.addEventListener('click', function(e) {{
|
||||||
|
e.preventDefault();
|
||||||
|
removeAllExamsFromField(widgetFieldName);
|
||||||
|
}});
|
||||||
|
}}
|
||||||
|
|
||||||
window.addExamToField = function(fieldName, id, text) {{
|
window.addExamToField = function(fieldName, id, text) {{
|
||||||
const sel = document.getElementById('id_' + fieldName);
|
const sel = document.getElementById('id_' + fieldName);
|
||||||
if (!sel) return;
|
if (!sel) return;
|
||||||
@@ -598,6 +610,9 @@ class ExamSearchSelectMultipleWidget:
|
|||||||
const btn = document.createElement('button'); btn.type = 'button'; btn.className = 'btn btn-sm btn-outline-danger ms-2'; btn.innerText = 'Remove';
|
const btn = document.createElement('button'); btn.type = 'button'; btn.className = 'btn btn-sm btn-outline-danger ms-2'; btn.innerText = 'Remove';
|
||||||
btn.addEventListener('click', function() {{ removeExamFromField(fieldName, id); }});
|
btn.addEventListener('click', function() {{ removeExamFromField(fieldName, id); }});
|
||||||
li.appendChild(span); li.appendChild(btn); listEl.appendChild(li);
|
li.appendChild(span); li.appendChild(btn); listEl.appendChild(li);
|
||||||
|
// update the remove-all button visibility
|
||||||
|
var remBtn = document.getElementById('exam_remove_all_' + fieldName);
|
||||||
|
if (remBtn) {{ remBtn.classList.toggle('d-none', listEl.querySelectorAll('li').length < 2); }}
|
||||||
}}
|
}}
|
||||||
|
|
||||||
window.removeExamFromField = function(fieldName, id) {{
|
window.removeExamFromField = function(fieldName, id) {{
|
||||||
@@ -606,6 +621,10 @@ class ExamSearchSelectMultipleWidget:
|
|||||||
for (let i=sel.options.length-1;i>=0;i--) {{ if (sel.options[i].value == id) sel.remove(i); }}
|
for (let i=sel.options.length-1;i>=0;i--) {{ if (sel.options[i].value == id) sel.remove(i); }}
|
||||||
const li = document.getElementById('selected_exam_' + fieldName + '_' + id);
|
const li = document.getElementById('selected_exam_' + fieldName + '_' + id);
|
||||||
if (li && li.parentNode) li.parentNode.removeChild(li);
|
if (li && li.parentNode) li.parentNode.removeChild(li);
|
||||||
|
// update the remove-all button visibility
|
||||||
|
var remBtn = document.getElementById('exam_remove_all_' + fieldName);
|
||||||
|
var listEl = document.getElementById('selected_exams_' + fieldName);
|
||||||
|
if (remBtn && listEl) {{ remBtn.classList.toggle('d-none', listEl.querySelectorAll('li').length < 2); }}
|
||||||
}}
|
}}
|
||||||
|
|
||||||
window.addAllExamsFromResults = function(fieldName) {{
|
window.addAllExamsFromResults = function(fieldName) {{
|
||||||
@@ -617,6 +636,23 @@ class ExamSearchSelectMultipleWidget:
|
|||||||
const text = it.getAttribute('data-exam-text') || it.textContent.trim();
|
const text = it.getAttribute('data-exam-text') || it.textContent.trim();
|
||||||
addExamToField(fieldName, id, text);
|
addExamToField(fieldName, id, text);
|
||||||
}});
|
}});
|
||||||
|
// ensure remove-all button is visible if more than one
|
||||||
|
var remBtn = document.getElementById('exam_remove_all_' + fieldName);
|
||||||
|
var listEl = document.getElementById('selected_exams_' + fieldName);
|
||||||
|
if (remBtn && listEl) {{ remBtn.classList.toggle('d-none', listEl.querySelectorAll('li').length < 2); }}
|
||||||
|
}}
|
||||||
|
|
||||||
|
window.removeAllExamsFromField = function(fieldName) {{
|
||||||
|
const sel = document.getElementById('id_' + fieldName);
|
||||||
|
if (!sel) return;
|
||||||
|
// remove all options
|
||||||
|
for (let i=sel.options.length-1;i>=0;i--) {{ sel.remove(i); }}
|
||||||
|
// clear visible list
|
||||||
|
const listEl = document.getElementById('selected_exams_' + fieldName);
|
||||||
|
if (listEl) {{ listEl.innerHTML = ''; }}
|
||||||
|
// hide remove-all button
|
||||||
|
const remBtn = document.getElementById('exam_remove_all_' + fieldName);
|
||||||
|
if (remBtn) {{ remBtn.classList.add('d-none'); }}
|
||||||
}}
|
}}
|
||||||
}})();
|
}})();
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
Reference in New Issue
Block a user