Files
dashboard-cpsp-executive/backend/apps/sync/views.py
T

227 lines
8.9 KiB
Python

from rest_framework import permissions, status, viewsets
from rest_framework.decorators import action
from rest_framework.response import Response
from django.db import IntegrityError
from apps.accounts.permissions import CanRegisterActiveSite, IsExecutive, can_sync
from apps.accounts.scoping import filter_active_sites_queryset
from apps.farms.services.active_site_registry import ensure_hq_site_for_active_site
from apps.sync.models import ActiveSite
from apps.sync.serializers import (
ActiveSiteClaimSerializer,
ActiveSiteSerializer,
ActiveSiteWriteSerializer,
SiteRegisterSerializer,
)
class ActiveSiteViewSet(viewsets.ModelViewSet):
"""
List/read: scoped (director all; BUH managed GMs; GM own).
Create (claim): GM and GM Admin claim a site-registered ActiveSite by code.
Sync: GM and GM Admin only (stubs until mirror pull is implemented).
Site dashboards create/fill ActiveSite via register-from-site (API key auth).
Claiming attaches managed_by and ensures an HQ Site for the view list.
Destroy (Hapus) unclaims only — does not delete the ActiveSite row.
"""
executive_sync_action = True
http_method_names = ["get", "post", "put", "patch", "delete", "head", "options"]
def get_queryset(self):
qs = ActiveSite.objects.select_related("managed_by").all()
return filter_active_sites_queryset(qs, self.request.user)
def get_permissions(self):
if self.action == "register_from_site":
return [permissions.IsAuthenticated()]
if self.action in {"create", "update", "partial_update", "destroy"}:
return [CanRegisterActiveSite()]
return [IsExecutive()]
def get_serializer_class(self):
if self.action == "register_from_site":
return SiteRegisterSerializer
if self.action == "create":
return ActiveSiteClaimSerializer
if self.action in {"update", "partial_update"}:
return ActiveSiteWriteSerializer
return ActiveSiteSerializer
def create(self, request, *args, **kwargs):
"""GM claim-by-code: site must have registered first."""
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
code = serializer.validated_data["code"]
active_site = ActiveSite.objects.filter(code=code).first()
if active_site is None:
return Response(
{
"detail": (
"Site belum mendaftar dari dasbor site. "
"Minta staf site mendaftar dulu dengan kode ini."
)
},
status=status.HTTP_404_NOT_FOUND,
)
owner_id = active_site.managed_by_id
if owner_id and owner_id != request.user.pk:
return Response(
{"detail": "Kode sudah diklaim oleh GM lain."},
status=status.HTTP_409_CONFLICT,
)
if owner_id != request.user.pk:
active_site.managed_by = request.user
active_site.save(update_fields=["managed_by", "updated_at"])
ensure_hq_site_for_active_site(active_site)
return Response(
ActiveSiteSerializer(active_site).data,
status=status.HTTP_200_OK,
)
def perform_update(self, serializer):
active_site = serializer.save()
if active_site.is_active and active_site.managed_by_id:
ensure_hq_site_for_active_site(active_site)
def destroy(self, request, *args, **kwargs):
"""
Hapus from daftar tampilan: unclaim only.
Keeps the ActiveSite registry row (credentials / source_site_id) so the
site dashboard registration is not wiped. Clears managed_by and unlinks
any HQ Site.active_site pointer.
"""
from apps.farms.models import Site
active_site = self.get_object()
Site.objects.filter(active_site=active_site).update(active_site=None)
active_site.managed_by = None
active_site.save(update_fields=["managed_by", "updated_at"])
return Response(status=status.HTTP_204_NO_CONTENT)
@action(detail=False, methods=["post"], url_path="register-from-site")
def register_from_site(self, request):
"""
Machine endpoint: site dashboard creates/updates ActiveSite credentials.
Does not set managed_by (GM claim is separate).
One farm site_id maps to at most one ActiveSite. Re-register with a new
code updates that row; it never inserts a second ActiveSite.
"""
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
data = serializer.validated_data
code = data["code"]
source_site_id = data["site_id"]
active_site = ActiveSite.objects.filter(source_site_id=source_site_id).first()
if active_site is None:
by_code = ActiveSite.objects.filter(code=code).first()
if by_code is not None:
if by_code.source_site_id and by_code.source_site_id != source_site_id:
return Response(
{"detail": "Kode sudah dipakai site lain."},
status=status.HTTP_409_CONFLICT,
)
# Legacy row missing source_site_id: attach this site to it.
active_site = by_code
if active_site is None:
try:
active_site = ActiveSite.objects.create(
code=code,
name=data["name"],
source_site_id=source_site_id,
api_base_url=data["api_base_url"],
api_key=data["api_key"],
managed_by=None,
is_active=True,
)
except IntegrityError:
# Concurrent register for same source_site_id or code.
active_site = ActiveSite.objects.filter(
source_site_id=source_site_id
).first() or ActiveSite.objects.filter(code=code).first()
if active_site is None:
return Response(
{"detail": "Gagal mendaftarkan site — coba lagi."},
status=status.HTTP_409_CONFLICT,
)
else:
return Response(
ActiveSiteSerializer(active_site).data,
status=status.HTTP_201_CREATED,
)
if (
code != active_site.code
and ActiveSite.objects.filter(code=code).exclude(pk=active_site.pk).exists()
):
return Response(
{"detail": "Kode sudah dipakai site lain."},
status=status.HTTP_409_CONFLICT,
)
active_site.code = code
active_site.name = data["name"]
active_site.source_site_id = source_site_id
active_site.api_base_url = data["api_base_url"]
active_site.api_key = data["api_key"]
active_site.is_active = True
try:
active_site.save(
update_fields=[
"code",
"name",
"source_site_id",
"api_base_url",
"api_key",
"is_active",
"updated_at",
]
)
except IntegrityError:
return Response(
{"detail": "Site atau kode bentrok dengan pendaftaran lain."},
status=status.HTTP_409_CONFLICT,
)
return Response(ActiveSiteSerializer(active_site).data, status=status.HTTP_200_OK)
@action(detail=True, methods=["post"], url_path="sync")
def sync(self, request, pk=None):
if not can_sync(request.user):
return Response({"detail": "Sync not allowed."}, status=status.HTTP_403_FORBIDDEN)
active_site = self.get_object()
if not active_site.is_active:
return Response(
{"detail": "Active site is inactive."},
status=status.HTTP_400_BAD_REQUEST,
)
return Response(
{
"detail": "Active site sync not implemented yet.",
"active_site": ActiveSiteSerializer(active_site).data,
},
status=status.HTTP_501_NOT_IMPLEMENTED,
)
@action(detail=False, methods=["post"], url_path="sync-all")
def sync_all(self, request):
if not can_sync(request.user):
return Response({"detail": "Sync not allowed."}, status=status.HTTP_403_FORBIDDEN)
sites = list(self.get_queryset().filter(is_active=True))
return Response(
{
"detail": "Active site sync-all not implemented yet.",
"active_site_count": len(sites),
"active_sites": ActiveSiteSerializer(sites, many=True).data,
},
status=status.HTTP_501_NOT_IMPLEMENTED,
)