diff --git a/atlas/forms.py b/atlas/forms.py index fd0041bf..2d80f167 100755 --- a/atlas/forms.py +++ b/atlas/forms.py @@ -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( diff --git a/atlas/templates/atlas/condition_detail.html b/atlas/templates/atlas/condition_detail.html index d48e1f56..b56332d3 100755 --- a/atlas/templates/atlas/condition_detail.html +++ b/atlas/templates/atlas/condition_detail.html @@ -36,6 +36,28 @@ {% empty %} — {% endfor %} + {% if request.user.is_authenticated and can_merge %} +
+ +
+
+
+
+ {% csrf_token %} + +
+ + {{ synonym_form.condition }} +
+
+ + +
+ +
+
+
+ {% endif %}
Parent
diff --git a/atlas/views.py b/atlas/views.py index fad32331..9f92498c 100755 --- a/atlas/views.py +++ b/atlas/views.py @@ -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, + }, )