Skip to content

Commit

Permalink
Merge pull request #98 from questionlp/develop
Browse files Browse the repository at this point in the history
Add panelist, guest, host, scorekeeper slug string fuzzy matching redirects
  • Loading branch information
questionlp authored Jan 9, 2025
2 parents 6b8b27e + d1b519a commit 3649921
Show file tree
Hide file tree
Showing 13 changed files with 168 additions and 8 deletions.
23 changes: 23 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,28 @@
# Changes

## 6.3.0

### Application Changes

- Add fuzzy matching for panelist slugs in the `panelists.details` route. The fuzzy matching slugifies the input `panelist_slug` value and compares it against the list of all panelist slugs.

If the slugified value matches a valid panelist slug and the slugified value does not match the original `panelist_slug` value, then redirect the user to the correct path for the panelist. If there isn't a match, then redirect the user to `panelists.index`.

The extra check between the slugified `panelist_slug` value against the request's `panelist_slug` is to prevent the chance of an infinite redirect loop from happening.

For example, if the user requests `/panelists/Luke%20Burbank` will match the slugified value of `Luke%20Burbank` to `luke-burbank` and redirects the user to `/panelists/luke-burbank`. However, if the user requests `/panelists/Luke%20Burbonk`, there won't be a match and redirects the user to `/panelists`.
- Similar updates to the corresponding guests, hosts and scorekeepers routes have also been made.
- Add testing for the new slug fuzzy matching redirects for guests, hosts, panelists and scorekeepers.

### Component Updates

- Upgrade Flask from 3.0.3 to 3.1.0
- Upgrade Markdown from 3.5.2 to 3.7.0

### Development Changes

- Added test for `errors.not_found`

## 6.2.5

### Application Changes
Expand Down
10 changes: 10 additions & 0 deletions app/guests/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"""Guests Routes for Wait Wait Stats Page."""
import mysql.connector
from flask import Blueprint, Response, current_app, redirect, render_template, url_for
from slugify import slugify
from wwdtm.guest import Guest

from app.utility import redirect_url
Expand Down Expand Up @@ -53,6 +54,15 @@ def details(guest_slug: str) -> Response | str:
"""View: Guest Details."""
database_connection = mysql.connector.connect(**current_app.config["database"])
guest = Guest(database_connection=database_connection)
slugs = guest.retrieve_all_slugs()
_slug = slugify(guest_slug)

if _slug not in slugs:
return redirect(url_for("guests.index"))

if _slug in slugs and _slug != guest_slug:
return redirect(url_for("guests.details", guest_slug=_slug))

_details = guest.retrieve_details_by_slug(guest_slug)
database_connection.close()

Expand Down
10 changes: 10 additions & 0 deletions app/hosts/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"""Hosts Routes for Wait Wait Stats Page."""
import mysql.connector
from flask import Blueprint, Response, current_app, redirect, render_template, url_for
from slugify import slugify
from wwdtm.host import Host

from app.utility import redirect_url
Expand Down Expand Up @@ -53,6 +54,15 @@ def details(host_slug: str) -> Response | str:
"""View: Host Details."""
database_connection = mysql.connector.connect(**current_app.config["database"])
host = Host(database_connection=database_connection)
slugs = host.retrieve_all_slugs()
_slug = slugify(host_slug)

if _slug not in slugs:
return redirect(url_for("hosts.index"))

if _slug in slugs and _slug != host_slug:
return redirect_url(url_for("hosts.details", host_slug=_slug))

_details = host.retrieve_details_by_slug(host_slug)
database_connection.close()

Expand Down
10 changes: 10 additions & 0 deletions app/panelists/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"""Panelists Routes for Wait Wait Stats Page."""
import mysql.connector
from flask import Blueprint, Response, current_app, redirect, render_template, url_for
from slugify import slugify
from wwdtm.panelist import Panelist

from app.utility import redirect_url
Expand Down Expand Up @@ -53,6 +54,15 @@ def details(panelist_slug: str) -> Response | str:
"""View: Panelists Details."""
database_connection = mysql.connector.connect(**current_app.config["database"])
panelist = Panelist(database_connection=database_connection)
slugs = panelist.retrieve_all_slugs()
_slug = slugify(panelist_slug)

if _slug not in slugs:
return redirect(url_for("panelists.index"))

if _slug in slugs and _slug != panelist_slug:
return redirect(url_for("panelists.details", panelist_slug=_slug))

_details = panelist.retrieve_details_by_slug(
panelist_slug,
use_decimal_scores=current_app.config["app_settings"]["use_decimal_scores"],
Expand Down
10 changes: 10 additions & 0 deletions app/scorekeepers/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"""Scorekeepers Routes for Wait Wait Stats Page."""
import mysql.connector
from flask import Blueprint, Response, current_app, redirect, render_template, url_for
from slugify import slugify
from wwdtm.scorekeeper import Scorekeeper

from app.utility import redirect_url
Expand Down Expand Up @@ -53,6 +54,15 @@ def details(scorekeeper_slug: str) -> Response | str:
"""View: Scorekeeper Details."""
database_connection = mysql.connector.connect(**current_app.config["database"])
scorekeeper = Scorekeeper(database_connection=database_connection)
slugs = scorekeeper.retrieve_all_slugs()
_slug = slugify(scorekeeper_slug)

if _slug not in slugs:
return redirect_url(url_for("scorekeepers.index"))

if _slug in slugs and _slug != scorekeeper_slug:
return redirect_url(url_for("scorekeepers.details", scorekeeper_slug=_slug))

_details = scorekeeper.retrieve_details_by_slug(scorekeeper_slug)
database_connection.close()

Expand Down
2 changes: 1 addition & 1 deletion app/version.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@
#
# vim: set noai syntax=python ts=4 sw=4:
"""Application Version for Wait Wait Stats Page."""
APP_VERSION = "6.2.5"
APP_VERSION = "6.3.0"
4 changes: 2 additions & 2 deletions requirements-dev.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ black==24.10.0
pytest==8.3.3
pytest-cov==5.0.0

Flask==3.0.3
Flask==3.1.0
gunicorn==23.0.0
Markdown==3.5.2
Markdown==3.7.0

wwdtm==2.14.0
4 changes: 2 additions & 2 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
Flask==3.0.3
Flask==3.1.0
gunicorn==23.0.0
Markdown==3.5.2
Markdown==3.7.0

wwdtm==2.14.0
15 changes: 15 additions & 0 deletions tests/test_errors.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Copyright (c) 2018-2025 Linh Pham
# stats.wwdt.me is released under the terms of the Apache License 2.0
# SPDX-License-Identifier: Apache-2.0
#
# vim: set noai syntax=python ts=4 sw=4:
"""Testing Errors Module and Blueprint Views."""
from flask.testing import FlaskClient
from werkzeug.test import TestResponse


def test_not_found(client: FlaskClient) -> None:
"""Testing errors.not_found."""
response: TestResponse = client.get("/host/peter-seagull")
assert response.status_code == 404
assert b"Not Found" in response.data
19 changes: 19 additions & 0 deletions tests/test_guests.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"""Testing Guests Module and Blueprint Views."""
import pytest
from flask.testing import FlaskClient
from slugify import slugify
from werkzeug.test import TestResponse


Expand All @@ -27,6 +28,24 @@ def test_details(client: FlaskClient, guest_slug: str) -> None:
assert b"Appearances" in response.data


@pytest.mark.parametrize("guest_slug", ["Tom Hanks", "A'ja Wilson"])
def test_details_slug_match_redirect(client: FlaskClient, guest_slug: str) -> None:
"""Testing guests.details with slug matching redirection."""
response: TestResponse = client.get(f"/guests/{guest_slug}")
_slug = slugify(guest_slug)
assert response.status_code == 302
assert f"{_slug}" in response.headers["Location"]


@pytest.mark.parametrize("guest_slug", ["Thom Thanks", "A'j'a Wilsong"])
def test_details_slug_non_match_redirect(client: FlaskClient, guest_slug: str) -> None:
"""Testing guests.details with slug not matching redirection."""
response: TestResponse = client.get(f"/guests/{guest_slug}")
_slug = slugify(guest_slug)
assert response.status_code == 302
assert f"{_slug}" not in response.headers["Location"]


def test_all(client: FlaskClient) -> None:
"""Testing guests._all."""
response: TestResponse = client.get("/guests/all")
Expand Down
19 changes: 19 additions & 0 deletions tests/test_hosts.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"""Testing Hosts Module and Blueprint Views."""
import pytest
from flask.testing import FlaskClient
from slugify import slugify
from werkzeug.test import TestResponse


Expand All @@ -27,6 +28,24 @@ def test_details(client: FlaskClient, host_slug: str) -> None:
assert b"Appearances" in response.data


@pytest.mark.parametrize("host_slug", ["Tom Hanks", "Luke Burbank"])
def test_details_slug_match_redirect(client: FlaskClient, host_slug: str) -> None:
"""Testing hosts.details with slug matching redirection."""
response: TestResponse = client.get(f"/hosts/{host_slug}")
_slug = slugify(host_slug)
assert response.status_code == 302
assert f"{_slug}" in response.headers["Location"]


@pytest.mark.parametrize("host_slug", ["Thom Thanks", "Luuuke Burbonk"])
def test_details_slug_non_match_redirect(client: FlaskClient, host_slug: str) -> None:
"""Testing hosts.details with slug not matching redirection."""
response: TestResponse = client.get(f"/hosts/{host_slug}")
_slug = slugify(host_slug)
assert response.status_code == 302
assert f"{_slug}" not in response.headers["Location"]


def test_all(client: FlaskClient) -> None:
"""Testing hosts._all."""
response: TestResponse = client.get("/hosts/all")
Expand Down
21 changes: 21 additions & 0 deletions tests/test_panelists.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"""Testing Panelists Module and Blueprint Views."""
import pytest
from flask.testing import FlaskClient
from slugify import slugify
from werkzeug.test import TestResponse


Expand All @@ -27,6 +28,26 @@ def test_details(client: FlaskClient, panelist_slug: str) -> None:
assert b"Appearances" in response.data


@pytest.mark.parametrize("panelist_slug", ["Luke Burbank", "Faith Salie"])
def test_details_slug_match_redirect(client: FlaskClient, panelist_slug: str) -> None:
"""Testing panelists.details with slug matching redirection."""
response: TestResponse = client.get(f"/panelists/{panelist_slug}")
_slug = slugify(panelist_slug)
assert response.status_code == 302
assert f"{_slug}" in response.headers["Location"]


@pytest.mark.parametrize("panelist_slug", ["Luuuke Burbonk", "Faiths Sally"])
def test_details_slug_non_match_redirect(
client: FlaskClient, panelist_slug: str
) -> None:
"""Testing panelists.details with slug not matching redirection."""
response: TestResponse = client.get(f"/panelists/{panelist_slug}")
_slug = slugify(panelist_slug)
assert response.status_code == 302
assert f"{_slug}" not in response.headers["Location"]


def test_all(client: FlaskClient) -> None:
"""Testing panelists._all."""
response: TestResponse = client.get("/panelists/all")
Expand Down
29 changes: 26 additions & 3 deletions tests/test_scorekeepers.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"""Testing Scorekeepers Module and Blueprint Views."""
import pytest
from flask.testing import FlaskClient
from slugify import slugify
from werkzeug.test import TestResponse


Expand All @@ -17,16 +18,38 @@ def test_index(client: FlaskClient) -> None:
assert b"Random" in response.data


@pytest.mark.parametrize("scorekeepers_slug", ["bill-kurtis"])
def test_details(client: FlaskClient, scorekeepers_slug: str) -> None:
@pytest.mark.parametrize("scorekeeper_slug", ["bill-kurtis"])
def test_details(client: FlaskClient, scorekeeper_slug: str) -> None:
"""Testing scorekeepers.details."""
response: TestResponse = client.get(f"/scorekeepers/{scorekeepers_slug}")
response: TestResponse = client.get(f"/scorekeepers/{scorekeeper_slug}")
assert response.status_code == 200
assert b"Scorekeeper Details" in response.data
assert b"DB ID" in response.data
assert b"Appearances" in response.data


@pytest.mark.parametrize("scorekeeper_slug", ["Carl Kasell", "Chioke I'Anson"])
def test_details_slug_match_redirect(
client: FlaskClient, scorekeeper_slug: str
) -> None:
"""Testing scorekeepers.details with slug matching redirection."""
response: TestResponse = client.get(f"/scorekeepers/{scorekeeper_slug}")
_slug = slugify(scorekeeper_slug)
assert response.status_code == 302
assert f"{_slug}" in response.headers["Location"]


@pytest.mark.parametrize("scorekeeper_slug", ["Karl Cassel", "Chi'oke Ianson"])
def test_details_slug_non_match_redirect(
client: FlaskClient, scorekeeper_slug: str
) -> None:
"""Testing scorekeepers.details with slug not matching redirection."""
response: TestResponse = client.get(f"/scorekeepers/{scorekeeper_slug}")
_slug = slugify(scorekeeper_slug)
assert response.status_code == 302
assert f"{_slug}" not in response.headers["Location"]


def test_all(client: FlaskClient) -> None:
"""Testing scorekeepers._all."""
response: TestResponse = client.get("/scorekeepers/all")
Expand Down

0 comments on commit 3649921

Please sign in to comment.