This commit is contained in:
pacnpal
2024-10-31 17:27:31 +00:00
parent c1591af871
commit 71272e36a6
10 changed files with 119 additions and 78 deletions

View File

@@ -66,17 +66,7 @@ class Park(models.Model):
self.slug = slugify(self.name) self.slug = slugify(self.name)
# Update the location field to combine country, region, and city # Update the location field to combine country, region, and city
location_parts = [] self.location = self.get_formatted_location()
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) super().save(*args, **kwargs)
@@ -93,15 +83,15 @@ class Park(models.Model):
raise cls.DoesNotExist("No park found with this slug") raise cls.DoesNotExist("No park found with this slug")
def get_formatted_location(self): def get_formatted_location(self):
"""Get a formatted location string combining city, region, and country""" """Get a formatted location string: $COUNTRY, $REGION, $CITY"""
location_parts = [] location = self.country.name
if self.city:
location_parts.append(self.city.name) if self.region and self.city:
if self.region: location += f", {self.region.name}, {self.city.name}"
location_parts.append(self.region.name) elif self.region:
if self.country: location += f", {self.region.name}"
location_parts.append(self.country.name)
return ', '.join(location_parts) return location
class ParkArea(models.Model): class ParkArea(models.Model):
name = models.CharField(max_length=255) name = models.CharField(max_length=255)

View File

@@ -11,7 +11,6 @@ urlpatterns = [
path('ajax/countries/', views.get_countries, name='get_countries'), path('ajax/countries/', views.get_countries, name='get_countries'),
path('ajax/regions/', views.get_regions, name='get_regions'), path('ajax/regions/', views.get_regions, name='get_regions'),
path('ajax/cities/', views.get_cities, name='get_cities'), 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:slug>/', views.ParkDetailView.as_view(), name='park_detail'),
path('<slug:park_slug>/rides/', include('rides.urls', namespace='rides')), path('<slug:park_slug>/rides/', include('rides.urls', namespace='rides')),
] ]

View File

@@ -45,24 +45,6 @@ def get_cities(request):
).values_list('name', flat=True)[:10] ).values_list('name', flat=True)[:10]
return JsonResponse(list(cities), safe=False) 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): class ParkCreateView(LoginRequiredMixin, CreateView):
model = Park model = Park
form_class = ParkForm form_class = ParkForm
@@ -163,26 +145,27 @@ class ParkListView(ListView):
def get_queryset(self): def get_queryset(self):
queryset = Park.objects.select_related('owner', 'country', 'region', 'city').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 search = self.request.GET.get('search', '').strip()
location = self.request.GET.get('location', '').strip() or None country = self.request.GET.get('country', '').strip()
status = self.request.GET.get('status', '').strip() or None region = self.request.GET.get('region', '').strip()
city = self.request.GET.get('city', '').strip()
status = self.request.GET.get('status', '').strip()
if search: if search:
queryset = queryset.filter( queryset = queryset.filter(
Q(name__icontains=search) | 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:
# 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 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 status: if status:
queryset = queryset.filter(status=status) queryset = queryset.filter(status=status)
@@ -192,7 +175,9 @@ class ParkListView(ListView):
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)
context['current_filters'] = { context['current_filters'] = {
'search': self.request.GET.get('search', ''), 'search': self.request.GET.get('search', ''),
'location': self.request.GET.get('location', ''), 'country': self.request.GET.get('country', ''),
'region': self.request.GET.get('region', ''),
'city': self.request.GET.get('city', ''),
'status': self.request.GET.get('status', '') 'status': self.request.GET.get('status', '')
} }
return context return context

View File

@@ -3491,6 +3491,10 @@ select {
grid-template-columns: repeat(4, minmax(0, 1fr)); grid-template-columns: repeat(4, minmax(0, 1fr));
} }
.lg\:grid-cols-5 {
grid-template-columns: repeat(5, minmax(0, 1fr));
}
.lg\:text-6xl { .lg\:text-6xl {
font-size: 3.75rem; font-size: 3.75rem;
line-height: 1; line-height: 1;

View File

@@ -18,8 +18,7 @@
data-field-name="name">{{ park.name }}</h1> data-field-name="name">{{ park.name }}</h1>
<p class="text-gray-600 dark:text-gray-300"> <p class="text-gray-600 dark:text-gray-300">
<i class="mr-2 fas fa-map-marker-alt"></i> <i class="mr-2 fas fa-map-marker-alt"></i>
<span data-editable data-content-id="{{ park.id }}" <span>{{ park.get_formatted_location }}</span>
data-field-name="location">{{ park.location }}</span>
</p> </p>
</div> </div>
<div class="flex gap-2 mt-4 md:mt-0"> <div class="flex gap-2 mt-4 md:mt-0">
@@ -201,12 +200,10 @@
<div class="p-4 transition-transform transform rounded-lg bg-gray-50 dark:bg-gray-700/50 hover:-translate-y-1"> <div class="p-4 transition-transform transform rounded-lg bg-gray-50 dark:bg-gray-700/50 hover:-translate-y-1">
<dt class="flex items-center mb-1 text-gray-600 dark:text-gray-300"> <dt class="flex items-center mb-1 text-gray-600 dark:text-gray-300">
<i class="w-5 text-blue-500 fas fa-globe dark:text-blue-400"></i> <i class="w-5 text-blue-500 fas fa-globe dark:text-blue-400"></i>
<span class="ml-2">Country</span> <span class="ml-2">Location</span>
</dt> </dt>
<dd class="font-medium text-gray-900 dark:text-white" <dd class="font-medium text-gray-900 dark:text-white">
data-editable data-content-id="{{ park.id }}" {{ park.get_formatted_location }}
data-field-name="country">
{{ park.get_country_name }}
</dd> </dd>
</div> </div>
{% if park.opening_date %} {% if park.opening_date %}

View File

@@ -16,9 +16,9 @@
<!-- Filters --> <!-- Filters -->
<div class="p-4 mb-6 bg-white rounded-lg shadow dark:bg-gray-800"> <div class="p-4 mb-6 bg-white rounded-lg shadow dark:bg-gray-800">
<form class="grid grid-cols-1 gap-4 md:grid-cols-3" <form id="park-filters" class="grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-5"
hx-get="{% url 'parks:park_list' %}" hx-get="{% url 'parks:park_list' %}"
hx-trigger="change from:select, input[type='text'] delay:500ms" hx-trigger="change from:select, input from:input[type='text'] delay:500ms"
hx-target="#parks-grid" hx-target="#parks-grid"
hx-push-url="true"> hx-push-url="true">
<div> <div>
@@ -29,11 +29,25 @@
placeholder="Search parks..."> placeholder="Search parks...">
</div> </div>
<div> <div>
<label for="location" class="block mb-1 text-sm font-medium text-gray-700 dark:text-gray-300">Location</label> <label for="country" class="block mb-1 text-sm font-medium text-gray-700 dark:text-gray-300">Country</label>
<input type="text" name="location" id="location" <input type="text" name="country" id="country"
value="{{ current_filters.location }}" value="{{ current_filters.country }}"
class="w-full border-gray-300 rounded-lg form-input dark:border-gray-600 dark:bg-gray-700 dark:text-white" class="w-full border-gray-300 rounded-lg form-input dark:border-gray-600 dark:bg-gray-700 dark:text-white"
placeholder="Search locations..."> placeholder="Select country...">
</div>
<div>
<label for="region" class="block mb-1 text-sm font-medium text-gray-700 dark:text-gray-300">State/Region</label>
<input type="text" name="region" id="region"
value="{{ current_filters.region }}"
class="w-full border-gray-300 rounded-lg form-input dark:border-gray-600 dark:bg-gray-700 dark:text-white"
placeholder="Select state/region...">
</div>
<div>
<label for="city" class="block mb-1 text-sm font-medium text-gray-700 dark:text-gray-300">City</label>
<input type="text" name="city" id="city"
value="{{ current_filters.city }}"
class="w-full border-gray-300 rounded-lg form-input dark:border-gray-600 dark:bg-gray-700 dark:text-white"
placeholder="Select city...">
</div> </div>
<div> <div>
<label for="status" class="block mb-1 text-sm font-medium text-gray-700 dark:text-gray-300">Status</label> <label for="status" class="block mb-1 text-sm font-medium text-gray-700 dark:text-gray-300">Status</label>
@@ -84,27 +98,79 @@
<script src="https://cdnjs.cloudflare.com/ajax/libs/awesomplete/1.1.5/awesomplete.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/awesomplete/1.1.5/awesomplete.min.js"></script>
<script> <script>
document.addEventListener('DOMContentLoaded', function() { document.addEventListener('DOMContentLoaded', function() {
var locationInput = document.getElementById('location'); const countryInput = document.getElementById('country');
if (locationInput) { const regionInput = document.getElementById('region');
var locationList = new Awesomplete(locationInput, { const cityInput = document.getElementById('city');
// Initialize Awesomplete for country
if (countryInput) {
const countryList = new Awesomplete(countryInput, {
minChars: 1, minChars: 1,
maxItems: 10, maxItems: 10,
autoFirst: true autoFirst: true
}); });
locationInput.addEventListener('input', function() { countryInput.addEventListener('input', function() {
fetch(`/parks/ajax/locations/?q=${encodeURIComponent(this.value)}`) fetch(`/parks/ajax/countries/?q=${encodeURIComponent(this.value)}`)
.then(response => response.json()) .then(response => response.json())
.then(data => { .then(data => {
locationList.list = data; countryList.list = data;
}); });
}); });
}
// Trigger HTMX request when a location is selected // Initialize Awesomplete for region
locationInput.addEventListener('awesomplete-select', function(event) { if (regionInput) {
htmx.trigger(this, 'change'); const regionList = new Awesomplete(regionInput, {
minChars: 1,
maxItems: 10,
autoFirst: true
});
regionInput.addEventListener('input', function() {
const country = countryInput.value;
fetch(`/parks/ajax/regions/?q=${encodeURIComponent(this.value)}&country=${encodeURIComponent(country)}`)
.then(response => response.json())
.then(data => {
regionList.list = data;
});
}); });
} }
// Initialize Awesomplete for city
if (cityInput) {
const cityList = new Awesomplete(cityInput, {
minChars: 1,
maxItems: 10,
autoFirst: true
});
cityInput.addEventListener('input', function() {
const country = countryInput.value;
const region = regionInput.value;
fetch(`/parks/ajax/cities/?q=${encodeURIComponent(this.value)}&country=${encodeURIComponent(country)}&region=${encodeURIComponent(region)}`)
.then(response => response.json())
.then(data => {
cityList.list = data;
});
});
}
// Handle location link clicks
document.querySelectorAll('.location-link').forEach(link => {
link.addEventListener('click', function(e) {
e.preventDefault();
const params = new URLSearchParams(this.getAttribute('href').split('?')[1]);
// Update form inputs
countryInput.value = params.get('country') || '';
regionInput.value = params.get('region') || '';
cityInput.value = params.get('city') || '';
// Trigger form submission
htmx.trigger('#park-filters', 'change');
});
});
}); });
</script> </script>
{% endblock %} {% endblock %}

View File

@@ -18,7 +18,7 @@
</h2> </h2>
<p class="mb-3 text-gray-600 dark:text-gray-400"> <p class="mb-3 text-gray-600 dark:text-gray-400">
<i class="mr-1 fas fa-map-marker-alt"></i> <i class="mr-1 fas fa-map-marker-alt"></i>
{{ park.location }} {{ park.get_formatted_location }}
</p> </p>
<div class="flex flex-wrap gap-2"> <div class="flex flex-wrap gap-2">
<span class="status-badge {% if park.status == 'OPERATING' %}status-operating <span class="status-badge {% if park.status == 'OPERATING' %}status-operating