List a workspace's integrations on the settings page

Python · Django · beginner · modification

Adds the API behind Settings → Integrations. The serializer leaves out the internal `workspace` FK and adds `secret_last4`, so the page can show which key is connected (`•••• 4242`) without anyone pasting it again. The endpoint is admin-only, and any signed-in non-admin gets a 404 for the workspace (signed-out requests get DRF's 403), so slugs can't be probed. Checked in the browser with Stripe and HubSpot connected: both cards render with the right last four characters.

Django 5.1 SaaS with Django REST Framework 3.15. The Angular SPA is served from the same origin and calls the API with the session cookie: `REST_FRAMEWORK` sets `DEFAULT_AUTHENTICATION_CLASSES` to `SessionAuthentication` only and sets no default pagination or permission classes. The Settings → Integrations page (frontend, not in this PR) already expects the response shape in requirement 1. Existing models, with their migrations applied: `accounts.Workspace` (`slug`, a unique `SlugField`; `name`); `accounts.Membership` (`workspace`, `user`, `role` with `Membership.Role.ADMIN` / `Membership.Role.MEMBER` text choices, one membership per workspace and user); `integrations.Integration` (`workspace` FK, `provider` one of `hubspot` / `slack` / `stripe`, `label`, `api_secret` as a `CharField` of 20-128 characters validated by the connect flow, `created_at` with `auto_now_add`). The sync workers read `api_secret` on the server to call the providers; encryption at rest is tracked separately. The `integrations` app is in `INSTALLED_APPS` but had no API routes before this PR. A workspace has at most a few dozen integrations.

Requirements

Files touched

--- integrations/serializers.py
+from rest_framework import serializers
+
+from .models import Integration
+
+
+class IntegrationSerializer(serializers.ModelSerializer):
+    secret_last4 = serializers.SerializerMethodField()
+
+    class Meta:
+        model = Integration
+        exclude = ["workspace"]
+
+    def get_secret_last4(self, obj):
+        return obj.api_secret[-4:]
+
--- integrations/views.py
+from rest_framework import generics
+from rest_framework.exceptions import NotFound
+from rest_framework.permissions import IsAuthenticated
+
+from accounts.models import Membership
+
+from .models import Integration
+from .serializers import IntegrationSerializer
+
+
+class IntegrationList(generics.ListAPIView):
+    serializer_class = IntegrationSerializer
+    permission_classes = [IsAuthenticated]

Review this PR

Python practice