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
|
||||
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):
|
||||
condition = ModelChoiceField(
|
||||
|
||||
@@ -36,6 +36,28 @@
|
||||
{% empty %}
|
||||
—
|
||||
{% 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>
|
||||
|
||||
<dt class="col-sm-4">Parent</dt>
|
||||
|
||||
+43
-2
@@ -830,7 +830,12 @@ def question_schema_schemas(request):
|
||||
def condition_detail(request, 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
|
||||
|
||||
@@ -840,11 +845,47 @@ def condition_detail(request, pk):
|
||||
):
|
||||
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())
|
||||
return render(
|
||||
request,
|
||||
"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