mirror of
https://github.com/pacnpal/thrillwiki_django_no_react.git
synced 2025-12-22 15:11:09 -05:00
here we go
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,19 +1,49 @@
|
||||
from django.contrib import admin
|
||||
from django.utils.html import format_html
|
||||
from simple_history.admin import SimpleHistoryAdmin
|
||||
from .models import Park, ParkArea
|
||||
|
||||
@admin.register(Park)
|
||||
class ParkAdmin(SimpleHistoryAdmin):
|
||||
list_display = ('name', 'location', 'owner', 'status', 'opening_date')
|
||||
list_filter = ('status', 'owner')
|
||||
list_display = ('name', 'location', 'status', 'owner', 'created_at', 'updated_at')
|
||||
list_filter = ('status', 'country', 'region', 'city')
|
||||
search_fields = ('name', 'location', 'description')
|
||||
readonly_fields = ('created_at', 'updated_at')
|
||||
prepopulated_fields = {'slug': ('name',)}
|
||||
readonly_fields = ('id', 'created_at', 'updated_at')
|
||||
|
||||
@admin.register(ParkArea)
|
||||
def get_history_list_display(self, request):
|
||||
"""Customize the list display for history records"""
|
||||
return ('name', 'location', 'status', 'history_date', 'history_user')
|
||||
|
||||
class ParkAreaAdmin(SimpleHistoryAdmin):
|
||||
list_display = ('name', 'park', 'opening_date')
|
||||
list_display = ('name', 'park', 'created_at', 'updated_at')
|
||||
list_filter = ('park',)
|
||||
search_fields = ('name', 'description')
|
||||
search_fields = ('name', 'description', 'park__name')
|
||||
readonly_fields = ('created_at', 'updated_at')
|
||||
prepopulated_fields = {'slug': ('name',)}
|
||||
readonly_fields = ('id', 'created_at', 'updated_at')
|
||||
|
||||
def get_history_list_display(self, request):
|
||||
"""Customize the list display for history records"""
|
||||
return ('name', 'park', 'history_date', 'history_user')
|
||||
|
||||
# Register the models with their admin classes
|
||||
admin.site.register(Park, ParkAdmin)
|
||||
admin.site.register(ParkArea, ParkAreaAdmin)
|
||||
|
||||
# Register the historical records for direct editing
|
||||
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')
|
||||
readonly_fields = ('history_date', 'history_type', 'history_id')
|
||||
|
||||
def has_add_permission(self, request):
|
||||
return False # Prevent adding new historical records directly
|
||||
|
||||
def has_delete_permission(self, request, obj=None):
|
||||
return False # Prevent deleting historical records
|
||||
|
||||
# Register the historical model
|
||||
admin.site.register(Park.history.model, HistoricalParkAdmin)
|
||||
|
||||
106
parks/forms.py
106
parks/forms.py
@@ -3,48 +3,89 @@ from django.urls import reverse_lazy
|
||||
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 Awesomplete
|
||||
# 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': 'Start typing a country name...',
|
||||
'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': 'Start typing a region/state name...',
|
||||
'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': 'Start typing a city name...',
|
||||
'placeholder': 'Select or type a city',
|
||||
'data-autocomplete': 'true'
|
||||
})
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = Park
|
||||
fields = ['name', 'country', 'region', 'city', 'description', 'owner', 'status',
|
||||
'opening_date', 'closing_date', 'operating_season', 'size_acres', 'website']
|
||||
fields = ['name', 'description', 'owner', 'status', 'opening_date', 'closing_date',
|
||||
'operating_season', 'size_acres', 'website', 'country', 'region', 'city']
|
||||
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={
|
||||
'rows': 4,
|
||||
'class': 'w-full border-gray-300 rounded-lg form-textarea dark:border-gray-600 dark:bg-gray-700 dark:text-white'
|
||||
'class': 'w-full border-gray-300 rounded-lg form-textarea dark:border-gray-600 dark:bg-gray-700 dark:text-white',
|
||||
'rows': 4
|
||||
}),
|
||||
'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'
|
||||
@@ -79,15 +120,26 @@ class ParkForm(forms.ModelForm):
|
||||
super().__init__(*args, **kwargs)
|
||||
instance = kwargs.get('instance')
|
||||
if instance:
|
||||
if instance.country:
|
||||
self.fields['country_name'].initial = instance.country.name
|
||||
self.fields['country'].initial = instance.country
|
||||
if instance.region:
|
||||
self.fields['region_name'].initial = instance.region.name
|
||||
self.fields['region'].initial = instance.region
|
||||
if instance.city:
|
||||
self.fields['city_name'].initial = instance.city.name
|
||||
self.fields['city'].initial = instance.city
|
||||
try:
|
||||
if instance.country:
|
||||
self.fields['country_name'].initial = instance.country.name
|
||||
self.fields['country'].initial = instance.country
|
||||
except Country.DoesNotExist:
|
||||
pass
|
||||
|
||||
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()
|
||||
@@ -95,12 +147,26 @@ class ParkForm(forms.ModelForm):
|
||||
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:
|
||||
self.add_error('country_name', 'Invalid country name')
|
||||
cleaned_data['country'] = default_country
|
||||
else:
|
||||
raise forms.ValidationError("Country is required")
|
||||
|
||||
if region_name and cleaned_data.get('country'):
|
||||
try:
|
||||
@@ -110,7 +176,7 @@ class ParkForm(forms.ModelForm):
|
||||
)
|
||||
cleaned_data['region'] = region
|
||||
except Region.DoesNotExist:
|
||||
self.add_error('region_name', 'Invalid region name for selected country')
|
||||
cleaned_data['region'] = None
|
||||
|
||||
if city_name and cleaned_data.get('region'):
|
||||
try:
|
||||
@@ -120,6 +186,6 @@ class ParkForm(forms.ModelForm):
|
||||
)
|
||||
cleaned_data['city'] = city
|
||||
except City.DoesNotExist:
|
||||
self.add_error('city_name', 'Invalid city name for selected region')
|
||||
cleaned_data['city'] = None
|
||||
|
||||
return cleaned_data
|
||||
|
||||
99
parks/management/commands/fix_historical_parks.py
Normal file
99
parks/management/commands/fix_historical_parks.py
Normal file
@@ -0,0 +1,99 @@
|
||||
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'))
|
||||
90
parks/management/commands/fix_locations.py
Normal file
90
parks/management/commands/fix_locations.py
Normal file
@@ -0,0 +1,90 @@
|
||||
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'))
|
||||
@@ -10,6 +10,11 @@
|
||||
"website": "https://disneyworld.disney.go.com/destinations/magic-kingdom/",
|
||||
"owner": "The Walt Disney Company",
|
||||
"size_acres": 142,
|
||||
"photos": [
|
||||
"https://upload.wikimedia.org/wikipedia/commons/thumb/8/8d/Walt_Disney_World_Magic_Kingdom_Cinderella_Castle.jpg/1280px-Walt_Disney_World_Magic_Kingdom_Cinderella_Castle.jpg",
|
||||
"https://upload.wikimedia.org/wikipedia/commons/thumb/9/96/Magic_Kingdom_Main_Street_USA_Panorama.jpg/1280px-Magic_Kingdom_Main_Street_USA_Panorama.jpg",
|
||||
"https://upload.wikimedia.org/wikipedia/commons/thumb/5/5d/Magic_Kingdom_-_Cinderella_Castle_at_Night.jpg/1280px-Magic_Kingdom_-_Cinderella_Castle_at_Night.jpg"
|
||||
],
|
||||
"rides": [
|
||||
{
|
||||
"name": "Space Mountain",
|
||||
@@ -18,6 +23,10 @@
|
||||
"status": "OPERATING",
|
||||
"manufacturer": "Walt Disney Imagineering",
|
||||
"description": "A high-speed roller coaster in the dark through space.",
|
||||
"photos": [
|
||||
"https://upload.wikimedia.org/wikipedia/commons/thumb/1/1b/Magic_Kingdom_Space_Mountain.jpg/1280px-Magic_Kingdom_Space_Mountain.jpg",
|
||||
"https://upload.wikimedia.org/wikipedia/commons/thumb/9/9a/Space_Mountain_%28Magic_Kingdom%29_entrance.jpg/1280px-Space_Mountain_%28Magic_Kingdom%29_entrance.jpg"
|
||||
],
|
||||
"stats": {
|
||||
"height_ft": 183,
|
||||
"length_ft": 3196,
|
||||
@@ -33,6 +42,10 @@
|
||||
"status": "OPERATING",
|
||||
"manufacturer": "Walt Disney Imagineering",
|
||||
"description": "A mine train roller coaster through the Old West.",
|
||||
"photos": [
|
||||
"https://upload.wikimedia.org/wikipedia/commons/thumb/9/9c/Big_Thunder_Mountain_Railroad_at_Magic_Kingdom.jpg/1280px-Big_Thunder_Mountain_Railroad_at_Magic_Kingdom.jpg",
|
||||
"https://upload.wikimedia.org/wikipedia/commons/thumb/5/5d/Big_Thunder_Mountain_Railroad_%28Magic_Kingdom%29.jpg/1280px-Big_Thunder_Mountain_Railroad_%28Magic_Kingdom%29.jpg"
|
||||
],
|
||||
"stats": {
|
||||
"height_ft": 104,
|
||||
"length_ft": 2671,
|
||||
@@ -48,6 +61,10 @@
|
||||
"status": "OPERATING",
|
||||
"manufacturer": "Vekoma",
|
||||
"description": "A family roller coaster featuring unique swinging cars.",
|
||||
"photos": [
|
||||
"https://upload.wikimedia.org/wikipedia/commons/thumb/d/d6/Seven_Dwarfs_Mine_Train_at_Magic_Kingdom.jpg/1280px-Seven_Dwarfs_Mine_Train_at_Magic_Kingdom.jpg",
|
||||
"https://upload.wikimedia.org/wikipedia/commons/thumb/f/f5/Seven_Dwarfs_Mine_Train_drop.jpg/1280px-Seven_Dwarfs_Mine_Train_drop.jpg"
|
||||
],
|
||||
"stats": {
|
||||
"height_ft": 112,
|
||||
"length_ft": 2000,
|
||||
@@ -62,7 +79,11 @@
|
||||
"opening_date": "1971-10-01",
|
||||
"status": "OPERATING",
|
||||
"manufacturer": "Walt Disney Imagineering",
|
||||
"description": "A dark ride through a haunted estate."
|
||||
"description": "A dark ride through a haunted estate.",
|
||||
"photos": [
|
||||
"https://upload.wikimedia.org/wikipedia/commons/thumb/4/4c/Haunted_Mansion_at_Magic_Kingdom.jpg/1280px-Haunted_Mansion_at_Magic_Kingdom.jpg",
|
||||
"https://upload.wikimedia.org/wikipedia/commons/thumb/2/2b/Haunted_Mansion_entrance.jpg/1280px-Haunted_Mansion_entrance.jpg"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Pirates of the Caribbean",
|
||||
@@ -70,7 +91,11 @@
|
||||
"opening_date": "1973-12-15",
|
||||
"status": "OPERATING",
|
||||
"manufacturer": "Walt Disney Imagineering",
|
||||
"description": "A boat ride through pirate-filled Caribbean waters."
|
||||
"description": "A boat ride through pirate-filled Caribbean waters.",
|
||||
"photos": [
|
||||
"https://upload.wikimedia.org/wikipedia/commons/thumb/2/2f/Pirates_of_the_Caribbean_%28Magic_Kingdom%29.jpg/1280px-Pirates_of_the_Caribbean_%28Magic_Kingdom%29.jpg",
|
||||
"https://upload.wikimedia.org/wikipedia/commons/thumb/8/89/Pirates_of_the_Caribbean_entrance.jpg/1280px-Pirates_of_the_Caribbean_entrance.jpg"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -84,6 +109,11 @@
|
||||
"website": "https://www.cedarpoint.com",
|
||||
"owner": "Cedar Fair",
|
||||
"size_acres": 364,
|
||||
"photos": [
|
||||
"https://upload.wikimedia.org/wikipedia/commons/thumb/7/7c/Cedar_Point_aerial_view.jpg/1280px-Cedar_Point_aerial_view.jpg",
|
||||
"https://upload.wikimedia.org/wikipedia/commons/thumb/8/8d/Cedar_Point_Beach.jpg/1280px-Cedar_Point_Beach.jpg",
|
||||
"https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cedar_Point_at_dusk.jpg/1280px-Cedar_Point_at_dusk.jpg"
|
||||
],
|
||||
"rides": [
|
||||
{
|
||||
"name": "Steel Vengeance",
|
||||
@@ -92,6 +122,10 @@
|
||||
"status": "OPERATING",
|
||||
"manufacturer": "Rocky Mountain Construction",
|
||||
"description": "A hybrid roller coaster featuring multiple inversions.",
|
||||
"photos": [
|
||||
"https://upload.wikimedia.org/wikipedia/commons/thumb/8/8c/Steel_Vengeance_at_Cedar_Point.jpg/1280px-Steel_Vengeance_at_Cedar_Point.jpg",
|
||||
"https://upload.wikimedia.org/wikipedia/commons/thumb/f/f2/Steel_Vengeance_first_drop.jpg/1280px-Steel_Vengeance_first_drop.jpg"
|
||||
],
|
||||
"stats": {
|
||||
"height_ft": 205,
|
||||
"length_ft": 5740,
|
||||
@@ -107,6 +141,10 @@
|
||||
"status": "OPERATING",
|
||||
"manufacturer": "Intamin",
|
||||
"description": "A giga coaster with stunning views of Lake Erie.",
|
||||
"photos": [
|
||||
"https://upload.wikimedia.org/wikipedia/commons/thumb/e/ec/Millennium_Force_at_Cedar_Point.jpg/1280px-Millennium_Force_at_Cedar_Point.jpg",
|
||||
"https://upload.wikimedia.org/wikipedia/commons/thumb/d/df/Millennium_Force_lift_hill.jpg/1280px-Millennium_Force_lift_hill.jpg"
|
||||
],
|
||||
"stats": {
|
||||
"height_ft": 310,
|
||||
"length_ft": 6595,
|
||||
@@ -122,6 +160,10 @@
|
||||
"status": "SBNO",
|
||||
"manufacturer": "Intamin",
|
||||
"description": "A strata coaster featuring a 420-foot top hat element.",
|
||||
"photos": [
|
||||
"https://upload.wikimedia.org/wikipedia/commons/thumb/0/0c/Top_Thrill_Dragster.jpg/1280px-Top_Thrill_Dragster.jpg",
|
||||
"https://upload.wikimedia.org/wikipedia/commons/thumb/8/8f/Top_Thrill_Dragster_launch.jpg/1280px-Top_Thrill_Dragster_launch.jpg"
|
||||
],
|
||||
"stats": {
|
||||
"height_ft": 420,
|
||||
"length_ft": 2800,
|
||||
@@ -137,6 +179,10 @@
|
||||
"status": "OPERATING",
|
||||
"manufacturer": "Intamin",
|
||||
"description": "A launched roller coaster with multiple inversions.",
|
||||
"photos": [
|
||||
"https://upload.wikimedia.org/wikipedia/commons/thumb/3/39/Maverick_at_Cedar_Point.jpg/1280px-Maverick_at_Cedar_Point.jpg",
|
||||
"https://upload.wikimedia.org/wikipedia/commons/thumb/b/b5/Maverick_first_drop.jpg/1280px-Maverick_first_drop.jpg"
|
||||
],
|
||||
"stats": {
|
||||
"height_ft": 105,
|
||||
"length_ft": 4450,
|
||||
@@ -157,6 +203,11 @@
|
||||
"website": "https://www.universalorlando.com/web/en/us/theme-parks/islands-of-adventure",
|
||||
"owner": "NBCUniversal",
|
||||
"size_acres": 110,
|
||||
"photos": [
|
||||
"https://upload.wikimedia.org/wikipedia/commons/thumb/d/d1/Islands_of_Adventure_entrance.jpg/1280px-Islands_of_Adventure_entrance.jpg",
|
||||
"https://upload.wikimedia.org/wikipedia/commons/thumb/c/c5/Hogwarts_Castle_at_Universal%27s_Islands_of_Adventure.jpg/1280px-Hogwarts_Castle_at_Universal%27s_Islands_of_Adventure.jpg",
|
||||
"https://upload.wikimedia.org/wikipedia/commons/thumb/2/2f/Port_of_Entry_at_Islands_of_Adventure.jpg/1280px-Port_of_Entry_at_Islands_of_Adventure.jpg"
|
||||
],
|
||||
"rides": [
|
||||
{
|
||||
"name": "Jurassic World VelociCoaster",
|
||||
@@ -165,6 +216,10 @@
|
||||
"status": "OPERATING",
|
||||
"manufacturer": "Intamin",
|
||||
"description": "A high-speed launch coaster featuring velociraptors.",
|
||||
"photos": [
|
||||
"https://upload.wikimedia.org/wikipedia/commons/thumb/f/f8/Jurassic_World_VelociCoaster.jpg/1280px-Jurassic_World_VelociCoaster.jpg",
|
||||
"https://upload.wikimedia.org/wikipedia/commons/thumb/e/e5/VelociCoaster_top_hat.jpg/1280px-VelociCoaster_top_hat.jpg"
|
||||
],
|
||||
"stats": {
|
||||
"height_ft": 155,
|
||||
"length_ft": 4700,
|
||||
@@ -180,6 +235,10 @@
|
||||
"status": "OPERATING",
|
||||
"manufacturer": "Intamin",
|
||||
"description": "A story coaster through the Forbidden Forest.",
|
||||
"photos": [
|
||||
"https://upload.wikimedia.org/wikipedia/commons/thumb/d/d7/Hagrid%27s_Magical_Creatures_Motorbike_Adventure.jpg/1280px-Hagrid%27s_Magical_Creatures_Motorbike_Adventure.jpg",
|
||||
"https://upload.wikimedia.org/wikipedia/commons/thumb/c/c8/Hagrid%27s_entrance.jpg/1280px-Hagrid%27s_entrance.jpg"
|
||||
],
|
||||
"stats": {
|
||||
"height_ft": 65,
|
||||
"length_ft": 5053,
|
||||
@@ -194,7 +253,11 @@
|
||||
"opening_date": "1999-05-28",
|
||||
"status": "OPERATING",
|
||||
"manufacturer": "Oceaneering International",
|
||||
"description": "A 3D dark ride featuring Spider-Man."
|
||||
"description": "A 3D dark ride featuring Spider-Man.",
|
||||
"photos": [
|
||||
"https://upload.wikimedia.org/wikipedia/commons/thumb/5/5e/The_Amazing_Adventures_of_Spider-Man.jpg/1280px-The_Amazing_Adventures_of_Spider-Man.jpg",
|
||||
"https://upload.wikimedia.org/wikipedia/commons/thumb/b/b2/Spider-Man_ride_entrance.jpg/1280px-Spider-Man_ride_entrance.jpg"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -208,6 +271,11 @@
|
||||
"website": "https://www.altontowers.com",
|
||||
"owner": "Merlin Entertainments",
|
||||
"size_acres": 910,
|
||||
"photos": [
|
||||
"https://upload.wikimedia.org/wikipedia/commons/thumb/5/5c/Alton_Towers_aerial_view.jpg/1280px-Alton_Towers_aerial_view.jpg",
|
||||
"https://upload.wikimedia.org/wikipedia/commons/thumb/3/37/Alton_Towers_mansion.jpg/1280px-Alton_Towers_mansion.jpg",
|
||||
"https://upload.wikimedia.org/wikipedia/commons/thumb/f/f8/Alton_Towers_gardens.jpg/1280px-Alton_Towers_gardens.jpg"
|
||||
],
|
||||
"rides": [
|
||||
{
|
||||
"name": "Nemesis",
|
||||
@@ -216,6 +284,10 @@
|
||||
"status": "CLOSED",
|
||||
"manufacturer": "Bolliger & Mabillard",
|
||||
"description": "An inverted roller coaster through ravines.",
|
||||
"photos": [
|
||||
"https://upload.wikimedia.org/wikipedia/commons/thumb/9/98/Nemesis_at_Alton_Towers.jpg/1280px-Nemesis_at_Alton_Towers.jpg",
|
||||
"https://upload.wikimedia.org/wikipedia/commons/thumb/2/29/Nemesis_loop.jpg/1280px-Nemesis_loop.jpg"
|
||||
],
|
||||
"stats": {
|
||||
"height_ft": 43,
|
||||
"length_ft": 2349,
|
||||
@@ -231,6 +303,10 @@
|
||||
"status": "OPERATING",
|
||||
"manufacturer": "Bolliger & Mabillard",
|
||||
"description": "The world's first vertical drop roller coaster.",
|
||||
"photos": [
|
||||
"https://upload.wikimedia.org/wikipedia/commons/thumb/6/67/Oblivion_at_Alton_Towers.jpg/1280px-Oblivion_at_Alton_Towers.jpg",
|
||||
"https://upload.wikimedia.org/wikipedia/commons/thumb/d/d4/Oblivion_vertical_drop.jpg/1280px-Oblivion_vertical_drop.jpg"
|
||||
],
|
||||
"stats": {
|
||||
"height_ft": 65,
|
||||
"length_ft": 1804,
|
||||
@@ -251,6 +327,11 @@
|
||||
"website": "https://www.europapark.de",
|
||||
"owner": "Mack Rides",
|
||||
"size_acres": 235,
|
||||
"photos": [
|
||||
"https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Europa-Park_entrance.jpg/1280px-Europa-Park_entrance.jpg",
|
||||
"https://upload.wikimedia.org/wikipedia/commons/thumb/f/f2/Europa-Park_aerial_view.jpg/1280px-Europa-Park_aerial_view.jpg",
|
||||
"https://upload.wikimedia.org/wikipedia/commons/thumb/d/d1/Europa-Park_at_night.jpg/1280px-Europa-Park_at_night.jpg"
|
||||
],
|
||||
"rides": [
|
||||
{
|
||||
"name": "Silver Star",
|
||||
@@ -259,6 +340,10 @@
|
||||
"status": "OPERATING",
|
||||
"manufacturer": "Bolliger & Mabillard",
|
||||
"description": "A hypercoaster with stunning views.",
|
||||
"photos": [
|
||||
"https://upload.wikimedia.org/wikipedia/commons/thumb/2/24/Silver_Star_at_Europa-Park.jpg/1280px-Silver_Star_at_Europa-Park.jpg",
|
||||
"https://upload.wikimedia.org/wikipedia/commons/thumb/7/75/Silver_Star_first_drop.jpg/1280px-Silver_Star_first_drop.jpg"
|
||||
],
|
||||
"stats": {
|
||||
"height_ft": 239,
|
||||
"length_ft": 4003,
|
||||
@@ -274,6 +359,10 @@
|
||||
"status": "OPERATING",
|
||||
"manufacturer": "Mack Rides",
|
||||
"description": "A launched roller coaster with multiple inversions.",
|
||||
"photos": [
|
||||
"https://upload.wikimedia.org/wikipedia/commons/thumb/1/1c/Blue_Fire_at_Europa-Park.jpg/1280px-Blue_Fire_at_Europa-Park.jpg",
|
||||
"https://upload.wikimedia.org/wikipedia/commons/thumb/8/8b/Blue_Fire_launch.jpg/1280px-Blue_Fire_launch.jpg"
|
||||
],
|
||||
"stats": {
|
||||
"height_ft": 125,
|
||||
"length_ft": 3465,
|
||||
@@ -285,4 +374,4 @@
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ from faker import Faker
|
||||
import requests
|
||||
from io import BytesIO
|
||||
from PIL import Image
|
||||
from cities_light.models import City, Country
|
||||
|
||||
from parks.models import Park
|
||||
from rides.models import Ride, RollerCoasterStats
|
||||
@@ -30,25 +31,30 @@ class Command(BaseCommand):
|
||||
parser.add_argument('--users', type=int, default=50)
|
||||
parser.add_argument('--reviews-per-item', type=int, default=10)
|
||||
|
||||
def download_and_save_image(self, url, prefix):
|
||||
def download_and_save_image(self, url):
|
||||
try:
|
||||
response = requests.get(url)
|
||||
img = Image.open(BytesIO(response.content))
|
||||
img_io = BytesIO()
|
||||
img.save(img_io, format='JPEG')
|
||||
img_io.seek(0)
|
||||
return f'{prefix}_{fake.uuid4()}.jpg', File(img_io)
|
||||
except:
|
||||
filename = url.split('/')[-1]
|
||||
return filename, File(img_io)
|
||||
except Exception as e:
|
||||
self.stdout.write(self.style.WARNING(f'Failed to download image {url}: {str(e)}'))
|
||||
return None, None
|
||||
|
||||
def create_users(self, count):
|
||||
self.stdout.write('Creating users...')
|
||||
users = []
|
||||
|
||||
# Get existing admin user
|
||||
admin_user = User.objects.get(username='admin')
|
||||
users.append(admin_user)
|
||||
self.stdout.write('Added existing admin user')
|
||||
try:
|
||||
# Get existing admin user
|
||||
admin_user = User.objects.get(username='admin')
|
||||
users.append(admin_user)
|
||||
self.stdout.write('Added existing admin user')
|
||||
except User.DoesNotExist:
|
||||
self.stdout.write(self.style.WARNING('Admin user not found, skipping...'))
|
||||
|
||||
# Create regular users using raw SQL
|
||||
roles = ['USER'] * 20 + ['MODERATOR'] * 3 + ['ADMIN'] * 2
|
||||
@@ -232,72 +238,87 @@ class Command(BaseCommand):
|
||||
|
||||
parks = []
|
||||
for park_data in seed_data['parks']:
|
||||
# Create park with company instance
|
||||
park = Park.objects.create(
|
||||
name=park_data['name'],
|
||||
slug=slugify(park_data['name']),
|
||||
location=park_data['location'],
|
||||
country=park_data['country'],
|
||||
opening_date=datetime.strptime(park_data['opening_date'], '%Y-%m-%d').date(),
|
||||
status=park_data['status'],
|
||||
description=park_data['description'],
|
||||
website=park_data['website'],
|
||||
owner=companies[park_data['owner']],
|
||||
size_acres=park_data['size_acres']
|
||||
)
|
||||
|
||||
# Add park photos
|
||||
for _ in range(random.randint(2, 5)):
|
||||
img_url = f'https://picsum.photos/800/600?random={fake.random_number(5)}'
|
||||
filename, file = self.download_and_save_image(img_url, 'park')
|
||||
if filename and file:
|
||||
Photo.objects.create(
|
||||
content_object=park,
|
||||
image=file,
|
||||
uploaded_by=random.choice(users),
|
||||
caption=fake.sentence(),
|
||||
is_approved=True
|
||||
)
|
||||
|
||||
# Create rides for this park
|
||||
for ride_data in park_data['rides']:
|
||||
ride = Ride.objects.create(
|
||||
name=ride_data['name'],
|
||||
slug=slugify(ride_data['name']),
|
||||
category=ride_data['category'],
|
||||
park=park,
|
||||
status=ride_data['status'],
|
||||
opening_date=datetime.strptime(ride_data['opening_date'], '%Y-%m-%d').date(),
|
||||
manufacturer=manufacturers[ride_data['manufacturer']],
|
||||
description=ride_data['description']
|
||||
try:
|
||||
# Get country from cities_light
|
||||
country = Country.objects.get(code2=park_data['country'])
|
||||
|
||||
# Try to find city, but don't require it
|
||||
city = None
|
||||
try:
|
||||
city_name = park_data['location'].split(',')[0].strip()
|
||||
city = City.objects.filter(name__iexact=city_name, country=country).first()
|
||||
except:
|
||||
self.stdout.write(self.style.WARNING(f'City not found for {park_data["name"]}, using location text'))
|
||||
|
||||
# Create park
|
||||
park = Park.objects.create(
|
||||
name=park_data['name'],
|
||||
slug=slugify(park_data['name']),
|
||||
location=park_data['location'],
|
||||
country=country,
|
||||
city=city,
|
||||
opening_date=datetime.strptime(park_data['opening_date'], '%Y-%m-%d').date(),
|
||||
status=park_data['status'],
|
||||
description=park_data['description'],
|
||||
website=park_data['website'],
|
||||
owner=companies[park_data['owner']],
|
||||
size_acres=park_data['size_acres']
|
||||
)
|
||||
|
||||
# Add roller coaster stats if applicable
|
||||
if ride_data['category'] == 'RC' and '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']
|
||||
)
|
||||
|
||||
# Add ride photos
|
||||
for _ in range(random.randint(2, 5)):
|
||||
img_url = f'https://picsum.photos/800/600?random={fake.random_number(5)}'
|
||||
filename, file = self.download_and_save_image(img_url, 'ride')
|
||||
# Add park photos
|
||||
for photo_url in park_data.get('photos', []):
|
||||
filename, file = self.download_and_save_image(photo_url)
|
||||
if filename and file:
|
||||
Photo.objects.create(
|
||||
content_object=ride,
|
||||
content_object=park,
|
||||
image=file,
|
||||
uploaded_by=random.choice(users),
|
||||
caption=fake.sentence(),
|
||||
caption=f"Photo of {park.name}",
|
||||
is_approved=True
|
||||
)
|
||||
|
||||
# Create rides for this park
|
||||
for ride_data in park_data['rides']:
|
||||
ride = Ride.objects.create(
|
||||
name=ride_data['name'],
|
||||
slug=slugify(ride_data['name']),
|
||||
category=ride_data['category'],
|
||||
park=park,
|
||||
status=ride_data['status'],
|
||||
opening_date=datetime.strptime(ride_data['opening_date'], '%Y-%m-%d').date(),
|
||||
manufacturer=manufacturers[ride_data['manufacturer']],
|
||||
description=ride_data['description']
|
||||
)
|
||||
|
||||
# Add roller coaster stats if applicable
|
||||
if ride_data['category'] == 'RC' and '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']
|
||||
)
|
||||
|
||||
# Add ride photos
|
||||
for photo_url in ride_data.get('photos', []):
|
||||
filename, file = self.download_and_save_image(photo_url)
|
||||
if filename and file:
|
||||
Photo.objects.create(
|
||||
content_object=ride,
|
||||
image=file,
|
||||
uploaded_by=random.choice(users),
|
||||
caption=f"Photo of {ride.name}",
|
||||
is_approved=True
|
||||
)
|
||||
|
||||
parks.append(park)
|
||||
self.stdout.write(f'Created park and rides: {park.name}')
|
||||
|
||||
parks.append(park)
|
||||
self.stdout.write(f'Created park and rides: {park.name}')
|
||||
except Exception as e:
|
||||
self.stdout.write(self.style.ERROR(f'Failed to create park {park_data["name"]}: {str(e)}'))
|
||||
continue
|
||||
|
||||
return parks
|
||||
|
||||
|
||||
17
parks/migrations/0007_fix_historical_park_city_null.py
Normal file
17
parks/migrations/0007_fix_historical_park_city_null.py
Normal file
@@ -0,0 +1,17 @@
|
||||
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'),
|
||||
),
|
||||
]
|
||||
43
parks/migrations/0008_fix_historical_park_data.py
Normal file
43
parks/migrations/0008_fix_historical_park_data.py
Normal file
@@ -0,0 +1,43 @@
|
||||
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),
|
||||
]
|
||||
33
parks/migrations/0009_fix_historical_park_fields.py
Normal file
33
parks/migrations/0009_fix_historical_park_fields.py
Normal file
@@ -0,0 +1,33 @@
|
||||
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'),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,34 @@
|
||||
# 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"
|
||||
),
|
||||
),
|
||||
]
|
||||
72
parks/migrations/0010_fix_historical_records.py
Normal file
72
parks/migrations/0010_fix_historical_records.py
Normal file
@@ -0,0 +1,72 @@
|
||||
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),
|
||||
),
|
||||
]
|
||||
52
parks/migrations/0011_alter_historicalpark_fields.py
Normal file
52
parks/migrations/0011_alter_historicalpark_fields.py
Normal file
@@ -0,0 +1,52 @@
|
||||
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),
|
||||
),
|
||||
]
|
||||
13
parks/migrations/0011_merge_20241031_1617.py
Normal file
13
parks/migrations/0011_merge_20241031_1617.py
Normal file
@@ -0,0 +1,13 @@
|
||||
# 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 = []
|
||||
13
parks/migrations/0012_merge_20241031_1635.py
Normal file
13
parks/migrations/0012_merge_20241031_1635.py
Normal file
@@ -0,0 +1,13 @@
|
||||
# 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 = []
|
||||
67
parks/migrations/0013_fix_null_locations.py
Normal file
67
parks/migrations/0013_fix_null_locations.py
Normal file
@@ -0,0 +1,67 @@
|
||||
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),
|
||||
]
|
||||
52
parks/migrations/0014_alter_location_fields.py
Normal file
52
parks/migrations/0014_alter_location_fields.py
Normal file
@@ -0,0 +1,52 @@
|
||||
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),
|
||||
),
|
||||
]
|
||||
16
parks/migrations/0015_fix_historical_park_city_constraint.py
Normal file
16
parks/migrations/0015_fix_historical_park_city_constraint.py
Normal file
@@ -0,0 +1,16 @@
|
||||
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),
|
||||
]
|
||||
22
parks/migrations/0016_alter_historicalpark_city_nullable.py
Normal file
22
parks/migrations/0016_alter_historicalpark_city_nullable.py
Normal file
@@ -0,0 +1,22 @@
|
||||
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'),
|
||||
),
|
||||
]
|
||||
22
parks/migrations/0017_fix_historicalpark_city_column.py
Normal file
22
parks/migrations/0017_fix_historicalpark_city_column.py
Normal file
@@ -0,0 +1,22 @@
|
||||
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;'
|
||||
),
|
||||
]
|
||||
48
parks/migrations/0018_fix_historicalpark_location_fields.py
Normal file
48
parks/migrations/0018_fix_historicalpark_location_fields.py
Normal file
@@ -0,0 +1,48 @@
|
||||
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'
|
||||
),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,15 @@
|
||||
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;'
|
||||
),
|
||||
]
|
||||
16
parks/migrations/0020_remove_historicalpark_city_text.py
Normal file
16
parks/migrations/0020_remove_historicalpark_city_text.py
Normal file
@@ -0,0 +1,16 @@
|
||||
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);'
|
||||
),
|
||||
]
|
||||
@@ -11,7 +11,7 @@ class Park(models.Model):
|
||||
('CLOSED_PERM', 'Permanently Closed'),
|
||||
('UNDER_CONSTRUCTION', 'Under Construction'),
|
||||
('DEMOLISHED', 'Demolished'),
|
||||
('RELOCATED', 'Relocated'), # Added to match Ride model
|
||||
('RELOCATED', 'Relocated'),
|
||||
]
|
||||
|
||||
name = models.CharField(max_length=255)
|
||||
@@ -84,6 +84,9 @@ class Park(models.Model):
|
||||
|
||||
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:
|
||||
|
||||
@@ -11,6 +11,7 @@ urlpatterns = [
|
||||
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')),
|
||||
]
|
||||
|
||||
157
parks/views.py
157
parks/views.py
@@ -1,48 +1,76 @@
|
||||
from django.views.generic import DetailView, ListView, CreateView
|
||||
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
|
||||
from django.urls import reverse
|
||||
from django.db.models import Q
|
||||
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
|
||||
from .models import Park, ParkArea
|
||||
from .forms import ParkForm
|
||||
from rides.models import Ride
|
||||
from core.views import SlugRedirectMixin
|
||||
from moderation.mixins import EditSubmissionMixin, PhotoSubmissionMixin, InlineEditMixin, HistoryMixin
|
||||
from moderation.mixins import EditSubmissionMixin, PhotoSubmissionMixin, HistoryMixin
|
||||
from moderation.models import EditSubmission
|
||||
from cities_light.models import Country, Region, City
|
||||
|
||||
def get_countries(request):
|
||||
query = request.GET.get('q', '')
|
||||
countries = Country.objects.filter(name__icontains=query).values_list('name', flat=True)[:10]
|
||||
filter_parks = request.GET.get('filter_parks', 'false') == 'true'
|
||||
|
||||
# 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
|
||||
).values_list('name', flat=True)[:10]
|
||||
)
|
||||
|
||||
# Only filter by parks if explicitly requested
|
||||
if filter_parks:
|
||||
regions = regions.filter(park__isnull=False)
|
||||
|
||||
regions = regions.distinct().values('id', 'name')[:10]
|
||||
return JsonResponse(list(regions), safe=False)
|
||||
|
||||
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'
|
||||
|
||||
if not region or not country:
|
||||
return JsonResponse([], safe=False)
|
||||
|
||||
# Base query
|
||||
cities = City.objects.filter(
|
||||
Q(name__icontains=query) | Q(alternate_names__icontains=query),
|
||||
region__name__iexact=region,
|
||||
region__country__name__iexact=country
|
||||
).values_list('name', flat=True)[:10]
|
||||
)
|
||||
|
||||
# Only filter by parks if explicitly requested
|
||||
if filter_parks:
|
||||
cities = cities.filter(park__isnull=False)
|
||||
|
||||
cities = cities.distinct().values('id', 'name')[:10]
|
||||
return JsonResponse(list(cities), safe=False)
|
||||
|
||||
class ParkCreateView(LoginRequiredMixin, CreateView):
|
||||
@@ -50,38 +78,111 @@ class ParkCreateView(LoginRequiredMixin, CreateView):
|
||||
form_class = ParkForm
|
||||
template_name = 'parks/park_form.html'
|
||||
|
||||
def form_valid(self, form):
|
||||
# If user is moderator or above, save directly
|
||||
if self.request.user.role in ['MODERATOR', 'ADMIN', 'SUPERUSER']:
|
||||
self.object = form.save()
|
||||
return HttpResponseRedirect(self.get_success_url())
|
||||
|
||||
# Otherwise, create a submission
|
||||
cleaned_data = form.cleaned_data.copy()
|
||||
def prepare_changes_data(self, cleaned_data):
|
||||
data = cleaned_data.copy()
|
||||
# Convert model instances to IDs for JSON serialization
|
||||
if cleaned_data.get('owner'):
|
||||
cleaned_data['owner'] = cleaned_data['owner'].id
|
||||
if cleaned_data.get('country'):
|
||||
cleaned_data['country'] = cleaned_data['country'].id
|
||||
if cleaned_data.get('region'):
|
||||
cleaned_data['region'] = cleaned_data['region'].id
|
||||
if cleaned_data.get('city'):
|
||||
cleaned_data['city'] = cleaned_data['city'].id
|
||||
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),
|
||||
submission_type='CREATE',
|
||||
changes=cleaned_data,
|
||||
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.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())
|
||||
|
||||
messages.success(self.request, 'Your park submission has been sent for review')
|
||||
return HttpResponseRedirect(reverse('parks:park_list'))
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse('parks:park_detail', kwargs={'slug': self.object.slug})
|
||||
|
||||
class ParkDetailView(SlugRedirectMixin, EditSubmissionMixin, PhotoSubmissionMixin, InlineEditMixin, HistoryMixin, DetailView):
|
||||
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
|
||||
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})
|
||||
|
||||
class ParkDetailView(SlugRedirectMixin, EditSubmissionMixin, PhotoSubmissionMixin, HistoryMixin, DetailView):
|
||||
model = Park
|
||||
template_name = 'parks/park_detail.html'
|
||||
context_object_name = 'park'
|
||||
@@ -104,7 +205,7 @@ class ParkDetailView(SlugRedirectMixin, EditSubmissionMixin, PhotoSubmissionMixi
|
||||
def get_redirect_url_pattern(self):
|
||||
return 'parks:park_detail'
|
||||
|
||||
class ParkAreaDetailView(SlugRedirectMixin, EditSubmissionMixin, PhotoSubmissionMixin, InlineEditMixin, HistoryMixin, DetailView):
|
||||
class ParkAreaDetailView(SlugRedirectMixin, EditSubmissionMixin, PhotoSubmissionMixin, HistoryMixin, DetailView):
|
||||
model = ParkArea
|
||||
template_name = 'parks/area_detail.html'
|
||||
context_object_name = 'area'
|
||||
@@ -149,7 +250,7 @@ class ParkListView(ListView):
|
||||
country = self.request.GET.get('country', '').strip()
|
||||
region = self.request.GET.get('region', '').strip()
|
||||
city = self.request.GET.get('city', '').strip()
|
||||
status = self.request.GET.get('status', '').strip()
|
||||
statuses = self.request.GET.getlist('status')
|
||||
|
||||
if search:
|
||||
queryset = queryset.filter(
|
||||
@@ -166,8 +267,8 @@ class ParkListView(ListView):
|
||||
if city:
|
||||
queryset = queryset.filter(city__name__icontains=city)
|
||||
|
||||
if status:
|
||||
queryset = queryset.filter(status=status)
|
||||
if statuses:
|
||||
queryset = queryset.filter(status__in=statuses)
|
||||
|
||||
return queryset
|
||||
|
||||
@@ -178,7 +279,7 @@ class ParkListView(ListView):
|
||||
'country': self.request.GET.get('country', ''),
|
||||
'region': self.request.GET.get('region', ''),
|
||||
'city': self.request.GET.get('city', ''),
|
||||
'status': self.request.GET.get('status', '')
|
||||
'statuses': self.request.GET.getlist('status')
|
||||
}
|
||||
return context
|
||||
|
||||
|
||||
Reference in New Issue
Block a user