diff --git a/accounts/management/commands/cleanup_test_data.py b/accounts/management/commands/cleanup_test_data.py index c59380ae..a35702b2 100644 --- a/accounts/management/commands/cleanup_test_data.py +++ b/accounts/management/commands/cleanup_test_data.py @@ -20,7 +20,7 @@ class Command(BaseCommand): self.stdout.write(self.style.SUCCESS(f"Deleted {count} test users")) # Delete test reviews - reviews = Review.objects.filter(author__username__in=["testuser", "moderator"]) + reviews = Review.objects.filter(user__username__in=["testuser", "moderator"]) count = reviews.count() reviews.delete() self.stdout.write(self.style.SUCCESS(f"Deleted {count} test reviews")) diff --git a/accounts/mixins.py b/accounts/mixins.py index 850793c0..adf6260b 100644 --- a/accounts/mixins.py +++ b/accounts/mixins.py @@ -26,7 +26,7 @@ class TurnstileMixin: 'remoteip': request.META.get('REMOTE_ADDR'), } - response = requests.post(settings.TURNSTILE_VERIFY_URL, data=data) + response = requests.post(settings.TURNSTILE_VERIFY_URL, data=data, timeout=60) result = response.json() if not result.get('success'): diff --git a/accounts/signals.py b/accounts/signals.py index 49465665..d9440fee 100644 --- a/accounts/signals.py +++ b/accounts/signals.py @@ -31,7 +31,7 @@ def create_user_profile(sender, instance, created, **kwargs): if avatar_url: try: - response = requests.get(avatar_url) + response = requests.get(avatar_url, timeout=60) if response.status_code == 200: img_temp = NamedTemporaryFile(delete=True) img_temp.write(response.content) diff --git a/email_service/management/commands/test_email_service.py b/email_service/management/commands/test_email_service.py index 32f19b69..ed1b5fbb 100644 --- a/email_service/management/commands/test_email_service.py +++ b/email_service/management/commands/test_email_service.py @@ -146,8 +146,8 @@ class Command(BaseCommand): }, headers={ 'Content-Type': 'application/json', - } - ) + }, + timeout=60) if response.status_code == 200: self.stdout.write(self.style.SUCCESS('✓ API endpoint test successful')) diff --git a/email_service/services.py b/email_service/services.py index 0a592340..46b3b0dc 100644 --- a/email_service/services.py +++ b/email_service/services.py @@ -74,7 +74,7 @@ class EmailService: f"{settings.FORWARD_EMAIL_BASE_URL}/v1/emails", json=data, headers=headers, - ) + timeout=60) # Debug output print(f"Response Status: {response.status_code}") diff --git a/globalLocators.js b/globalLocators.js new file mode 100644 index 00000000..a9e2075d --- /dev/null +++ b/globalLocators.js @@ -0,0 +1,3 @@ +const locators = {}; + +module.exports = { locators }; diff --git a/location/views.py b/location/views.py index 2af4b9dc..401551fe 100644 --- a/location/views.py +++ b/location/views.py @@ -52,8 +52,8 @@ class LocationSearchView(View): response = requests.get( 'https://nominatim.openstreetmap.org/search', params=params, - headers={'User-Agent': 'ThrillWiki/1.0'} - ) + headers={'User-Agent': 'ThrillWiki/1.0'}, + timeout=60) response.raise_for_status() results = response.json() except requests.RequestException as e: @@ -170,8 +170,8 @@ def reverse_geocode(request): 'format': 'json', 'addressdetails': 1 }, - headers={'User-Agent': 'ThrillWiki/1.0'} - ) + headers={'User-Agent': 'ThrillWiki/1.0'}, + timeout=60) response.raise_for_status() result = response.json() diff --git a/media/management/commands/download_photos.py b/media/management/commands/download_photos.py index 18d51a76..9309f7e8 100644 --- a/media/management/commands/download_photos.py +++ b/media/management/commands/download_photos.py @@ -33,7 +33,7 @@ class Command(BaseCommand): try: # Download image self.stdout.write(f'Downloading from URL: {photo_url}') - response = requests.get(photo_url) + response = requests.get(photo_url, timeout=60) if response.status_code == 200: # Delete any existing photos for this park Photo.objects.filter( @@ -74,7 +74,7 @@ class Command(BaseCommand): try: # Download image self.stdout.write(f'Downloading from URL: {photo_url}') - response = requests.get(photo_url) + response = requests.get(photo_url, timeout=60) if response.status_code == 200: # Delete any existing photos for this ride Photo.objects.filter( diff --git a/parks/management/commands/seed_data.py b/parks/management/commands/seed_data.py index 76ce714e..cb8fe520 100644 --- a/parks/management/commands/seed_data.py +++ b/parks/management/commands/seed_data.py @@ -189,7 +189,7 @@ class Command(BaseCommand): def download_image(self, url): """Download image from URL and return as Django File object""" - response = requests.get(url) + response = requests.get(url, timeout=60) if response.status_code == 200: img_temp = NamedTemporaryFile(delete=True) img_temp.write(response.content) diff --git a/parks/views.py b/parks/views.py index 9b47623a..e930afb3 100644 --- a/parks/views.py +++ b/parks/views.py @@ -79,7 +79,7 @@ def location_search(request: HttpRequest) -> JsonResponse: "limit": 10, }, headers={"User-Agent": "ThrillWiki/1.0"}, - ) + timeout=60) if response.status_code == 200: results = response.json() @@ -128,7 +128,7 @@ def reverse_geocode(request: HttpRequest) -> JsonResponse: "accept-language": "en", }, headers={"User-Agent": "ThrillWiki/1.0"}, - ) + timeout=60) if response.status_code == 200: result = response.json() diff --git a/pyproject.toml b/pyproject.toml index fbe6f161..8cea5633 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -13,7 +13,7 @@ django-cors-headers = "^4.3.1" [tool.poetry.dev-dependencies] black = "^25.1.0" -isort = "^5.13.0" +isort = "^6.0.0" mypy = "^1.8.0" [build-system] diff --git a/requirements.txt b/requirements.txt index 82b4184b..491aaedd 100644 --- a/requirements.txt +++ b/requirements.txt @@ -41,5 +41,5 @@ channels-redis==4.2.1 daphne==4.1.2 # React and Material UI will be handled via npm in the frontend directory -django-simple-history==3.7.0 +django-simple-history==3.8.0 django-tailwind-cli==2.21.1 diff --git a/uv.lock b/uv.lock index 6df6a47b..17cd680e 100644 --- a/uv.lock +++ b/uv.lock @@ -149,7 +149,7 @@ name = "click" version = "8.1.8" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "colorama", marker = "platform_system == 'Windows'" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, ] sdist = { url = "https://files.pythonhosted.[AWS-SECRET-REMOVED][AWS-SECRET-REMOVED]click-8.1.8.tar.gz", hash = "sha256:[AWS-SECRET-REMOVED]c040a31d4225c9574d16096a", size = 226593 } wheels = [ @@ -404,6 +404,30 @@ wheels = [ { url = "https://files.pythonhosted.[AWS-SECRET-REMOVED][AWS-SECRET-REMOVED]flake8-7.1.1-py2.py3-none-any.whl", hash = "sha256:[AWS-SECRET-REMOVED]e96ab708770ae0364dd03213", size = 57731 }, ] +[[package]] +name = "greenlet" +version = "3.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.[AWS-SECRET-REMOVED][AWS-SECRET-REMOVED]greenlet-3.1.1.tar.gz", hash = "sha256:[AWS-SECRET-REMOVED]377e070a7986bd2d5c515467", size = 186022 } +wheels = [ + { url = "https://files.pythonhosted.[AWS-SECRET-REMOVED][AWS-SECRET-REMOVED]greenlet-3.1.1-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:[AWS-SECRET-REMOVED]6d42713cb8a5137bd49375f1", size = 272990 }, + { url = "https://files.pythonhosted.[AWS-SECRET-REMOVED][AWS-SECRET-REMOVED]greenlet-3.1.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:[AWS-SECRET-REMOVED]cf9ef6e924188f762145c0ff", size = 649175 }, + { url = "https://files.pythonhosted.[AWS-SECRET-REMOVED][AWS-SECRET-REMOVED]greenlet-3.1.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:[AWS-SECRET-REMOVED]a51fba2ed39ceeeb1004798a", size = 663425 }, + { url = "https://files.pythonhosted.[AWS-SECRET-REMOVED][AWS-SECRET-REMOVED]greenlet-3.1.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:[AWS-SECRET-REMOVED]958895d68845fa4ab566509e", size = 657736 }, + { url = "https://files.pythonhosted.[AWS-SECRET-REMOVED][AWS-SECRET-REMOVED]greenlet-3.1.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:[AWS-SECRET-REMOVED]4c3c4e4430b1c3f853e498e4", size = 660347 }, + { url = "https://files.pythonhosted.[AWS-SECRET-REMOVED][AWS-SECRET-REMOVED]greenlet-3.1.1-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:[AWS-SECRET-REMOVED]eda6e14b88dc6bbc04d2049e", size = 615583 }, + { url = "https://files.pythonhosted.[AWS-SECRET-REMOVED][AWS-SECRET-REMOVED]greenlet-3.1.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:[AWS-SECRET-REMOVED]2710016a1c742df5da6e60a1", size = 1133039 }, + { url = "https://files.pythonhosted.[AWS-SECRET-REMOVED][AWS-SECRET-REMOVED]greenlet-3.1.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:[AWS-SECRET-REMOVED]2e929e3d4abfae97b682a12c", size = 1160716 }, + { url = "https://files.pythonhosted.[AWS-SECRET-REMOVED][AWS-SECRET-REMOVED]greenlet-3.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:[AWS-SECRET-REMOVED]55674d60e710f0af570f3761", size = 299490 }, + { url = "https://files.pythonhosted.[AWS-SECRET-REMOVED][AWS-SECRET-REMOVED]greenlet-3.1.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:[AWS-SECRET-REMOVED]6831a817ac04eebb2fd02011", size = 643731 }, + { url = "https://files.pythonhosted.[AWS-SECRET-REMOVED][AWS-SECRET-REMOVED]greenlet-3.1.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:[AWS-SECRET-REMOVED]47740c5302f74a9e9fa14b13", size = 649304 }, + { url = "https://files.pythonhosted.[AWS-SECRET-REMOVED][AWS-SECRET-REMOVED]greenlet-3.1.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:[AWS-SECRET-REMOVED]9da589cb4fa142bd3b57b475", size = 646537 }, + { url = "https://files.pythonhosted.[AWS-SECRET-REMOVED][AWS-SECRET-REMOVED]greenlet-3.1.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:[AWS-SECRET-REMOVED]293a25074cc5566160e4de7b", size = 642506 }, + { url = "https://files.pythonhosted.[AWS-SECRET-REMOVED][AWS-SECRET-REMOVED]greenlet-3.1.1-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:[AWS-SECRET-REMOVED]53d0a9873f55f6a59a65f822", size = 602753 }, + { url = "https://files.pythonhosted.[AWS-SECRET-REMOVED][AWS-SECRET-REMOVED]greenlet-3.1.1-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:[AWS-SECRET-REMOVED]7e2a9f59697fa14b5862ed01", size = 1122731 }, + { url = "https://files.pythonhosted.[AWS-SECRET-REMOVED][AWS-SECRET-REMOVED]greenlet-3.1.1-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:[AWS-SECRET-REMOVED]4bcb293f085bf2860c32c6f6", size = 1142112 }, +] + [[package]] name = "hyperlink" version = "21.0.0" @@ -559,6 +583,24 @@ wheels = [ { url = "https://files.pythonhosted.[AWS-SECRET-REMOVED][AWS-SECRET-REMOVED]platformdirs-4.3.6-py3-none-any.whl", hash = "sha256:[AWS-SECRET-REMOVED]a03e92031bab39e4554cc3fb", size = 18439 }, ] +[[package]] +name = "playwright" +version = "1.49.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "greenlet" }, + { name = "pyee" }, +] +wheels = [ + { url = "https://files.pythonhosted.[AWS-SECRET-REMOVED][AWS-SECRET-REMOVED]playwright-1.49.1-py3-none-macosx_10_13_x86_64.whl", hash = "sha256:[AWS-SECRET-REMOVED]540da43a0b70616ad52592b8", size = 39559859 }, + { url = "https://files.pythonhosted.[AWS-SECRET-REMOVED][AWS-SECRET-REMOVED]playwright-1.49.1-py3-none-macosx_11_0_arm64.whl", hash = "sha256:[AWS-SECRET-REMOVED]49d6f0cec28648d38a7137be", size = 38808973 }, + { url = "https://files.pythonhosted.[AWS-SECRET-REMOVED][AWS-SECRET-REMOVED]playwright-1.49.1-py3-none-macosx_11_0_universal2.whl", hash = "sha256:[AWS-SECRET-REMOVED]cd4249feb6f59e115aaddfb8", size = 39559863 }, + { url = "https://files.pythonhosted.[AWS-SECRET-REMOVED][AWS-SECRET-REMOVED]playwright-1.49.1-py3-none-manylinux1_x86_64.whl", hash = "sha256:[AWS-SECRET-REMOVED]1953f2a982ff612d85b147d2", size = 44163300 }, + { url = "https://files.pythonhosted.[AWS-SECRET-REMOVED][AWS-SECRET-REMOVED]playwright-1.49.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:[AWS-SECRET-REMOVED]0fef018ac34dfe86b34994b9", size = 43744353 }, + { url = "https://files.pythonhosted.[AWS-SECRET-REMOVED][AWS-SECRET-REMOVED]playwright-1.49.1-py3-none-win32.whl", hash = "sha256:[AWS-SECRET-REMOVED]fbed28de30f4f022cc1745bb", size = 34060663 }, + { url = "https://files.pythonhosted.[AWS-SECRET-REMOVED][AWS-SECRET-REMOVED]playwright-1.49.1-py3-none-win_amd64.whl", hash = "sha256:[AWS-SECRET-REMOVED]0aa0ca93dd0601a1400191df", size = 34060667 }, +] + [[package]] name = "pluggy" version = "1.5.0" @@ -634,6 +676,18 @@ wheels = [ { url = "https://files.pythonhosted.[AWS-SECRET-REMOVED][AWS-SECRET-REMOVED]pycparser-2.22-py3-none-any.whl", hash = "sha256:[AWS-SECRET-REMOVED]cdc24edd75470f4de499cfcc", size = 117552 }, ] +[[package]] +name = "pyee" +version = "12.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.[AWS-SECRET-REMOVED][AWS-SECRET-REMOVED]pyee-12.0.0.tar.gz", hash = "sha256:[AWS-SECRET-REMOVED]fdb8d688e0d08c55a534e145", size = 29675 } +wheels = [ + { url = "https://files.pythonhosted.[AWS-SECRET-REMOVED][AWS-SECRET-REMOVED]pyee-12.0.0-py3-none-any.whl", hash = "sha256:[AWS-SECRET-REMOVED]745758225e31a59f4095c990", size = 14831 }, +] + [[package]] name = "pyflakes" version = "3.2.0" @@ -679,6 +733,19 @@ wheels = [ { url = "https://files.pythonhosted.[AWS-SECRET-REMOVED][AWS-SECRET-REMOVED]pytest-8.3.4-py3-none-any.whl", hash = "sha256:[AWS-SECRET-REMOVED]30f0a3d3335edde19788b6f6", size = 343083 }, ] +[[package]] +name = "pytest-base-url" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pytest" }, + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.[AWS-SECRET-REMOVED][AWS-SECRET-REMOVED]pytest_base_url-2.1.0.tar.gz", hash = "sha256:[AWS-SECRET-REMOVED]1b753e950c63e03aee745d45", size = 6702 } +wheels = [ + { url = "https://files.pythonhosted.[AWS-SECRET-REMOVED][AWS-SECRET-REMOVED]pytest_base_url-2.1.0-py3-none-any.whl", hash = "sha256:[AWS-SECRET-REMOVED]1ea44cebfe45849d2d2812e6", size = 5302 }, +] + [[package]] name = "pytest-django" version = "4.9.0" @@ -691,6 +758,21 @@ wheels = [ { url = "https://files.pythonhosted.[AWS-SECRET-REMOVED][AWS-SECRET-REMOVED]pytest_django-4.9.0-py3-none-any.whl", hash = "sha256:[AWS-SECRET-REMOVED]a549a7d38a3154d5731b2b99", size = 23723 }, ] +[[package]] +name = "pytest-playwright" +version = "0.6.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "playwright" }, + { name = "pytest" }, + { name = "pytest-base-url" }, + { name = "python-slugify" }, +] +sdist = { url = "https://files.pythonhosted.[AWS-SECRET-REMOVED][AWS-SECRET-REMOVED]pytest_playwright-0.6.2.tar.gz", hash = "sha256:[AWS-SECRET-REMOVED]6d97f6cb9674db8a4d4ed06c", size = 16293 } +wheels = [ + { url = "https://files.pythonhosted.[AWS-SECRET-REMOVED][AWS-SECRET-REMOVED]pytest_playwright-0.6.2-py3-none-any.whl", hash = "sha256:[AWS-SECRET-REMOVED]1f8b3acf575beed84e7e9043", size = 16436 }, +] + [[package]] name = "python-dotenv" version = "1.0.1" @@ -700,6 +782,18 @@ wheels = [ { url = "https://files.pythonhosted.[AWS-SECRET-REMOVED][AWS-SECRET-REMOVED]python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:[AWS-SECRET-REMOVED]0b564ed0d674909a68ebf16a", size = 19863 }, ] +[[package]] +name = "python-slugify" +version = "8.0.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "text-unidecode" }, +] +sdist = { url = "https://files.pythonhosted.[AWS-SECRET-REMOVED][AWS-SECRET-REMOVED]python-slugify-8.0.4.tar.gz", hash = "sha256:[AWS-SECRET-REMOVED]fe41dd10822f3907b937c856", size = 10921 } +wheels = [ + { url = "https://files.pythonhosted.[AWS-SECRET-REMOVED][AWS-SECRET-REMOVED]python_slugify-8.0.4-py2.py3-none-any.whl", hash = "sha256:[AWS-SECRET-REMOVED]5fd9e3a70164fc8c50faa6b8", size = 10051 }, +] + [[package]] name = "redis" version = "5.2.1" @@ -766,6 +860,15 @@ wheels = [ { url = "https://files.pythonhosted.[AWS-SECRET-REMOVED][AWS-SECRET-REMOVED]sqlparse-0.5.3-py3-none-any.whl", hash = "sha256:[AWS-SECRET-REMOVED]cdfc72e281548517081f16ca", size = 44415 }, ] +[[package]] +name = "text-unidecode" +version = "1.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.[AWS-SECRET-REMOVED][AWS-SECRET-REMOVED]text-unidecode-1.3.tar.gz", hash = "sha256:[AWS-SECRET-REMOVED]fa49aa5b105294dd5c4aab93", size = 76885 } +wheels = [ + { url = "https://files.pythonhosted.[AWS-SECRET-REMOVED][AWS-SECRET-REMOVED]text_unidecode-1.3-py2.py3-none-any.whl", hash = "sha256:[AWS-SECRET-REMOVED]888b18189350b67134a822e8", size = 78154 }, +] + [[package]] name = "thrillwiki" version = "0.1.0" @@ -790,11 +893,13 @@ dependencies = [ { name = "djangorestframework" }, { name = "flake8" }, { name = "pillow" }, + { name = "playwright" }, { name = "psycopg2-binary" }, { name = "pycountry" }, { name = "pyjwt" }, { name = "pytest" }, { name = "pytest-django" }, + { name = "pytest-playwright" }, { name = "python-dotenv" }, { name = "requests" }, { name = "whitenoise" }, @@ -821,11 +926,13 @@ requires-dist = [ { name = "djangorestframework", specifier = ">=3.14.0" }, { name = "flake8", specifier = ">=7.1.1" }, { name = "pillow", specifier = ">=10.2.0" }, + { name = "playwright", specifier = ">=1.41.0" }, { name = "psycopg2-binary", specifier = ">=2.9.9" }, { name = "pycountry", specifier = ">=24.6.1" }, { name = "pyjwt", specifier = ">=2.10.1" }, { name = "pytest", specifier = ">=8.3.4" }, { name = "pytest-django", specifier = ">=4.9.0" }, + { name = "pytest-playwright", specifier = ">=0.4.3" }, { name = "python-dotenv", specifier = ">=1.0.1" }, { name = "requests", specifier = ">=2.32.3" }, { name = "whitenoise", specifier = ">=6.6.0" },