Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 9 additions & 9 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
# This file is autogenerated by pip-compile with Python 3.13
# by the following command:
#
# pip-compile requirements.in
# pip-compile --output-file=requirements.txt requirements.in
#
amqp==5.3.1
# via kombu
Expand Down Expand Up @@ -31,11 +31,11 @@ automat==25.4.16
# via twisted
billiard==4.2.4
# via celery
black==26.3.0
black==26.3.1
# via -r requirements.in
boto3==1.42.63
boto3==1.42.68
# via django-storages
botocore==1.42.63
botocore==1.42.68
# via
# boto3
# s3transfer
Expand Down Expand Up @@ -80,7 +80,7 @@ click-plugins==1.1.1.2
# via celery
click-repl==0.3.0
# via celery
coloraide==8.6
coloraide==8.7
# via -r requirements.in
constantly==23.10.4
# via twisted
Expand Down Expand Up @@ -121,7 +121,7 @@ django-storages[boto3]==1.14.6
# via -r requirements.in
django-timezone-field==7.2.1
# via django-celery-beat
django-unfold==0.83.1
django-unfold==0.84.0
# via -r requirements.in
docstring-parser==0.17.0
# via anthropic
Expand All @@ -131,11 +131,11 @@ execnet==2.1.2
# via pytest-xdist
factory-boy==3.3.3
# via -r requirements.in
faker==40.8.0
faker==40.11.0
# via factory-boy
flake8==7.3.0
# via -r requirements.in
fonttools[woff]==4.61.1
fonttools[woff]==4.62.1
# via weasyprint
gunicorn==25.1.0
# via -r requirements.in
Expand Down Expand Up @@ -311,7 +311,7 @@ tzdata==2025.3
# kombu
tzlocal==5.3.1
# via celery
ujson==5.11.0
ujson==5.12.0
# via autobahn
urllib3==2.6.3
# via
Expand Down
93 changes: 93 additions & 0 deletions src/assets/tests/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -7910,3 +7910,96 @@ def test_missing_q_param_returns_empty_json(self, client_logged_in):
resp = client_logged_in.get(url)
assert resp.status_code == 200
assert resp.json() == []


@pytest.mark.django_db
class TestLocationCreateInline:
"""Tests for the inline location creation AJAX endpoint."""

def test_create_new_top_level_location(self, client_logged_in):
"""Creating a new top-level location returns success JSON."""
url = reverse("assets:location_create_inline")
resp = client_logged_in.post(
url,
data=json.dumps({"name": "Test Box 1"}),
content_type="application/json",
)
assert resp.status_code == 200
data = resp.json()
assert data["created"] is True
assert data["name"] == "Test Box 1"
assert Location.objects.filter(name="Test Box 1").exists()

def test_duplicate_top_level_location_returns_existing(
self, client_logged_in
):
"""Creating a location with an existing top-level name returns
the existing location instead of crashing (bug #189)."""
Location.objects.create(name="Skirts long box 1")
url = reverse("assets:location_create_inline")
resp = client_logged_in.post(
url,
data=json.dumps({"name": "Skirts long box 1"}),
content_type="application/json",
)
# Should NOT return 500 — should return the existing location
assert resp.status_code == 200
data = resp.json()
assert data["id"] is not None
assert data["name"] == "Skirts long box 1"
# Should not have created a duplicate
assert (
Location.objects.filter(
name="Skirts long box 1", parent__isnull=True
).count()
== 1
)

def test_duplicate_child_location_under_same_parent(
self, client_logged_in, location
):
"""Creating a child location with same name under same parent
returns existing."""
Location.objects.create(name="Sub Box", parent=location)
url = reverse("assets:location_create_inline")
resp = client_logged_in.post(
url,
data=json.dumps({"name": "Sub Box", "parent_id": location.pk}),
content_type="application/json",
)
assert resp.status_code == 200
data = resp.json()
assert data["id"] is not None
# Should not have created a duplicate
assert (
Location.objects.filter(name="Sub Box", parent=location).count()
== 1
)

def test_same_name_different_parent_creates_new(
self, client_logged_in, location
):
"""Same name under different parent should create a new location."""
Location.objects.create(name="Box A", parent=None)
url = reverse("assets:location_create_inline")
resp = client_logged_in.post(
url,
data=json.dumps({"name": "Box A", "parent_id": location.pk}),
content_type="application/json",
)
assert resp.status_code == 200
data = resp.json()
assert data["created"] is True

def test_created_false_when_existing_returned(self, client_logged_in):
"""Response should indicate created=False when returning an
existing location."""
Location.objects.create(name="Existing Box")
url = reverse("assets:location_create_inline")
resp = client_logged_in.post(
url,
data=json.dumps({"name": "Existing Box"}),
content_type="application/json",
)
assert resp.status_code == 200
assert resp.json()["created"] is False
4 changes: 2 additions & 2 deletions src/assets/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -3700,8 +3700,8 @@ def location_create_inline(request):
return JsonResponse(
{"error": "Invalid parent location"}, status=400
)
loc = Location.objects.create(name=name, parent=parent)
return JsonResponse({"id": loc.id, "name": str(loc), "created": True})
loc, created = Location.objects.get_or_create(name=name, parent=parent)
return JsonResponse({"id": loc.id, "name": str(loc), "created": created})


# --- Stocktake ---
Expand Down
Loading