diff --git a/.clinerules/thrillwiki-context.xml b/.clinerules/thrillwiki-context.xml index 685ef1bb..6072ca9f 100644 --- a/.clinerules/thrillwiki-context.xml +++ b/.clinerules/thrillwiki-context.xml @@ -1,4 +1,5 @@ - + + ThrillWiki @@ -209,4 +210,56 @@ Always use uv run for Django management commands All functionality must work with progressive enhancement + + + + ALWAYS use Context7 MCP for documentation lookups before making changes + + Use for CSS utility classes, responsive design, and component styling + Use for models, views, forms, URL patterns, and Django-specific patterns + Use for component creation, template organization, and Cotton-specific syntax + Use for dynamic updates, form handling, and AJAX interactions + Use for client-side state management, reactive data, and JavaScript interactions + Use for API design, serializers, viewsets, and DRF patterns + Use for database queries, PostGIS functions, and advanced SQL features + Use for geographic data handling and spatial queries + Use for caching strategies, session management, and performance optimization + + + Before editing/creating code: Query Context7 for relevant library documentation + During debugging: Use Context7 to verify syntax, patterns, and best practices + When implementing new features: Reference Context7 for current API and method signatures + For performance issues: Consult Context7 for optimization techniques and patterns + For geographic data handling: Use Context7 for PostGIS functions and best practices + For caching strategies: Refer to Context7 for Redis patterns and best practices + For database queries: Utilize Context7 for PostgreSQL best practices and advanced SQL features + + + Creating new Django models or API endpoints + Implementing HTMX dynamic functionality + Writing AlpineJS reactive components + Designing responsive layouts with Tailwind CSS + Creating Django-Cotton components + Debugging CSS, JavaScript, or Django issues + Implementing caching or database optimizations + Handling geographic data with PostGIS + Utilizing Redis for session management + Implementing real-time features with WebSockets + + + Always call Context7:resolve-library-id first to get correct library ID + Then use Context7:get-library-docs with appropriate topic parameter + + responsive design, flexbox, grid, animations + models, views, forms, admin, signals + components, templates, slots, props + hx-get, hx-post, hx-swap, hx-trigger, hx-target + x-data, x-show, x-if, x-for, x-model + serializers, viewsets, routers, permissions + joins, indexes, transactions, window functions + geospatial queries, distance calculations, spatial indexes + caching strategies, pub/sub, data structures + + + \ No newline at end of file diff --git a/.gitignore b/.gitignore index 63e50d58..da51242a 100644 --- a/.gitignore +++ b/.gitignore @@ -122,4 +122,5 @@ frontend/.env django-forwardemail/ frontend/ frontend -.snapshots \ No newline at end of file +.snapshots +uv.lock diff --git a/apps/accounts/migrations/0002_remove_userprofile_insert_insert_and_more.py b/apps/accounts/migrations/0002_remove_userprofile_insert_insert_and_more.py index bb4645a2..265a0684 100644 --- a/apps/accounts/migrations/0002_remove_userprofile_insert_insert_and_more.py +++ b/apps/accounts/migrations/0002_remove_userprofile_insert_insert_and_more.py @@ -10,7 +10,6 @@ class Migration(migrations.Migration): dependencies = [ ("accounts", "0001_initial"), - ("django_cloudflareimages_toolkit", "0001_initial"), ] operations = [ diff --git a/apps/parks/views.py b/apps/parks/views.py index 37912daf..2bd79764 100644 --- a/apps/parks/views.py +++ b/apps/parks/views.py @@ -226,7 +226,7 @@ def reverse_geocode(request: HttpRequest) -> JsonResponse: class ParkListView(HTMXFilterableMixin, ListView): model = Park - template_name = "parks/park_list.html" + template_name = "parks/enhanced_park_list.html" context_object_name = "parks" filter_class = ParkFilter paginate_by = 20 @@ -242,9 +242,9 @@ class ParkListView(HTMXFilterableMixin, ListView): self.paginator_class = OptimizedPaginator def get_template_names(self) -> list[str]: - """Return park_list.html for HTMX requests""" + """Return enhanced park list templates for HTMX requests""" if self.request.htmx: - return ["parks/partials/park_list.html"] + return ["parks/partials/enhanced_park_list.html"] return [self.template_name] def get_view_mode(self) -> ViewMode: diff --git a/config/django/base.py b/config/django/base.py index 3bb5bee2..eec9e121 100644 --- a/config/django/base.py +++ b/config/django/base.py @@ -34,10 +34,10 @@ CACHE_MIDDLEWARE_KEY_PREFIX = config( "CACHE_MIDDLEWARE_KEY_PREFIX", default="thrillwiki" ) GDAL_LIBRARY_PATH = config( - "GDAL_LIBRARY_PATH", default="/nix/store/c5y314zvvrbr9lx4wh06ibl1b5c07x92-gdal-3.11.0/lib/libgdal.so" + "GDAL_LIBRARY_PATH", default="/opt/homebrew/opt/gdal/lib/libgdal.dylib" ) GEOS_LIBRARY_PATH = config( - "GEOS_LIBRARY_PATH", default="/nix/store/r5sgxqxrwfvms97v4v239qbivwsmdfjf-geos-3.13.1/lib/libgeos_c.so" + "GEOS_LIBRARY_PATH", default="/opt/homebrew/opt/geos/lib/libgeos_c.dylib" ) # Build paths inside the project like this: BASE_DIR / 'subdir'. diff --git a/config/django/local.py b/config/django/local.py index d09c6c48..fc969c22 100644 --- a/config/django/local.py +++ b/config/django/local.py @@ -22,8 +22,6 @@ CSRF_TRUSTED_ORIGINS = [ "https://beta.thrillwiki.com", ] -GDAL_LIBRARY_PATH = "/nix/store/c5y314zvvrbr9lx4wh06ibl1b5c07x92-gdal-3.11.0/lib/libgdal.so" -GEOS_LIBRARY_PATH = "/nix/store/r5sgxqxrwfvms97v4v239qbivwsmdfjf-geos-3.13.1/lib/libgeos_c.so" # Local cache configuration LOC_MEM_CACHE_BACKEND = "django.core.cache.backends.locmem.LocMemCache" diff --git a/config/settings/database.py b/config/settings/database.py index 781be7b0..93723a66 100644 --- a/config/settings/database.py +++ b/config/settings/database.py @@ -2,38 +2,68 @@ Database configuration for thrillwiki project. """ -import environ from pathlib import Path +from decouple import config # Build paths inside the project like this: BASE_DIR / 'subdir'. BASE_DIR = Path(__file__).resolve().parent.parent.parent -env = environ.Env( - DATABASE_URL=( - str, - "postgis://thrillwiki_user:thrillwiki@localhost:5432/thrillwiki_test_db", - ), - GDAL_LIBRARY_PATH=(str, "/nix/store/c5y314zvvrbr9lx4wh06ibl1b5c07x92-gdal-3.11.0/lib/libgdal.so"), - GEOS_LIBRARY_PATH=(str, "/nix/store/r5sgxqxrwfvms97v4v239qbivwsmdfjf-geos-3.13.1/lib/libgeos_c.so"), - CACHE_URL=(str, "locmemcache://"), - CACHE_MIDDLEWARE_SECONDS=(int, 300), - CACHE_MIDDLEWARE_KEY_PREFIX=(str, "thrillwiki"), -) +DATABASE_URL=config("DATABASE_URL") +GDAL_LIBRARY_PATH=config("GDAL_LIBRARY_PATH") +GEOS_LIBRARY_PATH=config("GEOS_LIBRARY_PATH") +CACHE_URL=config("CACHE_URL") +CACHE_MIDDLEWARE_SECONDS=config("CACHE_MIDDLEWARE_SECONDS") +CACHE_MIDDLEWARE_KEY_PREFIX=config("CACHE_MIDDLEWARE_KEY_PREFIX") + # Database configuration -db_config = env.db("DATABASE_URL") +db_url = config("DATABASE_URL") + +# Parse the database URL and create proper configuration dictionary +def parse_db_url(url): + # Simple parsing for PostgreSQL URLs + if url.startswith('postgres://') or url.startswith('postgis://'): + # Format: postgres://username:password@host:port/database + # Remove the protocol part + if url.startswith('postgis://'): + url = url.replace('postgis://', '') + elif url.startswith('postgres://'): + url = url.replace('postgres://', '') + # Split the URL into parts + auth_part, rest = url.split('@', 1) + host_port, database = rest.split('/', 1) + + username, password = auth_part.split(':', 1) if ':' in auth_part else (auth_part, '') + host, port = host_port.split(':', 1) if ':' in host_port else (host_port, '5432') + + return { + 'ENGINE': 'django.contrib.gis.db.backends.postgis', + 'NAME': database, + 'USER': username, + 'PASSWORD': password, + 'HOST': host, + 'PORT': port, + } + # Add support for other database types if needed + else: + raise ValueError(f"Unsupported database URL format: {url}") # Switch back to PostgreSQL - GeoDjango issues resolved separately DATABASES = { - "default": db_config, + "default": parse_db_url(db_url), } # GeoDjango Settings - Environment specific with fallbacks -GDAL_LIBRARY_PATH = env("GDAL_LIBRARY_PATH") -GEOS_LIBRARY_PATH = env("GEOS_LIBRARY_PATH") +GDAL_LIBRARY_PATH = config("GDAL_LIBRARY_PATH") +GEOS_LIBRARY_PATH = config("GEOS_LIBRARY_PATH") -# Cache settings -CACHES = {"default": env.cache("CACHE_URL")} +# Cache settings - Use simple local memory cache for development +CACHES = { + 'default': { + 'BACKEND': 'django.core.cache.backends.locmem.LocMemCache', + 'LOCATION': 'unique-snowflake', + } +} -CACHE_MIDDLEWARE_SECONDS = env.int("CACHE_MIDDLEWARE_SECONDS") -CACHE_MIDDLEWARE_KEY_PREFIX = env("CACHE_MIDDLEWARE_KEY_PREFIX") +CACHE_MIDDLEWARE_SECONDS = config("CACHE_MIDDLEWARE_SECONDS") +CACHE_MIDDLEWARE_KEY_PREFIX = config("CACHE_MIDDLEWARE_KEY_PREFIX") diff --git a/static/css/tailwind.css b/static/css/tailwind.css index 74a0f20b..3ca5d879 100644 --- a/static/css/tailwind.css +++ b/static/css/tailwind.css @@ -217,7 +217,7 @@ } html, :host { line-height: 1.5; - -webkit-text-size-adjust: 100%; + -webkit-text-size-adjust: none; tab-size: 4; font-family: var(--default-font-family, ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'); font-feature-settings: var(--default-font-feature-settings, normal); @@ -2466,15 +2466,6 @@ -webkit-user-select: none; user-select: none; } - .\[coverage\:report\] { - coverage: report; - } - .\[coverage\:run\] { - coverage: run; - } - .\[tool\:pytest\] { - tool: pytest; - } .group-hover\:translate-x-1 { &:is(:where(.group):hover *) { @media (hover: hover) { diff --git a/templates/base/base.html b/templates/base/base.html index f88b1251..e316283f 100644 --- a/templates/base/base.html +++ b/templates/base/base.html @@ -66,7 +66,7 @@ - + diff --git a/templates/cotton/advanced_filters.html b/templates/cotton/advanced_filters.html new file mode 100644 index 00000000..11b5084e --- /dev/null +++ b/templates/cotton/advanced_filters.html @@ -0,0 +1,438 @@ +{% comment %} +Advanced Filters Component - Django Cotton Version + +Comprehensive filtering panel with collapsible sections, range inputs, and advanced options. +Provides extensive filtering capabilities for park listings with intuitive UX. + +Usage Examples: + + + + + +Parameters: +- filter_counts: Dictionary with filter statistics (required) +- current_filters: Current filter values from request.GET (required) +- show_advanced: Whether to show advanced filters by default (default: False) +- class: Additional CSS classes (optional) + +Features: +- Collapsible filter sections +- Range sliders for numeric filters +- Multi-select options +- Location-based filtering +- Date range pickers +- Real-time filter counts +- Mobile-optimized interface +- HTMX integration for seamless updates +{% endcomment %} + + + + +
+ + +
+ + +
+ + + + + + diff --git a/templates/cotton/enhanced_park_card.html b/templates/cotton/enhanced_park_card.html new file mode 100644 index 00000000..5203dfcc --- /dev/null +++ b/templates/cotton/enhanced_park_card.html @@ -0,0 +1,381 @@ +{% comment %} +Enhanced Park Card Component - Django Cotton Version + +A modern, responsive park card component with improved layouts for both grid and list views. +Features enhanced visual design, better mobile optimization, and rich interactive elements. + +Usage Examples: + +Grid View: + + +List View: + + +Compact Grid View: + + +Parameters: +- park: Park object (required) +- view_mode: "list" or "grid" (default: "grid") +- size: "normal" or "compact" (default: "normal") +- show_stats: Whether to show ride/coaster stats (default: True) +- show_rating: Whether to show rating (default: True) +- class: Additional CSS classes (optional) + +Features: +- Modern card design with enhanced visual hierarchy +- Responsive image handling with CloudFlare Images +- Interactive hover effects and animations +- Accessibility improvements with ARIA labels +- Status badges with improved styling +- Rating display with star visualization +- Optimized for both mobile and desktop +- Support for compact grid layout +- Enhanced typography and spacing +{% endcomment %} + + + +{% if park %} + {% if view_mode == 'list' %} + {# Enhanced List View Layout #} +
+ +
+
+ {# Enhanced Image Section for List View #} +
+
+ {% if park.card_image.image or park.photos.first.image %} + {% with image=park.card_image.image|default:park.photos.first.image %} + + + + {{ park.name }} - {% if park.card_image.alt_text %}{{ park.card_image.alt_text }}{% elif park.photos.first.alt_text %}{{ park.photos.first.alt_text }}{% else %}Theme park exterior view{% endif %} + + {% endwith %} + {% else %} +
+
+ + + +

No Image Available

+
+
+ {% endif %} + + {# Enhanced Status Badge #} +
+ + {{ park.get_status_display }} + +
+ + {# Rating Badge (if enabled) #} + {% if show_rating == "true" and park.average_rating %} +
+
+ + + + {{ park.average_rating|floatformat:1 }} +
+
+ {% endif %} +
+
+ + {# Enhanced Content Section #} +
+
+ {# Title and Operator #} +
+

+ {% if park.slug %} + + {{ park.name }} + + {% else %} + + {{ park.name }} + + {% endif %} +

+ + {% if park.operator %} +
+ + + + {{ park.operator.name }} +
+ {% endif %} +
+ + {# Description #} + {% if park.description %} +

+ {{ park.description|truncatewords:40 }} +

+ {% endif %} + + {# Location Info #} + {% if park.location %} +
+ + + + + + {% if park.location.city %}{{ park.location.city }}{% endif %}{% if park.location.city and park.location.state %}, {% endif %}{% if park.location.state %}{{ park.location.state }}{% endif %}{% if park.location.country and park.location.country != "United States" %}, {{ park.location.country }}{% endif %} + +
+ {% endif %} +
+ + {# Stats Section #} + {% if show_stats == "true" and park.ride_count or park.coaster_count %} +
+
+ {% if park.ride_count %} + + {% endif %} + {% if park.coaster_count %} + + {% endif %} +
+ + {# View Details Arrow #} +
+ + + +
+
+ {% endif %} +
+
+
+
+ {% else %} + {# Enhanced Grid View Layout #} +
+ + {# Enhanced Image Section for Grid View #} +
+ {% if park.card_image.image or park.photos.first.image %} + {% with image=park.card_image.image|default:park.photos.first.image %} + + {% if size == "compact" %} + + + {% else %} + + + {% endif %} + {{ park.name }} - {% if park.card_image.alt_text %}{{ park.card_image.alt_text }}{% elif park.photos.first.alt_text %}{{ park.photos.first.alt_text }}{% else %}Theme park exterior view{% endif %} + + + {# Image Overlay Effects #} +
+ {% endwith %} + {% else %} +
+
+ + + +

No Image Available

+

{{ park.name }}

+
+
+ {% endif %} + + {# Enhanced Status Badge #} +
+ + {{ park.get_status_display }} + +
+ + {# Rating Badge (if enabled) #} + {% if show_rating == "true" and park.average_rating %} +
+
+ + + + {{ park.average_rating|floatformat:1 }} +
+
+ {% endif %} +
+ + {# Enhanced Content Area #} +
+
+ {# Title #} +

+ {% if park.slug %} + + {{ park.name }} + + {% else %} + + {{ park.name }} + + {% endif %} +

+ + {# Operator #} + {% if park.operator %} +
+ + + + {{ park.operator.name }} +
+ {% endif %} +
+ + {# Description #} + {% if park.description and size != "compact" %} +

+ {{ park.description|truncatewords:20 }} +

+ {% endif %} + + {# Stats Footer #} + {% if show_stats == "true" and park.ride_count or park.coaster_count %} +
+
+ {% if park.ride_count %} + + {% endif %} + {% if park.coaster_count %} + + {% endif %} +
+ + {# View Details Arrow #} +
+ + + +
+
+ {% else %} + {# Show arrow even when no stats for consistent layout #} +
+
+ + + +
+
+ {% endif %} +
+
+ {% endif %} +{% endif %} diff --git a/templates/cotton/enhanced_search.html b/templates/cotton/enhanced_search.html index 40e502da..6826a5a9 100644 --- a/templates/cotton/enhanced_search.html +++ b/templates/cotton/enhanced_search.html @@ -54,13 +54,19 @@ Features: this.open = false; this.suggestions = []; this.selectedIndex = -1; - htmx.trigger(this.$refs.searchInput, 'keyup'); + // Trigger search update if HTMX is available + if (typeof htmx !== 'undefined' && this.$refs.searchInput) { + htmx.trigger(this.$refs.searchInput, 'keyup'); + } }, selectSuggestion(suggestion) { this.search = suggestion.name || suggestion; this.open = false; this.selectedIndex = -1; - htmx.trigger(this.$refs.searchInput, 'keyup'); + // Trigger search update if HTMX is available + if (typeof htmx !== 'undefined' && this.$refs.searchInput) { + htmx.trigger(this.$refs.searchInput, 'keyup'); + } }, handleKeydown(event) { if (!this.open) return; @@ -260,4 +266,4 @@ Features: `${suggestions.length} suggestion${suggestions.length !== 1 ? 's' : ''} available. Use arrow keys to navigate.` : (search.length >= 2 && !loading && suggestions.length === 0 ? 'No suggestions found.' : '')">
- \ No newline at end of file + diff --git a/templates/parks/enhanced_park_list.html b/templates/parks/enhanced_park_list.html new file mode 100644 index 00000000..12494492 --- /dev/null +++ b/templates/parks/enhanced_park_list.html @@ -0,0 +1,495 @@ +{% extends "base/base.html" %} +{% load static %} +{% load cotton %} + +{% block title %}Parks - Enhanced Experience{% endblock %} + +{% block content %} +{# Skip Navigation Links for Accessibility #} + + Skip to main content + + + Skip to search + + +{# Enhanced Container with Better Layout #} +
+ +
+ + {# Enhanced Header Section #} +
+
+

+ Discover Amazing Theme Parks +

+

+ Explore the world's most thrilling theme parks with comprehensive information, reviews, and insider details +

+
+ + {# Enhanced Statistics Cards #} +
+

Park Statistics Overview

+ + + + + + + + +
+
+ + {# Main Content Layout #} +
+ + {# Sidebar with Advanced Filters #} + + + {# Main Content Area #} +
+ + {# Controls Bar #} +
+
+ + {# Results Stats #} +
+ +
+ + {# View Controls #} +
+ {# Sort Controls #} + + + {# View Toggle #} + +
+
+ + {# Active Filter Chips #} + {% if active_filters %} +
+
+

+ Active Filters ({{ filter_count }}) +

+ +
+ +
+ {% endif %} +
+ + {# Loading Overlay #} +
+
+
+
+
+
+ + + +
+
+
+
Loading Parks...
+
Please wait while we fetch the latest data
+
+
+
+
+ + {# Park Results Container #} +
+ {% include "parks/partials/enhanced_park_list.html" %} +
+
+
+
+
+ + + + + + +{% endblock %} diff --git a/templates/parks/partials/enhanced_park_list.html b/templates/parks/partials/enhanced_park_list.html new file mode 100644 index 00000000..54084b8e --- /dev/null +++ b/templates/parks/partials/enhanced_park_list.html @@ -0,0 +1,280 @@ +{% load cotton %} + +{# Enhanced Park List Partial - Used for HTMX updates #} + +{% if view_mode == 'list' %} + {# Enhanced List View #} +
+ {% for park in parks %} +
+ +
+ {% empty %} + {# Enhanced Empty State for List View #} +
+
+
+ + + +
+

+ {% if is_search %} + No parks found for "{{ search_query }}" + {% else %} + No parks found + {% endif %} +

+

+ {% if is_search %} + Try adjusting your search terms or removing some filters to see more results. + {% else %} + Try adjusting your filters to see more parks. + {% endif %} +

+ {% if active_filters %} + + {% endif %} +
+
+ {% endfor %} +
+{% else %} + {# Enhanced Grid View with Responsive Columns #} +
+ {% for park in parks %} +
+ +
+ {% empty %} + {# Enhanced Empty State for Grid View #} +
+
+
+ + + +
+

+ {% if is_search %} + No parks found for "{{ search_query }}" + {% else %} + No parks available + {% endif %} +

+

+ {% if is_search %} + We couldn't find any parks matching your search. Try different keywords or remove some filters. + {% else %} + No parks match your current filter criteria. Try adjusting your filters to see more results. + {% endif %} +

+ + {# Helpful suggestions #} +
+

Try these suggestions:

+
    +
  • • Check your spelling
  • +
  • • Use more general search terms
  • +
  • • Remove some filters to broaden your search
  • + {% if is_search %} +
  • • Search for park operators like "Disney" or "Universal"
  • + {% endif %} +
+
+ +
+ {% if active_filters %} + + {% endif %} + +
+
+
+ {% endfor %} +
+{% endif %} + +{# Enhanced Pagination #} +{% if is_paginated %} + +{% endif %}