fixed the damn discord button

This commit is contained in:
pacnpal
2024-10-31 16:13:05 +00:00
parent 0075f7da6c
commit c1591af871
31 changed files with 1184 additions and 500 deletions

125
parks/forms.py Normal file
View File

@@ -0,0 +1,125 @@
from django import forms
from django.urls import reverse_lazy
from .models import Park
from cities_light.models import Country, Region, City
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
country_name = forms.CharField(
label="Country",
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...',
})
)
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...',
})
)
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...',
})
)
class Meta:
model = Park
fields = ['name', 'country', 'region', 'city', 'description', 'owner', 'status',
'opening_date', 'closing_date', 'operating_season', 'size_acres', 'website']
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'
}),
'owner': forms.Select(attrs={
'class': 'w-full border-gray-300 rounded-lg form-select dark:border-gray-600 dark:bg-gray-700 dark:text-white'
}),
'status': forms.Select(attrs={
'class': 'w-full border-gray-300 rounded-lg form-select dark:border-gray-600 dark:bg-gray-700 dark:text-white'
}),
'opening_date': forms.DateInput(attrs={
'type': 'date',
'class': 'w-full border-gray-300 rounded-lg form-input dark:border-gray-600 dark:bg-gray-700 dark:text-white'
}),
'closing_date': forms.DateInput(attrs={
'type': 'date',
'class': 'w-full border-gray-300 rounded-lg form-input dark:border-gray-600 dark:bg-gray-700 dark:text-white'
}),
'operating_season': forms.TextInput(attrs={
'class': 'w-full border-gray-300 rounded-lg form-input dark:border-gray-600 dark:bg-gray-700 dark:text-white',
'placeholder': 'e.g., Year-round, Summer only, etc.'
}),
'size_acres': forms.NumberInput(attrs={
'class': 'w-full border-gray-300 rounded-lg form-input dark:border-gray-600 dark:bg-gray-700 dark:text-white',
'step': '0.01',
'min': '0'
}),
'website': forms.URLInput(attrs={
'class': 'w-full border-gray-300 rounded-lg form-input dark:border-gray-600 dark:bg-gray-700 dark:text-white',
'placeholder': 'https://example.com'
}),
}
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
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
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')
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')
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:
self.add_error('region_name', 'Invalid region name for selected country')
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:
self.add_error('city_name', 'Invalid city name for selected region')
return cleaned_data

View File

@@ -0,0 +1,37 @@
# 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
),
),
]

View File

@@ -0,0 +1,23 @@
# 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),
),
]

View File

@@ -0,0 +1,125 @@
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'),
),
]

View File

@@ -2,7 +2,7 @@ from django.db import models
from django.contrib.contenttypes.fields import GenericRelation
from django.utils.text import slugify
from simple_history.models import HistoricalRecords
import pycountry
from cities_light.models import Country, Region, City
class Park(models.Model):
STATUS_CHOICES = [
@@ -17,7 +17,9 @@ class Park(models.Model):
name = models.CharField(max_length=255)
slug = models.SlugField(max_length=255, unique=True)
location = models.CharField(max_length=255)
country = models.CharField(max_length=2, help_text='Two-letter country code (ISO 3166-1 alpha-2)')
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',
@@ -62,6 +64,20 @@ class Park(models.Model):
def save(self, *args, **kwargs):
if not self.slug:
self.slug = slugify(self.name)
# Update the location field to combine country, region, and city
location_parts = []
if self.city:
location_parts.append(self.city.name)
if self.region:
location_parts.append(self.region.name)
if self.country:
location_parts.append(self.country.name)
# Only update location if we have parts to combine
if location_parts:
self.location = ', '.join(location_parts)
super().save(*args, **kwargs)
@classmethod
@@ -76,13 +92,16 @@ class Park(models.Model):
return cls.objects.get(id=history.id), True
raise cls.DoesNotExist("No park found with this slug")
def get_country_name(self):
"""Get the full country name from the country code"""
try:
country = pycountry.countries.get(alpha_2=self.country)
return country.name if country else self.country
except:
return self.country
def get_formatted_location(self):
"""Get a formatted location string combining city, region, and country"""
location_parts = []
if self.city:
location_parts.append(self.city.name)
if self.region:
location_parts.append(self.region.name)
if self.country:
location_parts.append(self.country.name)
return ', '.join(location_parts)
class ParkArea(models.Model):
name = models.CharField(max_length=255)

View File

@@ -8,8 +8,10 @@ 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('countries/search/', views.search_countries, name='search_countries'),
path('countries/select/', views.select_country, name='select_country'),
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('ajax/locations/', views.get_locations, name='get_locations'),
path('<slug:slug>/', views.ParkDetailView.as_view(), name='park_detail'),
path('<slug:park_slug>/rides/', include('rides.urls', namespace='rides')),
]

View File

@@ -7,17 +7,66 @@ from django.contrib.auth.mixins import LoginRequiredMixin
from django.contrib.contenttypes.models import ContentType
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.models import EditSubmission
import pycountry
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]
return JsonResponse(list(countries), safe=False)
def get_regions(request):
query = request.GET.get('q', '')
country = request.GET.get('country', '')
if not country:
return JsonResponse([], safe=False)
regions = Region.objects.filter(
Q(name__icontains=query) | Q(alternate_names__icontains=query),
country__name__iexact=country
).values_list('name', flat=True)[: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', '')
if not region or not country:
return JsonResponse([], safe=False)
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]
return JsonResponse(list(cities), safe=False)
def get_locations(request):
query = request.GET.get('q', '')
locations = set()
# Search countries
countries = Country.objects.filter(name__icontains=query).values_list('name', flat=True)[:5]
locations.update(countries)
# Search regions
regions = Region.objects.filter(name__icontains=query).values_list('name', flat=True)[:5]
locations.update(regions)
# Search cities
cities = City.objects.filter(name__icontains=query).values_list('name', flat=True)[:5]
locations.update(cities)
return JsonResponse(list(locations), safe=False)
class ParkCreateView(LoginRequiredMixin, CreateView):
model = Park
form_class = ParkForm
template_name = 'parks/park_form.html'
fields = ['name', 'location', 'country', 'description', 'owner', 'status',
'opening_date', 'closing_date', 'operating_season', 'size_acres', 'website']
def form_valid(self, form):
# If user is moderator or above, save directly
@@ -30,6 +79,12 @@ class ParkCreateView(LoginRequiredMixin, CreateView):
# 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
submission = EditSubmission.objects.create(
user=self.request.user,
@@ -39,41 +94,10 @@ class ParkCreateView(LoginRequiredMixin, CreateView):
reason=self.request.POST.get('reason', ''),
source=self.request.POST.get('source', '')
)
return HttpResponseRedirect(reverse('park_list'))
return HttpResponseRedirect(reverse('parks:park_list'))
def get_success_url(self):
return reverse('park_detail', kwargs={'slug': self.object.slug})
def search_countries(request):
query = request.GET.get('q', '').strip()
countries = []
if query:
# Use pycountry's search functionality for fuzzy matching
try:
# Try exact search first
country = pycountry.countries.get(name=query)
if country:
countries = [country]
else:
# If no exact match, try fuzzy search
countries = pycountry.countries.search_fuzzy(query)
except LookupError:
# If search fails, fallback to manual filtering
countries = [
country for country in pycountry.countries
if query.lower() in country.name.lower()
]
return render(request, 'parks/partials/country_search_results.html', {
'countries': countries[:10] # Limit to top 10 results
})
def select_country(request):
if request.method == 'POST':
country = request.POST.get('country', '')
return HttpResponse(country)
return HttpResponse('Invalid request', status=400)
return reverse('parks:park_detail', kwargs={'slug': self.object.slug})
class ParkDetailView(SlugRedirectMixin, EditSubmissionMixin, PhotoSubmissionMixin, InlineEditMixin, HistoryMixin, DetailView):
model = Park
@@ -96,7 +120,7 @@ class ParkDetailView(SlugRedirectMixin, EditSubmissionMixin, PhotoSubmissionMixi
return context
def get_redirect_url_pattern(self):
return 'park_detail'
return 'parks:park_detail'
class ParkAreaDetailView(SlugRedirectMixin, EditSubmissionMixin, PhotoSubmissionMixin, InlineEditMixin, HistoryMixin, DetailView):
model = ParkArea
@@ -123,7 +147,7 @@ class ParkAreaDetailView(SlugRedirectMixin, EditSubmissionMixin, PhotoSubmission
return context
def get_redirect_url_pattern(self):
return 'park_detail'
return 'parks:park_detail'
def get_redirect_url_kwargs(self):
return {
@@ -137,7 +161,7 @@ class ParkListView(ListView):
context_object_name = 'parks'
def get_queryset(self):
queryset = Park.objects.select_related('owner').prefetch_related('photos', 'rides')
queryset = Park.objects.select_related('owner', 'country', 'region', 'city').prefetch_related('photos', 'rides')
search = self.request.GET.get('search', '').strip() or None
location = self.request.GET.get('location', '').strip() or None
@@ -146,10 +170,19 @@ class ParkListView(ListView):
if search:
queryset = queryset.filter(
Q(name__icontains=search) |
Q(location__icontains=search)
Q(location__icontains=search) |
Q(country__name__icontains=search) |
Q(region__name__icontains=search) |
Q(city__name__icontains=search)
)
if location:
queryset = queryset.filter(location=location)
# Try to match against the formatted location or any location field
queryset = queryset.filter(
Q(location__icontains=location) |
Q(country__name__icontains=location) |
Q(region__name__icontains=location) |
Q(city__name__icontains=location)
)
if status:
queryset = queryset.filter(status=status)
@@ -157,18 +190,11 @@ class ParkListView(ListView):
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
# Get unique locations for filter dropdown
context['locations'] = list(Park.objects.values_list('location', flat=True)
.distinct().order_by('location'))
# Add current filter values to context
context['current_filters'] = {
'search': self.request.GET.get('search', ''),
'location': self.request.GET.get('location', ''),
'status': self.request.GET.get('status', '')
}
return context
def get(self, request, *args, **kwargs):