update link registration site to executive dashboard

This commit is contained in:
Alberto-Audrix committed 2026-09-15 09:44:45 +07:00
1 parent 1e6f6b3b57
commit d0d1ae7d24
26 files changed
+1044 -55

No files matched your search

@@ -0,0 +1,36 @@
# Generated manually for PusatRegistration
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
("farms", "0015_cycle_close_request"),
]
operations = [
migrations.CreateModel(
name="PusatRegistration",
fields=[
("registration_id", models.BigAutoField(primary_key=True, serialize=False)),
("code", models.SlugField(max_length=64)),
("active_site_id", models.BigIntegerField()),
("registered_at", models.DateTimeField(auto_now=True)),
("created_at", models.DateTimeField(auto_now_add=True)),
(
"site",
models.OneToOneField(
on_delete=django.db.models.deletion.CASCADE,
related_name="pusat_registration",
to="farms.site",
),
),
],
options={
"db_table": "pusat_registrations",
"ordering": ["-registered_at"],
},
),
]
@@ -0,0 +1,121 @@
# Convert Site.site_id from BigAutoField to name-based SlugField PK with numeric suffix.
# atomic=False: PostgreSQL cannot ALTER TABLE in the same transaction after PK UPDATEs.
from django.db import migrations, models
import django.db.models.deletion
from django.utils.text import slugify
def _unique_slug(site_name: str, old_pk: str, used: set[str]) -> str:
base = slugify(str(site_name or "").strip())[:55] or f"site-{old_pk}"[:55]
try:
seed = int(old_pk) % 10000
except ValueError:
seed = sum(ord(c) for c in old_pk) % 10000
for i in range(10000):
code = f"{base}-{(seed + i) % 10000:04d}"
if code not in used:
used.add(code)
return code
raise RuntimeError(f"Could not allocate site id for {old_pk}")
def forwards_slug_pks(apps, schema_editor):
Site = apps.get_model("farms", "Site")
Kandang = apps.get_model("farms", "Kandang")
PusatRegistration = apps.get_model("farms", "PusatRegistration")
used: set[str] = set()
mapping: dict[str, str] = {}
for site in Site.objects.all().order_by("site_id"):
old = str(site.site_id)
mapping[old] = _unique_slug(site.site_name, old, used)
for old_id, new_id in mapping.items():
if old_id == new_id:
continue
Kandang.objects.filter(site_id=old_id).update(site_id=new_id)
PusatRegistration.objects.filter(site_id=old_id).update(site_id=new_id)
Site.objects.filter(site_id=old_id).update(site_id=new_id)
# Ensure site names are unique before adding the constraint.
seen_names: dict[str, int] = {}
for site in Site.objects.all().order_by("site_id"):
name = site.site_name
count = seen_names.get(name, 0)
seen_names[name] = count + 1
if count == 0:
continue
suffix = f"-{count + 1}"
site.site_name = f"{name[: 30 - len(suffix)]}{suffix}"
site.save(update_fields=["site_name"])
def noop_reverse(apps, schema_editor):
raise RuntimeError("Cannot reverse site_id slug migration.")
class Migration(migrations.Migration):
atomic = False
dependencies = [
("farms", "0016_pusat_registration"),
]
operations = [
migrations.AlterField(
model_name="kandang",
name="site",
field=models.ForeignKey(
db_constraint=False,
on_delete=django.db.models.deletion.CASCADE,
related_name="kandangs",
to="farms.site",
),
),
migrations.AlterField(
model_name="pusatregistration",
name="site",
field=models.OneToOneField(
db_constraint=False,
on_delete=django.db.models.deletion.CASCADE,
related_name="pusat_registration",
to="farms.site",
),
),
migrations.AlterField(
model_name="site",
name="site_id",
field=models.CharField(max_length=64, primary_key=True, serialize=False),
),
migrations.RunPython(forwards_slug_pks, noop_reverse),
migrations.AlterField(
model_name="site",
name="site_id",
field=models.SlugField(max_length=64, primary_key=True, serialize=False),
),
migrations.AlterField(
model_name="site",
name="site_name",
field=models.CharField(max_length=30, unique=True),
),
migrations.AlterField(
model_name="kandang",
name="site",
field=models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="kandangs",
to="farms.site",
),
),
migrations.AlterField(
model_name="pusatregistration",
name="site",
field=models.OneToOneField(
on_delete=django.db.models.deletion.CASCADE,
related_name="pusat_registration",
to="farms.site",
),
),
]
@@ -0,0 +1,93 @@
# Append a 4-digit suffix to site ids that are still name-only slugs.
import re
import secrets
from django.db import migrations, models
import django.db.models.deletion
from django.utils.text import slugify
_HAS_SUFFIX = re.compile(r"-\d{4}$")
def forwards_add_numeric_suffix(apps, schema_editor):
Site = apps.get_model("farms", "Site")
Kandang = apps.get_model("farms", "Kandang")
PusatRegistration = apps.get_model("farms", "PusatRegistration")
used = set(Site.objects.values_list("site_id", flat=True))
mapping: dict[str, str] = {}
for site in Site.objects.all().order_by("site_id"):
old = str(site.site_id)
if _HAS_SUFFIX.search(old):
continue
base = slugify(str(site.site_name or "").strip())[:55] or old[:55]
for _ in range(32):
candidate = f"{base}-{secrets.randbelow(10_000):04d}"
if candidate not in used:
used.add(candidate)
mapping[old] = candidate
break
else:
raise RuntimeError(f"Could not allocate numeric site id for {old}")
for old_id, new_id in mapping.items():
Kandang.objects.filter(site_id=old_id).update(site_id=new_id)
PusatRegistration.objects.filter(site_id=old_id).update(site_id=new_id)
Site.objects.filter(site_id=old_id).update(site_id=new_id)
def noop_reverse(apps, schema_editor):
raise RuntimeError("Cannot reverse site id numeric suffix migration.")
class Migration(migrations.Migration):
atomic = False
dependencies = [
("farms", "0017_site_slug_pk"),
]
operations = [
migrations.AlterField(
model_name="kandang",
name="site",
field=models.ForeignKey(
db_constraint=False,
on_delete=django.db.models.deletion.CASCADE,
related_name="kandangs",
to="farms.site",
),
),
migrations.AlterField(
model_name="pusatregistration",
name="site",
field=models.OneToOneField(
db_constraint=False,
on_delete=django.db.models.deletion.CASCADE,
related_name="pusat_registration",
to="farms.site",
),
),
migrations.RunPython(forwards_add_numeric_suffix, noop_reverse),
migrations.AlterField(
model_name="kandang",
name="site",
field=models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="kandangs",
to="farms.site",
),
),
migrations.AlterField(
model_name="pusatregistration",
name="site",
field=models.OneToOneField(
on_delete=django.db.models.deletion.CASCADE,
related_name="pusat_registration",
to="farms.site",
),
),
]
@@ -0,0 +1,52 @@
# Move pusat registration fields onto sites; drop pusat_registrations.
from django.db import migrations, models
def forwards_copy_registrations(apps, schema_editor):
Site = apps.get_model("farms", "Site")
PusatRegistration = apps.get_model("farms", "PusatRegistration")
for row in PusatRegistration.objects.select_related("site").all():
site = row.site
site.pusat_code = row.code or ""
site.pusat_active_site_id = row.active_site_id
site.pusat_registered_at = row.registered_at
site.save(
update_fields=[
"pusat_code",
"pusat_active_site_id",
"pusat_registered_at",
"updated_at",
]
)
def noop_reverse(apps, schema_editor):
raise RuntimeError("Cannot reverse pusat fields onto sites.")
class Migration(migrations.Migration):
dependencies = [
("farms", "0018_site_id_numeric_suffix"),
]
operations = [
migrations.AddField(
model_name="site",
name="pusat_code",
field=models.SlugField(blank=True, default="", max_length=64),
),
migrations.AddField(
model_name="site",
name="pusat_active_site_id",
field=models.BigIntegerField(blank=True, null=True),
),
migrations.AddField(
model_name="site",
name="pusat_registered_at",
field=models.DateTimeField(blank=True, null=True),
),
migrations.RunPython(forwards_copy_registrations, noop_reverse),
migrations.DeleteModel(name="PusatRegistration"),
]
+41 -2
View File
@@ -1,16 +1,46 @@
from datetime import date
import secrets
from django.core.exceptions import ValidationError
from django.db import models
from django.utils import timezone
from django.utils.text import slugify
from apps.accounts.models import User
def site_name_slug(site_name: str) -> str:
"""Slug portion of a site id (no numeric suffix)."""
code = slugify(str(site_name or "").strip())[:55]
if not code:
raise ValidationError({"site_name": "Nama site tidak valid untuk dijadikan id."})
return code
def site_id_from_name(site_name: str, *, numeric_suffix: int | None = None) -> str:
"""Site PK: `{name-slug}-{4-digit number}`, e.g. sukawarna-4821."""
base = site_name_slug(site_name)
if numeric_suffix is None:
numeric_suffix = secrets.randbelow(10_000)
return f"{base}-{numeric_suffix:04d}"
def allocate_site_id(site_name: str) -> str:
"""Pick an unused site_id for this name."""
for _ in range(32):
candidate = site_id_from_name(site_name)
if not Site.objects.filter(pk=candidate).exists():
return candidate
raise ValidationError({"site_name": "Gagal membuat id site unik — coba lagi."})
class Site(models.Model):
site_id = models.BigAutoField(primary_key=True)
site_name = models.CharField(max_length=30)
site_id = models.SlugField(primary_key=True, max_length=64)
site_name = models.CharField(max_length=30, unique=True)
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name="sites")
pusat_code = models.SlugField(max_length=64, blank=True, default="")
pusat_active_site_id = models.BigIntegerField(null=True, blank=True)
pusat_registered_at = models.DateTimeField(null=True, blank=True)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
@@ -21,6 +51,15 @@ class Site(models.Model):
def __str__(self) -> str:
return self.site_name
@property
def is_registered_to_pusat(self) -> bool:
return bool(self.pusat_active_site_id and self.pusat_code)
def save(self, *args, **kwargs):
if not self.site_id:
self.site_id = allocate_site_id(self.site_name)
super().save(*args, **kwargs)
class Kandang(models.Model):
kandang_id = models.BigAutoField(primary_key=True)
+20 -3
View File
@@ -1,15 +1,31 @@
from rest_framework import serializers
from apps.accounts.serializers import PkAsIdMixin
from apps.farms.models import Cycle, Flock, Kandang, Site
from apps.accounts.serializers import PkAsIdMixin, SlugPkAsIdMixin
from apps.farms.models import Cycle, Flock, Kandang, Site, allocate_site_id, site_name_slug
class SiteSerializer(PkAsIdMixin, serializers.ModelSerializer):
class SiteSerializer(SlugPkAsIdMixin, serializers.ModelSerializer):
class Meta:
model = Site
fields = ["id", "site_name", "user", "created_at", "updated_at"]
read_only_fields = ["id", "created_at", "updated_at"]
def create(self, validated_data):
validated_data["site_id"] = allocate_site_id(validated_data["site_name"])
return super().create(validated_data)
def validate_site_name(self, value):
name = str(value or "").strip()
if not name:
raise serializers.ValidationError("Nama site wajib diisi.")
site_name_slug(name) # reject names that cannot form a slug
qs = Site.objects.filter(site_name__iexact=name)
if self.instance:
qs = qs.exclude(pk=self.instance.pk)
if qs.exists():
raise serializers.ValidationError("Nama site sudah dipakai.")
return name
class FeedInButtonUrlSerializer(serializers.Serializer):
label = serializers.CharField(max_length=50, required=False, allow_blank=True, default="")
@@ -18,6 +34,7 @@ class FeedInButtonUrlSerializer(serializers.Serializer):
class KandangSerializer(PkAsIdMixin, serializers.ModelSerializer):
feed_in_button_urls = FeedInButtonUrlSerializer(many=True, required=False)
site = serializers.PrimaryKeyRelatedField(queryset=Site.objects.all())
class Meta:
model = Kandang
+14 -10
View File
@@ -10,6 +10,7 @@ from django.conf import settings
from apps.farms.models import Cycle
logger = logging.getLogger(__name__)
@@ -19,35 +20,38 @@ class PusatCloseError(Exception):
super().__init__(message)
def _configured() -> bool:
return bool(
settings.PUSAT_BASE_URL
and settings.PUSAT_API_KEY
and settings.PUSAT_CITY_SITE_ID
)
def _active_site_id_for_cycle(cycle: Cycle) -> int | None:
site = cycle.kandang.site
if site.pusat_active_site_id is not None:
return int(site.pusat_active_site_id)
return None
def _configured(active_site_id: int | None) -> bool:
return bool(settings.PUSAT_BASE_URL and settings.PUSAT_API_KEY and active_site_id)
def notify_pusat_close_request(cycle: Cycle, *, cancelled: bool = False) -> None:
"""POST ingest/cancel to pusat. Raises PusatCloseError on failure when configured."""
if not _configured():
active_site_id = _active_site_id_for_cycle(cycle)
if not _configured(active_site_id):
logger.info("Pusat close channel not configured; skipping notify for cycle %s", cycle.pk)
return
base = settings.PUSAT_BASE_URL.rstrip("/")
headers = {"X-API-Key": settings.PUSAT_API_KEY, "Content-Type": "application/json"}
city_site_id = int(settings.PUSAT_CITY_SITE_ID)
try:
if cancelled:
url = f"{base}/cycle-close/cancel/"
payload: dict[str, Any] = {
"city_site_id": city_site_id,
"active_site_id": active_site_id,
"source_cycle_id": cycle.pk,
}
else:
url = f"{base}/cycle-close/ingest/"
payload = {
"city_site_id": city_site_id,
"active_site_id": active_site_id,
"source_cycle_id": cycle.pk,
"kandang_name": cycle.kandang.kandang_name,
"site_name": cycle.kandang.site.site_name,
@@ -0,0 +1,111 @@
"""Register this site deployment with executive ActiveSite registry."""
from __future__ import annotations
import logging
from typing import Any
import httpx
from django.conf import settings
from django.utils import timezone
from django.utils.text import slugify
from apps.farms.models import Site
logger = logging.getLogger(__name__)
class PusatRegisterError(Exception):
def __init__(self, message: str):
self.message = message
super().__init__(message)
def _normalize_code(value: str) -> str:
code = slugify(str(value or "").strip())[:64]
if not code:
raise PusatRegisterError("Kode tidak valid.")
return code
def _site_already_registered(site: Site) -> bool:
return bool(site.pusat_active_site_id and site.pusat_code)
def register_site_to_pusat(
*,
site: Site,
code: str,
allow_code_change: bool = False,
) -> Site:
"""
POST to executive register-from-site and persist fields on Site.
First-time register: allow_code_change=False.
Change code after register: allow_code_change=True (requires existing registration).
"""
base = (settings.PUSAT_BASE_URL or "").rstrip("/")
api_key = (settings.PUSAT_API_KEY or "").strip()
site_api = (settings.SITE_API_BASE_URL or "").rstrip("/")
if not base:
raise PusatRegisterError("PUSAT_BASE_URL belum dikonfigurasi.")
if not api_key:
raise PusatRegisterError("PUSAT_API_KEY belum dikonfigurasi.")
if not site_api:
raise PusatRegisterError("SITE_API_BASE_URL belum dikonfigurasi.")
normalized = _normalize_code(code)
already = _site_already_registered(site)
if allow_code_change:
if not already:
raise PusatRegisterError("Site belum terdaftar ke pusat — daftar dulu.")
if site.pusat_code == normalized:
raise PusatRegisterError("Kode baru sama dengan kode saat ini.")
elif already:
raise PusatRegisterError(
f"Site sudah terdaftar ke pusat dengan kode '{site.pusat_code}'. "
"Gunakan Ubah kode untuk mengganti."
)
url = f"{base}/active-sites/register-from-site/"
payload = {
"code": normalized,
"site_id": site.pk,
"name": site.site_name,
"api_base_url": site_api,
"api_key": api_key,
}
headers = {"X-API-Key": api_key, "Content-Type": "application/json"}
try:
with httpx.Client(timeout=30) as client:
response = client.post(url, json=payload, headers=headers)
except httpx.HTTPError as exc:
logger.exception("Pusat register failed for site %s", site.pk)
raise PusatRegisterError("gagal daftar ke pusat — coba lagi") from exc
if response.status_code >= 400:
detail = response.text[:300]
action = "ubah kode" if allow_code_change else "daftar"
raise PusatRegisterError(
f"gagal {action} ke pusat — coba lagi ({response.status_code}: {detail})"
)
try:
data: dict[str, Any] = response.json()
except ValueError as exc:
raise PusatRegisterError("respons pusat tidak valid") from exc
active_site_id = data.get("id")
if active_site_id is None:
raise PusatRegisterError("pusat tidak mengembalikan id active site")
site.pusat_code = data.get("code") or normalized
site.pusat_active_site_id = int(active_site_id)
site.pusat_registered_at = timezone.now()
site.save(
update_fields=["pusat_code", "pusat_active_site_id", "pusat_registered_at", "updated_at"]
)
return site
+127
View File
@@ -0,0 +1,127 @@
from unittest.mock import patch
from django.test import TestCase, override_settings
from rest_framework.test import APIClient
from apps.accounts.models import User
from apps.farms.models import Site
@override_settings(
PUSAT_BASE_URL="http://pusat.example/api/v1",
PUSAT_API_KEY="shared-key",
SITE_API_BASE_URL="https://site.example/api/v1",
)
class PusatRegisterTests(TestCase):
def setUp(self):
self.client = APIClient()
self.staff = User.objects.create_user(
user_name="staff1", password="x", status=User.STATUS_ACTIVE
)
self.admin = User.objects.create_user(
user_name="admin1", password="x", status=User.STATUS_SUPERADMIN
)
self.site = Site.objects.create(site_name="Sukawarna", user=self.admin)
@patch("apps.farms.services.pusat_register.httpx.Client")
def test_active_staff_can_register(self, mock_client_cls):
mock_client = mock_client_cls.return_value.__enter__.return_value
mock_client.post.return_value.status_code = 201
mock_client.post.return_value.json.return_value = {
"id": 42,
"code": "sukawarna",
"name": "Sukawarna",
}
self.client.force_authenticate(self.staff)
response = self.client.post(
"/api/v1/pusat/register/",
{"code": "Sukawarna!", "site_id": self.site.pk},
format="json",
)
self.assertEqual(response.status_code, 200, response.data)
self.assertEqual(response.data["active_site_id"], 42)
self.assertEqual(response.data["code"], "sukawarna")
self.site.refresh_from_db()
self.assertEqual(self.site.pusat_active_site_id, 42)
self.assertEqual(self.site.pusat_code, "sukawarna")
self.assertIsNotNone(self.site.pusat_registered_at)
kwargs = mock_client.post.call_args.kwargs
self.assertEqual(kwargs["json"]["api_base_url"], "https://site.example/api/v1")
self.assertEqual(kwargs["json"]["name"], "Sukawarna")
self.assertEqual(kwargs["json"]["site_id"], self.site.pk)
self.assertEqual(kwargs["headers"]["X-API-Key"], "shared-key")
def test_cannot_register_when_already_registered(self):
self.site.pusat_code = "sukawarna"
self.site.pusat_active_site_id = 42
self.site.save(
update_fields=["pusat_code", "pusat_active_site_id", "updated_at"]
)
self.client.force_authenticate(self.staff)
response = self.client.post(
"/api/v1/pusat/register/",
{"code": "other-code", "site_id": self.site.pk},
format="json",
)
self.assertEqual(response.status_code, 400)
self.assertIn("Ubah kode", response.data["detail"])
@patch("apps.farms.services.pusat_register.httpx.Client")
def test_change_code(self, mock_client_cls):
self.site.pusat_code = "sukawarna"
self.site.pusat_active_site_id = 42
self.site.save(
update_fields=["pusat_code", "pusat_active_site_id", "updated_at"]
)
mock_client = mock_client_cls.return_value.__enter__.return_value
mock_client.post.return_value.status_code = 200
mock_client.post.return_value.json.return_value = {
"id": 42,
"code": "new-code",
"name": "Sukawarna",
}
self.client.force_authenticate(self.staff)
response = self.client.post(
"/api/v1/pusat/change-code/",
{"code": "new-code", "site_id": self.site.pk},
format="json",
)
self.assertEqual(response.status_code, 200, response.data)
self.assertEqual(response.data["code"], "new-code")
self.site.refresh_from_db()
self.assertEqual(self.site.pusat_code, "new-code")
self.assertEqual(self.site.pusat_active_site_id, 42)
def test_change_code_requires_registration(self):
self.client.force_authenticate(self.staff)
response = self.client.post(
"/api/v1/pusat/change-code/",
{"code": "new-code", "site_id": self.site.pk},
format="json",
)
self.assertEqual(response.status_code, 400)
def test_superadmin_cannot_register(self):
self.client.force_authenticate(self.admin)
response = self.client.post(
"/api/v1/pusat/register/",
{"code": "sukawarna", "site_id": self.site.pk},
format="json",
)
self.assertEqual(response.status_code, 403)
def test_registration_status(self):
self.site.pusat_code = "sukawarna"
self.site.pusat_active_site_id = 7
self.site.save(
update_fields=["pusat_code", "pusat_active_site_id", "updated_at"]
)
self.client.force_authenticate(self.staff)
response = self.client.get(
"/api/v1/pusat/registration/", {"site_id": self.site.pk}
)
self.assertEqual(response.status_code, 200)
self.assertEqual(len(response.data), 1)
self.assertEqual(response.data[0]["active_site_id"], 7)
+16 -2
View File
@@ -1,6 +1,15 @@
from django.urls import path
from rest_framework.routers import DefaultRouter
from apps.farms.views import CycleViewSet, FlockViewSet, KandangViewSet, SiteViewSet
from apps.farms.views import (
CycleViewSet,
FlockViewSet,
KandangViewSet,
SiteViewSet,
pusat_change_code,
pusat_register,
pusat_registration_status,
)
router = DefaultRouter()
router.register("sites", SiteViewSet, basename="site")
@@ -8,4 +17,9 @@ router.register("kandangs", KandangViewSet, basename="kandang")
router.register("flocks", FlockViewSet, basename="flock")
router.register("cycles", CycleViewSet, basename="cycle")
urlpatterns = router.urls
urlpatterns = [
path("pusat/register/", pusat_register, name="pusat-register"),
path("pusat/change-code/", pusat_change_code, name="pusat-change-code"),
path("pusat/registration/", pusat_registration_status, name="pusat-registration-status"),
*router.urls,
]
+82 -1
View File
@@ -2,7 +2,7 @@ from django.conf import settings
from django.core.exceptions import ValidationError
from django.utils.dateparse import parse_date
from rest_framework import permissions, status, viewsets
from rest_framework.decorators import action
from rest_framework.decorators import action, api_view, permission_classes
from rest_framework.response import Response
import secrets
@@ -11,6 +11,7 @@ from apps.accounts.models import User
from apps.farms.models import Cycle, Flock, Kandang, Site
from apps.farms.serializers import CycleSerializer, FlockSerializer, KandangSerializer, SiteSerializer
from apps.farms.services.pusat_close import PusatCloseError, notify_pusat_close_request
from apps.farms.services.pusat_register import PusatRegisterError, register_site_to_pusat
from apps.operations.services.feed_sacks import (
cleanup_opening_balance_feed_rows,
recompute_feed_sack_in_totals,
@@ -84,6 +85,20 @@ def _restore_pending_close(
)
class IsActiveStaff(permissions.BasePermission):
"""Staff with status=active only (not superadmin)."""
message = "Hanya staf aktif yang dapat mendaftar ke pusat."
def has_permission(self, request, view):
user = request.user
return bool(
user
and user.is_authenticated
and getattr(user, "status", None) == User.STATUS_ACTIVE
)
class IsSuperAdminForWrite(permissions.BasePermission):
"""Reads for any authenticated user; mutations only for superadmin.
@@ -99,6 +114,72 @@ class IsSuperAdminForWrite(permissions.BasePermission):
return bool(request.user.is_superuser or request.user.status == User.STATUS_SUPERADMIN)
def _pusat_registration_payload(site: Site) -> dict:
return {
"site_id": site.pk,
"site_name": site.site_name,
"code": site.pusat_code,
"active_site_id": site.pusat_active_site_id,
"registered_at": site.pusat_registered_at.isoformat()
if site.pusat_registered_at
else None,
}
@api_view(["GET"])
@permission_classes([permissions.IsAuthenticated])
def pusat_registration_status(request):
"""Return pusat registration status for sites (optional site_id filter)."""
site_id = request.query_params.get("site_id")
qs = Site.objects.exclude(pusat_active_site_id__isnull=True).exclude(pusat_code="")
if site_id:
qs = qs.filter(pk=site_id)
return Response([_pusat_registration_payload(row) for row in qs])
@api_view(["POST"])
@permission_classes([IsActiveStaff])
def pusat_register(request):
"""Staff registers the selected farm site to executive ActiveSite registry."""
code = request.data.get("code")
site_id = request.data.get("site_id")
try:
site = Site.objects.get(pk=site_id)
except (Site.DoesNotExist, TypeError, ValueError):
return Response(
{"detail": "Pilih site yang akan didaftarkan."},
status=status.HTTP_400_BAD_REQUEST,
)
try:
site = register_site_to_pusat(site=site, code=str(code or ""))
except PusatRegisterError as exc:
return Response({"detail": exc.message}, status=status.HTTP_400_BAD_REQUEST)
return Response(_pusat_registration_payload(site), status=status.HTTP_200_OK)
@api_view(["POST"])
@permission_classes([IsActiveStaff])
def pusat_change_code(request):
"""Staff changes the pusat registration code for an already-registered site."""
code = request.data.get("code")
site_id = request.data.get("site_id")
try:
site = Site.objects.get(pk=site_id)
except (Site.DoesNotExist, TypeError, ValueError):
return Response(
{"detail": "Pilih site yang akan diubah kodenya."},
status=status.HTTP_400_BAD_REQUEST,
)
try:
site = register_site_to_pusat(
site=site, code=str(code or ""), allow_code_change=True
)
except PusatRegisterError as exc:
return Response({"detail": exc.message}, status=status.HTTP_400_BAD_REQUEST)
return Response(_pusat_registration_payload(site), status=status.HTTP_200_OK)
class SiteViewSet(viewsets.ModelViewSet):
queryset = Site.objects.select_related("user").all()
serializer_class = SiteSerializer