Skip to content
Closed
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
6 changes: 4 additions & 2 deletions backend/DOSPORTAL/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,11 @@
)

# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
DEBUG = os.getenv("DEBUG", "False") == "True"

ALLOWED_HOSTS = ["*", "localhost", "127.0.0.1", "0.0.0.0", "backend"]
ALLOWED_HOSTS = os.getenv(
"ALLOWED_HOSTS", "localhost,127.0.0.1,0.0.0.0,backend"
).split(",")

# Site URL configuration
SITE_URL = os.getenv("SITE_URL", "http://localhost:8080")
Expand Down
37 changes: 37 additions & 0 deletions backend/DOSPORTAL/tests/api/test_detector.py
Original file line number Diff line number Diff line change
Expand Up @@ -174,3 +174,40 @@ def test_get_detectors_with_data():
assert response.status_code == 200
assert len(response.data) == 2
assert response.data[0]["name"] in ["Detector 1", "Detector 2"]


@pytest.mark.django_db
def test_get_detector_detail_success():
"""GET /detector/<id>/ - returns single detector"""
user = User.objects.create_user(username="detailuser", password="pass123")
manuf = DetectorManufacturer.objects.create(name="Manuf", url="http://manuf.com")
dtype = DetectorType.objects.create(name="TypeA", manufacturer=manuf)
org = Organization.objects.create(name="OrgA")
detector = Detector.objects.create(name="Det1", type=dtype, owner=org, sn="SN-DETAIL")

client = APIClient()
client.force_authenticate(user=user)
response = client.get(f"/api/detector/{detector.id}/")
assert response.status_code == 200
assert response.data["id"] == str(detector.id)
assert response.data["name"] == "Det1"
assert response.data["sn"] == "SN-DETAIL"


@pytest.mark.django_db
def test_get_detector_detail_not_found():
"""GET /detector/<id>/ - non-existent detector returns 404"""
user = User.objects.create_user(username="detailuser2", password="pass123")
client = APIClient()
client.force_authenticate(user=user)
response = client.get("/api/detector/00000000-0000-0000-0000-000000000000/")
assert response.status_code == 404
assert "not found" in response.data["detail"].lower()


@pytest.mark.django_db
def test_get_detector_detail_unauthenticated():
"""GET /detector/<id>/ - requires authentication"""
client = APIClient()
response = client.get("/api/detector/00000000-0000-0000-0000-000000000000/")
assert response.status_code in [401, 403]
13 changes: 13 additions & 0 deletions backend/api/permissions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
"""Shared permission helpers for API views."""

from DOSPORTAL.models import OrganizationUser


def check_org_admin_permission(user, org):
"""
Check if user is admin or owner of the organization.
Returns (has_permission: bool, org_user: OrganizationUser|None)
"""
org_user = OrganizationUser.objects.filter(user=user, organization=org).first()
has_permission = org_user and org_user.user_type in ["OW", "AD"]
return has_permission, org_user
1 change: 1 addition & 0 deletions backend/api/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
path("measurement/add/", views.MeasurementsPost),
path("record/", views.RecordGet),
path("detector/", views.DetectorGet),
path("detector/<uuid:detector_id>/", views.DetectorDetail),
path("detector/<uuid:detector_id>/qr/", views.DetectorQRCode),
path("detector-manufacturer/", views.detector_manufacturer_list),
path(
Expand Down
2 changes: 2 additions & 0 deletions backend/api/views/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
DetectorTypeList,
DetectorTypeDetail,
DetectorGet,
DetectorDetail,
DetectorLogbookGet,
DetectorLogbookPost,
DetectorLogbookPut,
Expand Down Expand Up @@ -46,6 +47,7 @@
"DetectorTypeList",
"DetectorTypeDetail",
"DetectorGet",
"DetectorDetail",
"DetectorLogbookGet",
"DetectorLogbookPost",
"DetectorLogbookPut",
Expand Down
42 changes: 29 additions & 13 deletions backend/api/views/detectors.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,22 +24,11 @@
DetectorLogbookSerializer,
)
from ..qr_utils import generate_qr_code, generate_qr_detector_with_label
from ..permissions import check_org_admin_permission

logger = logging.getLogger("api.detectors")


def check_org_admin_permission(user, org):
"""
Check if user is admin or owner of the organization.
Returns (has_permission: bool, org_user: OrganizationUser|None)
"""
from DOSPORTAL.models import OrganizationUser

org_user = OrganizationUser.objects.filter(user=user, organization=org).first()
has_permission = org_user and org_user.user_type in ["OW", "AD"]
return has_permission, org_user


@extend_schema(
responses={200: DetectorManufacturerSerializer(many=True)},
request=DetectorManufacturerSerializer,
Expand Down Expand Up @@ -200,6 +189,33 @@ def DetectorGet(request):
)


@extend_schema(
responses={200: DetectorSerializer},
description="Get a single detector by ID",
tags=["Detectors"],
parameters=[
OpenApiParameter(
name="detector_id",
type=OpenApiTypes.UUID,
location=OpenApiParameter.PATH,
description="Detector ID",
)
],
)
@api_view(["GET"])
@permission_classes((IsAuthenticated,))
def DetectorDetail(request, detector_id):
"""Get a single detector by ID."""
try:
detector = Detector.objects.select_related("type__manufacturer", "owner").get(
id=detector_id
)
except Detector.DoesNotExist:
return Response({"detail": "Detector not found."}, status=status.HTTP_404_NOT_FOUND)
serializer = DetectorSerializer(detector)
return Response(serializer.data)


@extend_schema(
responses={200: DetectorLogbookSerializer(many=True)},
description="Get detector logbook entries with optional filters by detector, type, or date range",
Expand Down Expand Up @@ -285,7 +301,7 @@ def DetectorLogbookPost(request):
)
except Detector.DoesNotExist:
return Response(
{"detail": "Detektor not found."}, status=status.HTTP_404_NOT_FOUND
{"detail": "Detector not found."}, status=status.HTTP_404_NOT_FOUND
)

serializer = DetectorLogbookSerializer(data=request.data)
Expand Down
1 change: 1 addition & 0 deletions backend/api/views/measurements.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ def MeasurementsPost(request):
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
return Response(serializer.errors, status=400)


@extend_schema(tags=["Measurements"])
Expand Down
13 changes: 2 additions & 11 deletions backend/api/views/organizations.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,22 +26,13 @@
CreateInviteRequestSerializer,
CreateInviteResponseSerializer,
)
from ..permissions import check_org_admin_permission

import logging

logger = logging.getLogger(__name__)


def check_org_admin_permission(user, org):
"""
Check if user is admin or owner of the organization.
Returns (has_permission: bool, org_user: OrganizationUser|None)
"""
org_user = OrganizationUser.objects.filter(user=user, organization=org).first()
has_permission = org_user and org_user.user_type in ["OW", "AD"]
return has_permission, org_user


@extend_schema(
request=CreateOrganizationRequestSerializer,
responses={201: OrganizationDetailSerializer},
Expand Down Expand Up @@ -76,7 +67,7 @@ def Organizations(request):
user=request.user, organization=org, user_type="OW"
)
serializer = OrganizationDetailSerializer(org)
logger.exception("Error creating organization")
logger.info("Organization created: %s by user %s", org.id, request.user)
return Response(serializer.data, status=status.HTTP_201_CREATED)
except Exception:
return Response(
Expand Down
20 changes: 17 additions & 3 deletions frontend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion frontend/package.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"name": "dosportal-rect",
"name": "dosportal-react",
"private": true,
"version": "0.0.0",
"type": "module",
Expand Down
Loading
Loading