from django import forms from decimal import Decimal, InvalidOperation, ROUND_DOWN from .models import Park from location.models import Location class ParkForm(forms.ModelForm): """Form for creating and updating Park objects with location support""" # Location fields latitude = forms.DecimalField( max_digits=9, decimal_places=6, required=False, widget=forms.HiddenInput() ) longitude = forms.DecimalField( max_digits=10, decimal_places=6, required=False, widget=forms.HiddenInput() ) street_address = forms.CharField( max_length=255, required=False, widget=forms.TextInput( attrs={ "class": "w-full border-gray-300 rounded-lg form-input dark:border-gray-600 dark:bg-gray-700 dark:text-white" } ) ) city = forms.CharField( max_length=255, required=False, widget=forms.TextInput( attrs={ "class": "w-full border-gray-300 rounded-lg form-input dark:border-gray-600 dark:bg-gray-700 dark:text-white" } ) ) state = forms.CharField( max_length=255, required=False, widget=forms.TextInput( attrs={ "class": "w-full border-gray-300 rounded-lg form-input dark:border-gray-600 dark:bg-gray-700 dark:text-white" } ) ) country = forms.CharField( max_length=255, required=False, widget=forms.TextInput( attrs={ "class": "w-full border-gray-300 rounded-lg form-input dark:border-gray-600 dark:bg-gray-700 dark:text-white" } ) ) postal_code = forms.CharField( max_length=20, required=False, widget=forms.TextInput( attrs={ "class": "w-full border-gray-300 rounded-lg form-input dark:border-gray-600 dark:bg-gray-700 dark:text-white" } ) ) class Meta: model = Park fields = [ "name", "description", "owner", "status", "opening_date", "closing_date", "operating_season", "size_acres", "website", # Location fields handled separately "latitude", "longitude", "street_address", "city", "state", "country", "postal_code", ] widgets = { "name": forms.TextInput( attrs={ "class": "w-full border-gray-300 rounded-lg form-input dark:border-gray-600 dark:bg-gray-700 dark:text-white" } ), "description": forms.Textarea( attrs={ "class": "w-full border-gray-300 rounded-lg form-textarea dark:border-gray-600 dark:bg-gray-700 dark:text-white", "rows": 2, } ), "owner": forms.Select( attrs={ "class": "w-full border-gray-300 rounded-lg form-select dark:border-gray-600 dark:bg-gray-700 dark:text-white" } ), "status": forms.Select( attrs={ "class": "w-full border-gray-300 rounded-lg form-select dark:border-gray-600 dark:bg-gray-700 dark:text-white" } ), "opening_date": forms.DateInput( attrs={ "type": "date", "class": "w-full border-gray-300 rounded-lg form-input dark:border-gray-600 dark:bg-gray-700 dark:text-white", } ), "closing_date": forms.DateInput( attrs={ "type": "date", "class": "w-full border-gray-300 rounded-lg form-input dark:border-gray-600 dark:bg-gray-700 dark:text-white", } ), "operating_season": forms.TextInput( attrs={ "class": "w-full border-gray-300 rounded-lg form-input dark:border-gray-600 dark:bg-gray-700 dark:text-white", "placeholder": "e.g., Year-round, Summer only, etc.", } ), "size_acres": forms.NumberInput( attrs={ "class": "w-full border-gray-300 rounded-lg form-input dark:border-gray-600 dark:bg-gray-700 dark:text-white", "step": "0.01", "min": "0", } ), "website": forms.URLInput( attrs={ "class": "w-full border-gray-300 rounded-lg form-input dark:border-gray-600 dark:bg-gray-700 dark:text-white", "placeholder": "https://example.com", } ), } def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) # Pre-fill location fields if editing existing park if self.instance and self.instance.pk and self.instance.location.exists(): location = self.instance.location.first() self.fields['latitude'].initial = location.latitude self.fields['longitude'].initial = location.longitude self.fields['street_address'].initial = location.street_address self.fields['city'].initial = location.city self.fields['state'].initial = location.state self.fields['country'].initial = location.country self.fields['postal_code'].initial = location.postal_code def clean_latitude(self): latitude = self.cleaned_data.get('latitude') if latitude is not None: try: # Convert to Decimal for precise handling latitude = Decimal(str(latitude)) # Round to exactly 6 decimal places latitude = latitude.quantize(Decimal('0.000001'), rounding=ROUND_DOWN) # Validate range if latitude < -90 or latitude > 90: raise forms.ValidationError("Latitude must be between -90 and 90 degrees.") # Convert to string to preserve exact decimal places return str(latitude) except (InvalidOperation, TypeError): raise forms.ValidationError("Invalid latitude value.") return latitude def clean_longitude(self): longitude = self.cleaned_data.get('longitude') if longitude is not None: try: # Convert to Decimal for precise handling longitude = Decimal(str(longitude)) # Round to exactly 6 decimal places longitude = longitude.quantize(Decimal('0.000001'), rounding=ROUND_DOWN) # Validate range if longitude < -180 or longitude > 180: raise forms.ValidationError("Longitude must be between -180 and 180 degrees.") # Convert to string to preserve exact decimal places return str(longitude) except (InvalidOperation, TypeError): raise forms.ValidationError("Invalid longitude value.") return longitude def save(self, commit=True): park = super().save(commit=False) # Prepare location data location_data = { 'name': park.name, 'location_type': 'park', 'latitude': self.cleaned_data.get('latitude'), 'longitude': self.cleaned_data.get('longitude'), 'street_address': self.cleaned_data.get('street_address'), 'city': self.cleaned_data.get('city'), 'state': self.cleaned_data.get('state'), 'country': self.cleaned_data.get('country'), 'postal_code': self.cleaned_data.get('postal_code'), } # Handle location: update if exists, create if not if park.location.exists(): location = park.location.first() for key, value in location_data.items(): setattr(location, key, value) location.save() else: Location.objects.create(content_object=park, **location_data) if commit: park.save() return park