added vehicles and vehicles extra, dynamic tables for entities, dynamic dropdowns with overlay for selecting and manage data
This commit is contained in:
@@ -0,0 +1,52 @@
|
||||
# Generated by Django 4.2.8 on 2026-02-05 23:24
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("api", "0001_initial"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name="Report",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.BigAutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
("name", models.CharField(max_length=255)),
|
||||
("page_width", models.IntegerField(default=80)),
|
||||
("page_height", models.IntegerField(default=66)),
|
||||
(
|
||||
"api_endpoint",
|
||||
models.CharField(blank=True, default="", max_length=500),
|
||||
),
|
||||
("elements", models.JSONField(default=list)),
|
||||
("created_at", models.DateTimeField(auto_now_add=True)),
|
||||
("updated_at", models.DateTimeField(auto_now=True)),
|
||||
(
|
||||
"created_by",
|
||||
models.ForeignKey(
|
||||
blank=True,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
related_name="reports",
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
"ordering": ["-updated_at"],
|
||||
},
|
||||
),
|
||||
]
|
||||
+23
-2
@@ -26,13 +26,34 @@ class ComPortReading(models.Model):
|
||||
data = models.TextField()
|
||||
timestamp = models.DateTimeField(auto_now_add=True)
|
||||
source_ip = models.GenericIPAddressField(null=True, blank=True)
|
||||
|
||||
|
||||
class Meta:
|
||||
ordering = ['-timestamp']
|
||||
indexes = [
|
||||
models.Index(fields=['-timestamp']),
|
||||
models.Index(fields=['port', '-timestamp']),
|
||||
]
|
||||
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.port} - {self.timestamp}"
|
||||
|
||||
|
||||
class Report(models.Model):
|
||||
"""Model to store report templates for the report editor"""
|
||||
name = models.CharField(max_length=255)
|
||||
page_width = models.IntegerField(default=80)
|
||||
page_height = models.IntegerField(default=66)
|
||||
api_endpoint = models.CharField(max_length=500, blank=True, default='')
|
||||
elements = models.JSONField(default=list)
|
||||
created_by = models.ForeignKey(
|
||||
User, on_delete=models.SET_NULL, null=True, blank=True,
|
||||
related_name='reports'
|
||||
)
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
updated_at = models.DateTimeField(auto_now=True)
|
||||
|
||||
class Meta:
|
||||
ordering = ['-updated_at']
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
+160
-21
@@ -1,8 +1,8 @@
|
||||
from rest_framework import serializers
|
||||
from django.contrib.auth.password_validation import validate_password
|
||||
from .models import ComPortReading, User
|
||||
from .models import ComPortReading, User, Report
|
||||
from vehicles.models import Vehicle, VehicleExtra
|
||||
from nomenclatures.models import Nomenclature, NomenclatureField
|
||||
from nomenclatures.models import Nomenclature, NomenclatureField, NomenclatureEntry
|
||||
|
||||
|
||||
class UserSerializer(serializers.ModelSerializer):
|
||||
@@ -80,14 +80,58 @@ class VehicleExtraSerializer(serializers.ModelSerializer):
|
||||
fields = ['id', 'data']
|
||||
read_only_fields = ['id']
|
||||
|
||||
def validate_data(self, value):
|
||||
if not value:
|
||||
return value
|
||||
|
||||
nomenclatures = {
|
||||
n.code: n for n in
|
||||
Nomenclature.objects.filter(applies_to='vehicle').prefetch_related('fields', 'entries')
|
||||
}
|
||||
|
||||
errors = {}
|
||||
for key, val in value.items():
|
||||
if key not in nomenclatures:
|
||||
errors[key] = f"Unknown nomenclature code '{key}'."
|
||||
continue
|
||||
|
||||
nom = nomenclatures[key]
|
||||
|
||||
if nom.kind == Nomenclature.LOOKUP:
|
||||
if not isinstance(val, int):
|
||||
errors[key] = "Must be a nomenclature entry ID (integer)."
|
||||
elif not nom.entries.filter(id=val, is_active=True).exists():
|
||||
errors[key] = f"Entry ID {val} not found in '{nom.code}'."
|
||||
|
||||
elif nom.kind == Nomenclature.FIELD:
|
||||
field_def = nom.fields.first()
|
||||
if field_def:
|
||||
if field_def.field_type == NomenclatureField.TEXT and not isinstance(val, str):
|
||||
errors[key] = "Must be a string."
|
||||
elif field_def.field_type == NomenclatureField.NUMBER and not isinstance(val, (int, float)):
|
||||
errors[key] = "Must be a number."
|
||||
elif field_def.field_type == NomenclatureField.BOOL and not isinstance(val, bool):
|
||||
errors[key] = "Must be a boolean."
|
||||
elif field_def.field_type == NomenclatureField.CHOICE and val not in (field_def.choices or []):
|
||||
errors[key] = f"Must be one of {field_def.choices}."
|
||||
|
||||
if errors:
|
||||
raise serializers.ValidationError(errors)
|
||||
|
||||
return value
|
||||
|
||||
|
||||
class VehicleSerializer(serializers.ModelSerializer):
|
||||
extra = VehicleExtraSerializer(required=False, allow_null=True)
|
||||
tare_user_name = serializers.CharField(source='tare_user.username', read_only=True, default=None)
|
||||
gross_user_name = serializers.CharField(source='gross_user.username', read_only=True, default=None)
|
||||
|
||||
class Meta:
|
||||
model = Vehicle
|
||||
fields = ['id', 'vehicle_number', 'extra']
|
||||
read_only_fields = ['id']
|
||||
fields = ['id', 'vehicle_number', 'tare', 'tare_date', 'tare_user', 'tare_user_name',
|
||||
'gross', 'gross_date', 'gross_user', 'gross_user_name', 'extra']
|
||||
read_only_fields = ['id', 'tare_date', 'tare_user', 'tare_user_name',
|
||||
'gross_date', 'gross_user', 'gross_user_name']
|
||||
|
||||
def create(self, validated_data):
|
||||
extra_data = validated_data.pop('extra', None)
|
||||
@@ -101,19 +145,17 @@ class VehicleSerializer(serializers.ModelSerializer):
|
||||
def update(self, instance, validated_data):
|
||||
extra_data = validated_data.pop('extra', None)
|
||||
|
||||
# Update Vehicle fields
|
||||
instance.vehicle_number = validated_data.get('vehicle_number', instance.vehicle_number)
|
||||
for attr, value in validated_data.items():
|
||||
setattr(instance, attr, value)
|
||||
instance.save()
|
||||
|
||||
# Handle VehicleExtra update/creation
|
||||
if extra_data is not None:
|
||||
if hasattr(instance, 'extra'):
|
||||
# Update existing VehicleExtra
|
||||
for attr, value in extra_data.items():
|
||||
setattr(instance.extra, attr, value)
|
||||
instance.extra.save()
|
||||
else:
|
||||
# Create new VehicleExtra
|
||||
VehicleExtra.objects.create(vehicle=instance, **extra_data)
|
||||
|
||||
return instance
|
||||
@@ -127,7 +169,7 @@ class VehicleSerializer(serializers.ModelSerializer):
|
||||
class NomenclatureFieldSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = NomenclatureField
|
||||
fields = ['id', 'key', 'field_type']
|
||||
fields = ['id', 'key', 'label', 'field_type', 'required', 'choices', 'sort_order']
|
||||
read_only_fields = ['id']
|
||||
|
||||
def validate_key(self, value):
|
||||
@@ -143,17 +185,80 @@ class NomenclatureFieldSerializer(serializers.ModelSerializer):
|
||||
)
|
||||
return value
|
||||
|
||||
def validate(self, data):
|
||||
if data.get('field_type') == NomenclatureField.CHOICE:
|
||||
choices = data.get('choices')
|
||||
if not choices or not isinstance(choices, list) or len(choices) == 0:
|
||||
raise serializers.ValidationError({
|
||||
'choices': 'Choices must be a non-empty list for choice fields.'
|
||||
})
|
||||
elif data.get('choices'):
|
||||
raise serializers.ValidationError({
|
||||
'choices': 'Only choice-type fields can have choices.'
|
||||
})
|
||||
return data
|
||||
|
||||
|
||||
class NomenclatureEntrySerializer(serializers.ModelSerializer):
|
||||
display_value = serializers.SerializerMethodField()
|
||||
|
||||
class Meta:
|
||||
model = NomenclatureEntry
|
||||
fields = ['id', 'data', 'is_active', 'display_value']
|
||||
read_only_fields = ['id']
|
||||
|
||||
def get_display_value(self, obj):
|
||||
display_key = obj.nomenclature.display_field or "name"
|
||||
return obj.data.get(display_key, f"Entry #{obj.pk}")
|
||||
|
||||
def validate_data(self, value):
|
||||
nomenclature = self.context.get('nomenclature')
|
||||
if not nomenclature:
|
||||
return value
|
||||
|
||||
field_defs = {f.key: f for f in nomenclature.fields.all()}
|
||||
|
||||
for key in value.keys():
|
||||
if key not in field_defs:
|
||||
raise serializers.ValidationError(
|
||||
f"Unknown field '{key}'. Valid fields: {list(field_defs.keys())}"
|
||||
)
|
||||
|
||||
for key, field_def in field_defs.items():
|
||||
if field_def.required and key not in value:
|
||||
raise serializers.ValidationError(
|
||||
f"Required field '{key}' is missing."
|
||||
)
|
||||
|
||||
for key, val in value.items():
|
||||
if key not in field_defs:
|
||||
continue
|
||||
field_def = field_defs[key]
|
||||
|
||||
if field_def.field_type == NomenclatureField.TEXT and not isinstance(val, str):
|
||||
raise serializers.ValidationError(f"Field '{key}' must be a string.")
|
||||
elif field_def.field_type == NomenclatureField.NUMBER and not isinstance(val, (int, float)):
|
||||
raise serializers.ValidationError(f"Field '{key}' must be a number.")
|
||||
elif field_def.field_type == NomenclatureField.BOOL and not isinstance(val, bool):
|
||||
raise serializers.ValidationError(f"Field '{key}' must be a boolean.")
|
||||
elif field_def.field_type == NomenclatureField.CHOICE and val not in (field_def.choices or []):
|
||||
raise serializers.ValidationError(f"Field '{key}' must be one of {field_def.choices}.")
|
||||
|
||||
return value
|
||||
|
||||
|
||||
class NomenclatureSerializer(serializers.ModelSerializer):
|
||||
fields = NomenclatureFieldSerializer(many=True, required=False, source='nomenclaturefield_set')
|
||||
fields = NomenclatureFieldSerializer(many=True, required=False)
|
||||
entry_count = serializers.IntegerField(source='entries.count', read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = Nomenclature
|
||||
fields = ['id', 'code', 'name', 'applies_to', 'fields']
|
||||
fields = ['id', 'code', 'name', 'applies_to', 'kind', 'display_field',
|
||||
'sort_order', 'fields', 'entry_count']
|
||||
read_only_fields = ['id']
|
||||
|
||||
def create(self, validated_data):
|
||||
fields_data = validated_data.pop('nomenclaturefield_set', [])
|
||||
fields_data = validated_data.pop('fields', [])
|
||||
nomenclature = Nomenclature.objects.create(**validated_data)
|
||||
|
||||
for field_data in fields_data:
|
||||
@@ -162,19 +267,14 @@ class NomenclatureSerializer(serializers.ModelSerializer):
|
||||
return nomenclature
|
||||
|
||||
def update(self, instance, validated_data):
|
||||
fields_data = validated_data.pop('nomenclaturefield_set', None)
|
||||
fields_data = validated_data.pop('fields', None)
|
||||
|
||||
# Update Nomenclature fields
|
||||
instance.code = validated_data.get('code', instance.code)
|
||||
instance.name = validated_data.get('name', instance.name)
|
||||
instance.applies_to = validated_data.get('applies_to', instance.applies_to)
|
||||
for attr, value in validated_data.items():
|
||||
setattr(instance, attr, value)
|
||||
instance.save()
|
||||
|
||||
# Handle fields update - replace all fields
|
||||
if fields_data is not None:
|
||||
# Delete existing fields
|
||||
instance.nomenclaturefield_set.all().delete()
|
||||
# Create new fields
|
||||
instance.fields.all().delete()
|
||||
for field_data in fields_data:
|
||||
NomenclatureField.objects.create(nomenclature=instance, **field_data)
|
||||
|
||||
@@ -192,3 +292,42 @@ class NomenclatureSerializer(serializers.ModelSerializer):
|
||||
f"Invalid applies_to value. Must be one of: {', '.join(valid_choices)}"
|
||||
)
|
||||
return value
|
||||
|
||||
def validate(self, data):
|
||||
kind = data.get('kind', getattr(self.instance, 'kind', Nomenclature.LOOKUP))
|
||||
fields_data = data.get('fields', [])
|
||||
|
||||
if kind == Nomenclature.FIELD and fields_data and len(fields_data) != 1:
|
||||
raise serializers.ValidationError(
|
||||
"A 'field' nomenclature must have exactly one field definition."
|
||||
)
|
||||
|
||||
if kind == Nomenclature.LOOKUP:
|
||||
display_field = data.get('display_field', getattr(self.instance, 'display_field', 'name'))
|
||||
field_keys = [f['key'] for f in fields_data] if fields_data else []
|
||||
if field_keys and display_field not in field_keys:
|
||||
raise serializers.ValidationError(
|
||||
f"display_field '{display_field}' must match one of the defined field keys."
|
||||
)
|
||||
|
||||
return data
|
||||
|
||||
|
||||
class ReportSerializer(serializers.ModelSerializer):
|
||||
created_by_name = serializers.CharField(source='created_by.username', read_only=True, default=None)
|
||||
|
||||
class Meta:
|
||||
model = Report
|
||||
fields = ['id', 'name', 'page_width', 'page_height', 'api_endpoint',
|
||||
'elements', 'created_by', 'created_by_name', 'created_at', 'updated_at']
|
||||
read_only_fields = ['id', 'created_by', 'created_by_name', 'created_at', 'updated_at']
|
||||
|
||||
def validate_name(self, value):
|
||||
if not value or not value.strip():
|
||||
raise serializers.ValidationError("Report name cannot be empty")
|
||||
return value.strip()
|
||||
|
||||
def validate_elements(self, value):
|
||||
if not isinstance(value, list):
|
||||
raise serializers.ValidationError("Elements must be a list")
|
||||
return value
|
||||
|
||||
@@ -11,6 +11,8 @@ router.register(r'users', views.UserViewSet, basename='user')
|
||||
router.register(r'readings', views.ComPortReadingViewSet, basename='reading')
|
||||
router.register(r'vehicles', views.VehicleViewSet, basename='vehicle')
|
||||
router.register(r'nomenclatures', views.NomenclatureViewSet, basename='nomenclature')
|
||||
router.register(r'nomenclature-entries', views.NomenclatureEntryViewSet, basename='nomenclature-entry')
|
||||
router.register(r'reports', views.ReportViewSet, basename='report')
|
||||
|
||||
urlpatterns = [
|
||||
# JWT token endpoints
|
||||
|
||||
+168
-11
@@ -2,10 +2,16 @@ from rest_framework import viewsets, status
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.permissions import IsAuthenticated, AllowAny
|
||||
from .models import ComPortReading, User
|
||||
from .serializers import ComPortReadingSerializer, UserSerializer, UserDetailSerializer, ChangePasswordSerializer, VehicleSerializer, NomenclatureSerializer
|
||||
from .models import ComPortReading, User, Report
|
||||
from .serializers import (
|
||||
ComPortReadingSerializer, UserSerializer, UserDetailSerializer,
|
||||
ChangePasswordSerializer, VehicleSerializer, NomenclatureSerializer,
|
||||
NomenclatureEntrySerializer, ReportSerializer,
|
||||
)
|
||||
from django.utils import timezone
|
||||
from vehicles.models import Vehicle
|
||||
from nomenclatures.models import Nomenclature
|
||||
from nomenclatures.models import Nomenclature, NomenclatureEntry
|
||||
from scalesapp.sse import sse_broadcast_update
|
||||
|
||||
|
||||
class UserViewSet(viewsets.ModelViewSet):
|
||||
@@ -77,7 +83,7 @@ class ComPortReadingViewSet(viewsets.ModelViewSet):
|
||||
serializer = self.get_serializer(reading)
|
||||
return Response(serializer.data)
|
||||
return Response({'detail': 'No readings yet'}, status=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
|
||||
@action(detail=False, methods=['get'])
|
||||
def by_port(self, request):
|
||||
"""Get readings for a specific port"""
|
||||
@@ -90,7 +96,7 @@ class ComPortReadingViewSet(viewsets.ModelViewSet):
|
||||
readings = ComPortReading.objects.filter(port=port)
|
||||
serializer = self.get_serializer(readings, many=True)
|
||||
return Response(serializer.data)
|
||||
|
||||
|
||||
def get_client_ip(self, request):
|
||||
"""Get client IP address"""
|
||||
x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
|
||||
@@ -99,7 +105,7 @@ class ComPortReadingViewSet(viewsets.ModelViewSet):
|
||||
else:
|
||||
ip = request.META.get('REMOTE_ADDR')
|
||||
return ip
|
||||
|
||||
|
||||
def perform_create(self, serializer):
|
||||
"""Save the reading with client IP"""
|
||||
serializer.save(source_ip=self.get_client_ip(self.request))
|
||||
@@ -116,14 +122,31 @@ class VehicleViewSet(viewsets.ModelViewSet):
|
||||
partial_update: Partial update of vehicle
|
||||
destroy: Delete a vehicle (cascades to VehicleExtra)
|
||||
by_number: Get vehicle by vehicle_number
|
||||
set_tare: Set tare weight from scale reading
|
||||
set_gross: Set gross weight from scale reading
|
||||
"""
|
||||
queryset = Vehicle.objects.select_related('extra').all()
|
||||
queryset = Vehicle.objects.select_related('extra', 'tare_user', 'gross_user').all()
|
||||
serializer_class = VehicleSerializer
|
||||
permission_classes = [IsAuthenticated]
|
||||
filterset_fields = ['vehicle_number']
|
||||
search_fields = ['vehicle_number']
|
||||
ordering = ['vehicle_number']
|
||||
|
||||
def perform_create(self, serializer):
|
||||
instance = serializer.save()
|
||||
data = VehicleSerializer(instance).data
|
||||
sse_broadcast_update('vehicle', 'created', data)
|
||||
|
||||
def perform_update(self, serializer):
|
||||
instance = serializer.save()
|
||||
data = VehicleSerializer(instance).data
|
||||
sse_broadcast_update('vehicle', 'updated', data)
|
||||
|
||||
def perform_destroy(self, instance):
|
||||
data = VehicleSerializer(instance).data
|
||||
instance.delete()
|
||||
sse_broadcast_update('vehicle', 'deleted', data)
|
||||
|
||||
@action(detail=False, methods=['get'], url_path='by-number')
|
||||
def by_number(self, request):
|
||||
"""Get vehicle by vehicle_number query parameter"""
|
||||
@@ -144,6 +167,48 @@ class VehicleViewSet(viewsets.ModelViewSet):
|
||||
status=status.HTTP_404_NOT_FOUND
|
||||
)
|
||||
|
||||
@action(detail=True, methods=['post'], url_path='set-tare')
|
||||
def set_tare(self, request, pk=None):
|
||||
"""Set tare weight from scale reading"""
|
||||
vehicle = self.get_object()
|
||||
value = request.data.get('value')
|
||||
if value is None:
|
||||
return Response({'detail': 'value is required'}, status=status.HTTP_400_BAD_REQUEST)
|
||||
try:
|
||||
value = int(value)
|
||||
except (ValueError, TypeError):
|
||||
return Response({'detail': 'value must be a number'}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
vehicle.tare = value
|
||||
vehicle.tare_date = timezone.now()
|
||||
vehicle.tare_user = request.user
|
||||
vehicle.save()
|
||||
|
||||
serializer = self.get_serializer(vehicle)
|
||||
sse_broadcast_update('vehicle', 'updated', serializer.data)
|
||||
return Response(serializer.data)
|
||||
|
||||
@action(detail=True, methods=['post'], url_path='set-gross')
|
||||
def set_gross(self, request, pk=None):
|
||||
"""Set gross weight from scale reading"""
|
||||
vehicle = self.get_object()
|
||||
value = request.data.get('value')
|
||||
if value is None:
|
||||
return Response({'detail': 'value is required'}, status=status.HTTP_400_BAD_REQUEST)
|
||||
try:
|
||||
value = int(value)
|
||||
except (ValueError, TypeError):
|
||||
return Response({'detail': 'value must be a number'}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
vehicle.gross = value
|
||||
vehicle.gross_date = timezone.now()
|
||||
vehicle.gross_user = request.user
|
||||
vehicle.save()
|
||||
|
||||
serializer = self.get_serializer(vehicle)
|
||||
sse_broadcast_update('vehicle', 'updated', serializer.data)
|
||||
return Response(serializer.data)
|
||||
|
||||
|
||||
class NomenclatureViewSet(viewsets.ModelViewSet):
|
||||
"""
|
||||
@@ -154,16 +219,30 @@ class NomenclatureViewSet(viewsets.ModelViewSet):
|
||||
retrieve: Get a specific nomenclature by ID
|
||||
update: Full update of nomenclature and fields
|
||||
partial_update: Partial update of nomenclature
|
||||
destroy: Delete a nomenclature (cascades to fields)
|
||||
destroy: Delete a nomenclature (cascades to fields and entries)
|
||||
by_code: Get nomenclature by code
|
||||
by_applies_to: Filter nomenclatures by applies_to type
|
||||
entries: List or create entries for a specific nomenclature
|
||||
"""
|
||||
queryset = Nomenclature.objects.prefetch_related('nomenclaturefield_set').all()
|
||||
queryset = Nomenclature.objects.prefetch_related('fields').all()
|
||||
serializer_class = NomenclatureSerializer
|
||||
permission_classes = [IsAuthenticated]
|
||||
filterset_fields = ['applies_to', 'code']
|
||||
filterset_fields = ['applies_to', 'code', 'kind']
|
||||
search_fields = ['code', 'name']
|
||||
ordering = ['code']
|
||||
ordering = ['sort_order', 'code']
|
||||
|
||||
def perform_create(self, serializer):
|
||||
instance = serializer.save()
|
||||
sse_broadcast_update('nomenclature', 'insert', NomenclatureSerializer(instance).data)
|
||||
|
||||
def perform_update(self, serializer):
|
||||
instance = serializer.save()
|
||||
sse_broadcast_update('nomenclature', 'update', NomenclatureSerializer(instance).data)
|
||||
|
||||
def perform_destroy(self, instance):
|
||||
data = NomenclatureSerializer(instance).data
|
||||
instance.delete()
|
||||
sse_broadcast_update('nomenclature', 'delete', data)
|
||||
|
||||
@action(detail=False, methods=['get'], url_path='by-code')
|
||||
def by_code(self, request):
|
||||
@@ -209,3 +288,81 @@ class NomenclatureViewSet(viewsets.ModelViewSet):
|
||||
|
||||
serializer = self.get_serializer(nomenclatures, many=True)
|
||||
return Response(serializer.data)
|
||||
|
||||
@action(detail=True, methods=['get', 'post'], url_path='entries')
|
||||
def entries(self, request, pk=None):
|
||||
"""List or create entries for a specific nomenclature."""
|
||||
nomenclature = self.get_object()
|
||||
|
||||
if request.method == 'GET':
|
||||
entries = nomenclature.entries.all()
|
||||
is_active = request.query_params.get('is_active')
|
||||
if is_active is not None:
|
||||
entries = entries.filter(is_active=is_active.lower() == 'true')
|
||||
serializer = NomenclatureEntrySerializer(entries, many=True)
|
||||
return Response(serializer.data)
|
||||
|
||||
serializer = NomenclatureEntrySerializer(
|
||||
data=request.data,
|
||||
context={'nomenclature': nomenclature},
|
||||
)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
serializer.save(nomenclature=nomenclature)
|
||||
entry_data = {**serializer.data, 'nomenclature_code': nomenclature.code}
|
||||
sse_broadcast_update('nomenclature_entry', 'insert', entry_data)
|
||||
return Response(serializer.data, status=status.HTTP_201_CREATED)
|
||||
|
||||
|
||||
class NomenclatureEntryViewSet(viewsets.ModelViewSet):
|
||||
"""
|
||||
API endpoint for individual nomenclature entry management.
|
||||
For list/create use the nested endpoint: /api/nomenclatures/{id}/entries/
|
||||
This viewset handles retrieve/update/delete of individual entries.
|
||||
"""
|
||||
queryset = NomenclatureEntry.objects.select_related('nomenclature').all()
|
||||
serializer_class = NomenclatureEntrySerializer
|
||||
permission_classes = [IsAuthenticated]
|
||||
|
||||
def get_serializer_context(self):
|
||||
context = super().get_serializer_context()
|
||||
if self.kwargs.get('pk'):
|
||||
try:
|
||||
entry = NomenclatureEntry.objects.select_related('nomenclature').get(pk=self.kwargs['pk'])
|
||||
context['nomenclature'] = entry.nomenclature
|
||||
except NomenclatureEntry.DoesNotExist:
|
||||
pass
|
||||
return context
|
||||
|
||||
def perform_update(self, serializer):
|
||||
instance = serializer.save()
|
||||
entry_data = {**NomenclatureEntrySerializer(instance).data, 'nomenclature_code': instance.nomenclature.code}
|
||||
sse_broadcast_update('nomenclature_entry', 'update', entry_data)
|
||||
|
||||
def perform_destroy(self, instance):
|
||||
entry_data = {**NomenclatureEntrySerializer(instance).data, 'nomenclature_code': instance.nomenclature.code}
|
||||
instance.delete()
|
||||
sse_broadcast_update('nomenclature_entry', 'delete', entry_data)
|
||||
|
||||
|
||||
class ReportViewSet(viewsets.ModelViewSet):
|
||||
"""
|
||||
API endpoint for report template management.
|
||||
|
||||
list: Get all reports
|
||||
create: Create a new report
|
||||
retrieve: Get a specific report by ID
|
||||
update: Update a report
|
||||
destroy: Delete a report
|
||||
"""
|
||||
queryset = Report.objects.select_related('created_by').all()
|
||||
serializer_class = ReportSerializer
|
||||
permission_classes = [IsAuthenticated]
|
||||
filterset_fields = ['name']
|
||||
search_fields = ['name']
|
||||
ordering = ['-updated_at']
|
||||
|
||||
def perform_create(self, serializer):
|
||||
serializer.save(created_by=self.request.user)
|
||||
|
||||
def perform_update(self, serializer):
|
||||
serializer.save()
|
||||
|
||||
Reference in New Issue
Block a user