Browse Source

fix: Fix device loading issue with search and organization join

Issues fixed:
- Applied missing Alembic migration (WiFi credentials)
- Fixed conflicting join and joinedload in device_service
- Changed to outerjoin for proper left outer join
- Always load organization relationship with joinedload

Now using:
- outerjoin for search filtering (when search provided)
- joinedload for eager loading organization (always)

This prevents N+1 queries and ensures organization data is available.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
root 1 month ago
parent
commit
ccde23fb80
1 changed files with 8 additions and 9 deletions
  1. 8 9
      backend/app/services/device_service.py

+ 8 - 9
backend/app/services/device_service.py

@@ -105,8 +105,8 @@ async def list_devices(
     Returns:
         Tuple of (devices list, total count)
     """
-    # Build query with Organization join for search
-    query = select(Device).options(joinedload(Device.organization))
+    # Build query
+    query = select(Device)
 
     # Base filters
     filters = []
@@ -117,26 +117,25 @@ async def list_devices(
     if status:
         filters.append(Device.status == status)
 
-    # Universal search filter
+    # Universal search filter - requires join with Organization
     if search and len(search) >= 2:
         # Join with Organization for searching by org name/email
-        query = query.join(Organization, Device.organization_id == Organization.id, isouter=True)
+        query = query.outerjoin(Organization, Device.organization_id == Organization.id)
 
         # Search across multiple fields
         search_pattern = f"%{search}%"
         search_filters = [
             Device.mac_address.ilike(search_pattern),
             func.cast(Device.simple_id, String).ilike(search_pattern),
-        ]
-
-        # Only add organization filters if we have organization data
-        search_filters.extend([
             Organization.name.ilike(search_pattern),
             Organization.contact_email.ilike(search_pattern),
-        ])
+        ]
 
         filters.append(or_(*search_filters))
 
+    # Always load organization relationship
+    query = query.options(joinedload(Device.organization))
+
     # Apply all filters
     if filters:
         query = query.where(*filters)