Implement synonym management in condition detail: add forms for adding synonyms and handle POST requests for synonym creation.
This commit is contained in:
@@ -1033,6 +1033,37 @@ class ConditionAutocomplete(ModelAutocomplete):
|
|||||||
model = Condition
|
model = Condition
|
||||||
search_attrs = ["name"]
|
search_attrs = ["name"]
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_items_from_keys(cls, keys, context_obj=None):
|
||||||
|
"""Return items for given keys, ignoring empty strings.
|
||||||
|
|
||||||
|
The autocomplete frontend sometimes sends empty string keys which
|
||||||
|
causes the ORM to raise ValueError when filtering numeric PK fields.
|
||||||
|
Filter out falsy/blank keys before querying. If no valid keys remain
|
||||||
|
return an empty queryset.
|
||||||
|
"""
|
||||||
|
# normalize and remove empty/blank values
|
||||||
|
clean_keys = [k for k in keys if k is not None and str(k).strip() != ""]
|
||||||
|
if not clean_keys:
|
||||||
|
return cls.model.objects.none()
|
||||||
|
|
||||||
|
# try to convert numeric keys to ints where possible
|
||||||
|
parsed_keys = []
|
||||||
|
for k in clean_keys:
|
||||||
|
try:
|
||||||
|
parsed_keys.append(int(k))
|
||||||
|
except Exception:
|
||||||
|
parsed_keys.append(k)
|
||||||
|
|
||||||
|
qs = cls.model.objects.filter(pk__in=parsed_keys)
|
||||||
|
|
||||||
|
# Return a list of dicts matching the autocomplete core expectations
|
||||||
|
# (items subscriptable by keys like 'key' / 'label').
|
||||||
|
return [
|
||||||
|
{"key": obj.pk, "label": getattr(obj, "name", str(obj)), "value": str(obj)}
|
||||||
|
for obj in qs
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
class ConditionAutocompleteForm(Form):
|
class ConditionAutocompleteForm(Form):
|
||||||
condition = ModelChoiceField(
|
condition = ModelChoiceField(
|
||||||
|
|||||||
@@ -36,6 +36,28 @@
|
|||||||
{% empty %}
|
{% empty %}
|
||||||
—
|
—
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
{% if request.user.is_authenticated and can_merge %}
|
||||||
|
<div class="mt-2">
|
||||||
|
<a class="btn btn-sm btn-outline-primary" data-bs-toggle="collapse" href="#addSynonymCollapse" role="button" aria-expanded="false" aria-controls="addSynonymCollapse">Add synonym</a>
|
||||||
|
</div>
|
||||||
|
<div class="collapse mt-2" id="addSynonymCollapse">
|
||||||
|
<div class="card card-body">
|
||||||
|
<form method="POST">
|
||||||
|
{% csrf_token %}
|
||||||
|
<input type="hidden" name="action" value="add_synonym" />
|
||||||
|
<div class="mb-2">
|
||||||
|
<label class="form-label small">Select existing condition</label>
|
||||||
|
{{ synonym_form.condition }}
|
||||||
|
</div>
|
||||||
|
<div class="mb-2">
|
||||||
|
<label class="form-label small">Or create new synonym name</label>
|
||||||
|
<input class="form-control" type="text" name="synonym_name" placeholder="e.g. Granulomatosis with Polyangiitis" />
|
||||||
|
</div>
|
||||||
|
<button class="btn btn-primary btn-sm" type="submit">Add</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
</dd>
|
</dd>
|
||||||
|
|
||||||
<dt class="col-sm-4">Parent</dt>
|
<dt class="col-sm-4">Parent</dt>
|
||||||
|
|||||||
+43
-2
@@ -830,7 +830,12 @@ def question_schema_schemas(request):
|
|||||||
def condition_detail(request, pk):
|
def condition_detail(request, pk):
|
||||||
condition = get_object_or_404(Condition, pk=pk)
|
condition = get_object_or_404(Condition, pk=pk)
|
||||||
|
|
||||||
form = ConditionAutocompleteForm()
|
# form used for the merge UI
|
||||||
|
merge_form = ConditionAutocompleteForm()
|
||||||
|
|
||||||
|
# form used to add a synonym (can post either an existing condition via autocomplete
|
||||||
|
# or provide a plain name in 'synonym_name')
|
||||||
|
synonym_form = ConditionAutocompleteForm()
|
||||||
|
|
||||||
can_merge = False
|
can_merge = False
|
||||||
|
|
||||||
@@ -840,11 +845,47 @@ def condition_detail(request, pk):
|
|||||||
):
|
):
|
||||||
can_merge = True
|
can_merge = True
|
||||||
|
|
||||||
|
# Handle POST to add a synonym
|
||||||
|
if request.method == "POST" and request.POST.get("action") == "add_synonym":
|
||||||
|
# only allow staff/editors
|
||||||
|
if not (
|
||||||
|
request.user.is_superuser
|
||||||
|
or request.user.groups.filter(name="atlas_editor").exists()
|
||||||
|
):
|
||||||
|
return HttpResponse("Forbidden", status=403)
|
||||||
|
|
||||||
|
# Try autocomplete-selected condition first
|
||||||
|
form = ConditionAutocompleteForm(request.POST)
|
||||||
|
other = None
|
||||||
|
if form.is_valid():
|
||||||
|
other = form.cleaned_data.get("condition")
|
||||||
|
|
||||||
|
# If no autocomplete selection, accept a free-text name
|
||||||
|
synonym_name = request.POST.get("synonym_name")
|
||||||
|
if not other and synonym_name:
|
||||||
|
other, created = Condition.objects.get_or_create(name=synonym_name)
|
||||||
|
if created:
|
||||||
|
# mark created synonym as non-primary
|
||||||
|
other.primary = False
|
||||||
|
other.save()
|
||||||
|
|
||||||
|
if other and other != condition:
|
||||||
|
# add mutual synonym relationship
|
||||||
|
condition.synonym.add(other)
|
||||||
|
other.synonym.add(condition)
|
||||||
|
|
||||||
|
return redirect("atlas:condition_detail", pk=condition.pk)
|
||||||
|
|
||||||
# logging.debug(atlas.subspecialty.first().name.all())
|
# logging.debug(atlas.subspecialty.first().name.all())
|
||||||
return render(
|
return render(
|
||||||
request,
|
request,
|
||||||
"atlas/condition_detail.html",
|
"atlas/condition_detail.html",
|
||||||
{"condition": condition, "form": form, "can_merge": can_merge},
|
{
|
||||||
|
"condition": condition,
|
||||||
|
"form": merge_form,
|
||||||
|
"synonym_form": synonym_form,
|
||||||
|
"can_merge": can_merge,
|
||||||
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user