added vehicles and vehicles extra, dynamic tables for entities, dynamic dropdowns with overlay for selecting and manage data
This commit is contained in:
+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
|
||||
|
||||
Reference in New Issue
Block a user