Refactor test utilities and enhance ASGI settings

- Cleaned up and standardized assertions in ApiTestMixin for API response validation.
- Updated ASGI settings to use os.environ for setting the DJANGO_SETTINGS_MODULE.
- Removed unused imports and improved formatting in settings.py.
- Refactored URL patterns in urls.py for better readability and organization.
- Enhanced view functions in views.py for consistency and clarity.
- Added .flake8 configuration for linting and style enforcement.
- Introduced type stubs for django-environ to improve type checking with Pylance.
This commit is contained in:
pacnpal
2025-08-20 19:51:59 -04:00
parent 69c07d1381
commit 66ed4347a9
230 changed files with 15094 additions and 11578 deletions

View File

@@ -11,6 +11,6 @@ import os
from django.core.asgi import get_asgi_application
os***REMOVED***iron.setdefault("DJANGO_SETTINGS_MODULE", "config.django.production")
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.django.production")
application = get_asgi_application()

View File

@@ -2,7 +2,6 @@
Django settings for thrillwiki project.
"""
import dj_database_url
from pathlib import Path
import os
@@ -88,7 +87,7 @@ TEMPLATES = [
"django.contrib.messages.context_processors.messages",
"moderation.context_processors.moderation_access",
]
}
},
}
]
@@ -164,8 +163,8 @@ AUTHENTICATION_BACKENDS = [
# django-allauth settings
SITE_ID = 1
ACCOUNT_SIGNUP_FIELDS = ['email*', 'username*', 'password1*', 'password2*']
ACCOUNT_LOGIN_METHODS = {'email', 'username'}
ACCOUNT_SIGNUP_FIELDS = ["email*", "username*", "password1*", "password2*"]
ACCOUNT_LOGIN_METHODS = {"email", "username"}
ACCOUNT_EMAIL_VERIFICATION = "optional"
LOGIN_REDIRECT_URL = "/"
ACCOUNT_LOGOUT_REDIRECT_URL = "/"
@@ -176,7 +175,8 @@ SOCIALACCOUNT_ADAPTER = "accounts.adapters.CustomSocialAccountAdapter"
# Social account settings
# OAuth provider configuration moved to database SocialApp objects
# This prevents conflicts between settings-based and database-based configurations
# This prevents conflicts between settings-based and database-based
# configurations
SOCIALACCOUNT_PROVIDERS = {
"google": {
"SCOPE": [
@@ -188,7 +188,7 @@ SOCIALACCOUNT_PROVIDERS = {
"discord": {
"SCOPE": ["identify", "email"],
"OAUTH_PKCE_ENABLED": True,
}
},
}
# Additional social account settings

View File

@@ -5,55 +5,55 @@ from django.conf.urls.static import static
from django.views.static import serve
from accounts import views as accounts_views
from django.views.generic import TemplateView
from .views import HomeView, SearchView
from .views import HomeView
from . import views
from autocomplete import urls as autocomplete_urls
import os
# Import API documentation views
try:
from drf_spectacular.views import SpectacularAPIView, SpectacularSwaggerView, SpectacularRedocView
from drf_spectacular.views import (
SpectacularAPIView,
SpectacularSwaggerView,
SpectacularRedocView,
)
HAS_SPECTACULAR = True
except ImportError:
HAS_SPECTACULAR = False
# Import enhanced health check views
try:
from core.views.health_views import HealthCheckAPIView, PerformanceMetricsView, SimpleHealthView
from core.views.health_views import (
HealthCheckAPIView,
PerformanceMetricsView,
SimpleHealthView,
)
HAS_HEALTH_VIEWS = True
except ImportError:
HAS_HEALTH_VIEWS = False
# Import autocomplete URLs
try:
from autocomplete import urls as autocomplete_urls
HAS_AUTOCOMPLETE = True
except ImportError:
HAS_AUTOCOMPLETE = False
# Build URL patterns list dynamically
urlpatterns = [
path("admin/", admin.site.urls),
# Main app URLs
path("", HomeView.as_view(), name="home"),
# Autocomplete URLs (must be before other URLs)
path("ac/", autocomplete_urls),
# API Documentation URLs
path("api/schema/", SpectacularAPIView.as_view(),
name="schema") if HAS_SPECTACULAR else path("", lambda r: None),
path("api/docs/", SpectacularSwaggerView.as_view(url_name="schema"),
name="swagger-ui") if HAS_SPECTACULAR else path("", lambda r: None),
path("api/redoc/", SpectacularRedocView.as_view(url_name="schema"),
name="redoc") if HAS_SPECTACULAR else path("", lambda r: None),
# Health Check URLs
path("health/", include("health_check.urls")),
path("health/api/", HealthCheckAPIView.as_view(),
name="health-api") if HAS_HEALTH_VIEWS else path("", lambda r: None),
path("health/simple/", SimpleHealthView.as_view(),
name="health-simple") if HAS_HEALTH_VIEWS else path("", lambda r: None),
path("health/metrics/", PerformanceMetricsView.as_view(),
name="health-metrics") if HAS_HEALTH_VIEWS else path("", lambda r: None),
# API URLs (before app URLs to avoid conflicts)
path("api/v1/", include("parks.api.urls", namespace="parks_api")),
path("api/v1/", include("rides.api.urls", namespace="rides_api")),
path("api/v1/map/", include("core.urls.map_urls",
namespace="map_api")), # Map API URLs
path(
"api/v1/map/", include("core.urls.map_urls", namespace="map_api")
), # Map API URLs
# Parks and Rides URLs
path("parks/", include("parks.urls", namespace="parks")),
# Global rides URLs
@@ -61,11 +61,15 @@ urlpatterns = [
# Operators URLs
path("operators/", include("parks.urls", namespace="operators")),
# Other URLs
path("photos/", include("media.urls", namespace="photos")), # Add photos URLs
path("photos/", include("media.urls", namespace="photos")),
# Add photos URLs
path("search/", include("core.urls.search", namespace="search")),
path("maps/", include("core.urls.maps", namespace="maps")), # Map HTML views
path("maps/", include("core.urls.maps", namespace="maps")),
# Map HTML views
path(
"terms/", TemplateView.as_view(template_name="pages/terms.html"), name="terms"
"terms/",
TemplateView.as_view(template_name="pages/terms.html"),
name="terms",
),
path(
"privacy/",
@@ -77,7 +81,9 @@ urlpatterns = [
# Default allauth URLs (for social auth and other features)
path("accounts/", include("allauth.urls")),
path(
"accounts/email-required/", accounts_views.email_required, name="email_required"
"accounts/email-required/",
accounts_views.email_required,
name="email_required",
),
# User profile URLs
path(
@@ -86,7 +92,9 @@ urlpatterns = [
name="user_profile",
),
path(
"profile/<str:username>/", accounts_views.ProfileView.as_view(), name="profile"
"profile/<str:username>/",
accounts_views.ProfileView.as_view(),
name="profile",
),
path("settings/", accounts_views.SettingsView.as_view(), name="settings"),
# Redirect /user/ to the user's profile if logged in
@@ -100,39 +108,84 @@ urlpatterns = [
),
]
# Add autocomplete URLs if available
if HAS_AUTOCOMPLETE:
urlpatterns.insert(2, path("ac/", include((autocomplete_urls[0], autocomplete_urls[1]), namespace=autocomplete_urls[2])))
# Add API Documentation URLs if available
if HAS_SPECTACULAR:
urlpatterns.extend(
[
path("api/schema/", SpectacularAPIView.as_view(), name="schema"),
path(
"api/docs/",
SpectacularSwaggerView.as_view(url_name="schema"),
name="swagger-ui",
),
path(
"api/redoc/",
SpectacularRedocView.as_view(url_name="schema"),
name="redoc",
),
]
)
# Add enhanced health check URLs if available
if HAS_HEALTH_VIEWS:
urlpatterns.extend(
[
path("health/api/", HealthCheckAPIView.as_view(), name="health-api"),
path(
"health/simple/",
SimpleHealthView.as_view(),
name="health-simple",
),
path(
"health/metrics/",
PerformanceMetricsView.as_view(),
name="health-metrics",
),
]
)
# Serve static files in development
if settings.DEBUG:
urlpatterns += static(settings.STATIC_URL,
document_root=settings.STATIC_ROOT)
urlpatterns += static(settings.MEDIA_URL,
document_root=settings.MEDIA_ROOT)
urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
# Development monitoring URLs
try:
import debug_toolbar
urlpatterns = [
path('__debug__/', include(debug_toolbar.urls)),
path("__debug__/", include(debug_toolbar.urls)),
] + urlpatterns
except ImportError:
pass
try:
import silk
urlpatterns += [path('silk/', include('silk.urls', namespace='silk'))]
pass
urlpatterns += [path("silk/", include("silk.urls", namespace="silk"))]
except ImportError:
pass
# Serve test coverage reports in development
coverage_dir = os.path.join(settings.BASE_DIR, 'tests', 'coverage_html')
coverage_dir = os.path.join(settings.BASE_DIR, "tests", "coverage_html")
if os.path.exists(coverage_dir):
urlpatterns += [
path('coverage/', serve, {
'document_root': coverage_dir,
'path': 'index.html'
}),
path('coverage/<path:path>', serve, {
'document_root': coverage_dir,
}),
path(
"coverage/",
serve,
{"document_root": coverage_dir, "path": "index.html"},
),
path(
"coverage/<path:path>",
serve,
{
"document_root": coverage_dir,
},
),
]
handler404 = "thrillwiki.views.handler404"

View File

@@ -1,10 +1,8 @@
from django.shortcuts import render
from django.views.generic import TemplateView
from django.db.models import Count, Q, Value, CharField
from django.db.models.functions import Concat
from django.db.models import Q
from django.core.cache import cache
from parks.models.parks import Park
from parks.models.companies import Company
from parks.models import Park, Company
from rides.models import Ride
from core.analytics import PageView
from django.conf import settings
@@ -13,118 +11,148 @@ import secrets
def handler404(request, exception):
return render(request, '404.html', status=404)
return render(request, "404.html", status=404)
def handler500(request):
return render(request, '500.html', status=500)
return render(request, "500.html", status=500)
class HomeView(TemplateView):
template_name = 'home.html'
template_name = "home.html"
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
# Get stats
context['stats'] = {
'total_parks': Park.objects.count(),
'ride_count': Ride.objects.count(),
'coaster_count': Ride.objects.filter(category='RC').count(),
context["stats"] = {
"total_parks": Park.objects.count(),
"ride_count": Ride.objects.count(),
"coaster_count": Ride.objects.filter(category="RC").count(),
}
# Try to get trending items from cache first
trending_parks = cache.get('trending_parks')
trending_rides = cache.get('trending_rides')
trending_parks = cache.get("trending_parks")
trending_rides = cache.get("trending_rides")
# If not in cache, get them directly and cache them
if trending_parks is None:
try:
trending_parks = list(PageView.get_trending_items(Park, hours=24, limit=10))
trending_parks = list(
PageView.get_trending_items(Park, hours=24, limit=10)
)
if trending_parks:
cache.set('trending_parks', trending_parks, 3600) # Cache for 1 hour
cache.set(
"trending_parks", trending_parks, 3600
) # Cache for 1 hour
else:
# Fallback to highest rated parks if no trending data
trending_parks = Park.objects.exclude(
average_rating__isnull=True
).order_by('-average_rating')[:10]
).order_by("-average_rating")[:10]
except Exception:
# Fallback to highest rated parks if trending calculation fails
trending_parks = Park.objects.exclude(
average_rating__isnull=True
).order_by('-average_rating')[:10]
).order_by("-average_rating")[:10]
if trending_rides is None:
try:
trending_rides = list(PageView.get_trending_items(Ride, hours=24, limit=10))
trending_rides = list(
PageView.get_trending_items(Ride, hours=24, limit=10)
)
if trending_rides:
cache.set('trending_rides', trending_rides, 3600) # Cache for 1 hour
cache.set(
"trending_rides", trending_rides, 3600
) # Cache for 1 hour
else:
# Fallback to highest rated rides if no trending data
trending_rides = Ride.objects.exclude(
average_rating__isnull=True
).order_by('-average_rating')[:10]
).order_by("-average_rating")[:10]
except Exception:
# Fallback to highest rated rides if trending calculation fails
trending_rides = Ride.objects.exclude(
average_rating__isnull=True
).order_by('-average_rating')[:10]
).order_by("-average_rating")[:10]
# Get highest rated items (mix of parks and rides)
highest_rated_parks = list(Park.objects.exclude(
average_rating__isnull=True
).order_by('-average_rating')[:20]) # Get more items to randomly select from
highest_rated_rides = list(Ride.objects.exclude(
average_rating__isnull=True
).order_by('-average_rating')[:20]) # Get more items to randomly select from
highest_rated_parks = list(
Park.objects.exclude(average_rating__isnull=True).order_by(
"-average_rating"
)[:20]
) # Get more items to randomly select from
highest_rated_rides = list(
Ride.objects.exclude(average_rating__isnull=True).order_by(
"-average_rating"
)[:20]
) # Get more items to randomly select from
# Combine and shuffle highest rated items
all_highest_rated = highest_rated_parks + highest_rated_rides
secrets.SystemRandom().shuffle(all_highest_rated)
# Keep the same context variable names for template compatibility
context['popular_parks'] = trending_parks
context['popular_rides'] = trending_rides
context['highest_rated'] = all_highest_rated[:10] # Take first 10 after shuffling
context["popular_parks"] = trending_parks
context["popular_rides"] = trending_rides
context["highest_rated"] = all_highest_rated[
:10
] # Take first 10 after shuffling
return context
class SearchView(TemplateView):
template_name = 'search_results.html'
template_name = "search_results.html"
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
if query := self.request.GET.get('q', '').strip():
if query := self.request.GET.get("q", "").strip():
# Search parks
context['parks'] = Park.objects.filter(
Q(name__icontains=query) |
Q(location__icontains=query) |
Q(description__icontains=query)
).select_related('operating_company').prefetch_related('photos')[:10]
context["parks"] = (
Park.objects.filter(
Q(name__icontains=query)
| Q(location__icontains=query)
| Q(description__icontains=query)
)
.select_related("operating_company")
.prefetch_related("photos")[:10]
)
# Search rides
context['rides'] = Ride.objects.filter(
Q(name__icontains=query) |
Q(description__icontains=query) |
Q(manufacturer__name__icontains=query)
).select_related('park', 'coaster_stats').prefetch_related('photos')[:10]
context["rides"] = (
Ride.objects.filter(
Q(name__icontains=query)
| Q(description__icontains=query)
| Q(manufacturer__name__icontains=query)
)
.select_related("park", "coaster_stats")
.prefetch_related("photos")[:10]
)
# Search companies
context['companies'] = Company.objects.filter(
Q(name__icontains=query) |
Q(description__icontains=query)
).prefetch_related('operated_parks', 'owned_parks')[:10]
context["companies"] = Company.objects.filter(
Q(name__icontains=query) | Q(description__icontains=query)
).prefetch_related("operated_parks", "owned_parks")[:10]
return context
def environment_and_settings_view(request):
# Get all environment variables
env_vars = dict(os.environ)
# Get all Django settings as a dictionary
settings_vars = {setting: getattr(settings, setting) for setting in dir(settings) if setting.isupper()}
return render(request, 'environment_and_settings.html', {
'env_vars': env_vars,
'settings_vars': settings_vars
})
settings_vars = {
setting: getattr(settings, setting)
for setting in dir(settings)
if setting.isupper()
}
return render(
request,
"environment_and_settings.html",
{"env_vars": env_vars, "settings_vars": settings_vars},
)