Marwan Alwali 263292f6be update
2025-11-04 00:50:06 +03:00

631 lines
29 KiB
Python

from django import forms
from django.core.exceptions import ValidationError
from django.utils import timezone
from decimal import Decimal
from .models import *
from accounts.models import User
class BuildingForm(forms.ModelForm):
"""Form for managing buildings"""
class Meta:
model = Building
fields = [
'name', 'code', 'building_type', 'address',
'floor_count', 'total_area_sqm', 'construction_year',
'architect', 'contractor', 'facility_manager',
'last_major_renovation', 'is_active'
]
widgets = {
'name': forms.TextInput(attrs={'class': 'form-control'}),
'code': forms.TextInput(attrs={'class': 'form-control'}),
'building_type': forms.Select(attrs={'class': 'form-select'}),
'address': forms.Textarea(attrs={'class': 'form-control', 'rows': 3}),
'floor_count': forms.NumberInput(attrs={'class': 'form-control', 'min': '1'}),
'total_area_sqm': forms.NumberInput(attrs={'class': 'form-control', 'step': '0.01', 'min': '0'}),
'construction_year': forms.NumberInput(attrs={'class': 'form-control', 'min': '1900', 'max': '2030'}),
'architect': forms.TextInput(attrs={'class': 'form-control'}),
'contractor': forms.TextInput(attrs={'class': 'form-control'}),
'facility_manager': forms.Select(attrs={'class': 'form-select'}),
'last_major_renovation': forms.DateInput(attrs={'class': 'form-control', 'type': 'date'}),
'is_active': forms.CheckboxInput(attrs={'class': 'form-check-input'}),
}
def clean_construction_year(self):
year = self.cleaned_data.get('construction_year')
if year and year > timezone.now().year:
raise ValidationError('Construction year cannot be in the future.')
return year
class FloorForm(forms.ModelForm):
"""Form for managing floors"""
class Meta:
model = Floor
fields = [
'building', 'floor_number', 'name', 'area_sqm',
'ceiling_height_m', 'is_public_access'
]
widgets = {
'building': forms.Select(attrs={'class': 'form-select'}),
'floor_number': forms.NumberInput(attrs={'class': 'form-control'}),
'name': forms.TextInput(attrs={'class': 'form-control'}),
'area_sqm': forms.NumberInput(attrs={'class': 'form-control', 'step': '0.01', 'min': '0'}),
'ceiling_height_m': forms.NumberInput(attrs={'class': 'form-control', 'step': '0.01', 'min': '0'}),
'is_public_access': forms.CheckboxInput(attrs={'class': 'form-check-input'}),
}
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['building'].queryset = Building.objects.filter(is_active=True)
class RoomForm(forms.ModelForm):
"""Form for managing rooms"""
class Meta:
model = Room
fields = [
'floor', 'room_number', 'name', 'area_sqm',
'capacity', 'occupancy_status', 'is_accessible', 'notes'
]
widgets = {
'floor': forms.Select(attrs={'class': 'form-select'}),
'room_number': forms.TextInput(attrs={'class': 'form-control'}),
'name': forms.TextInput(attrs={'class': 'form-control'}),
'room_type': forms.Select(attrs={'class': 'form-select'}),
'area_sqm': forms.NumberInput(attrs={'class': 'form-control', 'step': '0.01', 'min': '0'}),
'capacity': forms.NumberInput(attrs={'class': 'form-control', 'min': '1'}),
'is_accessible': forms.CheckboxInput(attrs={'class': 'form-check-input'}),
'notes': forms.Textarea(attrs={'class': 'form-control', 'rows': 3}),
}
def clean(self):
cleaned_data = super().clean()
return cleaned_data
class AssetCategoryForm(forms.ModelForm):
"""Form for managing asset categories"""
class Meta:
model = AssetCategory
fields = ['name', 'code', 'description', 'parent_category', 'is_active']
widgets = {
'name': forms.TextInput(attrs={'class': 'form-control'}),
'code': forms.TextInput(attrs={'class': 'form-control'}),
'description': forms.Textarea(attrs={'class': 'form-control', 'rows': 3}),
'parent_category': forms.Select(attrs={'class': 'form-select'}),
'is_active': forms.CheckboxInput(attrs={'class': 'form-check-input'}),
}
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# Exclude self from parent category choices
if self.instance.pk:
self.fields['parent_category'].queryset = AssetCategory.objects.exclude(pk=self.instance.pk)
class AssetForm(forms.ModelForm):
"""Form for managing assets"""
class Meta:
model = Asset
fields = [
'name', 'category', 'building', 'floor', 'room',
'location_description', 'manufacturer', 'model', 'serial_number',
'purchase_date', 'purchase_cost', 'current_value', 'depreciation_rate',
'warranty_start_date', 'warranty_end_date', 'service_provider',
'service_contract_number', 'status', 'condition',
'last_inspection_date', 'next_maintenance_date', 'assigned_to', 'notes'
]
exclude = ['asset_id']
widgets = {
'name': forms.TextInput(attrs={'class': 'form-control'}),
'category': forms.Select(attrs={'class': 'form-select'}),
'building': forms.Select(attrs={'class': 'form-select'}),
'floor': forms.Select(attrs={'class': 'form-select'}),
'room': forms.Select(attrs={'class': 'form-select'}),
'location_description': forms.TextInput(attrs={'class': 'form-control'}),
'manufacturer': forms.TextInput(attrs={'class': 'form-control'}),
'model': forms.TextInput(attrs={'class': 'form-control'}),
'serial_number': forms.TextInput(attrs={'class': 'form-control'}),
'purchase_date': forms.DateInput(attrs={'class': 'form-control', 'type': 'date'}),
'purchase_cost': forms.NumberInput(attrs={'class': 'form-control', 'step': '0.01', 'min': '0'}),
'current_value': forms.NumberInput(attrs={'class': 'form-control', 'step': '0.01', 'min': '0'}),
'depreciation_rate': forms.NumberInput(attrs={'class': 'form-control', 'step': '0.01', 'min': '0', 'max': '100'}),
'warranty_start_date': forms.DateInput(attrs={'class': 'form-control', 'type': 'date'}),
'warranty_end_date': forms.DateInput(attrs={'class': 'form-control', 'type': 'date'}),
'service_provider': forms.TextInput(attrs={'class': 'form-control'}),
'service_contract_number': forms.TextInput(attrs={'class': 'form-control'}),
'status': forms.Select(attrs={'class': 'form-select'}),
'condition': forms.Select(attrs={'class': 'form-select'}),
'last_inspection_date': forms.DateInput(attrs={'class': 'form-control', 'type': 'date'}),
'next_maintenance_date': forms.DateInput(attrs={'class': 'form-control', 'type': 'date'}),
'assigned_to': forms.Select(attrs={'class': 'form-select'}),
'notes': forms.Textarea(attrs={'class': 'form-control', 'rows': 3}),
}
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['category'].queryset = AssetCategory.objects.filter(is_active=True)
self.fields['building'].queryset = Building.objects.filter(is_active=True)
self.fields['floor'].required = False
self.fields['room'].required = False
def clean(self):
cleaned_data = super().clean()
warranty_start = cleaned_data.get('warranty_start_date')
warranty_end = cleaned_data.get('warranty_end_date')
purchase_date = cleaned_data.get('purchase_date')
purchase_cost = cleaned_data.get('purchase_cost')
current_value = cleaned_data.get('current_value')
if warranty_start and warranty_end and warranty_start >= warranty_end:
raise ValidationError('Warranty end date must be after warranty start date.')
if purchase_date and warranty_start and warranty_start < purchase_date:
raise ValidationError('Warranty start date cannot be before purchase date.')
if purchase_cost and current_value and current_value > purchase_cost:
raise ValidationError('Current value cannot be greater than purchase cost.')
return cleaned_data
class MaintenanceRequestForm(forms.ModelForm):
"""Form for creating maintenance requests"""
class Meta:
model = MaintenanceRequest
exclude = ['request_id', 'requested_date']
fields = [
'title', 'description', 'maintenance_type', 'building',
'floor', 'room', 'asset', 'priority', 'scheduled_date',
'estimated_cost', 'actual_cost', 'notes', 'assigned_to',
'estimated_hours', 'status', 'completion_notes'
]
widgets = {
'title': forms.TextInput(attrs={'class': 'form-control'}),
'description': forms.Textarea(attrs={'class': 'form-control', 'rows': 4}),
'maintenance_type': forms.Select(attrs={'class': 'form-select'}),
'building': forms.Select(attrs={'class': 'form-select'}),
'floor': forms.Select(attrs={'class': 'form-select'}),
'room': forms.Select(attrs={'class': 'form-select'}),
'asset': forms.Select(attrs={'class': 'form-select'}),
'priority': forms.Select(attrs={'class': 'form-select'}),
'scheduled_date': forms.DateTimeInput(attrs={'class': 'form-control', 'type': 'datetime-local'}),
'estimated_cost': forms.NumberInput(attrs={'class': 'form-control', 'step': '0.01', 'min': '0'}),
'actual_cost': forms.NumberInput(attrs={'class': 'form-control', 'step': '0.01', 'min': '0'}),
'notes': forms.Textarea(attrs={'class': 'form-control', 'rows': 3}),
'assigned_to': forms.Select(attrs={'class': 'form-select'}),
'estimated_hours': forms.NumberInput(attrs={'class': 'form-control', 'step': '0.5', 'min': '0'}),
'status': forms.Select(attrs={'class': 'form-select'}),
'completion_notes': forms.Textarea(attrs={'class': 'form-control', 'rows': 4}),
}
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['maintenance_type'].queryset = MaintenanceType.objects.filter(is_active=True)
self.fields['building'].queryset = Building.objects.filter(is_active=True)
self.fields['floor'].required = False
self.fields['room'].required = False
self.fields['asset'].required = False
class MaintenanceUpdateForm(forms.ModelForm):
"""Form for updating maintenance requests"""
class Meta:
model = MaintenanceRequest
fields = [
'status', 'assigned_to', 'scheduled_date', 'started_date',
'completed_date', 'actual_cost', 'completion_notes'
]
widgets = {
'status': forms.Select(attrs={'class': 'form-select'}),
'assigned_to': forms.Select(attrs={'class': 'form-select'}),
'scheduled_date': forms.DateTimeInput(attrs={'class': 'form-control', 'type': 'datetime-local'}),
'started_date': forms.DateTimeInput(attrs={'class': 'form-control', 'type': 'datetime-local'}),
'completed_date': forms.DateTimeInput(attrs={'class': 'form-control', 'type': 'datetime-local'}),
'actual_cost': forms.NumberInput(attrs={'class': 'form-control', 'step': '0.01', 'min': '0'}),
'completion_notes': forms.Textarea(attrs={'class': 'form-control', 'rows': 4}),
}
def clean(self):
cleaned_data = super().clean()
scheduled_date = cleaned_data.get('scheduled_date')
started_date = cleaned_data.get('started_date')
completed_date = cleaned_data.get('completed_date')
if started_date and scheduled_date and started_date < scheduled_date:
raise ValidationError('Start date cannot be before scheduled date.')
if completed_date and started_date and completed_date < started_date:
raise ValidationError('Completion date cannot be before start date.')
return cleaned_data
class MaintenanceScheduleForm(forms.ModelForm):
"""Form for creating maintenance schedules"""
class Meta:
model = MaintenanceSchedule
fields = [
'name', 'description', 'maintenance_type', 'asset', 'building',
'room', 'frequency_interval', 'start_date',
'end_date', 'assigned_to', 'estimated_duration_hours', 'is_active'
]
widgets = {
'name': forms.TextInput(attrs={'class': 'form-control'}),
'description': forms.Textarea(attrs={'class': 'form-control', 'rows': 3}),
'maintenance_type': forms.Select(attrs={'class': 'form-select'}),
'asset': forms.Select(attrs={'class': 'form-select'}),
'building': forms.Select(attrs={'class': 'form-select'}),
'room': forms.Select(attrs={'class': 'form-select'}),
'frequency': forms.Select(attrs={'class': 'form-select'}),
'frequency_interval': forms.NumberInput(attrs={'class': 'form-control', 'min': '1'}),
'start_date': forms.DateInput(attrs={'class': 'form-control', 'type': 'date'}),
'end_date': forms.DateInput(attrs={'class': 'form-control', 'type': 'date'}),
'assigned_to': forms.Select(attrs={'class': 'form-select'}),
'estimated_duration_hours': forms.NumberInput(attrs={'class': 'form-control', 'step': '0.5', 'min': '0'}),
'is_active': forms.CheckboxInput(attrs={'class': 'form-check-input'}),
}
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['maintenance_type'].queryset = MaintenanceType.objects.filter(is_active=True)
self.fields['asset'].required = False
self.fields['building'].required = False
self.fields['room'].required = False
def clean(self):
cleaned_data = super().clean()
start_date = cleaned_data.get('start_date')
end_date = cleaned_data.get('end_date')
asset = cleaned_data.get('asset')
building = cleaned_data.get('building')
room = cleaned_data.get('room')
if start_date and end_date and start_date >= end_date:
raise ValidationError('End date must be after start date.')
# At least one scope must be specified
if not any([asset, building, room]):
raise ValidationError('Please specify at least one scope: asset, building, or room.')
return cleaned_data
class VendorForm(forms.ModelForm):
"""Form for managing vendors"""
class Meta:
model = Vendor
fields = [
'name', 'vendor_type', 'contact_person', 'email', 'phone',
'address', 'crn', 'vrn', 'rating', 'is_active'
]
widgets = {
'name': forms.TextInput(attrs={'class': 'form-control'}),
'vendor_type': forms.Select(attrs={'class': 'form-select'}),
'contact_person': forms.TextInput(attrs={'class': 'form-control'}),
'email': forms.EmailInput(attrs={'class': 'form-control'}),
'phone': forms.TextInput(attrs={'class': 'form-control'}),
'address': forms.Textarea(attrs={'class': 'form-control', 'rows': 3}),
'crn': forms.TextInput(attrs={'class': 'form-control'}),
'vrn': forms.TextInput(attrs={'class': 'form-control'}),
'rating': forms.NumberInput(attrs={'class': 'form-control', 'step': '0.1', 'min': '0', 'max': '5'}),
'is_active': forms.CheckboxInput(attrs={'class': 'form-check-input'}),
}
class ServiceContractForm(forms.ModelForm):
"""Form for managing service contracts"""
class Meta:
model = ServiceContract
fields = [
'contract_number', 'vendor', 'title', 'description',
'start_date', 'end_date', 'contract_value', 'payment_terms',
'buildings', 'service_areas', 'auto_renewal', 'renewal_notice_days'
]
widgets = {
'contract_number': forms.TextInput(attrs={'class': 'form-control'}),
'vendor': forms.Select(attrs={'class': 'form-select'}),
'title': forms.TextInput(attrs={'class': 'form-control'}),
'description': forms.Textarea(attrs={'class': 'form-control', 'rows': 4}),
'start_date': forms.DateInput(attrs={'class': 'form-control', 'type': 'date'}),
'end_date': forms.DateInput(attrs={'class': 'form-control', 'type': 'date'}),
'contract_value': forms.NumberInput(attrs={'class': 'form-control', 'step': '0.01', 'min': '0'}),
'payment_terms': forms.TextInput(attrs={'class': 'form-control'}),
'buildings': forms.CheckboxSelectMultiple(),
'service_areas': forms.Textarea(attrs={'class': 'form-control', 'rows': 3}),
'auto_renewal': forms.CheckboxInput(attrs={'class': 'form-check-input'}),
'renewal_notice_days': forms.NumberInput(attrs={'class': 'form-control', 'min': '1'}),
}
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['vendor'].queryset = Vendor.objects.filter(is_active=True)
self.fields['buildings'].queryset = Building.objects.filter(is_active=True)
def clean(self):
cleaned_data = super().clean()
start_date = cleaned_data.get('start_date')
end_date = cleaned_data.get('end_date')
if start_date and end_date and start_date >= end_date:
raise ValidationError('End date must be after start date.')
return cleaned_data
class FacilitySearchForm(forms.Form):
"""Form for searching facilities"""
search_query = forms.CharField(
max_length=100,
required=False,
widget=forms.TextInput(attrs={
'class': 'form-control',
'placeholder': 'Search buildings, rooms, assets...'
})
)
building = forms.ModelChoiceField(
queryset=Building.objects.filter(is_active=True),
required=False,
empty_label="All Buildings",
widget=forms.Select(attrs={'class': 'form-select'})
)
asset_category = forms.ModelChoiceField(
queryset=AssetCategory.objects.filter(is_active=True),
required=False,
empty_label="All Categories",
widget=forms.Select(attrs={'class': 'form-select'})
)
class MaintenanceFilterForm(forms.Form):
"""Form for filtering maintenance requests"""
STATUS_CHOICES = [('', 'All Statuses')] + MaintenanceRequest.MaintenanceStatus.choices
PRIORITY_CHOICES = [('', 'All Priorities')] + MaintenanceRequest.Priority.choices
status = forms.ChoiceField(
choices=STATUS_CHOICES,
required=False,
widget=forms.Select(attrs={'class': 'form-select'})
)
priority = forms.ChoiceField(
choices=PRIORITY_CHOICES,
required=False,
widget=forms.Select(attrs={'class': 'form-select'})
)
building = forms.ModelChoiceField(
queryset=Building.objects.filter(is_active=True),
required=False,
empty_label="All Buildings",
widget=forms.Select(attrs={'class': 'form-select'})
)
assigned_to = forms.ModelChoiceField(
queryset=None, # Will be set in __init__
required=False,
empty_label="All Assignees",
widget=forms.Select(attrs={'class': 'form-select'})
)
date_from = forms.DateField(
required=False,
widget=forms.DateInput(attrs={'class': 'form-control', 'type': 'date'})
)
date_to = forms.DateField(
required=False,
widget=forms.DateInput(attrs={'class': 'form-control', 'type': 'date'})
)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['assigned_to'].queryset = User.objects.filter(is_active=True)
class InspectionForm(forms.ModelForm):
"""Form for managing inspections"""
class Meta:
model = Inspection
fields = [
'inspection_type', 'title', 'description', 'building',
'floors', 'rooms', 'assets', 'scheduled_date',
'estimated_duration_hours', 'inspector', 'inspector_external',
'inspector_organization', 'status', 'overall_rating',
'findings', 'recommendations', 'requires_followup', 'followup_date'
]
widgets = {
'inspection_type': forms.Select(attrs={'class': 'form-select'}),
'title': forms.TextInput(attrs={'class': 'form-control'}),
'description': forms.Textarea(attrs={'class': 'form-control', 'rows': 3}),
'building': forms.Select(attrs={'class': 'form-select'}),
'floors': forms.CheckboxSelectMultiple(),
'rooms': forms.CheckboxSelectMultiple(),
'assets': forms.CheckboxSelectMultiple(),
'scheduled_date': forms.DateTimeInput(attrs={'class': 'form-control', 'type': 'datetime-local'}),
'estimated_duration_hours': forms.NumberInput(attrs={'class': 'form-control', 'step': '0.5', 'min': '0'}),
'inspector': forms.Select(attrs={'class': 'form-select'}),
'inspector_external': forms.TextInput(attrs={'class': 'form-control'}),
'inspector_organization': forms.TextInput(attrs={'class': 'form-control'}),
'status': forms.Select(attrs={'class': 'form-select'}),
'overall_rating': forms.TextInput(attrs={'class': 'form-control', 'placeholder': 'e.g., Pass, Fail, Conditional'}),
'findings': forms.Textarea(attrs={'class': 'form-control', 'rows': 4}),
'recommendations': forms.Textarea(attrs={'class': 'form-control', 'rows': 4}),
'requires_followup': forms.CheckboxInput(attrs={'class': 'form-check-input'}),
'followup_date': forms.DateInput(attrs={'class': 'form-control', 'type': 'date'}),
}
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['building'].queryset = Building.objects.filter(is_active=True)
self.fields['inspector'].queryset = User.objects.filter(is_active=True)
self.fields['floors'].required = False
self.fields['rooms'].required = False
self.fields['assets'].required = False
def clean(self):
cleaned_data = super().clean()
requires_followup = cleaned_data.get('requires_followup')
followup_date = cleaned_data.get('followup_date')
if requires_followup and not followup_date:
raise ValidationError('Follow-up date is required when follow-up is needed.')
return cleaned_data
class EnergyMeterForm(forms.ModelForm):
"""Form for managing energy meters"""
class Meta:
model = EnergyMeter
fields = [
'meter_id', 'meter_type', 'building', 'location_description',
'manufacturer', 'model', 'serial_number', 'installation_date',
'is_active', 'calibration_date', 'next_calibration_date'
]
widgets = {
'meter_id': forms.TextInput(attrs={'class': 'form-control'}),
'meter_type': forms.Select(attrs={'class': 'form-select'}),
'building': forms.Select(attrs={'class': 'form-select'}),
'location_description': forms.TextInput(attrs={'class': 'form-control'}),
'manufacturer': forms.TextInput(attrs={'class': 'form-control'}),
'model': forms.TextInput(attrs={'class': 'form-control'}),
'serial_number': forms.TextInput(attrs={'class': 'form-control'}),
'installation_date': forms.DateInput(attrs={'class': 'form-control', 'type': 'date'}),
'is_active': forms.CheckboxInput(attrs={'class': 'form-check-input'}),
'calibration_date': forms.DateInput(attrs={'class': 'form-control', 'type': 'date'}),
'next_calibration_date': forms.DateInput(attrs={'class': 'form-control', 'type': 'date'}),
}
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['building'].queryset = Building.objects.filter(is_active=True)
def clean(self):
cleaned_data = super().clean()
calibration_date = cleaned_data.get('calibration_date')
next_calibration_date = cleaned_data.get('next_calibration_date')
if calibration_date and next_calibration_date and next_calibration_date <= calibration_date:
raise ValidationError('Next calibration date must be after calibration date.')
return cleaned_data
class EnergyReadingForm(forms.ModelForm):
"""Form for recording energy readings"""
class Meta:
model = EnergyReading
fields = [
'meter', 'reading_date', 'reading_value', 'cost',
'is_estimated', 'notes'
]
widgets = {
'meter': forms.Select(attrs={'class': 'form-select'}),
'reading_date': forms.DateTimeInput(attrs={'class': 'form-control', 'type': 'datetime-local'}),
'reading_value': forms.NumberInput(attrs={'class': 'form-control', 'step': '0.01', 'min': '0'}),
'cost': forms.NumberInput(attrs={'class': 'form-control', 'step': '0.01', 'min': '0'}),
'is_estimated': forms.CheckboxInput(attrs={'class': 'form-check-input'}),
'notes': forms.Textarea(attrs={'class': 'form-control', 'rows': 3}),
}
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['meter'].queryset = EnergyMeter.objects.filter(is_active=True)
def clean_reading_value(self):
reading_value = self.cleaned_data.get('reading_value')
meter = self.cleaned_data.get('meter')
if meter and reading_value < meter.current_reading:
raise ValidationError(
f'Reading value cannot be less than current meter reading ({meter.current_reading}).'
)
return reading_value
class SpaceReservationForm(forms.ModelForm):
"""Form for managing space reservations"""
class Meta:
model = SpaceReservation
fields = [
'room', 'title', 'description', 'start_datetime', 'end_datetime',
'contact_person', 'contact_email', 'contact_phone',
'expected_attendees', 'setup_requirements', 'catering_required',
'av_equipment_required', 'hourly_rate', 'notes'
]
widgets = {
'room': forms.Select(attrs={'class': 'form-select'}),
'title': forms.TextInput(attrs={'class': 'form-control'}),
'description': forms.Textarea(attrs={'class': 'form-control', 'rows': 3}),
'start_datetime': forms.DateTimeInput(attrs={'class': 'form-control', 'type': 'datetime-local'}),
'end_datetime': forms.DateTimeInput(attrs={'class': 'form-control', 'type': 'datetime-local'}),
'contact_person': forms.TextInput(attrs={'class': 'form-control'}),
'contact_email': forms.EmailInput(attrs={'class': 'form-control'}),
'contact_phone': forms.TextInput(attrs={'class': 'form-control'}),
'expected_attendees': forms.NumberInput(attrs={'class': 'form-control', 'min': '1'}),
'setup_requirements': forms.Textarea(attrs={'class': 'form-control', 'rows': 3}),
'catering_required': forms.CheckboxInput(attrs={'class': 'form-check-input'}),
'av_equipment_required': forms.CheckboxInput(attrs={'class': 'form-check-input'}),
'hourly_rate': forms.NumberInput(attrs={'class': 'form-control', 'step': '0.01', 'min': '0'}),
'notes': forms.Textarea(attrs={'class': 'form-control', 'rows': 3}),
}
def clean(self):
cleaned_data = super().clean()
start_datetime = cleaned_data.get('start_datetime')
end_datetime = cleaned_data.get('end_datetime')
room = cleaned_data.get('room')
if start_datetime and end_datetime:
if start_datetime >= end_datetime:
raise ValidationError('End time must be after start time.')
# Check for conflicts with existing reservations
if room:
conflicts = SpaceReservation.objects.filter(
room=room,
status__in=['PENDING', 'CONFIRMED']
).filter(
Q(start_datetime__lt=end_datetime, end_datetime__gt=start_datetime)
)
# Exclude current instance if updating
if self.instance.pk:
conflicts = conflicts.exclude(pk=self.instance.pk)
if conflicts.exists():
raise ValidationError(
f'This room is already reserved during the selected time period. '
f'Conflicting reservation: {conflicts.first().title}'
)
return cleaned_data
class SpaceReservationUpdateForm(forms.ModelForm):
"""Form for updating space reservation status"""
class Meta:
model = SpaceReservation
fields = ['status', 'notes']
widgets = {
'status': forms.Select(attrs={'class': 'form-select'}),
'notes': forms.Textarea(attrs={'class': 'form-control', 'rows': 3}),
}