mirror of
https://github.com/pacnpal/thrillwiki_django_no_react.git
synced 2025-12-21 21:31:08 -05:00
okay fine
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -4,15 +4,20 @@ from simple_history.admin import SimpleHistoryAdmin
|
||||
from .models import Park, ParkArea
|
||||
|
||||
class ParkAdmin(SimpleHistoryAdmin):
|
||||
list_display = ('name', 'location', 'status', 'owner', 'created_at', 'updated_at')
|
||||
list_filter = ('status', 'country', 'region', 'city')
|
||||
search_fields = ('name', 'location', 'description')
|
||||
list_display = ('name', 'formatted_location', 'status', 'owner', 'created_at', 'updated_at')
|
||||
list_filter = ('status',)
|
||||
search_fields = ('name', 'description', 'location__name', 'location__city', 'location__country')
|
||||
readonly_fields = ('created_at', 'updated_at')
|
||||
prepopulated_fields = {'slug': ('name',)}
|
||||
|
||||
def formatted_location(self, obj):
|
||||
"""Display formatted location string"""
|
||||
return obj.formatted_location
|
||||
formatted_location.short_description = 'Location'
|
||||
|
||||
def get_history_list_display(self, request):
|
||||
"""Customize the list display for history records"""
|
||||
return ('name', 'location', 'status', 'history_date', 'history_user')
|
||||
return ('name', 'formatted_location', 'status', 'history_date', 'history_user')
|
||||
|
||||
class ParkAreaAdmin(SimpleHistoryAdmin):
|
||||
list_display = ('name', 'park', 'created_at', 'updated_at')
|
||||
@@ -34,11 +39,16 @@ from simple_history.models import HistoricalRecords
|
||||
from .models import Park
|
||||
|
||||
class HistoricalParkAdmin(admin.ModelAdmin):
|
||||
list_display = ('name', 'location', 'status', 'history_date', 'history_user', 'history_type')
|
||||
list_filter = ('status', 'country', 'region', 'city', 'history_type')
|
||||
search_fields = ('name', 'location', 'description')
|
||||
list_display = ('name', 'formatted_location', 'status', 'history_date', 'history_user', 'history_type')
|
||||
list_filter = ('status', 'history_type')
|
||||
search_fields = ('name', 'description')
|
||||
readonly_fields = ('history_date', 'history_type', 'history_id')
|
||||
|
||||
def formatted_location(self, obj):
|
||||
"""Display formatted location string"""
|
||||
return obj.instance.formatted_location
|
||||
formatted_location.short_description = 'Location'
|
||||
|
||||
def has_add_permission(self, request):
|
||||
return False # Prevent adding new historical records directly
|
||||
|
||||
|
||||
311
parks/forms.py
311
parks/forms.py
@@ -1,191 +1,148 @@
|
||||
from django import forms
|
||||
from django.urls import reverse_lazy
|
||||
from decimal import Decimal, InvalidOperation, ROUND_DOWN
|
||||
from .models import Park
|
||||
from cities_light.models import Country, Region, City
|
||||
|
||||
class LocationFilterForm(forms.Form):
|
||||
country = forms.CharField(
|
||||
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',
|
||||
'placeholder': 'Select or type a country',
|
||||
'hx-get': reverse_lazy('parks:country_search'),
|
||||
'hx-trigger': 'focus, input',
|
||||
'hx-target': '#country-results',
|
||||
'autocomplete': 'off'
|
||||
})
|
||||
)
|
||||
region = forms.CharField(
|
||||
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',
|
||||
'placeholder': 'Select or type a region/state',
|
||||
'hx-get': reverse_lazy('parks:region_search'),
|
||||
'hx-trigger': 'focus, input',
|
||||
'hx-target': '#region-results',
|
||||
'autocomplete': 'off'
|
||||
})
|
||||
)
|
||||
city = forms.CharField(
|
||||
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',
|
||||
'placeholder': 'Select or type a city',
|
||||
'hx-get': reverse_lazy('parks:city_search'),
|
||||
'hx-trigger': 'focus, input',
|
||||
'hx-target': '#city-results',
|
||||
'autocomplete': 'off'
|
||||
})
|
||||
)
|
||||
|
||||
class ParkForm(forms.ModelForm):
|
||||
# Hidden fields for actual model relations
|
||||
country = forms.ModelChoiceField(queryset=Country.objects.all(), required=True, widget=forms.HiddenInput())
|
||||
region = forms.ModelChoiceField(queryset=Region.objects.all(), required=False, widget=forms.HiddenInput())
|
||||
city = forms.ModelChoiceField(queryset=City.objects.all(), required=False, widget=forms.HiddenInput())
|
||||
|
||||
# Visible fields for Alpine.js
|
||||
country_name = forms.CharField(
|
||||
label="Country",
|
||||
required=True,
|
||||
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',
|
||||
'placeholder': 'Select or type a country',
|
||||
'data-autocomplete': 'true'
|
||||
})
|
||||
)
|
||||
|
||||
region_name = forms.CharField(
|
||||
label="Region/State",
|
||||
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',
|
||||
'placeholder': 'Select or type a region/state',
|
||||
'data-autocomplete': 'true'
|
||||
})
|
||||
)
|
||||
|
||||
city_name = forms.CharField(
|
||||
label="City",
|
||||
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',
|
||||
'placeholder': 'Select or type a city',
|
||||
'data-autocomplete': 'true'
|
||||
})
|
||||
)
|
||||
"""Form for creating and updating Park objects with location support"""
|
||||
|
||||
class Meta:
|
||||
model = Park
|
||||
fields = ['name', 'description', 'owner', 'status', 'opening_date', 'closing_date',
|
||||
'operating_season', 'size_acres', 'website', 'country', 'region', 'city']
|
||||
fields = [
|
||||
"name",
|
||||
"description",
|
||||
"owner",
|
||||
"status",
|
||||
"opening_date",
|
||||
"closing_date",
|
||||
"operating_season",
|
||||
"size_acres",
|
||||
"website",
|
||||
"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'
|
||||
}),
|
||||
"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",
|
||||
}
|
||||
),
|
||||
# Location fields
|
||||
"latitude": forms.HiddenInput(),
|
||||
"longitude": forms.HiddenInput(),
|
||||
"street_address": 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.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.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.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.TextInput(
|
||||
attrs={
|
||||
"class": "w-full border-gray-300 rounded-lg form-input dark:border-gray-600 dark:bg-gray-700 dark:text-white"
|
||||
}
|
||||
),
|
||||
}
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
instance = kwargs.get('instance')
|
||||
if instance:
|
||||
def clean_latitude(self):
|
||||
latitude = self.cleaned_data.get('latitude')
|
||||
if latitude is not None:
|
||||
try:
|
||||
if instance.country:
|
||||
self.fields['country_name'].initial = instance.country.name
|
||||
self.fields['country'].initial = instance.country
|
||||
except Country.DoesNotExist:
|
||||
pass
|
||||
# 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:
|
||||
if instance.region:
|
||||
self.fields['region_name'].initial = instance.region.name
|
||||
self.fields['region'].initial = instance.region
|
||||
except Region.DoesNotExist:
|
||||
pass
|
||||
|
||||
try:
|
||||
if instance.city:
|
||||
self.fields['city_name'].initial = instance.city.name
|
||||
self.fields['city'].initial = instance.city
|
||||
except City.DoesNotExist:
|
||||
pass
|
||||
|
||||
def clean(self):
|
||||
cleaned_data = super().clean()
|
||||
country_name = cleaned_data.get('country_name')
|
||||
region_name = cleaned_data.get('region_name')
|
||||
city_name = cleaned_data.get('city_name')
|
||||
|
||||
# Get or create default country if needed
|
||||
default_country = Country.objects.first()
|
||||
if not default_country:
|
||||
default_country = Country.objects.create(
|
||||
name='Unknown',
|
||||
name_ascii='Unknown',
|
||||
slug='unknown',
|
||||
geoname_id=0,
|
||||
alternate_names='',
|
||||
search_names='Unknown'
|
||||
)
|
||||
|
||||
if country_name:
|
||||
try:
|
||||
country = Country.objects.get(name__iexact=country_name)
|
||||
cleaned_data['country'] = country
|
||||
except Country.DoesNotExist:
|
||||
cleaned_data['country'] = default_country
|
||||
else:
|
||||
raise forms.ValidationError("Country is required")
|
||||
|
||||
if region_name and cleaned_data.get('country'):
|
||||
try:
|
||||
region = Region.objects.get(
|
||||
name__iexact=region_name,
|
||||
country=cleaned_data['country']
|
||||
)
|
||||
cleaned_data['region'] = region
|
||||
except Region.DoesNotExist:
|
||||
cleaned_data['region'] = None
|
||||
|
||||
if city_name and cleaned_data.get('region'):
|
||||
try:
|
||||
city = City.objects.get(
|
||||
name__iexact=city_name,
|
||||
region=cleaned_data['region']
|
||||
)
|
||||
cleaned_data['city'] = city
|
||||
except City.DoesNotExist:
|
||||
cleaned_data['city'] = None
|
||||
|
||||
return cleaned_data
|
||||
# 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
|
||||
|
||||
55
parks/location_utils.py
Normal file
55
parks/location_utils.py
Normal file
@@ -0,0 +1,55 @@
|
||||
from decimal import Decimal, ROUND_DOWN, InvalidOperation
|
||||
|
||||
|
||||
def normalize_coordinate(value, max_digits, decimal_places):
|
||||
"""Normalize coordinate to have exactly 6 decimal places"""
|
||||
try:
|
||||
if value is None:
|
||||
return None
|
||||
|
||||
# Convert to Decimal for precise handling
|
||||
value = Decimal(str(value))
|
||||
# Round to exactly 6 decimal places
|
||||
value = value.quantize(Decimal('0.000001'), rounding=ROUND_DOWN)
|
||||
|
||||
return float(value)
|
||||
except (TypeError, ValueError, InvalidOperation):
|
||||
return None
|
||||
|
||||
|
||||
def get_english_name(tags):
|
||||
"""Extract English name from OSM tags, falling back to default name"""
|
||||
# Try name:en first
|
||||
if 'name:en' in tags:
|
||||
return tags['name:en']
|
||||
# Then try int_name (international name)
|
||||
if 'int_name' in tags:
|
||||
return tags['int_name']
|
||||
# Fall back to default name
|
||||
return tags.get('name')
|
||||
|
||||
|
||||
def normalize_osm_result(result):
|
||||
"""Normalize OpenStreetMap result to use English names and normalized coordinates"""
|
||||
# Normalize coordinates
|
||||
result['lat'] = normalize_coordinate(float(result['lat']), 9, 6)
|
||||
result['lon'] = normalize_coordinate(float(result['lon']), 10, 6)
|
||||
|
||||
# Get address details
|
||||
address = result.get('address', {})
|
||||
|
||||
# Normalize place names to English where possible
|
||||
if 'namedetails' in result:
|
||||
# For main display name
|
||||
result['display_name'] = get_english_name(result['namedetails'])
|
||||
|
||||
# For address components
|
||||
if 'city' in address and 'city_tags' in result:
|
||||
address['city'] = get_english_name(result['city_tags'])
|
||||
if 'state' in address and 'state_tags' in result:
|
||||
address['state'] = get_english_name(result['state_tags'])
|
||||
if 'country' in address and 'country_tags' in result:
|
||||
address['country'] = get_english_name(result['country_tags'])
|
||||
|
||||
result['address'] = address
|
||||
return result
|
||||
@@ -1,99 +0,0 @@
|
||||
from django.core.management.base import BaseCommand
|
||||
from django.db import connection
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = 'Fix historical park records with null location values'
|
||||
|
||||
def handle(self, *args, **options):
|
||||
with connection.cursor() as cursor:
|
||||
# Make fields nullable temporarily
|
||||
self.stdout.write('Making fields nullable...')
|
||||
cursor.execute("""
|
||||
ALTER TABLE parks_historicalpark
|
||||
ALTER COLUMN city_id DROP NOT NULL,
|
||||
ALTER COLUMN region_id DROP NOT NULL,
|
||||
ALTER COLUMN country_id DROP NOT NULL,
|
||||
ALTER COLUMN location DROP NOT NULL;
|
||||
""")
|
||||
|
||||
# Get or create default locations
|
||||
self.stdout.write('Creating default locations if needed...')
|
||||
|
||||
# Check if Unknown country exists
|
||||
cursor.execute("SELECT id FROM cities_light_country WHERE name = 'Unknown' LIMIT 1;")
|
||||
result = cursor.fetchone()
|
||||
if not result:
|
||||
cursor.execute("""
|
||||
INSERT INTO cities_light_country (name, name_ascii, slug, geoname_id, alternate_names, display_name, search_names)
|
||||
VALUES ('Unknown', 'Unknown', 'unknown', 0, '', 'Unknown', 'Unknown')
|
||||
RETURNING id;
|
||||
""")
|
||||
default_country_id = cursor.fetchone()[0]
|
||||
else:
|
||||
default_country_id = result[0]
|
||||
|
||||
# Check if Unknown region exists
|
||||
cursor.execute("""
|
||||
SELECT id FROM cities_light_region
|
||||
WHERE name = 'Unknown' AND country_id = %s LIMIT 1;
|
||||
""", [default_country_id])
|
||||
result = cursor.fetchone()
|
||||
if not result:
|
||||
cursor.execute("""
|
||||
INSERT INTO cities_light_region (name, name_ascii, slug, geoname_id, alternate_names, country_id, display_name, search_names)
|
||||
VALUES ('Unknown', 'Unknown', 'unknown', 0, '', %s, 'Unknown', 'Unknown')
|
||||
RETURNING id;
|
||||
""", [default_country_id])
|
||||
default_region_id = cursor.fetchone()[0]
|
||||
else:
|
||||
default_region_id = result[0]
|
||||
|
||||
# Check if Unknown city exists
|
||||
cursor.execute("""
|
||||
SELECT id FROM cities_light_city
|
||||
WHERE name = 'Unknown' AND region_id = %s LIMIT 1;
|
||||
""", [default_region_id])
|
||||
result = cursor.fetchone()
|
||||
if not result:
|
||||
cursor.execute("""
|
||||
INSERT INTO cities_light_city (
|
||||
name, name_ascii, slug, geoname_id, alternate_names,
|
||||
region_id, country_id, display_name, search_names,
|
||||
latitude, longitude, population
|
||||
)
|
||||
VALUES (
|
||||
'Unknown', 'Unknown', 'unknown', 0, '',
|
||||
%s, %s, 'Unknown', 'Unknown',
|
||||
0, 0, 0
|
||||
)
|
||||
RETURNING id;
|
||||
""", [default_region_id, default_country_id])
|
||||
default_city_id = cursor.fetchone()[0]
|
||||
else:
|
||||
default_city_id = result[0]
|
||||
|
||||
# Update historical records with null values
|
||||
self.stdout.write('Updating historical records...')
|
||||
cursor.execute("""
|
||||
UPDATE parks_historicalpark
|
||||
SET country_id = %s,
|
||||
region_id = %s,
|
||||
city_id = %s,
|
||||
location = 'Unknown, Unknown, Unknown'
|
||||
WHERE country_id IS NULL
|
||||
OR region_id IS NULL
|
||||
OR city_id IS NULL
|
||||
OR location IS NULL;
|
||||
""", [default_country_id, default_region_id, default_city_id])
|
||||
|
||||
# Make fields non-nullable again
|
||||
self.stdout.write('Making fields non-nullable...')
|
||||
cursor.execute("""
|
||||
ALTER TABLE parks_historicalpark
|
||||
ALTER COLUMN city_id SET NOT NULL,
|
||||
ALTER COLUMN region_id SET NOT NULL,
|
||||
ALTER COLUMN country_id SET NOT NULL,
|
||||
ALTER COLUMN location SET NOT NULL;
|
||||
""")
|
||||
|
||||
self.stdout.write(self.style.SUCCESS('Successfully fixed historical records'))
|
||||
@@ -1,90 +0,0 @@
|
||||
from django.core.management.base import BaseCommand
|
||||
from django.db import connection
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = 'Fix location fields in parks and historical records'
|
||||
|
||||
def handle(self, *args, **options):
|
||||
with connection.cursor() as cursor:
|
||||
# Check if Unknown country exists
|
||||
cursor.execute("""
|
||||
SELECT id FROM cities_light_country WHERE name = 'Unknown' LIMIT 1;
|
||||
""")
|
||||
result = cursor.fetchone()
|
||||
if result:
|
||||
default_country_id = result[0]
|
||||
else:
|
||||
cursor.execute("""
|
||||
INSERT INTO cities_light_country (name, name_ascii, slug, geoname_id, alternate_names)
|
||||
VALUES ('Unknown', 'Unknown', 'unknown', 0, '')
|
||||
RETURNING id;
|
||||
""")
|
||||
default_country_id = cursor.fetchone()[0]
|
||||
|
||||
# Check if Unknown region exists
|
||||
cursor.execute("""
|
||||
SELECT id FROM cities_light_region
|
||||
WHERE name = 'Unknown' AND country_id = %s LIMIT 1;
|
||||
""", [default_country_id])
|
||||
result = cursor.fetchone()
|
||||
if result:
|
||||
default_region_id = result[0]
|
||||
else:
|
||||
cursor.execute("""
|
||||
INSERT INTO cities_light_region (name, name_ascii, slug, geoname_id, alternate_names, country_id, display_name)
|
||||
VALUES ('Unknown', 'Unknown', 'unknown', 0, '', %s, 'Unknown')
|
||||
RETURNING id;
|
||||
""", [default_country_id])
|
||||
default_region_id = cursor.fetchone()[0]
|
||||
|
||||
# Check if Unknown city exists
|
||||
cursor.execute("""
|
||||
SELECT id FROM cities_light_city
|
||||
WHERE name = 'Unknown' AND region_id = %s LIMIT 1;
|
||||
""", [default_region_id])
|
||||
result = cursor.fetchone()
|
||||
if result:
|
||||
default_city_id = result[0]
|
||||
else:
|
||||
cursor.execute("""
|
||||
INSERT INTO cities_light_city (
|
||||
name, name_ascii, slug, geoname_id, alternate_names,
|
||||
region_id, country_id, display_name,
|
||||
latitude, longitude, population
|
||||
)
|
||||
VALUES (
|
||||
'Unknown', 'Unknown', 'unknown', 0, '',
|
||||
%s, %s, 'Unknown',
|
||||
0, 0, 0
|
||||
)
|
||||
RETURNING id;
|
||||
""", [default_region_id, default_country_id])
|
||||
default_city_id = cursor.fetchone()[0]
|
||||
|
||||
# Update parks with null locations
|
||||
cursor.execute("""
|
||||
UPDATE parks_park
|
||||
SET country_id = %s,
|
||||
region_id = %s,
|
||||
city_id = %s,
|
||||
location = 'Unknown, Unknown, Unknown'
|
||||
WHERE country_id IS NULL
|
||||
OR region_id IS NULL
|
||||
OR city_id IS NULL
|
||||
OR location IS NULL;
|
||||
""", [default_country_id, default_region_id, default_city_id])
|
||||
|
||||
# Update historical records with null locations
|
||||
cursor.execute("""
|
||||
UPDATE parks_historicalpark
|
||||
SET country_id = %s,
|
||||
region_id = %s,
|
||||
city_id = %s,
|
||||
location = 'Unknown, Unknown, Unknown'
|
||||
WHERE country_id IS NULL
|
||||
OR region_id IS NULL
|
||||
OR city_id IS NULL
|
||||
OR location IS NULL;
|
||||
""", [default_country_id, default_region_id, default_city_id])
|
||||
|
||||
self.stdout.write(self.style.SUCCESS('Successfully fixed location fields'))
|
||||
28
parks/management/commands/fix_migrations.py
Normal file
28
parks/management/commands/fix_migrations.py
Normal file
@@ -0,0 +1,28 @@
|
||||
from django.core.management.base import BaseCommand
|
||||
from django.db import connection
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = 'Fix migration history'
|
||||
|
||||
def handle(self, *args, **options):
|
||||
with connection.cursor() as cursor:
|
||||
# Drop existing historical tables
|
||||
cursor.execute("""
|
||||
DROP TABLE IF EXISTS parks_historicalpark CASCADE;
|
||||
DROP TABLE IF EXISTS parks_historicalparkarea CASCADE;
|
||||
""")
|
||||
|
||||
# Delete all existing parks migrations
|
||||
cursor.execute("""
|
||||
DELETE FROM django_migrations
|
||||
WHERE app = 'parks';
|
||||
""")
|
||||
|
||||
# Insert the new initial migration
|
||||
cursor.execute("""
|
||||
INSERT INTO django_migrations (app, name, applied)
|
||||
VALUES ('parks', '0001_initial', NOW());
|
||||
""")
|
||||
|
||||
self.stdout.write(self.style.SUCCESS('Successfully fixed migration history'))
|
||||
@@ -1,5 +1,6 @@
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
from django.core.management.base import BaseCommand
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
@@ -11,128 +12,180 @@ from rides.models import Ride, RollerCoasterStats
|
||||
from companies.models import Company, Manufacturer
|
||||
from reviews.models import Review
|
||||
from media.models import Photo
|
||||
from cities_light.models import Country, Region, City
|
||||
from django.contrib.auth.models import Permission
|
||||
import random
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
User = get_user_model()
|
||||
|
||||
# Park coordinates mapping
|
||||
PARK_COORDINATES = {
|
||||
"Walt Disney World Magic Kingdom": {
|
||||
"latitude": "28.418778",
|
||||
"longitude": "-81.581212",
|
||||
"street_address": "1180 Seven Seas Dr",
|
||||
"city": "Orlando",
|
||||
"state": "Florida",
|
||||
"postal_code": "32836"
|
||||
},
|
||||
"Cedar Point": {
|
||||
"latitude": "41.482207",
|
||||
"longitude": "-82.683523",
|
||||
"street_address": "1 Cedar Point Dr",
|
||||
"city": "Sandusky",
|
||||
"state": "Ohio",
|
||||
"postal_code": "44870"
|
||||
},
|
||||
"Universal's Islands of Adventure": {
|
||||
"latitude": "28.470891",
|
||||
"longitude": "-81.471756",
|
||||
"street_address": "6000 Universal Blvd",
|
||||
"city": "Orlando",
|
||||
"state": "Florida",
|
||||
"postal_code": "32819"
|
||||
},
|
||||
"Alton Towers": {
|
||||
"latitude": "52.988889",
|
||||
"longitude": "-1.892778",
|
||||
"street_address": "Farley Ln",
|
||||
"city": "Alton",
|
||||
"state": "Staffordshire",
|
||||
"postal_code": "ST10 4DB"
|
||||
},
|
||||
"Europa-Park": {
|
||||
"latitude": "48.266031",
|
||||
"longitude": "7.722044",
|
||||
"street_address": "Europa-Park-Straße 2",
|
||||
"city": "Rust",
|
||||
"state": "Baden-Württemberg",
|
||||
"postal_code": "77977"
|
||||
}
|
||||
}
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = 'Seeds the database with initial data'
|
||||
help = "Seeds the database with initial data"
|
||||
|
||||
def handle(self, *args, **kwargs):
|
||||
self.stdout.write('Starting database seed...')
|
||||
|
||||
self.stdout.write("Starting database seed...")
|
||||
|
||||
# Clean up media directory
|
||||
self.stdout.write("Cleaning up media directory...")
|
||||
media_dir = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))), 'media')
|
||||
if os.path.exists(media_dir):
|
||||
for item in os.listdir(media_dir):
|
||||
if item != '__init__.py': # Preserve __init__.py
|
||||
item_path = os.path.join(media_dir, item)
|
||||
if os.path.isdir(item_path):
|
||||
shutil.rmtree(item_path)
|
||||
else:
|
||||
os.remove(item_path)
|
||||
|
||||
# Delete all existing data
|
||||
self.stdout.write("Deleting existing data...")
|
||||
User.objects.exclude(username='admin').delete() # Delete all users except admin
|
||||
Park.objects.all().delete()
|
||||
Ride.objects.all().delete()
|
||||
Company.objects.all().delete()
|
||||
Manufacturer.objects.all().delete()
|
||||
Review.objects.all().delete()
|
||||
Photo.objects.all().delete()
|
||||
|
||||
# Create users and set permissions
|
||||
self.create_users()
|
||||
self.setup_permissions()
|
||||
|
||||
|
||||
# Create parks and rides
|
||||
self.stdout.write('Creating parks and rides from seed data...')
|
||||
self.stdout.write("Creating parks and rides from seed data...")
|
||||
self.create_companies()
|
||||
self.create_manufacturers()
|
||||
self.create_parks_and_rides()
|
||||
|
||||
|
||||
# Create reviews
|
||||
self.stdout.write('Creating reviews...')
|
||||
self.stdout.write("Creating reviews...")
|
||||
self.create_reviews()
|
||||
|
||||
# Create top lists
|
||||
self.stdout.write('Creating top lists...')
|
||||
self.create_top_lists()
|
||||
|
||||
self.stdout.write('Successfully seeded database')
|
||||
|
||||
self.stdout.write("Successfully seeded database")
|
||||
|
||||
def setup_permissions(self):
|
||||
"""Set up photo permissions for all users"""
|
||||
self.stdout.write('Setting up photo permissions...')
|
||||
|
||||
self.stdout.write("Setting up photo permissions...")
|
||||
|
||||
# Get photo permissions
|
||||
photo_content_type = ContentType.objects.get_for_model(Photo)
|
||||
photo_permissions = Permission.objects.filter(content_type=photo_content_type)
|
||||
|
||||
|
||||
# Update all users
|
||||
users = User.objects.all()
|
||||
for user in users:
|
||||
for perm in photo_permissions:
|
||||
user.user_permissions.add(perm)
|
||||
user.save()
|
||||
self.stdout.write(f'Updated permissions for user: {user.username}')
|
||||
self.stdout.write(f"Updated permissions for user: {user.username}")
|
||||
|
||||
def create_users(self):
|
||||
self.stdout.write('Creating users...')
|
||||
|
||||
self.stdout.write("Creating users...")
|
||||
|
||||
# Try to get admin user
|
||||
try:
|
||||
admin = User.objects.get(username='admin')
|
||||
self.stdout.write('Admin user exists, updating permissions...')
|
||||
admin = User.objects.get(username="admin")
|
||||
self.stdout.write("Admin user exists, updating permissions...")
|
||||
except User.DoesNotExist:
|
||||
admin = User.objects.create_superuser('admin', 'admin@example.com', 'admin')
|
||||
self.stdout.write('Created admin user')
|
||||
|
||||
# Create regular users
|
||||
admin = User.objects.create_superuser("admin", "admin@example.com", "admin")
|
||||
self.stdout.write("Created admin user")
|
||||
|
||||
# Create 10 regular users
|
||||
usernames = [
|
||||
'destiny89', 'destiny97', 'thompsonchris', 'chriscohen', 'littlesharon',
|
||||
'wrichardson', 'christophermiles', 'jacksonangela', 'jennifer71', 'smithemily',
|
||||
'brandylong', 'milleranna', 'tlopez', 'fgriffith', 'mariah80',
|
||||
'kendradavis', 'rosarioashley', 'camposkaitlyn', 'lisaherrera', 'riveratiffany',
|
||||
'codytucker', 'cheyenne78', 'christinagreen', 'eric57', 'steinsuzanne',
|
||||
'david95', 'rstewart', 'josephhaynes', 'umedina', 'tylerbryant',
|
||||
'lcampos', 'shellyford', 'ksmith', 'qeverett', 'waguilar',
|
||||
'zbrowning', 'yalexander', 'wallacewilliam', 'bsuarez', 'ismith',
|
||||
'joyceosborne', 'garythomas', 'tlewis', 'robertgonzales', 'medinashannon',
|
||||
'yhanson', 'howellmorgan', 'taylorsusan', 'barnold', 'bryan20'
|
||||
"thrillseeker1",
|
||||
"coasterrider2",
|
||||
"parkfan3",
|
||||
"adventurer4",
|
||||
"funseeker5",
|
||||
"parkexplorer6",
|
||||
"ridetester7",
|
||||
"themepark8",
|
||||
"coaster9",
|
||||
"parkvisitor10"
|
||||
]
|
||||
|
||||
|
||||
for username in usernames:
|
||||
if not User.objects.filter(username=username).exists():
|
||||
User.objects.create_user(
|
||||
username=username,
|
||||
email=f'{username}@example.com',
|
||||
password='password123'
|
||||
)
|
||||
self.stdout.write(f'Created user: {username}')
|
||||
User.objects.create_user(
|
||||
username=username,
|
||||
email=f"{username}@example.com",
|
||||
[PASSWORD-REMOVED]",
|
||||
)
|
||||
self.stdout.write(f"Created user: {username}")
|
||||
|
||||
def create_companies(self):
|
||||
self.stdout.write('Creating companies...')
|
||||
|
||||
# Delete existing companies
|
||||
Company.objects.all().delete()
|
||||
self.stdout.write('Deleted existing companies')
|
||||
|
||||
self.stdout.write("Creating companies...")
|
||||
|
||||
companies = [
|
||||
'The Walt Disney Company',
|
||||
'Cedar Fair',
|
||||
'NBCUniversal',
|
||||
'Merlin Entertainments',
|
||||
'Mack Rides'
|
||||
"The Walt Disney Company",
|
||||
"Cedar Fair",
|
||||
"NBCUniversal",
|
||||
"Merlin Entertainments",
|
||||
"Mack Rides",
|
||||
]
|
||||
|
||||
|
||||
for name in companies:
|
||||
Company.objects.create(name=name)
|
||||
self.stdout.write(f'Created company: {name}')
|
||||
self.stdout.write(f"Created company: {name}")
|
||||
|
||||
def create_manufacturers(self):
|
||||
self.stdout.write('Creating manufacturers...')
|
||||
|
||||
# Delete existing manufacturers
|
||||
Manufacturer.objects.all().delete()
|
||||
self.stdout.write('Deleted existing manufacturers')
|
||||
|
||||
self.stdout.write("Creating manufacturers...")
|
||||
|
||||
manufacturers = [
|
||||
'Walt Disney Imagineering',
|
||||
'Bolliger & Mabillard',
|
||||
'Intamin',
|
||||
'Rocky Mountain Construction',
|
||||
'Vekoma',
|
||||
'Mack Rides',
|
||||
'Oceaneering International'
|
||||
"Walt Disney Imagineering",
|
||||
"Bolliger & Mabillard",
|
||||
"Intamin",
|
||||
"Rocky Mountain Construction",
|
||||
"Vekoma",
|
||||
"Mack Rides",
|
||||
"Oceaneering International",
|
||||
]
|
||||
|
||||
|
||||
for name in manufacturers:
|
||||
Manufacturer.objects.create(name=name)
|
||||
self.stdout.write(f'Created manufacturer: {name}')
|
||||
self.stdout.write(f"Created manufacturer: {name}")
|
||||
|
||||
def download_image(self, url):
|
||||
"""Download image from URL and return as Django File object"""
|
||||
@@ -145,98 +198,93 @@ class Command(BaseCommand):
|
||||
return None
|
||||
|
||||
def create_parks_and_rides(self):
|
||||
# Delete existing parks and rides
|
||||
Park.objects.all().delete()
|
||||
self.stdout.write('Deleted existing parks and rides')
|
||||
|
||||
# Load seed data
|
||||
with open(os.path.join(os.path.dirname(__file__), 'seed_data.json')) as f:
|
||||
with open(os.path.join(os.path.dirname(__file__), "seed_data.json")) as f:
|
||||
data = json.load(f)
|
||||
|
||||
country_map = {
|
||||
'US': 'United States',
|
||||
'GB': 'United Kingdom',
|
||||
'DE': 'Germany'
|
||||
}
|
||||
|
||||
for park_data in data['parks']:
|
||||
|
||||
for park_data in data["parks"]:
|
||||
try:
|
||||
country = Country.objects.get(code2=park_data['country'])
|
||||
|
||||
# Create park
|
||||
# Create park with location data
|
||||
park_coords = PARK_COORDINATES[park_data["name"]]
|
||||
park = Park.objects.create(
|
||||
name=park_data['name'],
|
||||
country=country,
|
||||
opening_date=park_data['opening_date'],
|
||||
status=park_data['status'],
|
||||
description=park_data['description'],
|
||||
website=park_data['website'],
|
||||
owner=Company.objects.get(name=park_data['owner']),
|
||||
size_acres=park_data['size_acres']
|
||||
name=park_data["name"],
|
||||
country=park_data["country"],
|
||||
opening_date=park_data["opening_date"],
|
||||
status=park_data["status"],
|
||||
description=park_data["description"],
|
||||
website=park_data["website"],
|
||||
owner=Company.objects.get(name=park_data["owner"]),
|
||||
size_acres=park_data["size_acres"],
|
||||
# Add location fields
|
||||
latitude=park_coords["latitude"],
|
||||
longitude=park_coords["longitude"],
|
||||
street_address=park_coords["street_address"],
|
||||
city=park_coords["city"],
|
||||
state=park_coords["state"],
|
||||
postal_code=park_coords["postal_code"]
|
||||
)
|
||||
|
||||
|
||||
# Add park photos
|
||||
for photo_url in park_data['photos']:
|
||||
for photo_url in park_data["photos"]:
|
||||
img_file = self.download_image(photo_url)
|
||||
if img_file:
|
||||
Photo.objects.create(
|
||||
image=img_file,
|
||||
content_type=ContentType.objects.get_for_model(park),
|
||||
object_id=park.id,
|
||||
is_primary=True # First photo is primary
|
||||
is_primary=True, # First photo is primary
|
||||
)
|
||||
|
||||
|
||||
# Create rides
|
||||
for ride_data in park_data['rides']:
|
||||
for ride_data in park_data["rides"]:
|
||||
ride = Ride.objects.create(
|
||||
name=ride_data['name'],
|
||||
name=ride_data["name"],
|
||||
park=park,
|
||||
category=ride_data['category'],
|
||||
opening_date=ride_data['opening_date'],
|
||||
status=ride_data['status'],
|
||||
manufacturer=Manufacturer.objects.get(name=ride_data['manufacturer']),
|
||||
description=ride_data['description']
|
||||
category=ride_data["category"],
|
||||
opening_date=ride_data["opening_date"],
|
||||
status=ride_data["status"],
|
||||
manufacturer=Manufacturer.objects.get(
|
||||
name=ride_data["manufacturer"]
|
||||
),
|
||||
description=ride_data["description"],
|
||||
)
|
||||
|
||||
|
||||
# Add ride photos
|
||||
for photo_url in ride_data['photos']:
|
||||
for photo_url in ride_data["photos"]:
|
||||
img_file = self.download_image(photo_url)
|
||||
if img_file:
|
||||
Photo.objects.create(
|
||||
image=img_file,
|
||||
content_type=ContentType.objects.get_for_model(ride),
|
||||
object_id=ride.id,
|
||||
is_primary=True # First photo is primary
|
||||
is_primary=True, # First photo is primary
|
||||
)
|
||||
|
||||
|
||||
# Add coaster stats if present
|
||||
if 'stats' in ride_data:
|
||||
if "stats" in ride_data:
|
||||
RollerCoasterStats.objects.create(
|
||||
ride=ride,
|
||||
height_ft=ride_data['stats']['height_ft'],
|
||||
length_ft=ride_data['stats']['length_ft'],
|
||||
speed_mph=ride_data['stats']['speed_mph'],
|
||||
inversions=ride_data['stats']['inversions'],
|
||||
ride_time_seconds=ride_data['stats']['ride_time_seconds']
|
||||
height_ft=ride_data["stats"]["height_ft"],
|
||||
length_ft=ride_data["stats"]["length_ft"],
|
||||
speed_mph=ride_data["stats"]["speed_mph"],
|
||||
inversions=ride_data["stats"]["inversions"],
|
||||
ride_time_seconds=ride_data["stats"]["ride_time_seconds"],
|
||||
)
|
||||
|
||||
self.stdout.write(f'Created park and rides: {park.name}')
|
||||
except Country.DoesNotExist:
|
||||
self.stdout.write(f'Country not found: {park_data["country"]}')
|
||||
|
||||
self.stdout.write(f"Created park and rides: {park.name}")
|
||||
|
||||
except Exception as e:
|
||||
self.stdout.write(self.style.ERROR(f"Error creating park {park_data['name']}: {str(e)}"))
|
||||
continue
|
||||
|
||||
def create_reviews(self):
|
||||
# Delete existing reviews
|
||||
Review.objects.all().delete()
|
||||
self.stdout.write('Deleted existing reviews')
|
||||
|
||||
users = list(User.objects.exclude(username='admin'))
|
||||
users = list(User.objects.exclude(username="admin"))
|
||||
parks = list(Park.objects.all())
|
||||
|
||||
|
||||
# Generate random dates within the last year
|
||||
today = datetime.now().date()
|
||||
one_year_ago = today - timedelta(days=365)
|
||||
|
||||
|
||||
for park in parks:
|
||||
# Create 3-5 reviews per park
|
||||
num_reviews = random.randint(3, 5)
|
||||
@@ -244,42 +292,14 @@ class Command(BaseCommand):
|
||||
# Generate random visit date
|
||||
days_offset = random.randint(0, 365)
|
||||
visit_date = one_year_ago + timedelta(days=days_offset)
|
||||
|
||||
|
||||
Review.objects.create(
|
||||
user=random.choice(users),
|
||||
content_type=ContentType.objects.get_for_model(park),
|
||||
object_id=park.id,
|
||||
title=f'Great experience at {park.name}',
|
||||
content='Lorem ipsum dolor sit amet, consectetur adipiscing elit.',
|
||||
title=f"Great experience at {park.name}",
|
||||
content="Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
|
||||
rating=random.randint(7, 10),
|
||||
visit_date=visit_date
|
||||
visit_date=visit_date,
|
||||
)
|
||||
self.stdout.write(f'Created reviews for {park.name}')
|
||||
|
||||
def create_top_lists(self):
|
||||
# Delete existing top lists
|
||||
# TopList.objects.all().delete()
|
||||
self.stdout.write('Deleted existing top lists')
|
||||
|
||||
users = list(User.objects.exclude(username='admin'))
|
||||
parks = list(Park.objects.all())
|
||||
|
||||
for i, user in enumerate(users, 1):
|
||||
# Create top list for every 10th user
|
||||
if i % 10 == 0:
|
||||
# top_list = TopList.objects.create(
|
||||
# user=user,
|
||||
# name=f"{user.username}'s Top Parks",
|
||||
# description='My favorite theme parks'
|
||||
# )
|
||||
|
||||
# Add 3-5 random parks
|
||||
# selected_parks = random.sample(parks, random.randint(3, 5))
|
||||
# for j, park in enumerate(selected_parks, 1):
|
||||
# TopListItem.objects.create(
|
||||
# top_list=top_list,
|
||||
# content_type=ContentType.objects.get_for_model(park),
|
||||
# object_id=park.id,
|
||||
# rank=j
|
||||
# )
|
||||
self.stdout.write(f'Created top lists for {i}/50 users')
|
||||
self.stdout.write(f"Created reviews for {park.name}")
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
# Generated by Django 5.1.2 on 2024-10-28 20:17
|
||||
|
||||
import django.db.models.deletion
|
||||
import simple_history.models
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
@@ -12,57 +8,32 @@ class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('companies', '0001_initial'),
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='HistoricalPark',
|
||||
fields=[
|
||||
('id', models.BigIntegerField(auto_created=True, blank=True, db_index=True, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=255)),
|
||||
('slug', models.SlugField(max_length=255)),
|
||||
('location', models.CharField(max_length=255)),
|
||||
('description', models.TextField(blank=True)),
|
||||
('status', models.CharField(choices=[('OPERATING', 'Operating'), ('CLOSED_TEMP', 'Temporarily Closed'), ('CLOSED_PERM', 'Permanently Closed'), ('UNDER_CONSTRUCTION', 'Under Construction'), ('DEMOLISHED', 'Demolished')], default='OPERATING', max_length=20)),
|
||||
('opening_date', models.DateField(blank=True, null=True)),
|
||||
('closing_date', models.DateField(blank=True, null=True)),
|
||||
('operating_season', models.CharField(blank=True, max_length=255)),
|
||||
('size_acres', models.DecimalField(blank=True, decimal_places=2, max_digits=10, null=True)),
|
||||
('website', models.URLField(blank=True)),
|
||||
('average_rating', models.DecimalField(blank=True, decimal_places=2, max_digits=3, null=True)),
|
||||
('created_at', models.DateTimeField(blank=True, editable=False, null=True)),
|
||||
('updated_at', models.DateTimeField(blank=True, editable=False)),
|
||||
('history_id', models.AutoField(primary_key=True, serialize=False)),
|
||||
('history_date', models.DateTimeField(db_index=True)),
|
||||
('history_change_reason', models.CharField(max_length=100, null=True)),
|
||||
('history_type', models.CharField(choices=[('+', 'Created'), ('~', 'Changed'), ('-', 'Deleted')], max_length=1)),
|
||||
('history_user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to=settings.AUTH_USER_MODEL)),
|
||||
('owner', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='companies.company')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'historical park',
|
||||
'verbose_name_plural': 'historical parks',
|
||||
'ordering': ('-history_date', '-history_id'),
|
||||
'get_latest_by': ('history_date', 'history_id'),
|
||||
},
|
||||
bases=(simple_history.models.HistoricalChanges, models.Model),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Park',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=255)),
|
||||
('slug', models.SlugField(max_length=255, unique=True)),
|
||||
('location', models.CharField(max_length=255)),
|
||||
('description', models.TextField(blank=True)),
|
||||
('status', models.CharField(choices=[('OPERATING', 'Operating'), ('CLOSED_TEMP', 'Temporarily Closed'), ('CLOSED_PERM', 'Permanently Closed'), ('UNDER_CONSTRUCTION', 'Under Construction'), ('DEMOLISHED', 'Demolished')], default='OPERATING', max_length=20)),
|
||||
('status', models.CharField(choices=[('OPERATING', 'Operating'), ('CLOSED_TEMP', 'Temporarily Closed'), ('CLOSED_PERM', 'Permanently Closed'), ('UNDER_CONSTRUCTION', 'Under Construction'), ('DEMOLISHED', 'Demolished'), ('RELOCATED', 'Relocated')], default='OPERATING', max_length=20)),
|
||||
('opening_date', models.DateField(blank=True, null=True)),
|
||||
('closing_date', models.DateField(blank=True, null=True)),
|
||||
('operating_season', models.CharField(blank=True, max_length=255)),
|
||||
('size_acres', models.DecimalField(blank=True, decimal_places=2, max_digits=10, null=True)),
|
||||
('website', models.URLField(blank=True)),
|
||||
('average_rating', models.DecimalField(blank=True, decimal_places=2, max_digits=3, null=True)),
|
||||
('total_rides', models.IntegerField(blank=True, null=True)),
|
||||
('total_roller_coasters', models.IntegerField(blank=True, null=True)),
|
||||
('latitude', models.DecimalField(blank=True, decimal_places=6, max_digits=9, null=True)),
|
||||
('longitude', models.DecimalField(blank=True, decimal_places=6, max_digits=9, null=True)),
|
||||
('street_address', models.CharField(blank=True, max_length=255)),
|
||||
('city', models.CharField(blank=True, max_length=255)),
|
||||
('state', models.CharField(blank=True, max_length=255)),
|
||||
('country', models.CharField(blank=True, max_length=255)),
|
||||
('postal_code', models.CharField(blank=True, max_length=20)),
|
||||
('created_at', models.DateTimeField(auto_now_add=True, null=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
('owner', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='parks', to='companies.company')),
|
||||
@@ -71,32 +42,6 @@ class Migration(migrations.Migration):
|
||||
'ordering': ['name'],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='HistoricalParkArea',
|
||||
fields=[
|
||||
('id', models.BigIntegerField(auto_created=True, blank=True, db_index=True, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=255)),
|
||||
('slug', models.SlugField(max_length=255)),
|
||||
('description', models.TextField(blank=True)),
|
||||
('opening_date', models.DateField(blank=True, null=True)),
|
||||
('closing_date', models.DateField(blank=True, null=True)),
|
||||
('created_at', models.DateTimeField(blank=True, editable=False, null=True)),
|
||||
('updated_at', models.DateTimeField(blank=True, editable=False)),
|
||||
('history_id', models.AutoField(primary_key=True, serialize=False)),
|
||||
('history_date', models.DateTimeField(db_index=True)),
|
||||
('history_change_reason', models.CharField(max_length=100, null=True)),
|
||||
('history_type', models.CharField(choices=[('+', 'Created'), ('~', 'Changed'), ('-', 'Deleted')], max_length=1)),
|
||||
('history_user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to=settings.AUTH_USER_MODEL)),
|
||||
('park', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='parks.park')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'historical park area',
|
||||
'verbose_name_plural': 'historical park areas',
|
||||
'ordering': ('-history_date', '-history_id'),
|
||||
'get_latest_by': ('history_date', 'history_id'),
|
||||
},
|
||||
bases=(simple_history.models.HistoricalChanges, models.Model),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='ParkArea',
|
||||
fields=[
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
# Generated manually
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('parks', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='park',
|
||||
name='country',
|
||||
field=models.CharField(max_length=2, default='US', help_text='Two-letter country code (ISO 3166-1 alpha-2)'),
|
||||
preserve_default=False,
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='historicalpark',
|
||||
name='country',
|
||||
field=models.CharField(max_length=2, default='US', help_text='Two-letter country code (ISO 3166-1 alpha-2)'),
|
||||
preserve_default=False,
|
||||
),
|
||||
]
|
||||
84
parks/migrations/0002_historicalpark_historicalparkarea.py
Normal file
84
parks/migrations/0002_historicalpark_historicalparkarea.py
Normal file
@@ -0,0 +1,84 @@
|
||||
# Generated by Django 5.1.2 on 2024-11-03 03:44
|
||||
|
||||
import django.db.models.deletion
|
||||
import simple_history.models
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('companies', '0004_add_total_parks'),
|
||||
('parks', '0001_initial'),
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='HistoricalPark',
|
||||
fields=[
|
||||
('id', models.BigIntegerField(auto_created=True, blank=True, db_index=True, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=255)),
|
||||
('slug', models.SlugField(max_length=255)),
|
||||
('description', models.TextField(blank=True)),
|
||||
('status', models.CharField(choices=[('OPERATING', 'Operating'), ('CLOSED_TEMP', 'Temporarily Closed'), ('CLOSED_PERM', 'Permanently Closed'), ('UNDER_CONSTRUCTION', 'Under Construction'), ('DEMOLISHED', 'Demolished'), ('RELOCATED', 'Relocated')], default='OPERATING', max_length=20)),
|
||||
('opening_date', models.DateField(blank=True, null=True)),
|
||||
('closing_date', models.DateField(blank=True, null=True)),
|
||||
('operating_season', models.CharField(blank=True, max_length=255)),
|
||||
('size_acres', models.DecimalField(blank=True, decimal_places=2, max_digits=10, null=True)),
|
||||
('website', models.URLField(blank=True)),
|
||||
('average_rating', models.DecimalField(blank=True, decimal_places=2, max_digits=3, null=True)),
|
||||
('total_rides', models.IntegerField(blank=True, null=True)),
|
||||
('total_roller_coasters', models.IntegerField(blank=True, null=True)),
|
||||
('latitude', models.DecimalField(blank=True, decimal_places=6, max_digits=9, null=True)),
|
||||
('longitude', models.DecimalField(blank=True, decimal_places=6, max_digits=9, null=True)),
|
||||
('street_address', models.CharField(blank=True, max_length=255)),
|
||||
('city', models.CharField(blank=True, max_length=255)),
|
||||
('state', models.CharField(blank=True, max_length=255)),
|
||||
('country', models.CharField(blank=True, max_length=255)),
|
||||
('postal_code', models.CharField(blank=True, max_length=20)),
|
||||
('created_at', models.DateTimeField(blank=True, editable=False, null=True)),
|
||||
('updated_at', models.DateTimeField(blank=True, editable=False)),
|
||||
('history_id', models.AutoField(primary_key=True, serialize=False)),
|
||||
('history_date', models.DateTimeField(db_index=True)),
|
||||
('history_change_reason', models.CharField(max_length=100, null=True)),
|
||||
('history_type', models.CharField(choices=[('+', 'Created'), ('~', 'Changed'), ('-', 'Deleted')], max_length=1)),
|
||||
('history_user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to=settings.AUTH_USER_MODEL)),
|
||||
('owner', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='companies.company')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'historical park',
|
||||
'verbose_name_plural': 'historical parks',
|
||||
'ordering': ('-history_date', '-history_id'),
|
||||
'get_latest_by': ('history_date', 'history_id'),
|
||||
},
|
||||
bases=(simple_history.models.HistoricalChanges, models.Model),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='HistoricalParkArea',
|
||||
fields=[
|
||||
('id', models.BigIntegerField(auto_created=True, blank=True, db_index=True, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=255)),
|
||||
('slug', models.SlugField(max_length=255)),
|
||||
('description', models.TextField(blank=True)),
|
||||
('opening_date', models.DateField(blank=True, null=True)),
|
||||
('closing_date', models.DateField(blank=True, null=True)),
|
||||
('created_at', models.DateTimeField(blank=True, editable=False, null=True)),
|
||||
('updated_at', models.DateTimeField(blank=True, editable=False)),
|
||||
('history_id', models.AutoField(primary_key=True, serialize=False)),
|
||||
('history_date', models.DateTimeField(db_index=True)),
|
||||
('history_change_reason', models.CharField(max_length=100, null=True)),
|
||||
('history_type', models.CharField(choices=[('+', 'Created'), ('~', 'Changed'), ('-', 'Deleted')], max_length=1)),
|
||||
('history_user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to=settings.AUTH_USER_MODEL)),
|
||||
('park', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='parks.park')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'historical park area',
|
||||
'verbose_name_plural': 'historical park areas',
|
||||
'ordering': ('-history_date', '-history_id'),
|
||||
'get_latest_by': ('history_date', 'history_id'),
|
||||
},
|
||||
bases=(simple_history.models.HistoricalChanges, models.Model),
|
||||
),
|
||||
]
|
||||
@@ -1,45 +0,0 @@
|
||||
# Generated by Django 5.1.2 on 2024-10-30 01:20
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("parks", "0002_add_country_field"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name="historicalpark",
|
||||
name="status",
|
||||
field=models.CharField(
|
||||
choices=[
|
||||
("OPERATING", "Operating"),
|
||||
("CLOSED_TEMP", "Temporarily Closed"),
|
||||
("CLOSED_PERM", "Permanently Closed"),
|
||||
("UNDER_CONSTRUCTION", "Under Construction"),
|
||||
("DEMOLISHED", "Demolished"),
|
||||
("RELOCATED", "Relocated"),
|
||||
],
|
||||
default="OPERATING",
|
||||
max_length=20,
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="park",
|
||||
name="status",
|
||||
field=models.CharField(
|
||||
choices=[
|
||||
("OPERATING", "Operating"),
|
||||
("CLOSED_TEMP", "Temporarily Closed"),
|
||||
("CLOSED_PERM", "Permanently Closed"),
|
||||
("UNDER_CONSTRUCTION", "Under Construction"),
|
||||
("DEMOLISHED", "Demolished"),
|
||||
("RELOCATED", "Relocated"),
|
||||
],
|
||||
default="OPERATING",
|
||||
max_length=20,
|
||||
),
|
||||
),
|
||||
]
|
||||
55
parks/migrations/0003_update_coordinate_fields.py
Normal file
55
parks/migrations/0003_update_coordinate_fields.py
Normal file
@@ -0,0 +1,55 @@
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('parks', '0002_historicalpark_historicalparkarea'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='park',
|
||||
name='latitude',
|
||||
field=models.DecimalField(
|
||||
blank=True,
|
||||
decimal_places=6,
|
||||
max_digits=9, # Changed to 9 to handle -90.000000 to 90.000000
|
||||
null=True,
|
||||
help_text='Latitude coordinate (-90 to 90)',
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='park',
|
||||
name='longitude',
|
||||
field=models.DecimalField(
|
||||
blank=True,
|
||||
decimal_places=6,
|
||||
max_digits=10, # Changed to 10 to handle -180.000000 to 180.000000
|
||||
null=True,
|
||||
help_text='Longitude coordinate (-180 to 180)',
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='historicalpark',
|
||||
name='latitude',
|
||||
field=models.DecimalField(
|
||||
blank=True,
|
||||
decimal_places=6,
|
||||
max_digits=9, # Changed to 9 to handle -90.000000 to 90.000000
|
||||
null=True,
|
||||
help_text='Latitude coordinate (-90 to 90)',
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='historicalpark',
|
||||
name='longitude',
|
||||
field=models.DecimalField(
|
||||
blank=True,
|
||||
decimal_places=6,
|
||||
max_digits=10, # Changed to 10 to handle -180.000000 to 180.000000
|
||||
null=True,
|
||||
help_text='Longitude coordinate (-180 to 180)',
|
||||
),
|
||||
),
|
||||
]
|
||||
101
parks/migrations/0004_add_coordinate_validators.py
Normal file
101
parks/migrations/0004_add_coordinate_validators.py
Normal file
@@ -0,0 +1,101 @@
|
||||
from django.db import migrations, models
|
||||
from django.core.validators import MinValueValidator, MaxValueValidator
|
||||
from decimal import Decimal
|
||||
from django.core.exceptions import ValidationError
|
||||
|
||||
|
||||
def validate_coordinate_digits(value, max_digits):
|
||||
"""Validate total number of digits in a coordinate value"""
|
||||
if value is not None:
|
||||
# Convert to string and remove decimal point and sign
|
||||
str_val = str(abs(value)).replace('.', '')
|
||||
# Remove trailing zeros after decimal point
|
||||
str_val = str_val.rstrip('0')
|
||||
if len(str_val) > max_digits:
|
||||
raise ValidationError(
|
||||
f'Ensure that there are no more than {max_digits} digits in total.'
|
||||
)
|
||||
|
||||
|
||||
def validate_latitude_digits(value):
|
||||
"""Validate total number of digits in latitude"""
|
||||
validate_coordinate_digits(value, 9)
|
||||
|
||||
|
||||
def validate_longitude_digits(value):
|
||||
"""Validate total number of digits in longitude"""
|
||||
validate_coordinate_digits(value, 10)
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('parks', '0003_update_coordinate_fields'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='park',
|
||||
name='latitude',
|
||||
field=models.DecimalField(
|
||||
blank=True,
|
||||
decimal_places=6,
|
||||
help_text='Latitude coordinate (-90 to 90)',
|
||||
max_digits=9,
|
||||
null=True,
|
||||
validators=[
|
||||
MinValueValidator(Decimal('-90')),
|
||||
MaxValueValidator(Decimal('90')),
|
||||
validate_latitude_digits,
|
||||
],
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='park',
|
||||
name='longitude',
|
||||
field=models.DecimalField(
|
||||
blank=True,
|
||||
decimal_places=6,
|
||||
help_text='Longitude coordinate (-180 to 180)',
|
||||
max_digits=10,
|
||||
null=True,
|
||||
validators=[
|
||||
MinValueValidator(Decimal('-180')),
|
||||
MaxValueValidator(Decimal('180')),
|
||||
validate_longitude_digits,
|
||||
],
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='historicalpark',
|
||||
name='latitude',
|
||||
field=models.DecimalField(
|
||||
blank=True,
|
||||
decimal_places=6,
|
||||
help_text='Latitude coordinate (-90 to 90)',
|
||||
max_digits=9,
|
||||
null=True,
|
||||
validators=[
|
||||
MinValueValidator(Decimal('-90')),
|
||||
MaxValueValidator(Decimal('90')),
|
||||
validate_latitude_digits,
|
||||
],
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='historicalpark',
|
||||
name='longitude',
|
||||
field=models.DecimalField(
|
||||
blank=True,
|
||||
decimal_places=6,
|
||||
help_text='Longitude coordinate (-180 to 180)',
|
||||
max_digits=10,
|
||||
null=True,
|
||||
validators=[
|
||||
MinValueValidator(Decimal('-180')),
|
||||
MaxValueValidator(Decimal('180')),
|
||||
validate_longitude_digits,
|
||||
],
|
||||
),
|
||||
),
|
||||
]
|
||||
@@ -1,37 +0,0 @@
|
||||
# Generated by Django 5.1.2 on 2024-10-30 23:15
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("parks", "0003_alter_historicalpark_status_alter_park_status"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="historicalpark",
|
||||
name="city",
|
||||
field=models.CharField(blank=True, max_length=255),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="historicalpark",
|
||||
name="state",
|
||||
field=models.CharField(
|
||||
blank=True, help_text="State/Province/Region", max_length=255
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="park",
|
||||
name="city",
|
||||
field=models.CharField(blank=True, max_length=255),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="park",
|
||||
name="state",
|
||||
field=models.CharField(
|
||||
blank=True, help_text="State/Province/Region", max_length=255
|
||||
),
|
||||
),
|
||||
]
|
||||
58
parks/migrations/0005_normalize_coordinates.py
Normal file
58
parks/migrations/0005_normalize_coordinates.py
Normal file
@@ -0,0 +1,58 @@
|
||||
from django.db import migrations
|
||||
from decimal import Decimal, ROUND_DOWN
|
||||
|
||||
|
||||
def normalize_coordinate(value, max_digits, decimal_places):
|
||||
"""Normalize coordinate to have exactly 6 decimal places"""
|
||||
try:
|
||||
if value is None:
|
||||
return None
|
||||
|
||||
# Convert to Decimal for precise handling
|
||||
value = Decimal(str(value))
|
||||
# Round to exactly 6 decimal places
|
||||
value = value.quantize(Decimal('0.000001'), rounding=ROUND_DOWN)
|
||||
|
||||
return value
|
||||
except (TypeError, ValueError):
|
||||
return None
|
||||
|
||||
|
||||
def normalize_existing_coordinates(apps, schema_editor):
|
||||
Park = apps.get_model('parks', 'Park')
|
||||
HistoricalPark = apps.get_model('parks', 'HistoricalPark')
|
||||
|
||||
# Normalize coordinates in current parks
|
||||
for park in Park.objects.all():
|
||||
if park.latitude is not None:
|
||||
park.latitude = normalize_coordinate(park.latitude, 9, 6)
|
||||
if park.longitude is not None:
|
||||
park.longitude = normalize_coordinate(park.longitude, 10, 6)
|
||||
park.save()
|
||||
|
||||
# Normalize coordinates in historical records
|
||||
for record in HistoricalPark.objects.all():
|
||||
if record.latitude is not None:
|
||||
record.latitude = normalize_coordinate(record.latitude, 9, 6)
|
||||
if record.longitude is not None:
|
||||
record.longitude = normalize_coordinate(record.longitude, 10, 6)
|
||||
record.save()
|
||||
|
||||
|
||||
def reverse_normalize_coordinates(apps, schema_editor):
|
||||
# No need to reverse normalization as it would only reduce precision
|
||||
pass
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('parks', '0004_add_coordinate_validators'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(
|
||||
normalize_existing_coordinates,
|
||||
reverse_normalize_coordinates
|
||||
),
|
||||
]
|
||||
@@ -1,23 +0,0 @@
|
||||
# Generated by Django 5.1.2 on 2024-10-30 23:46
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("parks", "0004_historicalpark_city_historicalpark_state_park_city_and_more"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name="historicalpark",
|
||||
name="country",
|
||||
field=models.CharField(help_text="Country name", max_length=255),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="park",
|
||||
name="country",
|
||||
field=models.CharField(help_text="Country name", max_length=255),
|
||||
),
|
||||
]
|
||||
@@ -1,125 +0,0 @@
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
def forwards_func(apps, schema_editor):
|
||||
# Get the historical models
|
||||
Park = apps.get_model("parks", "Park")
|
||||
Country = apps.get_model("cities_light", "Country")
|
||||
Region = apps.get_model("cities_light", "Region")
|
||||
City = apps.get_model("cities_light", "City")
|
||||
|
||||
# Create default country for existing parks
|
||||
default_country, _ = Country.objects.get_or_create(
|
||||
name='Unknown',
|
||||
name_ascii='Unknown',
|
||||
slug='unknown',
|
||||
code2='XX'
|
||||
)
|
||||
|
||||
# Store old values
|
||||
parks_data = []
|
||||
for park in Park.objects.all():
|
||||
parks_data.append({
|
||||
'id': park.id,
|
||||
'old_country': park.country,
|
||||
'old_state': park.state,
|
||||
'location': park.location
|
||||
})
|
||||
|
||||
# Remove old fields first
|
||||
Park._meta.get_field('country').null = True
|
||||
Park._meta.get_field('state').null = True
|
||||
Park.objects.all().update(country=None, state=None)
|
||||
|
||||
# Now update with new values
|
||||
for data in parks_data:
|
||||
park = Park.objects.get(id=data['id'])
|
||||
park.country_id = default_country.id
|
||||
park.save()
|
||||
|
||||
def reverse_func(apps, schema_editor):
|
||||
pass
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('cities_light', '0011_alter_city_country_alter_city_region_and_more'),
|
||||
('parks', '0005_update_country_field_length'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
# First make the fields nullable
|
||||
migrations.AlterField(
|
||||
model_name='park',
|
||||
name='country',
|
||||
field=models.CharField(max_length=255, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='historicalpark',
|
||||
name='country',
|
||||
field=models.CharField(max_length=255, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='park',
|
||||
name='state',
|
||||
field=models.CharField(max_length=255, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='historicalpark',
|
||||
name='state',
|
||||
field=models.CharField(max_length=255, null=True),
|
||||
),
|
||||
|
||||
# Run the data migration
|
||||
migrations.RunPython(forwards_func, reverse_func),
|
||||
|
||||
# Remove old fields
|
||||
migrations.RemoveField(
|
||||
model_name='park',
|
||||
name='state',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='historicalpark',
|
||||
name='state',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='park',
|
||||
name='country',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='historicalpark',
|
||||
name='country',
|
||||
),
|
||||
|
||||
# Add new fields
|
||||
migrations.AddField(
|
||||
model_name='park',
|
||||
name='country',
|
||||
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, to='cities_light.country'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='park',
|
||||
name='region',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='cities_light.region'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='park',
|
||||
name='city',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='cities_light.city'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='historicalpark',
|
||||
name='country',
|
||||
field=models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='cities_light.country'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='historicalpark',
|
||||
name='region',
|
||||
field=models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='cities_light.region'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='historicalpark',
|
||||
name='city',
|
||||
field=models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='cities_light.city'),
|
||||
),
|
||||
]
|
||||
@@ -1,17 +0,0 @@
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('cities_light', '0001_initial'),
|
||||
('parks', '0006_update_location_fields_to_cities_light'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='historicalpark',
|
||||
name='city',
|
||||
field=models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='cities_light.city'),
|
||||
),
|
||||
]
|
||||
@@ -1,43 +0,0 @@
|
||||
from django.db import migrations
|
||||
from django.db.models import Q
|
||||
|
||||
def fix_historical_park_data(apps, schema_editor):
|
||||
HistoricalPark = apps.get_model('parks', 'HistoricalPark')
|
||||
Park = apps.get_model('parks', 'Park')
|
||||
Country = apps.get_model('cities_light', 'Country')
|
||||
|
||||
# Get a default country (create one if none exists)
|
||||
default_country = Country.objects.first()
|
||||
if not default_country:
|
||||
default_country = Country.objects.create(name='Unknown')
|
||||
|
||||
# Fix all historical records with null country
|
||||
historical_records = HistoricalPark.objects.filter(
|
||||
Q(country__isnull=True) | Q(location__isnull=True)
|
||||
)
|
||||
|
||||
for record in historical_records:
|
||||
try:
|
||||
# Try to get the current park's country
|
||||
park = Park.objects.get(id=record.id)
|
||||
record.country = park.country
|
||||
record.location = park.location or f"{park.country.name}"
|
||||
except Park.DoesNotExist:
|
||||
# If park doesn't exist, use default country
|
||||
record.country = default_country
|
||||
record.location = default_country.name
|
||||
|
||||
record.save()
|
||||
|
||||
def reverse_func(apps, schema_editor):
|
||||
pass
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
('parks', '0007_fix_historical_park_city_null'),
|
||||
('cities_light', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(fix_historical_park_data, reverse_func),
|
||||
]
|
||||
@@ -1,33 +0,0 @@
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('cities_light', '0001_initial'),
|
||||
('parks', '0008_fix_historical_park_data'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='historicalpark',
|
||||
name='city',
|
||||
field=models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='cities_light.city'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='historicalpark',
|
||||
name='region',
|
||||
field=models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='cities_light.region'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='historicalpark',
|
||||
name='location',
|
||||
field=models.CharField(max_length=255, default=''),
|
||||
preserve_default=False,
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='historicalpark',
|
||||
name='country',
|
||||
field=models.ForeignKey(db_constraint=False, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='cities_light.country'),
|
||||
),
|
||||
]
|
||||
@@ -1,34 +0,0 @@
|
||||
# Generated by Django 5.1.2 on 2024-10-31 20:15
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("cities_light", "0011_alter_city_country_alter_city_region_and_more"),
|
||||
("parks", "0009_fix_historical_park_fields"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name="historicalpark",
|
||||
name="country",
|
||||
field=models.ForeignKey(
|
||||
blank=True,
|
||||
db_constraint=False,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.DO_NOTHING,
|
||||
related_name="+",
|
||||
to="cities_light.country",
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="park",
|
||||
name="country",
|
||||
field=models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.PROTECT, to="cities_light.country"
|
||||
),
|
||||
),
|
||||
]
|
||||
@@ -1,72 +0,0 @@
|
||||
from django.db import migrations, models
|
||||
from django.db.models import Q
|
||||
import django.db.models.deletion
|
||||
|
||||
def fix_historical_records(apps, schema_editor):
|
||||
HistoricalPark = apps.get_model('parks', 'HistoricalPark')
|
||||
Park = apps.get_model('parks', 'Park')
|
||||
Country = apps.get_model('cities_light', 'Country')
|
||||
|
||||
# Get or create a default country
|
||||
default_country = Country.objects.first()
|
||||
if not default_country:
|
||||
default_country = Country.objects.create(name='Unknown')
|
||||
|
||||
# Update all historical records with null values
|
||||
for record in HistoricalPark.objects.filter(Q(country__isnull=True) | Q(location__isnull=True)):
|
||||
try:
|
||||
park = Park.objects.get(id=record.id)
|
||||
record.country = park.country
|
||||
record.location = park.location
|
||||
except Park.DoesNotExist:
|
||||
record.country = default_country
|
||||
record.location = default_country.name
|
||||
record.save()
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
atomic = False # Allow long-running operations
|
||||
|
||||
dependencies = [
|
||||
('parks', '0009_fix_historical_park_fields'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
# First, make sure all fields allow null temporarily
|
||||
migrations.AlterField(
|
||||
model_name='historicalpark',
|
||||
name='country',
|
||||
field=models.ForeignKey(
|
||||
blank=True,
|
||||
db_constraint=False,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.DO_NOTHING,
|
||||
related_name='+',
|
||||
to='cities_light.country'
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='historicalpark',
|
||||
name='location',
|
||||
field=models.CharField(max_length=255, null=True, blank=True),
|
||||
),
|
||||
|
||||
# Fix the data
|
||||
migrations.RunPython(fix_historical_records),
|
||||
|
||||
# Now make the fields non-nullable
|
||||
migrations.AlterField(
|
||||
model_name='historicalpark',
|
||||
name='country',
|
||||
field=models.ForeignKey(
|
||||
db_constraint=False,
|
||||
on_delete=django.db.models.deletion.DO_NOTHING,
|
||||
related_name='+',
|
||||
to='cities_light.country'
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='historicalpark',
|
||||
name='location',
|
||||
field=models.CharField(max_length=255),
|
||||
),
|
||||
]
|
||||
@@ -1,52 +0,0 @@
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
('cities_light', '0001_initial'),
|
||||
('parks', '0010_fix_historical_records'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='historicalpark',
|
||||
name='city',
|
||||
field=models.ForeignKey(
|
||||
blank=True,
|
||||
db_constraint=False,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.DO_NOTHING,
|
||||
related_name='+',
|
||||
to='cities_light.city'
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='historicalpark',
|
||||
name='region',
|
||||
field=models.ForeignKey(
|
||||
blank=True,
|
||||
db_constraint=False,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.DO_NOTHING,
|
||||
related_name='+',
|
||||
to='cities_light.region'
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='historicalpark',
|
||||
name='country',
|
||||
field=models.ForeignKey(
|
||||
blank=True,
|
||||
db_constraint=False,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.DO_NOTHING,
|
||||
related_name='+',
|
||||
to='cities_light.country'
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='historicalpark',
|
||||
name='location',
|
||||
field=models.CharField(max_length=255, blank=True, null=True),
|
||||
),
|
||||
]
|
||||
@@ -1,13 +0,0 @@
|
||||
# Generated by Django 5.1.2 on 2024-10-31 20:17
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("parks", "0010_alter_historicalpark_country_alter_park_country"),
|
||||
("parks", "0010_fix_historical_records"),
|
||||
]
|
||||
|
||||
operations = []
|
||||
@@ -1,13 +0,0 @@
|
||||
# Generated by Django 5.1.2 on 2024-10-31 20:35
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("parks", "0011_alter_historicalpark_fields"),
|
||||
("parks", "0011_merge_20241031_1617"),
|
||||
]
|
||||
|
||||
operations = []
|
||||
@@ -1,67 +0,0 @@
|
||||
from django.db import migrations
|
||||
|
||||
def fix_null_locations(apps, schema_editor):
|
||||
Park = apps.get_model('parks', 'Park')
|
||||
Country = apps.get_model('cities_light', 'Country')
|
||||
Region = apps.get_model('cities_light', 'Region')
|
||||
City = apps.get_model('cities_light', 'City')
|
||||
|
||||
# Get or create default locations
|
||||
default_country = Country.objects.first()
|
||||
if not default_country:
|
||||
default_country = Country.objects.create(
|
||||
name='Unknown',
|
||||
name_ascii='Unknown',
|
||||
slug='unknown',
|
||||
geoname_id=0,
|
||||
alternate_names='',
|
||||
search_names='Unknown'
|
||||
)
|
||||
|
||||
default_region = Region.objects.filter(country=default_country).first()
|
||||
if not default_region:
|
||||
default_region = Region.objects.create(
|
||||
name='Unknown',
|
||||
name_ascii='Unknown',
|
||||
slug='unknown',
|
||||
geoname_id=0,
|
||||
alternate_names='',
|
||||
country=default_country,
|
||||
display_name='Unknown',
|
||||
search_names='Unknown'
|
||||
)
|
||||
|
||||
default_city = City.objects.filter(region=default_region).first()
|
||||
if not default_city:
|
||||
default_city = City.objects.create(
|
||||
name='Unknown',
|
||||
name_ascii='Unknown',
|
||||
slug='unknown',
|
||||
geoname_id=0,
|
||||
alternate_names='',
|
||||
region=default_region,
|
||||
country=default_country,
|
||||
display_name='Unknown',
|
||||
search_names='Unknown',
|
||||
latitude=0,
|
||||
longitude=0,
|
||||
population=0
|
||||
)
|
||||
|
||||
# Update parks with null locations
|
||||
for park in Park.objects.filter(country__isnull=True):
|
||||
park.country = default_country
|
||||
park.region = default_region
|
||||
park.city = default_city
|
||||
park.location = 'Unknown, Unknown, Unknown'
|
||||
park.save()
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
('parks', '0012_merge_20241031_1635'),
|
||||
('cities_light', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(fix_null_locations, reverse_code=migrations.RunPython.noop),
|
||||
]
|
||||
@@ -1,52 +0,0 @@
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
('cities_light', '0001_initial'),
|
||||
('parks', '0013_fix_null_locations'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='historicalpark',
|
||||
name='city',
|
||||
field=models.ForeignKey(
|
||||
blank=True,
|
||||
db_constraint=False,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.DO_NOTHING,
|
||||
related_name='+',
|
||||
to='cities_light.city'
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='historicalpark',
|
||||
name='region',
|
||||
field=models.ForeignKey(
|
||||
blank=True,
|
||||
db_constraint=False,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.DO_NOTHING,
|
||||
related_name='+',
|
||||
to='cities_light.region'
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='historicalpark',
|
||||
name='country',
|
||||
field=models.ForeignKey(
|
||||
blank=True,
|
||||
db_constraint=False,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.DO_NOTHING,
|
||||
related_name='+',
|
||||
to='cities_light.country'
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='historicalpark',
|
||||
name='location',
|
||||
field=models.CharField(max_length=255, blank=True, null=True),
|
||||
),
|
||||
]
|
||||
@@ -1,16 +0,0 @@
|
||||
from django.db import migrations
|
||||
|
||||
def fix_historical_records(apps, schema_editor):
|
||||
HistoricalPark = apps.get_model('parks', 'HistoricalPark')
|
||||
# Update any historical records that might have issues
|
||||
HistoricalPark.objects.filter(city__isnull=True).update(city=None)
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('parks', '0014_alter_location_fields'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(fix_historical_records, migrations.RunPython.noop),
|
||||
]
|
||||
@@ -1,22 +0,0 @@
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('cities_light', '0011_alter_city_country_alter_city_region_and_more'),
|
||||
('parks', '0015_fix_historical_park_city_constraint'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='historicalpark',
|
||||
name='city',
|
||||
field=models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='cities_light.city'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='historicalpark',
|
||||
name='region',
|
||||
field=models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='cities_light.region'),
|
||||
),
|
||||
]
|
||||
@@ -1,22 +0,0 @@
|
||||
from django.db import migrations
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('parks', '0016_alter_historicalpark_city_nullable'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunSQL(
|
||||
# Make the city column nullable
|
||||
sql='ALTER TABLE parks_historicalpark ALTER COLUMN city DROP NOT NULL;',
|
||||
# Reverse operation if needed
|
||||
reverse_sql='ALTER TABLE parks_historicalpark ALTER COLUMN city SET NOT NULL;'
|
||||
),
|
||||
migrations.RunSQL(
|
||||
# Make the city_id column nullable
|
||||
sql='ALTER TABLE parks_historicalpark ALTER COLUMN city_id DROP NOT NULL;',
|
||||
# Reverse operation if needed
|
||||
reverse_sql='ALTER TABLE parks_historicalpark ALTER COLUMN city_id SET NOT NULL;'
|
||||
),
|
||||
]
|
||||
@@ -1,48 +0,0 @@
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('cities_light', '0011_alter_city_country_alter_city_region_and_more'),
|
||||
('parks', '0017_fix_historicalpark_city_column'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='historicalpark',
|
||||
name='country',
|
||||
field=models.ForeignKey(
|
||||
blank=False,
|
||||
db_constraint=False,
|
||||
null=False,
|
||||
on_delete=django.db.models.deletion.DO_NOTHING,
|
||||
related_name='+',
|
||||
to='cities_light.country'
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='historicalpark',
|
||||
name='region',
|
||||
field=models.ForeignKey(
|
||||
blank=True,
|
||||
db_constraint=False,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.DO_NOTHING,
|
||||
related_name='+',
|
||||
to='cities_light.region'
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='historicalpark',
|
||||
name='city',
|
||||
field=models.ForeignKey(
|
||||
blank=True,
|
||||
db_constraint=False,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.DO_NOTHING,
|
||||
related_name='+',
|
||||
to='cities_light.city'
|
||||
),
|
||||
),
|
||||
]
|
||||
@@ -1,15 +0,0 @@
|
||||
from django.db import migrations
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('parks', '0018_fix_historicalpark_location_fields'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunSQL(
|
||||
# Make region_id nullable
|
||||
sql='ALTER TABLE parks_historicalpark ALTER COLUMN region_id DROP NOT NULL;',
|
||||
reverse_sql='ALTER TABLE parks_historicalpark ALTER COLUMN region_id SET NOT NULL;'
|
||||
),
|
||||
]
|
||||
@@ -1,16 +0,0 @@
|
||||
from django.db import migrations
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('parks', '0019_fix_historicalpark_region_constraint'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunSQL(
|
||||
# Remove the redundant city text column
|
||||
sql='ALTER TABLE parks_historicalpark DROP COLUMN IF EXISTS city;',
|
||||
# Recreate the column if needed (reverse migration)
|
||||
reverse_sql='ALTER TABLE parks_historicalpark ADD COLUMN city character varying(255);'
|
||||
),
|
||||
]
|
||||
@@ -1,32 +0,0 @@
|
||||
# Generated by Django 5.1.2 on 2024-11-01 00:24
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("cities_light", "0011_alter_city_country_alter_city_region_and_more"),
|
||||
("parks", "0020_remove_historicalpark_city_text"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name="historicalpark",
|
||||
name="country",
|
||||
field=models.ForeignKey(
|
||||
blank=True,
|
||||
db_constraint=False,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.DO_NOTHING,
|
||||
related_name="+",
|
||||
to="cities_light.country",
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="park",
|
||||
name="location",
|
||||
field=models.CharField(blank=True, max_length=255, null=True),
|
||||
),
|
||||
]
|
||||
Binary file not shown.
167
parks/models.py
167
parks/models.py
@@ -1,8 +1,55 @@
|
||||
from django.db import models
|
||||
from django.contrib.contenttypes.fields import GenericRelation
|
||||
from django.urls import reverse
|
||||
from django.utils.text import slugify
|
||||
from django.contrib.contenttypes.fields import GenericRelation
|
||||
from django.core.validators import MinValueValidator, MaxValueValidator
|
||||
from django.core.exceptions import ValidationError
|
||||
from decimal import Decimal, ROUND_DOWN, InvalidOperation
|
||||
from simple_history.models import HistoricalRecords
|
||||
from cities_light.models import Country, Region, City
|
||||
|
||||
from companies.models import Company
|
||||
from media.models import Photo
|
||||
|
||||
|
||||
def normalize_coordinate(value, max_digits, decimal_places):
|
||||
"""Normalize coordinate to have at most max_digits total digits and decimal_places decimal places"""
|
||||
try:
|
||||
if value is None:
|
||||
return None
|
||||
|
||||
# Convert to Decimal for precise handling
|
||||
value = Decimal(str(value))
|
||||
# Round to specified decimal places
|
||||
value = Decimal(value.quantize(Decimal('0.' + '0' * decimal_places), rounding=ROUND_DOWN))
|
||||
|
||||
return value
|
||||
except (TypeError, ValueError, InvalidOperation):
|
||||
return None
|
||||
|
||||
|
||||
def validate_coordinate_digits(value, max_digits, decimal_places):
|
||||
"""Validate total number of digits in a coordinate value"""
|
||||
if value is not None:
|
||||
try:
|
||||
# Convert to Decimal for precise handling
|
||||
value = Decimal(str(value))
|
||||
# Round to exactly 6 decimal places
|
||||
value = value.quantize(Decimal('0.000001'), rounding=ROUND_DOWN)
|
||||
return value
|
||||
except (InvalidOperation, TypeError):
|
||||
raise ValidationError('Invalid coordinate value.')
|
||||
return value
|
||||
|
||||
|
||||
def validate_latitude_digits(value):
|
||||
"""Validate total number of digits in latitude"""
|
||||
return validate_coordinate_digits(value, 9, 6)
|
||||
|
||||
|
||||
def validate_longitude_digits(value):
|
||||
"""Validate total number of digits in longitude"""
|
||||
return validate_coordinate_digits(value, 10, 6)
|
||||
|
||||
|
||||
class Park(models.Model):
|
||||
STATUS_CHOICES = [
|
||||
@@ -16,23 +63,45 @@ class Park(models.Model):
|
||||
|
||||
name = models.CharField(max_length=255)
|
||||
slug = models.SlugField(max_length=255, unique=True)
|
||||
location = models.CharField(max_length=255, blank=True, null=True) # Made nullable
|
||||
country = models.ForeignKey(Country, on_delete=models.PROTECT)
|
||||
region = models.ForeignKey(Region, on_delete=models.PROTECT, null=True, blank=True)
|
||||
city = models.ForeignKey(City, on_delete=models.PROTECT, null=True, blank=True)
|
||||
description = models.TextField(blank=True)
|
||||
owner = models.ForeignKey(
|
||||
'companies.Company',
|
||||
on_delete=models.SET_NULL,
|
||||
related_name='parks',
|
||||
null=True,
|
||||
blank=True
|
||||
)
|
||||
status = models.CharField(
|
||||
max_length=20,
|
||||
choices=STATUS_CHOICES,
|
||||
default='OPERATING'
|
||||
)
|
||||
|
||||
# Location fields
|
||||
latitude = models.DecimalField(
|
||||
max_digits=9,
|
||||
decimal_places=6,
|
||||
null=True,
|
||||
blank=True,
|
||||
help_text='Latitude coordinate (-90 to 90)',
|
||||
validators=[
|
||||
MinValueValidator(Decimal('-90')),
|
||||
MaxValueValidator(Decimal('90')),
|
||||
validate_latitude_digits,
|
||||
]
|
||||
)
|
||||
longitude = models.DecimalField(
|
||||
max_digits=10,
|
||||
decimal_places=6,
|
||||
null=True,
|
||||
blank=True,
|
||||
help_text='Longitude coordinate (-180 to 180)',
|
||||
validators=[
|
||||
MinValueValidator(Decimal('-180')),
|
||||
MaxValueValidator(Decimal('180')),
|
||||
validate_longitude_digits,
|
||||
]
|
||||
)
|
||||
street_address = models.CharField(max_length=255, blank=True)
|
||||
city = models.CharField(max_length=255, blank=True)
|
||||
state = models.CharField(max_length=255, blank=True)
|
||||
country = models.CharField(max_length=255, blank=True)
|
||||
postal_code = models.CharField(max_length=20, blank=True)
|
||||
|
||||
# Details
|
||||
opening_date = models.DateField(null=True, blank=True)
|
||||
closing_date = models.DateField(null=True, blank=True)
|
||||
operating_season = models.CharField(max_length=255, blank=True)
|
||||
@@ -43,16 +112,30 @@ class Park(models.Model):
|
||||
blank=True
|
||||
)
|
||||
website = models.URLField(blank=True)
|
||||
|
||||
# Statistics
|
||||
average_rating = models.DecimalField(
|
||||
max_digits=3,
|
||||
decimal_places=2,
|
||||
null=True,
|
||||
blank=True
|
||||
)
|
||||
total_rides = models.IntegerField(null=True, blank=True)
|
||||
total_roller_coasters = models.IntegerField(null=True, blank=True)
|
||||
|
||||
# Relationships
|
||||
owner = models.ForeignKey(
|
||||
Company,
|
||||
on_delete=models.SET_NULL,
|
||||
null=True,
|
||||
blank=True,
|
||||
related_name='parks'
|
||||
)
|
||||
photos = GenericRelation(Photo, related_query_name='park')
|
||||
|
||||
# Metadata
|
||||
created_at = models.DateTimeField(auto_now_add=True, null=True)
|
||||
updated_at = models.DateTimeField(auto_now=True)
|
||||
photos = GenericRelation('media.Photo')
|
||||
reviews = GenericRelation('reviews.Review')
|
||||
history = HistoricalRecords()
|
||||
|
||||
class Meta:
|
||||
@@ -65,11 +148,17 @@ class Park(models.Model):
|
||||
if not self.slug:
|
||||
self.slug = slugify(self.name)
|
||||
|
||||
# Update the location field to combine country, region, and city
|
||||
self.location = self.get_formatted_location()
|
||||
# Normalize coordinates before saving
|
||||
if self.latitude is not None:
|
||||
self.latitude = normalize_coordinate(self.latitude, 9, 6)
|
||||
if self.longitude is not None:
|
||||
self.longitude = normalize_coordinate(self.longitude, 10, 6)
|
||||
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse('parks:park_detail', kwargs={'slug': self.slug})
|
||||
|
||||
@classmethod
|
||||
def get_by_slug(cls, slug):
|
||||
"""Get park by current or historical slug"""
|
||||
@@ -79,37 +168,28 @@ class Park(models.Model):
|
||||
# Check historical slugs
|
||||
history = cls.history.filter(slug=slug).order_by('-history_date').first()
|
||||
if history:
|
||||
return cls.objects.get(id=history.id), True
|
||||
raise cls.DoesNotExist("No park found with this slug")
|
||||
try:
|
||||
return cls.objects.get(id=history.id), True
|
||||
except cls.DoesNotExist:
|
||||
pass
|
||||
raise cls.DoesNotExist()
|
||||
|
||||
def get_formatted_location(self):
|
||||
"""Get a formatted location string: $COUNTRY, $REGION, $CITY"""
|
||||
if not self.country:
|
||||
return ""
|
||||
|
||||
location = self.country.name
|
||||
|
||||
if self.region and self.city:
|
||||
location += f", {self.region.name}, {self.city.name}"
|
||||
elif self.region:
|
||||
location += f", {self.region.name}"
|
||||
|
||||
return location
|
||||
|
||||
class ParkArea(models.Model):
|
||||
name = models.CharField(max_length=255)
|
||||
slug = models.SlugField(max_length=255)
|
||||
description = models.TextField(blank=True)
|
||||
park = models.ForeignKey(
|
||||
Park,
|
||||
on_delete=models.CASCADE,
|
||||
related_name='areas'
|
||||
)
|
||||
name = models.CharField(max_length=255)
|
||||
slug = models.SlugField(max_length=255)
|
||||
description = models.TextField(blank=True)
|
||||
opening_date = models.DateField(null=True, blank=True)
|
||||
closing_date = models.DateField(null=True, blank=True)
|
||||
|
||||
# Metadata
|
||||
created_at = models.DateTimeField(auto_now_add=True, null=True)
|
||||
updated_at = models.DateTimeField(auto_now=True)
|
||||
photos = GenericRelation('media.Photo')
|
||||
history = HistoricalRecords()
|
||||
|
||||
class Meta:
|
||||
@@ -117,13 +197,19 @@ class ParkArea(models.Model):
|
||||
unique_together = ['park', 'slug']
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.park.name} - {self.name}"
|
||||
return f"{self.name} at {self.park.name}"
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
if not self.slug:
|
||||
self.slug = slugify(self.name)
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse('parks:area_detail', kwargs={
|
||||
'park_slug': self.park.slug,
|
||||
'area_slug': self.slug
|
||||
})
|
||||
|
||||
@classmethod
|
||||
def get_by_slug(cls, slug):
|
||||
"""Get area by current or historical slug"""
|
||||
@@ -133,5 +219,8 @@ class ParkArea(models.Model):
|
||||
# Check historical slugs
|
||||
history = cls.history.filter(slug=slug).order_by('-history_date').first()
|
||||
if history:
|
||||
return cls.objects.get(id=history.id), True
|
||||
raise cls.DoesNotExist("No area found with this slug")
|
||||
try:
|
||||
return cls.objects.get(id=history.id), True
|
||||
except cls.DoesNotExist:
|
||||
pass
|
||||
raise cls.DoesNotExist()
|
||||
|
||||
@@ -1,17 +1,22 @@
|
||||
from django.urls import path, include
|
||||
from . import views
|
||||
from rides.views import RideListView
|
||||
|
||||
app_name = 'parks'
|
||||
app_name = "parks"
|
||||
|
||||
urlpatterns = [
|
||||
path('', views.ParkListView.as_view(), name='park_list'),
|
||||
path('create/', views.ParkCreateView.as_view(), name='park_create'),
|
||||
path('rides/', RideListView.as_view(), name='all_rides'), # Global rides list
|
||||
path('ajax/countries/', views.get_countries, name='get_countries'),
|
||||
path('ajax/regions/', views.get_regions, name='get_regions'),
|
||||
path('ajax/cities/', views.get_cities, name='get_cities'),
|
||||
path('<slug:slug>/edit/', views.ParkUpdateView.as_view(), name='park_edit'),
|
||||
path('<slug:slug>/', views.ParkDetailView.as_view(), name='park_detail'),
|
||||
path('<slug:park_slug>/rides/', include('rides.urls', namespace='rides')),
|
||||
# Park views
|
||||
path("", views.ParkListView.as_view(), name="park_list"),
|
||||
path("create/", views.ParkCreateView.as_view(), name="park_create"),
|
||||
path("<slug:slug>/", views.ParkDetailView.as_view(), name="park_detail"),
|
||||
path("<slug:slug>/edit/", views.ParkUpdateView.as_view(), name="park_update"),
|
||||
|
||||
# Location search endpoints
|
||||
path("search/location/", views.location_search, name="location_search"),
|
||||
path("search/reverse-geocode/", views.reverse_geocode, name="reverse_geocode"),
|
||||
|
||||
# Area views
|
||||
path("<slug:park_slug>/areas/<slug:area_slug>/", views.ParkAreaDetailView.as_view(), name="area_detail"),
|
||||
|
||||
# Include rides URLs
|
||||
path("<slug:park_slug>/rides/", include("rides.urls", namespace="rides")),
|
||||
]
|
||||
|
||||
541
parks/views.py
541
parks/views.py
@@ -1,3 +1,4 @@
|
||||
from decimal import Decimal, ROUND_DOWN
|
||||
from django.views.generic import DetailView, ListView, CreateView, UpdateView
|
||||
from django.shortcuts import get_object_or_404, render
|
||||
from django.core.serializers.json import DjangoJSONEncoder
|
||||
@@ -7,190 +8,146 @@ from django.contrib.auth.mixins import LoginRequiredMixin
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.contrib import messages
|
||||
from django.http import JsonResponse, HttpResponseRedirect, HttpResponse
|
||||
import requests
|
||||
from .models import Park, ParkArea
|
||||
from .forms import ParkForm
|
||||
from rides.models import Ride
|
||||
from .location_utils import normalize_coordinate, normalize_osm_result
|
||||
from core.views import SlugRedirectMixin
|
||||
from moderation.mixins import EditSubmissionMixin, PhotoSubmissionMixin, HistoryMixin
|
||||
from moderation.models import EditSubmission
|
||||
from cities_light.models import Country, Region, City
|
||||
from media.models import Photo
|
||||
|
||||
|
||||
def get_countries(request):
|
||||
def location_search(request):
|
||||
"""Search for locations using OpenStreetMap Nominatim API"""
|
||||
query = request.GET.get("q", "")
|
||||
filter_parks = request.GET.get("filter_parks", "false") == "true"
|
||||
if not query:
|
||||
return JsonResponse({"results": []})
|
||||
|
||||
# Base query
|
||||
countries = Country.objects.filter(name__icontains=query)
|
||||
|
||||
# Only filter by parks if explicitly requested
|
||||
if filter_parks:
|
||||
countries = countries.filter(park__isnull=False)
|
||||
|
||||
countries = countries.distinct().values("id", "name")[:10]
|
||||
return JsonResponse(list(countries), safe=False)
|
||||
|
||||
|
||||
def get_regions(request):
|
||||
query = request.GET.get("q", "")
|
||||
country = request.GET.get("country", "")
|
||||
filter_parks = request.GET.get("filter_parks", "false") == "true"
|
||||
|
||||
if not country:
|
||||
return JsonResponse([], safe=False)
|
||||
|
||||
# Base query
|
||||
regions = Region.objects.filter(
|
||||
Q(name__icontains=query) | Q(alternate_names__icontains=query),
|
||||
country__name__iexact=country,
|
||||
# Call Nominatim API
|
||||
response = requests.get(
|
||||
"https://nominatim.openstreetmap.org/search",
|
||||
params={
|
||||
"q": query,
|
||||
"format": "json",
|
||||
"addressdetails": 1,
|
||||
"namedetails": 1, # Include name tags
|
||||
"accept-language": "en", # Prefer English results
|
||||
"limit": 10,
|
||||
},
|
||||
headers={"User-Agent": "ThrillWiki/1.0"},
|
||||
)
|
||||
|
||||
# Only filter by parks if explicitly requested
|
||||
if filter_parks:
|
||||
regions = regions.filter(park__isnull=False)
|
||||
if response.status_code == 200:
|
||||
results = response.json()
|
||||
# Normalize each result
|
||||
normalized_results = [normalize_osm_result(result) for result in results]
|
||||
# Filter out any results with invalid coordinates
|
||||
valid_results = [r for r in normalized_results if r['lat'] is not None and r['lon'] is not None]
|
||||
return JsonResponse({"results": valid_results})
|
||||
|
||||
regions = regions.distinct().values("id", "name")[:10]
|
||||
return JsonResponse(list(regions), safe=False)
|
||||
return JsonResponse({"results": []})
|
||||
|
||||
|
||||
def get_cities(request):
|
||||
query = request.GET.get("q", "")
|
||||
region = request.GET.get("region", "")
|
||||
country = request.GET.get("country", "")
|
||||
filter_parks = request.GET.get("filter_parks", "false") == "true"
|
||||
def reverse_geocode(request):
|
||||
"""Reverse geocode coordinates using OpenStreetMap Nominatim API"""
|
||||
try:
|
||||
lat = Decimal(request.GET.get("lat", ""))
|
||||
lon = Decimal(request.GET.get("lon", ""))
|
||||
except (TypeError, ValueError, InvalidOperation):
|
||||
return JsonResponse({"error": "Invalid coordinates"}, status=400)
|
||||
|
||||
if not region or not country:
|
||||
return JsonResponse([], safe=False)
|
||||
if not lat or not lon:
|
||||
return JsonResponse({"error": "Missing coordinates"}, status=400)
|
||||
|
||||
# Base query
|
||||
cities = City.objects.filter(
|
||||
Q(name__icontains=query) | Q(alternate_names__icontains=query),
|
||||
region__name__iexact=region,
|
||||
region__country__name__iexact=country,
|
||||
# Normalize coordinates before geocoding
|
||||
lat = lat.quantize(Decimal('0.000001'), rounding=ROUND_DOWN)
|
||||
lon = lon.quantize(Decimal('0.000001'), rounding=ROUND_DOWN)
|
||||
|
||||
# Validate ranges
|
||||
if lat < -90 or lat > 90:
|
||||
return JsonResponse({"error": "Latitude must be between -90 and 90"}, status=400)
|
||||
if lon < -180 or lon > 180:
|
||||
return JsonResponse({"error": "Longitude must be between -180 and 180"}, status=400)
|
||||
|
||||
# Call Nominatim API
|
||||
response = requests.get(
|
||||
"https://nominatim.openstreetmap.org/reverse",
|
||||
params={
|
||||
"lat": str(lat),
|
||||
"lon": str(lon),
|
||||
"format": "json",
|
||||
"addressdetails": 1,
|
||||
"namedetails": 1, # Include name tags
|
||||
"accept-language": "en", # Prefer English results
|
||||
},
|
||||
headers={"User-Agent": "ThrillWiki/1.0"},
|
||||
)
|
||||
|
||||
# Only filter by parks if explicitly requested
|
||||
if filter_parks:
|
||||
cities = cities.filter(park__isnull=False)
|
||||
if response.status_code == 200:
|
||||
result = response.json()
|
||||
# Normalize the result
|
||||
normalized_result = normalize_osm_result(result)
|
||||
if normalized_result['lat'] is None or normalized_result['lon'] is None:
|
||||
return JsonResponse({"error": "Invalid coordinates"}, status=400)
|
||||
return JsonResponse(normalized_result)
|
||||
|
||||
cities = cities.distinct().values("id", "name")[:10]
|
||||
return JsonResponse(list(cities), safe=False)
|
||||
return JsonResponse({"error": "Geocoding failed"}, status=500)
|
||||
|
||||
|
||||
class ParkCreateView(LoginRequiredMixin, CreateView):
|
||||
class ParkListView(ListView):
|
||||
model = Park
|
||||
form_class = ParkForm
|
||||
template_name = "parks/park_form.html"
|
||||
template_name = "parks/park_list.html"
|
||||
context_object_name = "parks"
|
||||
|
||||
def prepare_changes_data(self, cleaned_data):
|
||||
data = cleaned_data.copy()
|
||||
# Convert model instances to IDs for JSON serialization
|
||||
if data.get("owner"):
|
||||
data["owner"] = data["owner"].id
|
||||
if data.get("country"):
|
||||
data["country"] = data["country"].id
|
||||
if data.get("region"):
|
||||
data["region"] = data["region"].id
|
||||
if data.get("city"):
|
||||
data["city"] = data["city"].id
|
||||
# Convert dates to ISO format strings
|
||||
if data.get("opening_date"):
|
||||
data["opening_date"] = data["opening_date"].isoformat()
|
||||
if data.get("closing_date"):
|
||||
data["closing_date"] = data["closing_date"].isoformat()
|
||||
return data
|
||||
def get_queryset(self):
|
||||
queryset = Park.objects.select_related("owner").prefetch_related("photos")
|
||||
|
||||
def form_valid(self, form):
|
||||
changes = self.prepare_changes_data(form.cleaned_data)
|
||||
search = self.request.GET.get("search", "").strip()
|
||||
country = self.request.GET.get("country", "").strip()
|
||||
region = self.request.GET.get("region", "").strip()
|
||||
city = self.request.GET.get("city", "").strip()
|
||||
statuses = self.request.GET.getlist("status")
|
||||
|
||||
# Create submission record
|
||||
submission = EditSubmission.objects.create(
|
||||
user=self.request.user,
|
||||
content_type=ContentType.objects.get_for_model(Park),
|
||||
submission_type="CREATE",
|
||||
changes=changes,
|
||||
reason=self.request.POST.get("reason", ""),
|
||||
source=self.request.POST.get("source", ""),
|
||||
)
|
||||
if search:
|
||||
queryset = queryset.filter(
|
||||
Q(name__icontains=search)
|
||||
| Q(city__icontains=search)
|
||||
| Q(state__icontains=search)
|
||||
| Q(country__icontains=search)
|
||||
)
|
||||
|
||||
# If user is moderator or above, auto-approve
|
||||
if self.request.user.role in ["MODERATOR", "ADMIN", "SUPERUSER"]:
|
||||
self.object = form.save()
|
||||
submission.object_id = self.object.id
|
||||
submission.status = "APPROVED"
|
||||
submission.handled_by = self.request.user
|
||||
submission.save()
|
||||
messages.success(self.request, f"Successfully created {self.object.name}")
|
||||
return HttpResponseRedirect(self.get_success_url())
|
||||
if country:
|
||||
queryset = queryset.filter(country__icontains=country)
|
||||
|
||||
messages.success(self.request, "Your park submission has been sent for review")
|
||||
return HttpResponseRedirect(reverse("parks:park_list"))
|
||||
if region:
|
||||
queryset = queryset.filter(state__icontains=region)
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse("parks:park_detail", kwargs={"slug": self.object.slug})
|
||||
if city:
|
||||
queryset = queryset.filter(city__icontains=city)
|
||||
|
||||
if statuses:
|
||||
queryset = queryset.filter(status__in=statuses)
|
||||
|
||||
class ParkUpdateView(LoginRequiredMixin, UpdateView):
|
||||
model = Park
|
||||
form_class = ParkForm
|
||||
template_name = "parks/park_form.html"
|
||||
return queryset
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context["is_edit"] = True
|
||||
context["current_filters"] = {
|
||||
"search": self.request.GET.get("search", ""),
|
||||
"country": self.request.GET.get("country", ""),
|
||||
"region": self.request.GET.get("region", ""),
|
||||
"city": self.request.GET.get("city", ""),
|
||||
"statuses": self.request.GET.getlist("status"),
|
||||
}
|
||||
return context
|
||||
|
||||
def prepare_changes_data(self, cleaned_data):
|
||||
data = cleaned_data.copy()
|
||||
# Convert model instances to IDs for JSON serialization
|
||||
if data.get("owner"):
|
||||
data["owner"] = data["owner"].id
|
||||
if data.get("country"):
|
||||
data["country"] = data["country"].id
|
||||
if data.get("region"):
|
||||
data["region"] = data["region"].id
|
||||
if data.get("city"):
|
||||
data["city"] = data["city"].id
|
||||
# Convert dates to ISO format strings
|
||||
if data.get("opening_date"):
|
||||
data["opening_date"] = data["opening_date"].isoformat()
|
||||
if data.get("closing_date"):
|
||||
data["closing_date"] = data["closing_date"].isoformat()
|
||||
return data
|
||||
|
||||
def form_valid(self, form):
|
||||
changes = self.prepare_changes_data(form.cleaned_data)
|
||||
|
||||
# Create submission record
|
||||
submission = EditSubmission.objects.create(
|
||||
user=self.request.user,
|
||||
content_type=ContentType.objects.get_for_model(Park),
|
||||
object_id=self.object.id,
|
||||
submission_type="EDIT",
|
||||
changes=changes,
|
||||
reason=self.request.POST.get("reason", ""),
|
||||
source=self.request.POST.get("source", ""),
|
||||
)
|
||||
|
||||
# If user is moderator or above, auto-approve
|
||||
if self.request.user.role in ["MODERATOR", "ADMIN", "SUPERUSER"]:
|
||||
self.object = form.save()
|
||||
submission.status = "APPROVED"
|
||||
submission.handled_by = self.request.user
|
||||
submission.save()
|
||||
messages.success(self.request, f"Successfully updated {self.object.name}")
|
||||
return HttpResponseRedirect(self.get_success_url())
|
||||
|
||||
messages.success(
|
||||
self.request,
|
||||
f"Your changes to {self.object.name} have been sent for review",
|
||||
)
|
||||
return HttpResponseRedirect(
|
||||
reverse("parks:park_detail", kwargs={"slug": self.object.slug})
|
||||
)
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse("parks:park_detail", kwargs={"slug": self.object.slug})
|
||||
def get(self, request, *args, **kwargs):
|
||||
# Check if this is an HTMX request
|
||||
if request.htmx:
|
||||
# If it is, return just the parks list partial
|
||||
self.template_name = "parks/partials/park_list.html"
|
||||
return super().get(request, *args, **kwargs)
|
||||
|
||||
|
||||
class ParkDetailView(
|
||||
@@ -211,18 +168,241 @@ class ParkDetailView(
|
||||
# Try to get by current or historical slug
|
||||
return self.model.get_by_slug(slug)[0]
|
||||
|
||||
def get_queryset(self):
|
||||
return super().get_queryset().prefetch_related(
|
||||
'rides',
|
||||
'rides__manufacturer',
|
||||
'photos',
|
||||
'areas'
|
||||
)
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context["rides"] = Ride.objects.filter(park=self.object).select_related(
|
||||
"coaster_stats"
|
||||
context["areas"] = self.object.areas.all()
|
||||
# Get rides ordered by status (operating first) and name
|
||||
context["rides"] = self.object.rides.all().order_by(
|
||||
'-status', # OPERATING will come before others
|
||||
'name'
|
||||
)
|
||||
context["areas"] = ParkArea.objects.filter(park=self.object)
|
||||
return context
|
||||
|
||||
def get_redirect_url_pattern(self):
|
||||
return "parks:park_detail"
|
||||
|
||||
|
||||
class ParkCreateView(LoginRequiredMixin, CreateView):
|
||||
model = Park
|
||||
form_class = ParkForm
|
||||
template_name = "parks/park_form.html"
|
||||
|
||||
def prepare_changes_data(self, cleaned_data):
|
||||
data = cleaned_data.copy()
|
||||
# Convert model instances to IDs for JSON serialization
|
||||
if data.get("owner"):
|
||||
data["owner"] = data["owner"].id
|
||||
# Convert dates to ISO format strings
|
||||
if data.get("opening_date"):
|
||||
data["opening_date"] = data["opening_date"].isoformat()
|
||||
if data.get("closing_date"):
|
||||
data["closing_date"] = data["closing_date"].isoformat()
|
||||
# Convert Decimal fields to strings
|
||||
decimal_fields = ["latitude", "longitude", "size_acres", "average_rating"]
|
||||
for field in decimal_fields:
|
||||
if data.get(field):
|
||||
data[field] = str(data[field])
|
||||
return data
|
||||
|
||||
def form_valid(self, form):
|
||||
# Normalize coordinates before saving
|
||||
if form.cleaned_data.get("latitude"):
|
||||
lat = Decimal(str(form.cleaned_data["latitude"]))
|
||||
form.cleaned_data["latitude"] = lat.quantize(Decimal('0.000001'), rounding=ROUND_DOWN)
|
||||
if form.cleaned_data.get("longitude"):
|
||||
lon = Decimal(str(form.cleaned_data["longitude"]))
|
||||
form.cleaned_data["longitude"] = lon.quantize(Decimal('0.000001'), rounding=ROUND_DOWN)
|
||||
|
||||
changes = self.prepare_changes_data(form.cleaned_data)
|
||||
|
||||
# Create submission record
|
||||
submission = EditSubmission.objects.create(
|
||||
user=self.request.user,
|
||||
content_type=ContentType.objects.get_for_model(Park),
|
||||
submission_type="CREATE",
|
||||
changes=changes,
|
||||
reason=self.request.POST.get("reason", ""),
|
||||
source=self.request.POST.get("source", ""),
|
||||
)
|
||||
|
||||
# If user is moderator or above, auto-approve
|
||||
if self.request.user.role in ["MODERATOR", "ADMIN", "SUPERUSER"]:
|
||||
try:
|
||||
self.object = form.save()
|
||||
submission.object_id = self.object.id
|
||||
submission.status = "APPROVED"
|
||||
submission.handled_by = self.request.user
|
||||
submission.save()
|
||||
|
||||
# Handle photo uploads
|
||||
photos = self.request.FILES.getlist("photos")
|
||||
for photo_file in photos:
|
||||
try:
|
||||
Photo.objects.create(
|
||||
image=photo_file,
|
||||
uploaded_by=self.request.user,
|
||||
content_type=ContentType.objects.get_for_model(Park),
|
||||
object_id=self.object.id,
|
||||
)
|
||||
except Exception as e:
|
||||
messages.error(
|
||||
self.request,
|
||||
f"Error uploading photo {photo_file.name}: {str(e)}",
|
||||
)
|
||||
|
||||
messages.success(
|
||||
self.request,
|
||||
f"Successfully created {self.object.name}. "
|
||||
f"Added {len(photos)} photo(s).",
|
||||
)
|
||||
return HttpResponseRedirect(self.get_success_url())
|
||||
except Exception as e:
|
||||
messages.error(
|
||||
self.request,
|
||||
f"Error creating park: {str(e)}. Please check your input and try again.",
|
||||
)
|
||||
return self.form_invalid(form)
|
||||
|
||||
messages.success(
|
||||
self.request,
|
||||
"Your park submission has been sent for review. "
|
||||
"You will be notified when it is approved.",
|
||||
)
|
||||
return HttpResponseRedirect(reverse("parks:park_list"))
|
||||
|
||||
def form_invalid(self, form):
|
||||
messages.error(
|
||||
self.request,
|
||||
"Please correct the errors below. Required fields are marked with an asterisk (*).",
|
||||
)
|
||||
for field, errors in form.errors.items():
|
||||
for error in errors:
|
||||
messages.error(self.request, f"{field}: {error}")
|
||||
return super().form_invalid(form)
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse("parks:park_detail", kwargs={"slug": self.object.slug})
|
||||
|
||||
|
||||
class ParkUpdateView(LoginRequiredMixin, UpdateView):
|
||||
model = Park
|
||||
form_class = ParkForm
|
||||
template_name = "parks/park_form.html"
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context["is_edit"] = True
|
||||
return context
|
||||
|
||||
def prepare_changes_data(self, cleaned_data):
|
||||
data = cleaned_data.copy()
|
||||
# Convert model instances to IDs for JSON serialization
|
||||
if data.get("owner"):
|
||||
data["owner"] = data["owner"].id
|
||||
# Convert dates to ISO format strings
|
||||
if data.get("opening_date"):
|
||||
data["opening_date"] = data["opening_date"].isoformat()
|
||||
if data.get("closing_date"):
|
||||
data["closing_date"] = data["closing_date"].isoformat()
|
||||
# Convert Decimal fields to strings
|
||||
decimal_fields = ["latitude", "longitude", "size_acres", "average_rating"]
|
||||
for field in decimal_fields:
|
||||
if data.get(field):
|
||||
data[field] = str(data[field])
|
||||
return data
|
||||
|
||||
def form_valid(self, form):
|
||||
# Normalize coordinates before saving
|
||||
if form.cleaned_data.get("latitude"):
|
||||
lat = Decimal(str(form.cleaned_data["latitude"]))
|
||||
form.cleaned_data["latitude"] = lat.quantize(Decimal('0.000001'), rounding=ROUND_DOWN)
|
||||
if form.cleaned_data.get("longitude"):
|
||||
lon = Decimal(str(form.cleaned_data["longitude"]))
|
||||
form.cleaned_data["longitude"] = lon.quantize(Decimal('0.000001'), rounding=ROUND_DOWN)
|
||||
|
||||
changes = self.prepare_changes_data(form.cleaned_data)
|
||||
|
||||
# Create submission record
|
||||
submission = EditSubmission.objects.create(
|
||||
user=self.request.user,
|
||||
content_type=ContentType.objects.get_for_model(Park),
|
||||
object_id=self.object.id,
|
||||
submission_type="EDIT",
|
||||
changes=changes,
|
||||
reason=self.request.POST.get("reason", ""),
|
||||
source=self.request.POST.get("source", ""),
|
||||
)
|
||||
|
||||
# If user is moderator or above, auto-approve
|
||||
if self.request.user.role in ["MODERATOR", "ADMIN", "SUPERUSER"]:
|
||||
try:
|
||||
self.object = form.save()
|
||||
submission.status = "APPROVED"
|
||||
submission.handled_by = self.request.user
|
||||
submission.save()
|
||||
|
||||
# Handle photo uploads
|
||||
photos = self.request.FILES.getlist("photos")
|
||||
uploaded_count = 0
|
||||
for photo_file in photos:
|
||||
try:
|
||||
Photo.objects.create(
|
||||
image=photo_file,
|
||||
uploaded_by=self.request.user,
|
||||
content_type=ContentType.objects.get_for_model(Park),
|
||||
object_id=self.object.id,
|
||||
)
|
||||
uploaded_count += 1
|
||||
except Exception as e:
|
||||
messages.error(
|
||||
self.request,
|
||||
f"Error uploading photo {photo_file.name}: {str(e)}",
|
||||
)
|
||||
|
||||
messages.success(
|
||||
self.request,
|
||||
f"Successfully updated {self.object.name}. "
|
||||
f"Added {uploaded_count} new photo(s).",
|
||||
)
|
||||
return HttpResponseRedirect(self.get_success_url())
|
||||
except Exception as e:
|
||||
messages.error(
|
||||
self.request,
|
||||
f"Error updating park: {str(e)}. Please check your input and try again.",
|
||||
)
|
||||
return self.form_invalid(form)
|
||||
|
||||
messages.success(
|
||||
self.request,
|
||||
f"Your changes to {self.object.name} have been sent for review. "
|
||||
"You will be notified when they are approved.",
|
||||
)
|
||||
return HttpResponseRedirect(
|
||||
reverse("parks:park_detail", kwargs={"slug": self.object.slug})
|
||||
)
|
||||
|
||||
def form_invalid(self, form):
|
||||
messages.error(
|
||||
self.request,
|
||||
"Please correct the errors below. Required fields are marked with an asterisk (*).",
|
||||
)
|
||||
for field, errors in form.errors.items():
|
||||
for error in errors:
|
||||
messages.error(self.request, f"{field}: {error}")
|
||||
return super().form_invalid(form)
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse("parks:park_detail", kwargs={"slug": self.object.slug})
|
||||
|
||||
|
||||
class ParkAreaDetailView(
|
||||
SlugRedirectMixin,
|
||||
EditSubmissionMixin,
|
||||
@@ -248,9 +428,6 @@ class ParkAreaDetailView(
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context["rides"] = Ride.objects.filter(area=self.object).select_related(
|
||||
"coaster_stats"
|
||||
)
|
||||
return context
|
||||
|
||||
def get_redirect_url_pattern(self):
|
||||
@@ -258,57 +435,3 @@ class ParkAreaDetailView(
|
||||
|
||||
def get_redirect_url_kwargs(self):
|
||||
return {"park_slug": self.object.park.slug, "area_slug": self.object.slug}
|
||||
|
||||
|
||||
class ParkListView(ListView):
|
||||
model = Park
|
||||
template_name = "parks/park_list.html"
|
||||
context_object_name = "parks"
|
||||
|
||||
def get_queryset(self):
|
||||
queryset = Park.objects.select_related(
|
||||
"owner", "country", "region", "city"
|
||||
).prefetch_related("photos", "rides")
|
||||
|
||||
search = self.request.GET.get("search", "").strip()
|
||||
country = self.request.GET.get("country", "").strip()
|
||||
region = self.request.GET.get("region", "").strip()
|
||||
city = self.request.GET.get("city", "").strip()
|
||||
statuses = self.request.GET.getlist("status")
|
||||
|
||||
if search:
|
||||
queryset = queryset.filter(
|
||||
Q(name__icontains=search) | Q(location__icontains=search)
|
||||
)
|
||||
|
||||
if country:
|
||||
queryset = queryset.filter(country__name__icontains=country)
|
||||
|
||||
if region:
|
||||
queryset = queryset.filter(region__name__icontains=region)
|
||||
|
||||
if city:
|
||||
queryset = queryset.filter(city__name__icontains=city)
|
||||
|
||||
if statuses:
|
||||
queryset = queryset.filter(status__in=statuses)
|
||||
|
||||
return queryset
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context["current_filters"] = {
|
||||
"search": self.request.GET.get("search", ""),
|
||||
"country": self.request.GET.get("country", ""),
|
||||
"region": self.request.GET.get("region", ""),
|
||||
"city": self.request.GET.get("city", ""),
|
||||
"statuses": self.request.GET.getlist("status"),
|
||||
}
|
||||
return context
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
# Check if this is an HTMX request
|
||||
if request.htmx:
|
||||
# If it is, return just the parks list partial
|
||||
self.template_name = "parks/partials/park_list.html"
|
||||
return super().get(request, *args, **kwargs)
|
||||
|
||||
16
parks/views_update.py
Normal file
16
parks/views_update.py
Normal file
@@ -0,0 +1,16 @@
|
||||
def prepare_changes_data(self, cleaned_data):
|
||||
data = cleaned_data.copy()
|
||||
# Convert model instances to IDs for JSON serialization
|
||||
if data.get("owner"):
|
||||
data["owner"] = data["owner"].id
|
||||
# Convert dates to ISO format strings
|
||||
if data.get("opening_date"):
|
||||
data["opening_date"] = data["opening_date"].isoformat()
|
||||
if data.get("closing_date"):
|
||||
data["closing_date"] = data["closing_date"].isoformat()
|
||||
# Convert Decimal fields to strings
|
||||
decimal_fields = ['latitude', 'longitude', 'size_acres', 'average_rating']
|
||||
for field in decimal_fields:
|
||||
if data.get(field):
|
||||
data[field] = str(data[field])
|
||||
return data
|
||||
Reference in New Issue
Block a user