adjust citysite table name to activesites

This commit is contained in:
Alberto-Audrix committed 2026-09-11 15:51:29 +07:00
1 parent fb0f996776
commit 64b37b6b6d
27 files changed
+382 -221

No files matched your search

@@ -0,0 +1,60 @@
# Generated manually: rename CitySite → ActiveSite
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
("sync", "0002_citysite_registered_by"),
# Farms FKs still reference CitySite; rename only after those exist.
("farms", "0017_site_city_site"),
]
operations = [
migrations.RenameModel(
old_name="CitySite",
new_name="ActiveSite",
),
migrations.RenameField(
model_name="activesite",
old_name="city_site_id",
new_name="active_site_id",
),
migrations.AlterModelTable(
name="activesite",
table="active_sites",
),
migrations.AlterField(
model_name="activesite",
name="api_base_url",
field=models.URLField(
help_text="Site API root including /api/v1, e.g. https://site.example/api/v1",
max_length=255,
),
),
migrations.AlterField(
model_name="activesite",
name="api_key",
field=models.CharField(
blank=True,
help_text="X-API-Key for pulling from the site API (keep server-side only)",
max_length=255,
),
),
migrations.AlterField(
model_name="activesite",
name="registered_by",
field=models.ForeignKey(
blank=True,
help_text="GM / GM Admin who added this site dashboard URL",
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name="registered_active_sites",
to=settings.AUTH_USER_MODEL,
),
),
]
+8 -8
View File
@@ -4,33 +4,33 @@ from django.conf import settings
from django.db import models
class CitySite(models.Model):
class ActiveSite(models.Model):
"""
Registry of a city dashboard-cpsp deployment that HQ can mirror from.
Registry of a farm-location dashboard-cpsp deployment that HQ can mirror from.
Outbound API key is stored here for local/dev scaffolding. Prefer a secret
manager or encrypted field before production.
"""
city_site_id = models.BigAutoField(primary_key=True)
active_site_id = models.BigAutoField(primary_key=True)
code = models.SlugField(max_length=64, unique=True, help_text="Stable id, e.g. sukawarna")
name = models.CharField(max_length=120)
api_base_url = models.URLField(
max_length=255,
help_text="City API root including /api/v1, e.g. https://site.example/api/v1",
help_text="Site API root including /api/v1, e.g. https://site.example/api/v1",
)
api_key = models.CharField(
max_length=255,
blank=True,
help_text="X-API-Key for pulling from the city API (keep server-side only)",
help_text="X-API-Key for pulling from the site API (keep server-side only)",
)
registered_by = models.ForeignKey(
settings.AUTH_USER_MODEL,
null=True,
blank=True,
on_delete=models.SET_NULL,
related_name="registered_city_sites",
help_text="GM / GM Admin who added this city dashboard URL",
related_name="registered_active_sites",
help_text="GM / GM Admin who added this site dashboard URL",
)
is_active = models.BooleanField(default=True)
last_synced_at = models.DateTimeField(null=True, blank=True)
@@ -40,7 +40,7 @@ class CitySite(models.Model):
updated_at = models.DateTimeField(auto_now=True)
class Meta:
db_table = "city_sites"
db_table = "active_sites"
ordering = ["name"]
def __str__(self) -> str:
+7 -7
View File
@@ -1,18 +1,18 @@
from rest_framework import serializers
from apps.sync.models import CitySite
from apps.sync.models import ActiveSite
class CitySiteSerializer(serializers.ModelSerializer):
class ActiveSiteSerializer(serializers.ModelSerializer):
id = serializers.IntegerField(source="pk", read_only=True)
# Never echo the outbound city API key back to the browser by default.
# Never echo the outbound site API key back to the browser by default.
has_api_key = serializers.SerializerMethodField()
registered_by_name = serializers.CharField(
source="registered_by.display_name", read_only=True, allow_null=True
)
class Meta:
model = CitySite
model = ActiveSite
fields = [
"id",
"code",
@@ -40,15 +40,15 @@ class CitySiteSerializer(serializers.ModelSerializer):
"updated_at",
]
def get_has_api_key(self, obj: CitySite) -> bool:
def get_has_api_key(self, obj: ActiveSite) -> bool:
return bool(obj.api_key)
class CitySiteWriteSerializer(serializers.ModelSerializer):
class ActiveSiteWriteSerializer(serializers.ModelSerializer):
id = serializers.IntegerField(source="pk", read_only=True)
class Meta:
model = CitySite
model = ActiveSite
fields = [
"id",
"code",
+2 -2
View File
@@ -1,10 +1,10 @@
from django.urls import include, path
from rest_framework.routers import DefaultRouter
from apps.sync.views import CitySiteViewSet
from apps.sync.views import ActiveSiteViewSet
router = DefaultRouter()
router.register("city-sites", CitySiteViewSet, basename="city-site")
router.register("active-sites", ActiveSiteViewSet, basename="active-site")
urlpatterns = [
path("", include(router.urls)),
+26 -26
View File
@@ -2,62 +2,62 @@ from rest_framework import status, viewsets
from rest_framework.decorators import action
from rest_framework.response import Response
from apps.accounts.permissions import CanRegisterCitySite, IsExecutive, can_sync
from apps.accounts.scoping import filter_city_sites_queryset
from apps.farms.services.city_registry import ensure_hq_site_for_city
from apps.sync.models import CitySite
from apps.sync.serializers import CitySiteSerializer, CitySiteWriteSerializer
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 ActiveSiteSerializer, ActiveSiteWriteSerializer
class CitySiteViewSet(viewsets.ModelViewSet):
class ActiveSiteViewSet(viewsets.ModelViewSet):
"""
List/read: scoped (director all; BUH managed GMs; GM own).
Create/update/delete: GM and GM Admin.
Sync: GM and GM Admin only (stubs until mirror pull is implemented).
Creating a city site also attaches (or creates) an HQ Site so farm data
Creating an active site also attaches (or creates) an HQ Site so farm data
appears in the executive view list.
"""
executive_sync_action = True
def get_queryset(self):
qs = CitySite.objects.select_related("registered_by").all()
return filter_city_sites_queryset(qs, self.request.user)
qs = ActiveSite.objects.select_related("registered_by").all()
return filter_active_sites_queryset(qs, self.request.user)
def get_permissions(self):
if self.action in {"create", "update", "partial_update", "destroy"}:
return [CanRegisterCitySite()]
return [CanRegisterActiveSite()]
return [IsExecutive()]
def get_serializer_class(self):
if self.action in {"create", "update", "partial_update"}:
return CitySiteWriteSerializer
return CitySiteSerializer
return ActiveSiteWriteSerializer
return ActiveSiteSerializer
def perform_create(self, serializer):
city = serializer.save(registered_by=self.request.user)
ensure_hq_site_for_city(city)
active_site = serializer.save(registered_by=self.request.user)
ensure_hq_site_for_active_site(active_site)
def perform_update(self, serializer):
city = serializer.save()
if city.is_active:
ensure_hq_site_for_city(city)
active_site = serializer.save()
if active_site.is_active:
ensure_hq_site_for_active_site(active_site)
@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)
city = self.get_object()
if not city.is_active:
active_site = self.get_object()
if not active_site.is_active:
return Response(
{"detail": "City site is inactive."},
{"detail": "Active site is inactive."},
status=status.HTTP_400_BAD_REQUEST,
)
return Response(
{
"detail": "City sync not implemented yet.",
"city_site": CitySiteSerializer(city).data,
"detail": "Active site sync not implemented yet.",
"active_site": ActiveSiteSerializer(active_site).data,
},
status=status.HTTP_501_NOT_IMPLEMENTED,
)
@@ -66,12 +66,12 @@ class CitySiteViewSet(viewsets.ModelViewSet):
def sync_all(self, request):
if not can_sync(request.user):
return Response({"detail": "Sync not allowed."}, status=status.HTTP_403_FORBIDDEN)
cities = list(self.get_queryset().filter(is_active=True))
sites = list(self.get_queryset().filter(is_active=True))
return Response(
{
"detail": "City sync-all not implemented yet.",
"city_count": len(cities),
"cities": CitySiteSerializer(cities, many=True).data,
"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,
)