Finish packaging and change to src-based packaging layout, replace caddy with haproxy for performance, and update docker-compose and Dockerfiles for new packaging.
This commit is contained in:
parent
959b06c425
commit
762361a21b
210 changed files with 235 additions and 168 deletions
0
src/pkmntrade_club/home/__init__.py
Normal file
0
src/pkmntrade_club/home/__init__.py
Normal file
1
src/pkmntrade_club/home/admin.py
Normal file
1
src/pkmntrade_club/home/admin.py
Normal file
|
|
@ -0,0 +1 @@
|
|||
from django.contrib import admin
|
||||
5
src/pkmntrade_club/home/apps.py
Normal file
5
src/pkmntrade_club/home/apps.py
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class HomeConfig(AppConfig):
|
||||
name = "pkmntrade_club.home"
|
||||
0
src/pkmntrade_club/home/migrations/__init__.py
Normal file
0
src/pkmntrade_club/home/migrations/__init__.py
Normal file
1
src/pkmntrade_club/home/models.py
Normal file
1
src/pkmntrade_club/home/models.py
Normal file
|
|
@ -0,0 +1 @@
|
|||
from django.db import models
|
||||
591
src/pkmntrade_club/home/tests.py
Normal file
591
src/pkmntrade_club/home/tests.py
Normal file
|
|
@ -0,0 +1,591 @@
|
|||
from django.test import TestCase, Client, RequestFactory
|
||||
from django.urls import reverse
|
||||
from django.contrib.auth import get_user_model
|
||||
from pkmntrade_club.cards.models import Card, Deck
|
||||
from pkmntrade_club.trades.models import TradeOffer, TradeOfferHaveCard, TradeOfferWantCard
|
||||
from pkmntrade_club.accounts.models import FriendCode
|
||||
from pkmntrade_club.home.views import HomePageView
|
||||
import json
|
||||
from collections import OrderedDict
|
||||
from unittest.mock import patch, MagicMock
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
import importlib
|
||||
from tests.utils.rarity import RARITY_MAPPING
|
||||
|
||||
User = get_user_model()
|
||||
|
||||
class HomePageViewTests(TestCase):
|
||||
"""Test suite for the HomePageView."""
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
"""Set up data for all test methods."""
|
||||
# Create a user
|
||||
cls.user = User.objects.create_user(
|
||||
username='testuser',
|
||||
email='testuser@example.com',
|
||||
password='testpass123'
|
||||
)
|
||||
|
||||
# Create a friend code for the user
|
||||
cls.friend_code = FriendCode.objects.create(
|
||||
user=cls.user,
|
||||
friend_code='SW-1234-5678-9012',
|
||||
in_game_name='TestTrainer'
|
||||
)
|
||||
|
||||
# Create decks
|
||||
cls.deck1 = Deck.objects.create(
|
||||
name='Test Deck 1',
|
||||
hex_color='#FF0000',
|
||||
cardset='TEST01'
|
||||
)
|
||||
|
||||
# Create cards with different rarities
|
||||
cls.common_card = Card.objects.create(
|
||||
name='Common Test Card',
|
||||
cardset='TEST01',
|
||||
cardnum=1,
|
||||
style='normal',
|
||||
rarity_icon='★',
|
||||
rarity_level=1
|
||||
)
|
||||
cls.common_card.decks.add(cls.deck1)
|
||||
|
||||
cls.rare_card = Card.objects.create(
|
||||
name='Rare Test Card',
|
||||
cardset='TEST01',
|
||||
cardnum=2,
|
||||
style='normal',
|
||||
rarity_icon='★★★',
|
||||
rarity_level=3
|
||||
)
|
||||
cls.rare_card.decks.add(cls.deck1)
|
||||
|
||||
cls.ultra_rare_card = Card.objects.create(
|
||||
name='Ultra Rare Test Card',
|
||||
cardset='TEST01',
|
||||
cardnum=3,
|
||||
style='normal',
|
||||
rarity_icon='★★★★',
|
||||
rarity_level=4
|
||||
)
|
||||
cls.ultra_rare_card.decks.add(cls.deck1)
|
||||
|
||||
# Create trade offers with consistent rarities
|
||||
cls.common_trade = TradeOffer.objects.create(
|
||||
initiated_by=cls.friend_code,
|
||||
rarity_icon=RARITY_MAPPING[1],
|
||||
rarity_level=1
|
||||
)
|
||||
|
||||
cls.rare_trade = TradeOffer.objects.create(
|
||||
initiated_by=cls.friend_code,
|
||||
rarity_icon=RARITY_MAPPING[3],
|
||||
rarity_level=3
|
||||
)
|
||||
|
||||
# Add have and want cards with the SAME rarity for each trade
|
||||
TradeOfferHaveCard.objects.create(
|
||||
trade_offer=cls.common_trade,
|
||||
card=cls.common_card,
|
||||
quantity=2
|
||||
)
|
||||
|
||||
TradeOfferHaveCard.objects.create(
|
||||
trade_offer=cls.rare_trade,
|
||||
card=cls.rare_card,
|
||||
quantity=1
|
||||
)
|
||||
|
||||
# Add want cards with the SAME rarity as the have cards for each trade
|
||||
TradeOfferWantCard.objects.create(
|
||||
trade_offer=cls.common_trade,
|
||||
card=cls.common_card,
|
||||
quantity=1
|
||||
)
|
||||
|
||||
TradeOfferWantCard.objects.create(
|
||||
trade_offer=cls.rare_trade,
|
||||
card=cls.rare_card, # Changed from ultra_rare_card to match the rarity
|
||||
quantity=1
|
||||
)
|
||||
|
||||
def setUp(self):
|
||||
"""Set up before each test method."""
|
||||
self.client = Client()
|
||||
self.url = reverse('home')
|
||||
self.factory = RequestFactory()
|
||||
|
||||
def test_home_page_status_code(self):
|
||||
"""Test that the home page returns a 200 status code."""
|
||||
response = self.client.get(self.url)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
def test_home_page_template(self):
|
||||
"""Test that the home page uses the correct template."""
|
||||
response = self.client.get(self.url)
|
||||
self.assertTemplateUsed(response, 'home/home.html')
|
||||
|
||||
def test_home_page_context_cards(self):
|
||||
"""Test that the home page contains all cards in the context."""
|
||||
response = self.client.get(self.url)
|
||||
self.assertIn('cards', response.context)
|
||||
self.assertEqual(response.context['cards'].count(), 3)
|
||||
|
||||
def test_home_page_context_recent_offers(self):
|
||||
"""Test that the home page contains recent offers in the context."""
|
||||
response = self.client.get(self.url)
|
||||
self.assertIn('recent_offers', response.context)
|
||||
self.assertEqual(len(response.context['recent_offers']), 2)
|
||||
# Recent offers should be ordered by most recent first
|
||||
self.assertEqual(response.context['recent_offers'][0], self.rare_trade)
|
||||
|
||||
def test_home_page_context_most_offered_cards(self):
|
||||
"""Test that the home page contains most offered cards in the context."""
|
||||
response = self.client.get(self.url)
|
||||
self.assertIn('most_offered_cards', response.context)
|
||||
most_offered = list(response.context['most_offered_cards'])
|
||||
self.assertEqual(len(most_offered), 2)
|
||||
# Common card should be most offered (quantity of 2)
|
||||
self.assertEqual(most_offered[0], self.common_card)
|
||||
|
||||
def test_home_page_context_most_wanted_cards(self):
|
||||
"""Test that the home page contains most wanted cards in the context."""
|
||||
response = self.client.get(self.url)
|
||||
self.assertIn('most_wanted_cards', response.context)
|
||||
most_wanted = list(response.context['most_wanted_cards'])
|
||||
self.assertEqual(len(most_wanted), 2)
|
||||
|
||||
def test_home_page_context_least_offered_cards(self):
|
||||
"""Test that the home page contains least offered cards in the context."""
|
||||
response = self.client.get(self.url)
|
||||
self.assertIn('least_offered_cards', response.context)
|
||||
|
||||
def test_home_page_context_featured_offers(self):
|
||||
"""Test that the home page contains featured offers in the context."""
|
||||
response = self.client.get(self.url)
|
||||
self.assertIn('featured_offers', response.context)
|
||||
featured = response.context['featured_offers']
|
||||
# Should be an OrderedDict
|
||||
self.assertIsInstance(featured, OrderedDict)
|
||||
# Should contain "All" category
|
||||
self.assertIn("All", featured)
|
||||
# Should contain both rarity icons
|
||||
self.assertIn('★★★', featured)
|
||||
self.assertIn('★', featured)
|
||||
# Higher rarity should come before lower rarity
|
||||
keys = list(featured.keys())
|
||||
# First key should be "All"
|
||||
self.assertEqual(keys[0], "All")
|
||||
# Higher rarity (★★★) should come before lower rarity (★)
|
||||
self.assertIn('★★★', keys)
|
||||
self.assertIn('★', keys)
|
||||
self.assertTrue(keys.index('★★★') < keys.index('★'))
|
||||
|
||||
def test_closed_offers_not_shown(self):
|
||||
"""Test that closed offers are not shown on the home page."""
|
||||
# Close one of the trade offers
|
||||
self.common_trade.is_closed = True
|
||||
self.common_trade.save()
|
||||
|
||||
response = self.client.get(self.url)
|
||||
recent_offers = response.context['recent_offers']
|
||||
# Should only show the rare trade now
|
||||
self.assertEqual(len(recent_offers), 1)
|
||||
self.assertEqual(recent_offers[0], self.rare_trade)
|
||||
|
||||
def test_home_page_with_no_data(self):
|
||||
"""Test home page rendering when there's no trade data."""
|
||||
# Delete all trade offers
|
||||
TradeOffer.objects.all().delete()
|
||||
|
||||
response = self.client.get(self.url)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
# Should have empty lists for offers
|
||||
self.assertEqual(len(response.context['recent_offers']), 0)
|
||||
|
||||
def test_home_page_with_authenticated_user(self):
|
||||
"""Test that the home page works for authenticated users."""
|
||||
self.client.login(username='testuser', password='testpass123')
|
||||
response = self.client.get(self.url)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
def test_rarity_sorting_in_featured_offers(self):
|
||||
"""Test that offers are sorted by rarity level in descending order."""
|
||||
# Create a new ultra rare trade with consistent rarity
|
||||
ultra_trade = TradeOffer.objects.create(
|
||||
initiated_by=self.friend_code,
|
||||
rarity_icon='★★★★',
|
||||
rarity_level=4
|
||||
)
|
||||
|
||||
# Add have and want cards with the same rarity
|
||||
TradeOfferHaveCard.objects.create(
|
||||
trade_offer=ultra_trade,
|
||||
card=self.ultra_rare_card,
|
||||
quantity=1
|
||||
)
|
||||
|
||||
TradeOfferWantCard.objects.create(
|
||||
trade_offer=ultra_trade,
|
||||
card=self.ultra_rare_card,
|
||||
quantity=1
|
||||
)
|
||||
|
||||
response = self.client.get(self.url)
|
||||
featured = response.context['featured_offers']
|
||||
keys = list(featured.keys())
|
||||
|
||||
# Order should be: "All", "★★★★" (level 4), "★★★" (level 3), "★" (level 1)
|
||||
self.assertEqual(keys[0], "All")
|
||||
self.assertEqual(keys[1], "★★★★")
|
||||
self.assertEqual(keys[2], "★★★")
|
||||
self.assertEqual(keys[3], "★")
|
||||
|
||||
|
||||
class HomePageViewMockTests(TestCase):
|
||||
"""Test suite using mocks for HomePageView."""
|
||||
|
||||
def setUp(self):
|
||||
self.factory = RequestFactory()
|
||||
self.view = HomePageView()
|
||||
|
||||
@patch('trades.models.TradeOffer.objects')
|
||||
@patch('cards.models.Card.objects')
|
||||
def test_get_context_data_with_mocks(self, mock_card_objects, mock_offer_objects):
|
||||
"""Test get_context_data using mocks."""
|
||||
# Set up request
|
||||
request = self.factory.get(reverse('home'))
|
||||
self.view.request = request
|
||||
|
||||
# Mock the queryset responses
|
||||
mock_offer_filter = MagicMock()
|
||||
mock_offer_objects.filter.return_value = mock_offer_filter
|
||||
mock_offer_filter.order_by.return_value = []
|
||||
|
||||
mock_card_filter = MagicMock()
|
||||
mock_card_objects.filter.return_value = mock_card_filter
|
||||
mock_card_objects.annotate.return_value = mock_card_filter
|
||||
mock_card_objects.all.return_value.order_by.return_value = []
|
||||
mock_card_filter.annotate.return_value = mock_card_filter
|
||||
mock_card_filter.order_by.return_value = []
|
||||
|
||||
mock_offer_filter.values_list.return_value.distinct.return_value = []
|
||||
|
||||
# Call the method
|
||||
context = self.view.get_context_data()
|
||||
|
||||
# Verify the expected context keys exist
|
||||
self.assertIn('cards', context)
|
||||
self.assertIn('recent_offers', context)
|
||||
self.assertIn('most_offered_cards', context)
|
||||
self.assertIn('most_wanted_cards', context)
|
||||
self.assertIn('least_offered_cards', context)
|
||||
self.assertIn('featured_offers', context)
|
||||
|
||||
@patch('trades.models.TradeOffer.objects')
|
||||
def test_empty_featured_offers(self, mock_offer_objects):
|
||||
"""Test handling of empty featured offers."""
|
||||
# Set up request
|
||||
request = self.factory.get(reverse('home'))
|
||||
self.view.request = request
|
||||
|
||||
# Configure mock to return empty queryset
|
||||
mock_offer_filter = MagicMock()
|
||||
mock_offer_objects.filter.return_value = mock_offer_filter
|
||||
mock_offer_filter.order_by.return_value = []
|
||||
mock_offer_filter.values_list.return_value.distinct.return_value = []
|
||||
|
||||
# Call the method
|
||||
context = self.view.get_context_data()
|
||||
|
||||
# Verify the featured_offers is an OrderedDict but with just the "All" key
|
||||
self.assertIsInstance(context['featured_offers'], OrderedDict)
|
||||
self.assertIn("All", context['featured_offers'])
|
||||
self.assertEqual(len(context['featured_offers']), 1)
|
||||
|
||||
@patch('trades.models.TradeOffer.objects.filter')
|
||||
def test_exception_handling(self, mock_filter):
|
||||
"""Test that exceptions are handled gracefully."""
|
||||
# Set up request
|
||||
request = self.factory.get(reverse('home'))
|
||||
self.view.request = request
|
||||
|
||||
# Configure mock to raise an exception
|
||||
mock_filter.side_effect = Exception("Database error")
|
||||
|
||||
# Call the method - should not raise an exception
|
||||
with self.assertLogs(level='ERROR') as cm:
|
||||
context = self.view.get_context_data()
|
||||
|
||||
# Check if error was logged
|
||||
self.assertIn("Unhandled error in HomePageView.get_context_data", cm.output[0])
|
||||
|
||||
# Verify fallback values were set
|
||||
self.assertEqual(len(context['cards']), 0)
|
||||
self.assertEqual(len(context['recent_offers']), 0)
|
||||
self.assertEqual(len(context['most_offered_cards']), 0)
|
||||
self.assertEqual(len(context['most_wanted_cards']), 0)
|
||||
self.assertEqual(len(context['least_offered_cards']), 0)
|
||||
self.assertIsInstance(context['featured_offers'], OrderedDict)
|
||||
self.assertEqual(len(context['featured_offers']), 1)
|
||||
self.assertIn("All", context['featured_offers'])
|
||||
|
||||
class HomePageEdgeCaseTests(TestCase):
|
||||
"""Test edge cases for the home page."""
|
||||
|
||||
def setUp(self):
|
||||
self.client = Client()
|
||||
self.url = reverse('home')
|
||||
|
||||
# Create a user
|
||||
self.user = User.objects.create_user(
|
||||
username='testuser',
|
||||
email='testuser@example.com',
|
||||
password='testpass123'
|
||||
)
|
||||
|
||||
# Create a friend code for the user
|
||||
self.friend_code = FriendCode.objects.create(
|
||||
user=self.user,
|
||||
friend_code='SW-1234-5678-9012',
|
||||
in_game_name='TestTrainer'
|
||||
)
|
||||
|
||||
def test_home_page_with_no_cards(self):
|
||||
"""Test home page with no cards in the database."""
|
||||
response = self.client.get(self.url)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(len(response.context['cards']), 0)
|
||||
|
||||
def test_home_page_with_many_offers(self):
|
||||
"""Test home page with many offers to verify pagination or limiting works."""
|
||||
# Create a card
|
||||
card = Card.objects.create(
|
||||
name='Test Card',
|
||||
cardset='TEST01',
|
||||
cardnum=1,
|
||||
style='normal',
|
||||
rarity_icon='★',
|
||||
rarity_level=1
|
||||
)
|
||||
|
||||
# Create 20 trade offers
|
||||
for i in range(20):
|
||||
trade = TradeOffer.objects.create(
|
||||
initiated_by=self.friend_code,
|
||||
rarity_icon='★',
|
||||
rarity_level=1
|
||||
)
|
||||
|
||||
# Add have and want cards
|
||||
TradeOfferHaveCard.objects.create(
|
||||
trade_offer=trade,
|
||||
card=card,
|
||||
quantity=1
|
||||
)
|
||||
|
||||
TradeOfferWantCard.objects.create(
|
||||
trade_offer=trade,
|
||||
card=card,
|
||||
quantity=1
|
||||
)
|
||||
|
||||
response = self.client.get(self.url)
|
||||
|
||||
# Check that recent_offers is limited to 6 as per the view
|
||||
self.assertEqual(len(response.context['recent_offers']), 6)
|
||||
|
||||
def test_home_page_with_invalid_parameters(self):
|
||||
"""Test home page with invalid GET parameters."""
|
||||
# The view should ignore invalid parameters
|
||||
response = self.client.get(f"{self.url}?invalid=param&another=invalid")
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
def test_performance_with_large_dataset(self):
|
||||
"""Test performance with a larger dataset (basic check)."""
|
||||
# Create a card
|
||||
card = Card.objects.create(
|
||||
name='Performance Test Card',
|
||||
cardset='PERF01',
|
||||
cardnum=1,
|
||||
style='normal',
|
||||
rarity_icon='★',
|
||||
rarity_level=1
|
||||
)
|
||||
|
||||
# Create 50 trade offers with different rarities
|
||||
for i in range(50):
|
||||
rarity_level = (i % 5) + 1 # 1-5
|
||||
rarity_icon = '★' * rarity_level
|
||||
|
||||
trade = TradeOffer.objects.create(
|
||||
initiated_by=self.friend_code,
|
||||
rarity_icon=rarity_icon,
|
||||
rarity_level=rarity_level
|
||||
)
|
||||
|
||||
# Add have and want cards with the same rarity
|
||||
rarity_card = Card.objects.create(
|
||||
name=f'Performance Test Card {i}',
|
||||
cardset='PERF01',
|
||||
cardnum=i+10,
|
||||
style='normal',
|
||||
rarity_icon=rarity_icon,
|
||||
rarity_level=rarity_level
|
||||
)
|
||||
|
||||
TradeOfferHaveCard.objects.create(
|
||||
trade_offer=trade,
|
||||
card=rarity_card,
|
||||
quantity=1
|
||||
)
|
||||
|
||||
TradeOfferWantCard.objects.create(
|
||||
trade_offer=trade,
|
||||
card=rarity_card,
|
||||
quantity=1
|
||||
)
|
||||
|
||||
# Basic performance test - just checking it completes without timeout
|
||||
import time
|
||||
start = time.time()
|
||||
response = self.client.get(self.url)
|
||||
end = time.time()
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
# Should be reasonably fast (adjust threshold as needed)
|
||||
execution_time = end - start
|
||||
self.assertLess(execution_time, 2.0) # Should complete in under 2 seconds
|
||||
|
||||
|
||||
class TemplateRenderingTests(TestCase):
|
||||
"""Tests focused on template rendering."""
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
# Create a user
|
||||
cls.user = User.objects.create_user(
|
||||
username='testuser',
|
||||
email='testuser@example.com',
|
||||
password='testpass123'
|
||||
)
|
||||
|
||||
# Create a friend code for the user
|
||||
cls.friend_code = FriendCode.objects.create(
|
||||
user=cls.user,
|
||||
friend_code='SW-1234-5678-9012',
|
||||
in_game_name='TestTrainer'
|
||||
)
|
||||
|
||||
# Create a card
|
||||
cls.card = Card.objects.create(
|
||||
name='Test Card',
|
||||
cardset='TEST01',
|
||||
cardnum=1,
|
||||
style='normal',
|
||||
rarity_icon='★',
|
||||
rarity_level=1
|
||||
)
|
||||
|
||||
# Create a trade offer
|
||||
cls.trade = TradeOffer.objects.create(
|
||||
initiated_by=cls.friend_code,
|
||||
rarity_icon='★',
|
||||
rarity_level=1
|
||||
)
|
||||
|
||||
# Add have and want cards
|
||||
TradeOfferHaveCard.objects.create(
|
||||
trade_offer=cls.trade,
|
||||
card=cls.card,
|
||||
quantity=1
|
||||
)
|
||||
|
||||
TradeOfferWantCard.objects.create(
|
||||
trade_offer=cls.trade,
|
||||
card=cls.card,
|
||||
quantity=1
|
||||
)
|
||||
|
||||
def setUp(self):
|
||||
self.client = Client()
|
||||
self.factory = RequestFactory()
|
||||
|
||||
def test_template_used(self):
|
||||
"""Test that the correct template is used."""
|
||||
response = self.client.get(reverse('home'))
|
||||
self.assertTemplateUsed(response, 'home/home.html')
|
||||
|
||||
def test_context_variables_exist(self):
|
||||
"""Test that all expected context variables exist."""
|
||||
response = self.client.get(reverse('home'))
|
||||
|
||||
# Check all required context variables
|
||||
expected_keys = [
|
||||
'cards',
|
||||
'recent_offers',
|
||||
'most_offered_cards',
|
||||
'most_wanted_cards',
|
||||
'least_offered_cards',
|
||||
'featured_offers',
|
||||
]
|
||||
|
||||
for key in expected_keys:
|
||||
self.assertIn(key, response.context)
|
||||
|
||||
def test_view_with_pagination_params(self):
|
||||
"""Test that view handles pagination parameters correctly, if applicable."""
|
||||
# Create additional trade offers if pagination is implemented
|
||||
for i in range(10):
|
||||
trade = TradeOffer.objects.create(
|
||||
initiated_by=self.friend_code,
|
||||
rarity_icon='★',
|
||||
rarity_level=1
|
||||
)
|
||||
|
||||
# Add have and want cards
|
||||
TradeOfferHaveCard.objects.create(
|
||||
trade_offer=trade,
|
||||
card=self.card,
|
||||
quantity=1
|
||||
)
|
||||
|
||||
TradeOfferWantCard.objects.create(
|
||||
trade_offer=trade,
|
||||
card=self.card,
|
||||
quantity=1
|
||||
)
|
||||
|
||||
# Test with page parameter
|
||||
response = self.client.get(f"{reverse('home')}?page=1")
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
# Test with invalid page parameter
|
||||
response = self.client.get(f"{reverse('home')}?page=999")
|
||||
self.assertEqual(response.status_code, 200) # Should still render with default page
|
||||
|
||||
# Test with non-numeric page parameter
|
||||
response = self.client.get(f"{reverse('home')}?page=abc")
|
||||
self.assertEqual(response.status_code, 200) # Should handle gracefully
|
||||
|
||||
@patch('home.views.HomePageView.get_context_data')
|
||||
def test_view_renders_with_missing_context(self, mock_get_context):
|
||||
"""Test that view renders even with incomplete context data."""
|
||||
# Return incomplete context
|
||||
mock_get_context.return_value = {'cards': []}
|
||||
|
||||
# Should still render without error even with missing context variables
|
||||
response = self.client.get(reverse('home'))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
def test_compatibility_with_multiple_django_versions(self):
|
||||
"""Ensure compatibility with different Django versions."""
|
||||
import django
|
||||
# Simply log the Django version - the test itself verifies the page renders
|
||||
# with the current version
|
||||
django_version = django.get_version()
|
||||
response = self.client.get(reverse('home'))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
9
src/pkmntrade_club/home/urls.py
Normal file
9
src/pkmntrade_club/home/urls.py
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
from django.urls import path
|
||||
|
||||
from .views import HomePageView, HealthCheckView
|
||||
|
||||
urlpatterns = [
|
||||
path("", HomePageView.as_view(), name="home"),
|
||||
path("health", HealthCheckView.as_view(), name="health"),
|
||||
path("health/", HealthCheckView.as_view(), name="health"),
|
||||
]
|
||||
164
src/pkmntrade_club/home/views.py
Normal file
164
src/pkmntrade_club/home/views.py
Normal file
|
|
@ -0,0 +1,164 @@
|
|||
from collections import defaultdict, OrderedDict
|
||||
from django.views.generic import TemplateView
|
||||
from django.urls import reverse_lazy
|
||||
from django.db.models import Count, Q, Prefetch, Sum, F, IntegerField, Value, BooleanField, Case, When
|
||||
from django.db.models.functions import Coalesce
|
||||
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
|
||||
from pkmntrade_club.trades.models import TradeOffer, TradeAcceptance, TradeOfferHaveCard, TradeOfferWantCard
|
||||
from pkmntrade_club.cards.models import Card
|
||||
from django.utils.decorators import method_decorator
|
||||
from django.template.response import TemplateResponse
|
||||
from django.http import HttpResponseRedirect
|
||||
import logging
|
||||
from django.views import View
|
||||
from django.http import HttpResponse
|
||||
import contextlib
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class HomePageView(TemplateView):
|
||||
template_name = "home/home.html"
|
||||
|
||||
#@silk_profile(name='Home Page')
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
|
||||
try:
|
||||
# Get all cards ordered by name, exclude cards with rarity level > 5
|
||||
context["cards"] = Card.objects.filter(rarity_level__lte=5).order_by("name", "rarity_level")
|
||||
|
||||
# Reuse base trade offer queryset for market stats
|
||||
base_offer_qs = TradeOffer.objects.filter(is_closed=False)
|
||||
|
||||
# Recent Offers
|
||||
try:
|
||||
recent_offers_qs = base_offer_qs.order_by("-created_at")[:6]
|
||||
context["recent_offers"] = recent_offers_qs
|
||||
context["cache_key_recent_offers"] = f"recent_offers_{recent_offers_qs.values_list('pk', 'updated_at')}"
|
||||
except Exception as e:
|
||||
logger.error(f"Error fetching recent offers: {str(e)}")
|
||||
context["recent_offers"] = []
|
||||
context["cache_key_recent_offers"] = "recent_offers_error"
|
||||
|
||||
# Most Offered Cards
|
||||
try:
|
||||
most_offered_cards_qs = (
|
||||
Card.objects.filter(tradeofferhavecard__isnull=False).filter(rarity_level__lte=5)
|
||||
.annotate(offer_count=Sum("tradeofferhavecard__quantity"))
|
||||
.order_by("-offer_count")[:6]
|
||||
)
|
||||
context["most_offered_cards"] = most_offered_cards_qs
|
||||
context["cache_key_most_offered_cards"] = f"most_offered_cards_{most_offered_cards_qs.values_list('pk', 'updated_at')}"
|
||||
except Exception as e:
|
||||
logger.error(f"Error fetching most offered cards: {str(e)}")
|
||||
context["most_offered_cards"] = []
|
||||
context["cache_key_most_offered_cards"] = "most_offered_cards_error"
|
||||
# Most Wanted Cards
|
||||
try:
|
||||
most_wanted_cards_qs = (
|
||||
Card.objects.filter(tradeofferwantcard__isnull=False).filter(rarity_level__lte=5)
|
||||
.annotate(offer_count=Sum("tradeofferwantcard__quantity"))
|
||||
.order_by("-offer_count")[:6]
|
||||
)
|
||||
context["most_wanted_cards"] = most_wanted_cards_qs
|
||||
context["cache_key_most_wanted_cards"] = f"most_wanted_cards_{most_wanted_cards_qs.values_list('pk', 'updated_at')}"
|
||||
except Exception as e:
|
||||
logger.error(f"Error fetching most wanted cards: {str(e)}")
|
||||
context["most_wanted_cards"] = []
|
||||
|
||||
# Least Offered Cards
|
||||
try:
|
||||
least_offered_cards_qs = (
|
||||
Card.objects.filter(rarity_level__lte=5).annotate(
|
||||
offer_count=Coalesce(Sum("tradeofferhavecard__quantity"), 0)
|
||||
)
|
||||
.order_by("offer_count")[:6]
|
||||
)
|
||||
context["least_offered_cards"] = least_offered_cards_qs
|
||||
context["cache_key_least_offered_cards"] = f"least_offered_cards_{least_offered_cards_qs.values_list('pk', 'updated_at')}"
|
||||
except Exception as e:
|
||||
logger.error(f"Error fetching least offered cards: {str(e)}")
|
||||
context["least_offered_cards"] = []
|
||||
context["cache_key_least_offered_cards"] = "least_offered_cards_error"
|
||||
# Build featured offers with custom ordering
|
||||
featured = OrderedDict()
|
||||
# Featured "All" offers remains fixed at the top
|
||||
try:
|
||||
featured["All"] = base_offer_qs.order_by("created_at")[:6]
|
||||
except Exception as e:
|
||||
logger.error(f"Error fetching 'All' featured offers: {str(e)}")
|
||||
featured["All"] = []
|
||||
|
||||
try:
|
||||
# Pull out distinct (rarity_level, rarity_icon) tuples
|
||||
distinct_rarities = base_offer_qs.values_list("rarity_level", "rarity_icon").distinct()
|
||||
|
||||
# Prepare a list that holds tuples of (rarity_level, rarity_icon, offers)
|
||||
rarity_offers = []
|
||||
for rarity_level, rarity_icon in distinct_rarities:
|
||||
offers = base_offer_qs.filter(rarity_level=rarity_level).order_by("created_at")[:6]
|
||||
rarity_offers.append((rarity_level, rarity_icon, offers))
|
||||
|
||||
# Sort by rarity_level (from greatest to least)
|
||||
rarity_offers.sort(key=lambda x: x[0], reverse=True)
|
||||
|
||||
# Add the sorted offers to the OrderedDict
|
||||
for rarity_level, rarity_icon, offers in rarity_offers:
|
||||
featured[rarity_icon] = offers
|
||||
except Exception as e:
|
||||
logger.error(f"Error processing rarity-based featured offers: {str(e)}")
|
||||
|
||||
context["featured_offers"] = featured
|
||||
# Generate a cache key based on the pks and updated_at timestamps of all featured offers
|
||||
all_offer_identifiers = []
|
||||
for section_name,section_offers in featured.items():
|
||||
# featured_section is a QuerySet. Fetch (pk, updated_at) tuples.
|
||||
identifiers = section_offers.values_list('pk', 'updated_at')
|
||||
# Format each tuple as "pk_timestamp" and add to the list
|
||||
section_strings = [f"{section_name}_{pk}_{ts.timestamp()}" for pk, ts in identifiers]
|
||||
all_offer_identifiers.extend(section_strings)
|
||||
|
||||
# Join all identifiers into a single string, sorted for consistency regardless of order
|
||||
combined_identifiers = "|".join(sorted(all_offer_identifiers))
|
||||
context["cache_key_featured_offers"] = f"featured_offers_{combined_identifiers}"
|
||||
except Exception as e:
|
||||
logger.error(f"Unhandled error in HomePageView.get_context_data: {str(e)}")
|
||||
# Provide fallback empty data
|
||||
context["cards"] = None
|
||||
context["recent_offers"] = []
|
||||
context["most_offered_cards"] = []
|
||||
context["most_wanted_cards"] = []
|
||||
context["least_offered_cards"] = []
|
||||
context["featured_offers"] = OrderedDict([("All", [])])
|
||||
|
||||
return context
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
"""Override get method to add caching"""
|
||||
return super().get(request, *args, **kwargs)
|
||||
|
||||
class HealthCheckView(View):
|
||||
def get(self, request, *args, **kwargs):
|
||||
|
||||
try:
|
||||
from django.db import connection
|
||||
connection.cursor().execute("SELECT 1")
|
||||
except Exception as e:
|
||||
return HttpResponse("Database connection failed", status=500)
|
||||
|
||||
try:
|
||||
from pkmntrade_club.trades.models import TradeOffer
|
||||
with contextlib.redirect_stdout(None):
|
||||
print(TradeOffer.objects.count())
|
||||
except Exception as e:
|
||||
return HttpResponse("DB models not reachable, but db is reachable", status=500)
|
||||
|
||||
try:
|
||||
from django.core.cache import cache
|
||||
cache.set("test", "test")
|
||||
with contextlib.redirect_stdout(None):
|
||||
print(cache.get("test"))
|
||||
except Exception as e:
|
||||
return HttpResponse("Cache not reachable", status=500)
|
||||
|
||||
return HttpResponse("OK/HEALTHY")
|
||||
Loading…
Add table
Add a link
Reference in a new issue