diff --git a/functional_tests/test_all_matches.py b/functional_tests/test_all_matches.py
new file mode 100644
index 0000000..237a3c8
--- /dev/null
+++ b/functional_tests/test_all_matches.py
@@ -0,0 +1,102 @@
+import time
+
+from django.test import LiveServerTestCase
+from selenium import webdriver
+from selenium.common.exceptions import NoSuchElementException
+
+from leaderboard.models import Player, Match
+
+
+class AllMatchesTest(LiveServerTestCase):
+
+ def setUp(self):
+ """Start all tests by setting up an authenticated browser."""
+ self.browser = webdriver.Firefox()
+
+ def tearDown(self):
+ """Stop all tests by shutting down the browser."""
+ self.browser.quit()
+
+ def test_all_matches(self):
+ """
+ Test all matches page view.
+
+ The page should load the 50 most recent matches, and show links
+ at the bottom to switch pages.
+ """
+ # Load database with Bob and Sue Hope
+ bob = Player.objects.create(first_name='Bob', last_name='Hope')
+ sue = Player.objects.create(first_name='Sue', last_name='Hope')
+
+ # Input 30 games
+ for _ in range(30):
+ Match.objects.create(
+ winner=bob,
+ loser=sue,
+ winning_score=21,
+ losing_score=19
+ )
+
+ # Bob loads the home page, and notices a link for all matches.
+ self.browser.get(self.live_server_url)
+ all_matches_link = self.browser.find_element_by_id('all-matches-link')
+ all_matches_link.click()
+
+ # It takes him to a page where he sees all matches.
+ matches_url = self.live_server_url + '/matches/'
+ self.assertEqual(matches_url, self.browser.current_url)
+ matches = self.browser.find_elements_by_id('match')
+ self.assertEqual(len(matches), 30)
+
+ # He notices a paginator showing him what page he's on.
+ current_page = self.browser.find_element_by_id('current-page')
+ self.assertEqual(current_page.text, 'Page 1 of 1')
+
+ # Because there's only 1 page, there are no links to next or previous page
+ with self.assertRaises(NoSuchElementException):
+ self.browser.find_element_by_id('previous-page-link')
+ with self.assertRaises(NoSuchElementException):
+ self.browser.find_element_by_id('next-page-link')
+
+ # Input 80 more games and refresh page
+ for _ in range(80):
+ Match.objects.create(
+ winner=bob,
+ loser=sue,
+ winning_score=21,
+ losing_score=19
+ )
+ self.browser.refresh()
+
+ # He notices a paginator showing him he's on page 1 of 3 with a link to the next page.
+ matches = self.browser.find_elements_by_id('match')
+ self.assertEqual(len(matches), 50)
+ current_page = self.browser.find_element_by_id('current-page')
+ self.assertEqual(current_page.text, 'Page 1 of 3')
+ with self.assertRaises(NoSuchElementException):
+ self.browser.find_element_by_id('previous-page-link')
+ next_page_link = self.browser.find_element_by_id('next-page-link')
+ self.assertEqual(next_page_link.text, 'Next')
+
+ # He goes to the next page, and sees a previous page link.
+ next_page_link.click()
+ matches = self.browser.find_elements_by_id('match')
+ self.assertEqual(len(matches), 50)
+ current_page = self.browser.find_element_by_id('current-page')
+ self.assertEqual(current_page.text, 'Page 2 of 3')
+ previous_page_link = self.browser.find_element_by_id('previous-page-link')
+ self.assertEqual(previous_page_link.text, 'Previous')
+
+ # He goes to the final page, and sees no next page link.
+ self.browser.find_element_by_id('next-page-link').click()
+ matches = self.browser.find_elements_by_id('match')
+ self.assertEqual(len(matches), 10)
+ current_page = self.browser.find_element_by_id('current-page')
+ self.assertEqual(current_page.text, 'Page 3 of 3')
+ with self.assertRaises(NoSuchElementException):
+ self.browser.find_element_by_id('next-page-link')
+
+ # He goes back to the previous page.
+ self.browser.find_element_by_id('previous-page-link').click()
+ current_page = self.browser.find_element_by_id('current-page')
+ self.assertEqual(current_page.text, 'Page 2 of 3')
diff --git a/leaderboard/models.py b/leaderboard/models.py
index 8cb6c68..f93e84d 100644
--- a/leaderboard/models.py
+++ b/leaderboard/models.py
@@ -48,12 +48,17 @@ def score(self):
"""Hyphenated version of match score, i.e. 21-19"""
score = f'{self.winning_score}-{self.losing_score}'
return score
+
+ @property
+ def date(self):
+ """Date part of match datetime."""
+ date = self.datetime.strftime('%m/%d/%Y')
+ return date
@property
def description(self):
- match_date = self.datetime.strftime('%m/%d/%Y')
description = (
- f'{match_date}: {self.winner} defeated {self.loser} {self.score}'
+ f'{self.date}: {self.winner} defeated {self.loser} {self.score}'
)
return description
diff --git a/leaderboard/templates/all_matches.html b/leaderboard/templates/all_matches.html
new file mode 100644
index 0000000..c99ef4b
--- /dev/null
+++ b/leaderboard/templates/all_matches.html
@@ -0,0 +1,45 @@
+
+
+
+
+
+
+ PongBoard - Matches
+
+
+
+ All Matches
+
+
+
+ Date |
+ Winner |
+ Loser |
+ Score |
+
+ {% for match in matches %}
+
+ {{ match.date }} |
+ {{ match.winner }} |
+ {{ match.loser }} |
+ {{ match.score }} |
+
+ {% endfor %}
+
+
+
+ {% if matches.has_previous %}
+ Previous
+ {% endif %}
+
+
+ Page {{ matches.number }} of {{ matches.paginator.num_pages }}
+
+
+ {% if matches.has_next %}
+ Next
+ {% endif %}
+
+
+
+
\ No newline at end of file
diff --git a/leaderboard/templates/home.html b/leaderboard/templates/home.html
index 8dbc343..2acab66 100644
--- a/leaderboard/templates/home.html
+++ b/leaderboard/templates/home.html
@@ -82,6 +82,7 @@ PongBoard
{% for match in recent_matches %}
{{ match.description }}
{% endfor %}
-
+
+ See all matches
\ No newline at end of file
diff --git a/leaderboard/tests/test_models.py b/leaderboard/tests/test_models.py
index d4a53d3..fe00420 100644
--- a/leaderboard/tests/test_models.py
+++ b/leaderboard/tests/test_models.py
@@ -94,6 +94,16 @@ def test_score(self):
expected_score = f'{self.winning_score}-{self.losing_score}'
self.assertEqual(self.match.score, expected_score)
+ def test_date(self):
+ """
+ Test match model has a date property.
+
+ Date is a truncated version of the datetime with only
+ the date part.
+ """
+ expected_date = self.match.datetime.strftime('%m/%d/%Y')
+ self.assertEqual(self.match.date, expected_date)
+
def test_description(self):
"""
Test match model has a description property.
diff --git a/leaderboard/tests/test_views.py b/leaderboard/tests/test_views.py
index b73d6de..9b8cdd0 100644
--- a/leaderboard/tests/test_views.py
+++ b/leaderboard/tests/test_views.py
@@ -1,6 +1,7 @@
from django.test import TestCase
from django.utils.html import escape
from django.contrib.auth.models import User
+from django.core.paginator import Paginator
from leaderboard.models import Player, Match
from leaderboard.forms import MatchForm, PlayerForm, DUPLICATE_ERROR
@@ -140,3 +141,67 @@ def test_invalid_player_returns_form(self):
form = response.context['player_form']
expected_form = PlayerForm(self.invalid_player_data)
self.assertEqual(form.as_p(), expected_form.as_p())
+
+
+class AllMatchesTest(TestCase):
+
+ @classmethod
+ def setUpClass(cls):
+ """Add matches to database for each test."""
+ super().setUpClass() # Needed to run only once for all tests
+ cls.player1 = Player.objects.create(first_name='Bob', last_name='Hope')
+ cls.player2 = Player.objects.create(first_name='Sue', last_name='Hope')
+ for _ in range(51):
+ Match.objects.create(
+ winner=cls.player1,
+ loser=cls.player2,
+ winning_score=21,
+ losing_score=10
+ )
+
+ def test_correct_template(self):
+ """Test that the match HTML template is used."""
+ response = self.client.get('/matches/')
+ self.assertTemplateUsed(response, 'all_matches.html')
+
+ def test_paginator_with_all_matches(self):
+ """Test view has paginator with all matches."""
+ response = self.client.get('/matches/')
+ matches = response.context['matches']
+ self.assertEqual(matches.paginator.count, 51)
+
+ def test_num_pages(self):
+ """Test paginator has correct number of pages."""
+ response = self.client.get('/matches/')
+ matches = response.context['matches']
+ self.assertEqual(matches.paginator.num_pages, 2)
+
+ def test_returns_num_matches(self):
+ """Test that 50 matches are returned."""
+ response = self.client.get('/matches/')
+ matches = response.context['matches']
+ self.assertEqual(len(matches), 50)
+
+ def test_default_first_page(self):
+ """Test that the default page is page 1."""
+ response = self.client.get('/matches/')
+ matches = response.context['matches']
+ self.assertFalse(matches.has_previous())
+
+ def test_get_page(self):
+ """Test page requested through GET."""
+ response = self.client.get('/matches/?page=2')
+ matches = response.context['matches']
+ self.assertEqual(matches.number, 2)
+
+ def test_empty_page(self):
+ """Test empty page defaults to last page."""
+ response = self.client.get('/matches/?page=1000')
+ matches = response.context['matches']
+ self.assertFalse(matches.has_next())
+
+ def test_num_matches_last_page(self):
+ """Test that 1 match returned in last page."""
+ response = self.client.get('/matches/?page=2')
+ matches = response.context['matches']
+ self.assertEqual(len(matches), 1)
diff --git a/leaderboard/views.py b/leaderboard/views.py
index 3c7da7d..a1d5394 100644
--- a/leaderboard/views.py
+++ b/leaderboard/views.py
@@ -1,4 +1,5 @@
from django.shortcuts import render, redirect
+from django.core.paginator import EmptyPage, PageNotAnInteger, Paginator
from leaderboard.models import Match, PlayerRating
from leaderboard.forms import MatchForm, PlayerForm
@@ -34,3 +35,23 @@ def home_page(request):
'unranked_players': unranked_players
}
)
+
+
+def all_matches(request):
+ """Render page to view all matches."""
+ all_matches = Match.objects.all().order_by('-datetime')
+ paginator = Paginator(all_matches, per_page=50)
+ page = request.GET.get('page')
+ try:
+ matches = paginator.page(page)
+ except PageNotAnInteger: # occurs when no page is passed through
+ matches = paginator.page(1)
+ except EmptyPage: # occurs when page is out of range
+ matches = paginator.page(paginator.num_pages) # deliver last page of results
+ return render(
+ request,
+ 'all_matches.html',
+ context={
+ 'matches': matches
+ }
+ )
diff --git a/pongboard/urls.py b/pongboard/urls.py
index 9588461..9a8376d 100644
--- a/pongboard/urls.py
+++ b/pongboard/urls.py
@@ -13,12 +13,15 @@
1. Import the include() function: from django.conf.urls import url, include
2. Add a URL to urlpatterns: url(r'^blog/', include('blog.urls'))
"""
+
from django.conf.urls import url, include
from django.contrib import admin
-from leaderboard.views import home_page
+
+from leaderboard.views import home_page, all_matches
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^$', view=home_page, name='home'),
url(r'accounts/', include('django.contrib.auth.urls')),
+ url(r'matches/', view=all_matches, name='all_matches'),
]