Skip to content

Commit 4293ca1

Browse files
marbindrakonclaude
andcommitted
Refactor: eliminate core↔health circular dependency, split monoliths
Phase 1 — AircraftViewSet action registry + health mixin: - core/action_registry.py: permission routing registry; plugins register via register_owner/pilot/read_pilot_write_owner_actions() - health/aircraft_actions.py: HealthAircraftActionsMixin with all 20 @action methods moved out of AircraftViewSet; registers permissions at module load time so AircraftViewSet.get_permissions() stays generic - health/serializer_mixins.py: AirworthinessMixin moved from core - core/serializers.py: import AirworthinessMixin from health - core/sharing.py: validate_share_token() helper extracted - health/views_public.py: PublicAircraftSummaryAPI + PublicLogbookEntriesAPI moved from core; core now has zero module-level health imports except the two intentional bridge imports Phase 2 — Split core/views.py (2168 lines) → core/views/ package: - aircraft.py, auth_views.py, public_views.py, import_export_views.py, template_views.py, logbook_import_view.py, notes_events.py, user_views.py, invitations.py - __init__.py re-exports all public names for backwards compatibility Phase 3 — Split aircraft_detail.html (6123 lines) → parent + 10 includes: - core/templates/includes/detail_{overview,components,logbook,squawks, compliance,records,consumables,documents,roles,flights}.html - Parent template reduced to ~262 lines (header + tab nav + {% include %}) Phase 4 — Per-app URL registration lists + shared router: - core/urls.py: ROUTER_REGISTRATIONS (5 core routes) - health/urls.py: ROUTER_REGISTRATIONS (15 health routes) - simple_aircraft_manager/urls.py: single loop replaces 20 inline calls All 719 tests pass. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent bda9700 commit 4293ca1

33 files changed

Lines changed: 8385 additions & 8073 deletions

CLAUDE.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,7 @@ Backfill ownership: `python manage.py assign_owners --user <username> --all`
170170

171171
Feature flag: `OIDC_ENABLED` env var (default `false`). Library: `mozilla-django-oidc`.
172172

173-
Backends (when enabled): `CustomOIDCAuthenticationBackend` (OIDC first) + `ModelBackend` (fallback — local/admin accounts always work). Username strategy: `preferred_username` → email local part → `sub`. Auto-creates/syncs users on login. Logout (`core.views.custom_logout`) handles both RP-initiated OIDC logout and Django session logout.
173+
Backends (when enabled): `CustomOIDCAuthenticationBackend` (OIDC first) + `ModelBackend` (fallback — local/admin accounts always work). Username strategy: `preferred_username` → email local part → `sub`. Auto-creates/syncs users on login. Logout (`core.views.auth_views.custom_logout`) handles both RP-initiated OIDC logout and Django session logout.
174174

175175
Key files: `core/oidc.py`, `core/context_processors.py` (exposes `OIDC_ENABLED` to templates).
176176

@@ -242,7 +242,10 @@ Documents have `collection` FK set or `collection=null` (uncollected). Both retu
242242
| Event logging | `core/events.py` (`log_event`) |
243243
| EventLoggingMixin + AircraftScopedMixin | `core/mixins.py` |
244244
| Permission classes | `core/permissions.py` |
245-
| Public sharing views | `core/views.py` |
245+
| Public sharing views | `health/views_public.py` |
246+
| Views package | `core/views/` (`aircraft.py`, `auth_views.py`, `public_views.py`, `import_export_views.py`, etc.) |
247+
| Token validation helper | `core/sharing.py` (`validate_share_token`) |
248+
| Action permission registry | `core/action_registry.py` |
246249
| Public base template | `core/templates/base_public.html` |
247250
| Logbook import | `health/logbook_import.py` |
248251
| OIDC backend | `core/oidc.py` |

core/action_registry.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# Action permission routing registry.
2+
# Each mixin registers its actions at module load time.
3+
# AircraftViewSet.get_permissions() consults the registry.
4+
5+
from core.permissions import IsAircraftOwnerOrAdmin, IsAircraftPilotOrAbove
6+
from rest_framework.permissions import SAFE_METHODS
7+
8+
_OWNER_ACTIONS = set() # require IsAircraftOwnerOrAdmin always
9+
_PILOT_ACTIONS = set() # require IsAircraftPilotOrAbove always
10+
_READ_PILOT_WRITE_OWNER = set() # GET → pilot, POST/DELETE → owner
11+
12+
13+
def register_owner_actions(*actions):
14+
_OWNER_ACTIONS.update(actions)
15+
16+
17+
def register_pilot_actions(*actions):
18+
_PILOT_ACTIONS.update(actions)
19+
20+
21+
def register_read_pilot_write_owner(*actions):
22+
_READ_PILOT_WRITE_OWNER.update(actions)
23+
24+
25+
def get_action_permissions(action_name, http_method=None):
26+
"""Returns (found: bool, permission_classes: list | None)."""
27+
if action_name in _OWNER_ACTIONS:
28+
return True, [IsAircraftOwnerOrAdmin()]
29+
if action_name in _PILOT_ACTIONS:
30+
return True, [IsAircraftPilotOrAbove()]
31+
if action_name in _READ_PILOT_WRITE_OWNER:
32+
if http_method and http_method in SAFE_METHODS:
33+
return True, [IsAircraftPilotOrAbove()]
34+
return True, [IsAircraftOwnerOrAdmin()]
35+
return False, None

core/serializers.py

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,7 @@
22
from django.urls import reverse
33

44
from .models import Aircraft, AircraftNote, AircraftEvent, AircraftRole, AircraftShareToken, InvitationCode, InvitationCodeAircraftRole, InvitationCodeRedemption
5-
from health.services import calculate_airworthiness
6-
7-
8-
class AirworthinessMixin:
9-
def get_airworthiness(self, obj):
10-
return calculate_airworthiness(obj).to_dict()
5+
from health.serializer_mixins import AirworthinessMixin
116

127

138
class UserRoleMixin:

core/sharing.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
"""Shared token validation helper used by public views."""
2+
from django.http import JsonResponse
3+
from django.utils import timezone
4+
5+
from core.models import AircraftShareToken
6+
7+
8+
def validate_share_token(share_token):
9+
"""
10+
Validate a share token UUID.
11+
Returns (token_obj, None) on success or (None, error_json_response) on failure.
12+
"""
13+
try:
14+
token_obj = AircraftShareToken.objects.select_related('aircraft').get(token=share_token)
15+
except AircraftShareToken.DoesNotExist:
16+
return None, JsonResponse({'error': 'Not found'}, status=404)
17+
if token_obj.expires_at and token_obj.expires_at < timezone.now():
18+
return None, JsonResponse({'error': 'Not found'}, status=404)
19+
return token_obj, None

core/templates/aircraft_detail.html

Lines changed: 10 additions & 5871 deletions
Large diffs are not rendered by default.

core/templates/includes/detail_compliance.html

Lines changed: 473 additions & 0 deletions
Large diffs are not rendered by default.

core/templates/includes/detail_components.html

Lines changed: 270 additions & 0 deletions
Large diffs are not rendered by default.

core/templates/includes/detail_consumables.html

Lines changed: 379 additions & 0 deletions
Large diffs are not rendered by default.

core/templates/includes/detail_documents.html

Lines changed: 219 additions & 0 deletions
Large diffs are not rendered by default.

core/templates/includes/detail_flights.html

Lines changed: 3274 additions & 0 deletions
Large diffs are not rendered by default.

0 commit comments

Comments
 (0)