diff --git a/.gitignore b/.gitignore index 188fd02..709c4bf 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ /venv/Lib/site-packages/ /Classes/gibberish.py /venv/Lib/site-packages/ /Classes/gibberish.py /Classes/notevengibberish.py /Classes/number3.py +*.pyc diff --git a/Classes/AppTourneydraw.py b/Classes/AppTourneydraw.py new file mode 100644 index 0000000..a3aac99 --- /dev/null +++ b/Classes/AppTourneydraw.py @@ -0,0 +1,225 @@ +# Name: Tournament draw generator +# Author: Somnifuguist (w.wiki/fDy) +# Date: 10-10-2020 +# Content: Generates wikitext for tennis tournament draws + +from ClassSupportFunctions import * +from GetFlagicon import GetFlagicon + +import itertools +from nameparser import HumanName +from num2words import num2words +import re + +def GetNameLink(name): + # Finds and returns formatted name and wikilinks for given name. + name = HumanName(name) + name.capitalize(force=True) + name = str(name) + soup = GetSoup("https://en.wikipedia.org/wiki/" + name.replace(" ", "_"), False).text + wikitext = name + tennis = ["International Tennis Federation", "Prize money", "Grand Slam", "tennis career", "Wikipedia does not have", "may refer to", "WTA", "ITF", "ATP"] + pipe = False + if soup != None: + if any([f in soup for f in tennis]): # player article exists, or no article exists + if "Redirected from" in soup: + soup = GetSoup(soup, True) + title = str(soup.title.string).replace(" - Wikipedia", "").strip() + wikitext = title + pipe = True # if name is redirect, pipes wikilink to avoid anachronist names, e.g. using "Margaret Court" instead of "Margaret Smith" before she married. + else: # article exists for name but for different person + wikitext = name + " (tennis)" + pipe = True + wikilink = "[[" + wikitext + ("|" + name if pipe else "") + "]]" + split_name = name.split(" ") + abbr_name = "-".join(f[0] for f in split_name[0].split("-")) + " " + " ".join(split_name[1:]) # reduce name to first name initials + last name, e.g. "J-L Struff" + abbr_wikilink = "[[" + wikitext + "|" + abbr_name + "]]" + return [name, wikilink, abbr_wikilink] + +class Page(): + def __init__(self): + self.text = [] # contains the draw wikitext + +class Player(): + def __init__(self, player): + self.name = player[0] + self.country = player[1] + self.seed = player[2] + +class Match(): + def __init__(self, match, sets): + self.parsed = False # match has been checked for retirements, tiebreakers etc. + self.teams = [[Player(f) for f in match[0]], [Player(f) for f in match[1]]] + self.score = match[2] + [["",""]] * (sets - len(match[2]) + 1) # add empty sets to matches where the full number of sets wasn't needed + self.winner = self.score[0][0] + if self.score[0][1] == ["w/o"]: + self.score[1] = ["", "w/o"] if self.winner else ["w/o", ""] # puts w/o on winner's side + self.bye = ["BYE"] in self.score + self.sets = sets + +class Tournament(): + def __init__(t, data, format, qual, year): + t.data = [] + t.match_tiebreak = format == 2 # deciding set tiebreak in e.g. 2001 Aus Open Mixed Doubles + format = 3 if format == 2 else format + for c, round in enumerate(data): + sets = 3 if format == 3 or (format == 35 and c < len(data) - 1) or (format == 355 and c < len(data) - 2) else 5 # doesn't handle sets formats like 5353. + t.data.append([Match(match, sets) for match in round]) + t.rounds = len(t.data) + t.format = format # number of sets in matches, 35 if final has 5 sets + t.doubles = len(data[0][0][0]) == 2 + t.qual = qual + t.byes = [["BYE"] in match[2][1] for match in data[0]] + round_names = ["First Round", "Second Round", "Third Round", "Fourth Round", "Fifth Round", "Quarterfinals", "Semifinals", "Final", "Champion" + ("s" if t.doubles else "")] + t.round_names = round_names[:t.rounds-1] + ["Qualifying Competition", "Qualified"] if t.qual else round_names[:t.rounds-3] + round_names[-4:] # sometimes called "Qualifying Round" + + # Get dicts for players' wikilinks, and year-accurate flagicons + t.name_links = LoadJSON("name_links.json") # database of all name links already looked up + t.flagicons = {} + teams = [[f for f in match.teams[0]] + [f for f in match.teams[1]] for match in t.data[0]] + players = list(itertools.chain.from_iterable(teams)) # list of players in first round i.e. all players + for player in players: + if player.name not in t.name_links: + t.name_links[player.name] = GetNameLink(player.name) + if player.country not in t.flagicons: + t.flagicons[player.country] = GetFlagicon(player.country, year) + SaveJSON("name_links.json", t.name_links) + t.lucky_losers = [] + + def SplitData(t, n, r): + # Splits first r rounds of data into n equal sections. + SplitData = [] + for i in range(n): + part = [] + for j in range(r): + matches = int(len(t.data[j])/n) + part.append(t.data[j][i*matches:(i+1)*matches]) + SplitData.append(part) + return SplitData + + def MakeSeeds(t, p, sections, seed_links): + # Generates and wikitext for seed section of draw article. + # If seed_links, link each seed's number to the player's section in draw using {{seeds}}/{{qseeds}} templates. + page = [] + seeds = {} # [team, round reached, final match retired/withdrew] + for c, round in enumerate(t.data): # find and add all seeds and their results to seeds dict + for match in round: + retiredwithdrew = "" if match.bye or str(match.score[0][1][-1]).isdigit() else match.score[0][1][-1] + for i in range(2): + seed = match.teams[i][0].seed + if seed != []: + if seed[0].isdigit(): + seeds[int(seed[0])] = [match.teams[i], (c if i != match.winner else c + 1), retiredwithdrew] + def FindSection(sections, seed): + # Finds section of draw containing given seed. + for c, section in enumerate(sections): + for round in section: + for match in round: + if (match.teams[0][0].seed != [] and match.teams[0][0].seed[0] == str(seed)) or (match.teams[1][0].seed != [] and match.teams[1][0].seed[0] == str(seed)): + return str(c + 1) + return "0" + + if seeds != {}: + page += ["", "==Seeds==", "{{columns-list|colwidth=30em|"] + for l in range(1, max(seeds) + 1): + number = "# " + if seed_links and len(sections) >= 4: + section = "{{seeds" + ("q" if t.qual else "") + "|" + str(l) + "|" + FindSection(sections, l) + "}}" + number = (section if section != 0 else "# ") + else: + seed_links = False + try: # create text for seed, formatting (bold/italics) depending on result + style = ("'''" if seeds[l][1] == t.rounds else "''") + reached = t.round_names[seeds[l][1]].replace("Round", "round").replace("Competition", "competition") # upper-case is used in draw templates but not seeds + lucky_loser = ", '''''Lucky loser'''''" if seeds[l][0] in [ll[0] for ll in t.lucky_losers] else "" + retiredwithdrew = ", retired" if seeds[l][2] == "r" else (", withdrew" if seeds[l][2] == "w/o" else "") + name_text = " / ".join([t.flagicons[f.country] + " " + t.name_links[f.name][1] for f in seeds[l][0]]) + page += [number + (style if style == "'''" else "") + name_text + " " + (style if style == "''" else "") + "(" + reached + retiredwithdrew + lucky_loser + ")" + style] + except KeyError: # seed not in draw, usually due to withdrawal + page += [number + "''(Withdrew)''"] + page += ["}}"] + (["", "{{Seeds explanation}}"] if seed_links else []) + p.text = page + p.text + + def MakeQualifiers(t, p): + # Generates wikitext for qualifiers. + p.text += ["", "==Qualifiers==", "{{columns-list|colwidth=30em|"] + for match in t.data[-1]: + winner = match.winner + p.text += ["# " + " / ".join([t.flagicons[f.country] + " '''" + t.name_links[f.name][1] + "'''" for f in match.teams[winner]])] + p.text += ["}}"] + + def MakeLuckyLosers(t, p): + # Generates wikitext for lucky losers; not currently used as t.lucky_losers is always empty. + if t.lucky_losers != []: + p.text += ["", "==Lucky losers==", "{{columns-list|colwidth=30em|"] + for ll in t.lucky_losers: + p.text += ["# " + t.flagicons[ll[0]] + " '''" + t.name_links[ll[0]][1] + "'''"] + p.text += ["}}"] + + def MakeSection(t, p, data, rounds, round_names, format, byes, compact, short_names): + # Generates wikitext for section of draw, using standard templates like {{16TeamBracket-Tennis3}}. + # Doesn't accommodate for "|byes=1" added with https://en.wikipedia.org/wiki/Wikipedia:Templates_for_discussion/Log/2017_May_10#Template:16TeamBracket-Compact-Tennis35-Byes. + p.text += ["{{" + str(2**rounds) + "TeamBracket" + ("-Compact" if compact else "") + "-Tennis" + str(format) + ("-Byes" if byes else "")] + for c, round in enumerate(round_names): + p.text += ["| RD" + str(c+1) + "=" + round] + for j in range(rounds): + team_no = 1 + for match in data[j]: + p.text += [""] + if not match.parsed: # add sup tags if not already added for retirements/tiebreakers + for c, set in enumerate(match.score[1:]): + if set[-1] == "Retired": + match.score[c+1][not match.score[0][0]] += "r" + elif set != ["", ""] and "w/o" not in set and len(set) == 3 and len(set[2]) == 2: + match.score[c+1][0] = set[0] + "" + set[2][0] + "" + match.score[c+1][1] = set[1] + "" + set[2][1] + "" + match.parsed = True + + for i in range(2): # add seed, team name/flag, score parameters for each team in given match + team = match.teams[i] + bold = "'''" if match.winner == i else "" + name_text = "
 ".join([(t.flagicons[f.country] + " " + (bold + t.name_links[f.name][1 + short_names] + bold) if not match.bye else "") for f in team]) + rd = "| RD" + str(j+1) + "-" + p.text += [rd + "seed" + str(team_no) + "=" + ("" if match.bye else (" " if team[0].seed == [] else "/".join(team[0].seed)))] + p.text += [rd + "team" + str(team_no) + "=" + (name_text if name_text != "
 " else "")] + + for set in range(match.sets): + p_score = "" if match.bye else match.score[set+1][i] + won_set = (i == match.score[0][1][set]) if set < len(match.score[0][1]) else 0 # no sets in byes/walkovers + p_score = "[" + p_score + "]" if t.match_tiebreak and set == 2 and p_score != "" else p_score # add square brackets for match tiebreak + p.text += [rd + "score" + str(team_no) + "-" + str(set+1) + "=" + ("'''" + p_score + "'''" if won_set else p_score)] + team_no += 1 + p.text += ["}}"] + + def MakeDraw(t, p, compact, abbr, seed_links): + if t.qual: + t.MakeQualifiers(p) + t.MakeLuckyLosers(p) + p.text += ["", "==Qualifying draw==", "{{Draw key}}"] + sections = t.SplitData(len(t.data[-1]), t.rounds) + for i in range(len(sections)): + ordinal = num2words(i+1, ordinal=True).capitalize() + p.text += ["", "===" + ordinal + " qualifier==="] # add section heading before each section + t.MakeSection(p, data=sections[i], rounds=t.rounds, round_names=t.round_names[:-1], format=t.format, byes=t.byes, compact=compact, short_names=abbr) + else: + p.text += ["", "==Draw==", "{{Draw key}}"] + parts = int((2**t.rounds)/16) # number of sections of 16 players needed + if parts > 1: # "Finals" section needed + p.text += ["===Finals==="] + final_rounds = {5:2, 6:3, 7:3, 8:4} # number of rounds to show in "Finals" section given the number of rounds in the tournament + final_rounds = final_rounds[t.rounds] + t.MakeSection(p, data=t.data[-final_rounds:], rounds=final_rounds, round_names=t.round_names[-final_rounds - 1:-1], format=t.format, byes=False, compact=False, short_names=False) + sections = t.SplitData(parts, 4) + halves = [["===Top half==="], ["===Bottom half==="]] + for c, section in enumerate(sections): + if parts > 1: + p.text += halves[c // (parts // 2)] if c % (parts // 2) == 0 else [] # add half heading before each half + p.text += ["====Section " + str(c+1) + "===="] if t.rounds > 5 else [] # add section heading before each section + t.MakeSection(p, data=section, rounds=4, round_names=t.round_names[:4], format=(3 if t.format > 5 else t.format), byes=t.byes, compact=compact, short_names=abbr) + t.MakeSeeds(p, sections, seed_links) + +def TournamentDrawOutput(data, year, format, qual, compact, abbr, seed_links): + p = Page() + t = Tournament(data=data, format=format, qual=qual, year=year) + t.MakeDraw(p, compact=compact, abbr=True, seed_links=seed_links) + return "\n".join(p.text) diff --git a/Classes/ClassSupportFunctions.py b/Classes/ClassSupportFunctions.py index 8d6761b..40ced09 100644 --- a/Classes/ClassSupportFunctions.py +++ b/Classes/ClassSupportFunctions.py @@ -1,5 +1,33 @@ import os +import json +import requests +from bs4 import BeautifulSoup def GetCountrycode(filepath): filename = os.path.basename(filepath) - return filename[0:3].upper() \ No newline at end of file + return filename[0:3].upper() + +def GetSoup(path, headers): + if headers == True: # want soup for html in path + return BeautifulSoup(path, "html.parser") + elif headers == False: # want request only + return requests.get(path) + elif headers == {}: + response = requests.get(path).text + else: + response = requests.get(path, headers=headers).text + return BeautifulSoup(response, 'lxml') + +def LoadJSON(path): + try: + with open(path, 'r') as fp: + return json.load(fp) + except FileNotFoundError: + return {} + +def SaveJSON(path, data): + with open(path, 'w') as fp: + json.dump(data, fp, sort_keys=True, indent=4) + +def Dedupe(x): + return list(dict.fromkeys(x)) diff --git a/Classes/GetFlagicon.py b/Classes/GetFlagicon.py new file mode 100644 index 0000000..4d849a5 --- /dev/null +++ b/Classes/GetFlagicon.py @@ -0,0 +1,99 @@ +# Adapted from https://en.wikipedia.org/wiki/User:Dantheox/flags.rb + +def GetFlagicon(nation, year): + if nation == "HOL": + return "{{flagicon|NED}}" + elif nation == "APA": + return "{{flagicon|Czechoslovakia}}" + elif nation == "AUT" and year <= 1918: + return "{{flagicon|AUT|empire}}" + elif nation == "BIH" and year <= 1998: + return "{{flagicon|BIH|1992}}" + elif nation == "BUL" and year <= 1971: + return "{{flagicon|BUL|1967}}" + elif nation == "BUL" and year <= 1990: + return "{{flagicon|BUL|1971}}" + elif nation == "CAN" and year <= 1920: + return "{{flagicon|CAN|1868}}" + elif nation == "CAN" and year <= 1964: + return "{{flagicon|CAN|1921}}" + elif nation == "CHN" and year <= 1949: + return "{{flagicon|Republic of China|1912}}" + elif nation == "EGY" and year < 1922: + return "{{flagicon|EGY|1882}}" + elif nation == "EGY" and year <= 1952: + return "{{flagicon|EGY|1922}}" + elif nation == "EGY" and year <= 1972: + return "{{flagicon|EGY|1952}}" + elif nation == "ESP" and year < 1931: + return "{{flagicon|ESP|1785}}" + elif nation == "ESP" and year < 1939: + return "{{flagicon|ESP|1931}}" + elif nation == "ESP" and year < 1977: + return "{{flagicon|ESP|1939}}" + elif nation == "ESP" and year < 1981: + return "{{flagicon|ESP|1977}}" + elif nation == "GEO" and year <= 2004: + return "{{flagicon|GEO|1990}}" + elif nation == "GER" and year <= 1918: + return "{{flagicon|GER|empire}}" + elif nation == "GER" and year <= 1934: + return "{{flagicon|GER|Weimar}}" + elif nation == "GER" and year <= 1945: + return "{{flagicon|GER|Nazi}}" + elif nation == "GRE" and year <= 1969: + return "{{flagicon|GRE|old}}" + elif nation == "HKG" and year <= 1997: + return "{{flagicon|HKG|colonial}}" + elif nation == "HUN" and year < 1940: + return "{{flagicon|HUN|1867}}" + elif nation == "HUN" and year <= 1945: + return "{{flagicon|HUN|1920}}" + elif nation == "HUN" and year <= 1956: + return "{{flagicon|HUN|1949}}" + elif nation == "IND" and year <= 1946: + return "{{flagicon|IND|British}}" + elif nation == "IRI" and year <= 1963: + return "{{flagicon|IRI|1925}}" + elif nation == "IRI" and year <= 1979: + return "{{flagicon|IRI|1964}}" + elif nation == "IRQ" and year < 1959: + return "{{flagicon|IRQ|1924}}" + elif nation == "IRQ" and year < 1963: + return "{{flagicon|IRQ|1959}}" + elif nation == "IRQ" and year < 1991: + return "{{flagicon|IRQ|1963}}" + elif nation == "IRQ" and year < 2003: + return "{{flagicon|IRQ|1991}}" + elif nation == "ITA" and year <= 1946: + return "{{flagicon|ITA|1861}}" + elif nation == "MAS" and year < 1948: + return "{{flagicon|MAS|1895}}" + elif nation == "MAS" and year <= 1963: + return "{{flagicon|MAS|1948}}" + elif nation == "MEX" and year <= 1933: + return "{{flagicon|MEX|1916}}" + elif nation == "MEX" and year <= 1968: + return "{{flagicon|MEX|1934}}" + elif nation == "RHO" and year <= 1967: + return "{{flagicon|Rhodesia|1964}}" + elif nation == "ROM" and year <= 1947: + return "{{flagicon|ROM|1867}}" + elif nation == "ROM" and year <= 1989: + return "{{flagicon|ROM|1947}}" + elif nation == "RSA" and year <= 1927: + return "{{flagicon|RSA|1910}}" + elif nation == "RSA" and year <= 1994: + return "{{flagicon|RSA|1928}}" + elif nation == "SRI" and year <= 1971: + return "{{flagicon|SRI}}" + elif nation == "VIE" and year <= 1974: + return "{{flagicon|South Vietnam}}" + elif nation == "YUG" and year <= 1941: + return "{{flagicon|Kingdom of Yugoslavia}}" + elif nation == "YUG" and year <= 2002: + return "{{flagicon|FR Yugoslavia}}" + elif nation == "" or nation == "ITF" or nation == "LAG": + return "" + else: + return "{{flagicon|" + nation + "}}" diff --git a/Classes/ScrapeTournamentITF.py b/Classes/ScrapeTournamentITF.py new file mode 100644 index 0000000..35fb3d6 --- /dev/null +++ b/Classes/ScrapeTournamentITF.py @@ -0,0 +1,148 @@ +# Name: ITF Tournament scraper +# Author: Somnifuguist (w.wiki/fDy) +# Date: 10-10-2020 +# Content: Scrapes data from ITF printable draws + +import itertools +import re + +from ClassSupportFunctions import GetSoup + +def ExtractTeam(team): + # Extracts [name, country code, [seeds]] for player(s) in team from string. + team = team.text.strip(" \n").replace("\xa0", " ") + players = team.split(" / ") + team_data = [] + for player in players: + if player == "": + team_data.append(["","",[]]) + elif player == "BYE": + team_data.append(["BYE", "", []]) + else: + country = re.search(r"\([A-Z]{3}\)", player).group()[1:4] + seed = [] + digit = re.search(r"\[\d{1,2}\]", player) + if digit: + seed.append(digit.group()[1:-1]) + types = ["LL", "WC", "Q", "PR", "Alt", "SR", "A"] + for t in types: + if re.search(r"\(" + t + "\)", player): + seed.append(t) + name = (player.replace("(" + country + ")", "").replace("(" + (seed[-1] if seed != [] else "") + ")", "").replace("[" + (seed[0] if seed != [] else "") + "]", "").strip(" ").replace(",", "")) + team_data.append([name, country, seed]) + return team_data + +def ExtractScore(score, match, winners): + # Extracts [[winner, [set winners]], [set 1 scores], [set 2 scores], ...] from string + # Set scores = [player 0 score, player 1 score, [tiebreak scores if tiebreak]/"Retired" if retirement] + # Player and scores ordered by order in draw. + # e.g. the score 6-4 5-7 7-6(5) 6-0, with player 1 as winner = [[1, [1, 0, 1, 1]], [4, 6], [7, 5], [6, 7, [5, 7]], [0, 6]] + # Walkovers = [[winner, ["w/o"]], ["Walkover"]] + # Byes = [[winner, []], ["BYE"]] + winner = 0 if match[0][0][0] in winners and match[0][0][0] != "BYE" else 1 + score = score.text.strip(" |\n") + score = [s.split("-") for s in score.split(" ")] + + if match[0][0][0] == "BYE" or match[1][0][0] == "BYE": + new_score = [[winner, []], ["BYE"]] + elif score[0][0] == "Walkover": + new_score = [[winner, ["w/o"]], ["Walkover"]] + else: + new_score = [[winner, []]] + for set in score: + set = [f.replace("[", "").replace("]", "") for f in set] + if set == ["Retired"]: + if max(int(new_score[-1][0]), int(new_score[-1][1])) > 5 and abs(int(new_score[-1][0]) - int(new_score[-1][1])) > 1: + new_score.append(['0', '0', "Retired"]) # retirement happened after set finished + else: + new_score[-1] += set # retirement happened mid-set + elif set != [""]: + tiebreaker = re.search(r"\(\d{1,}\)", set[1]) + if tiebreaker: + tie_l = int(tiebreaker.group()[1:-1]) + tie_w = tie_l + 2 if tie_l > 4 else 7 + if set[0] == '7': + set = ['7', '6', [str(tie_w), str(tie_l)]] + else: + set = ['6', '7', [str(tie_l), str(tie_w)]] + new_score.append(set) + + if winner == 1: # reverse score order, as scores are always ordered with winner first + for c, f in enumerate(new_score[1:]): + if "Retired" in f: + new_score[c+1] = f[:-1][::-1] + [f[-1]] + elif len(f) == 3 and len(f[2]) == 2: # tiebreak + new_score[c+1] = f[:-1][::-1] + [f[-1][::-1]] + else: + f.reverse() + for set in new_score[1:]: # fill new_score[0][1] with winner of each set, "r" if team retires + if 'Retired' in set: + new_score[0][1].append("r") + else: + new_score[0][1].append(int(int(set[0])', + '
', + '
', + '
'] + bye = ['
BYE
', + '
BYE
', + '
BYE
', + '
BYE
'] + html = ">".join([f.strip("\t\n ") for f in str(soup).split(">")]) + for i in range(4): + html = html.replace(empty[i], bye[i]) + soup = GetSoup(html, True) + return soup + +def ScrapeTournamentITF(url, qual, doubles): + soup = GetSoup(url, {}) + try: + data = ExtractTournament(soup, qual=qual, doubles=doubles) + except IndexError: # html is missing name in tournament bracket + soup = FixByes(soup) + data = ExtractTournament(soup, qual=qual, doubles=doubles) + return data diff --git a/Classes/app.py b/Classes/app.py index b2e00fb..4fe6a32 100644 --- a/Classes/app.py +++ b/Classes/app.py @@ -1,79 +1,111 @@ from flask import Flask, render_template, request, redirect, url_for from Settings import Config from AppPlayerTourneywins import TournamentWinsOutput, GetTournamentWins from AppPlayerWorldranking import GetWorldRanking, RankingOutput -from forms import FormPlayerWorldranking, FormPlayerTournamentwins +from AppTourneydraw import TournamentDrawOutput +from ScrapeTournamentITF import ScrapeTournamentITF +from forms import FormPlayerWorldranking, FormPlayerTournamentwins, FormTournamentdraw #Initiate Flask with config app = Flask(__name__) app.config.from_object(Config) @app.route('/') def home(): return render_template('about.html') @app.route('/about/') def about(): return render_template('about.html') @app.route('/playerworldranking/', methods=['GET', 'POST']) def playerworldranking(): form = FormPlayerWorldranking() if request.method == 'POST': org = request.form.get('org') type = request.form.get('type') cut = request.form.get('cut') language = request.form.get('language') date = request.form.get('date') return redirect(url_for('outputranking', org=org, type=type, cut=cut, language=language, date=date)) return render_template('playerworldranking.html', form=form) @app.route('/playerwins/', methods=['GET', 'POST']) def playerwins(): form = FormPlayerTournamentwins() if request.method == 'POST': org = request.form.get('org') type = request.form.get('type') player = request.form.get('player') language = request.form.get('language') level = request.form.get('level') return redirect(url_for('outputplayerwins', org=org, type=type, player=player, language=language, level=level)) return render_template('playerwins.html', form=form) -@app.route('/tourneydraw/') +@app.route('/tourneydraw/', methods=['GET', 'POST']) def tourneydraw(): - #org = request.args.get('org', default = 1, type = str) - #type = request.args.get('type', default = '*', type = str) - #year = request.args.get('cut', default = '*', type = str) - #language = request.args.get('language', default = '*', type = str) - return render_template('tourneydraw.html') + form = FormTournamentdraw() + if request.method == 'POST': + language = request.form.get('language') + org = request.form.get('org') + url = request.form.get('url') + year = request.form.get('year') + doubles = request.form.get('doubles') + format = request.form.get('format') + qual = request.form.get('qual') + compact = request.form.get('compact') + abbr = request.form.get('abbr') + seed_links = request.form.get('seed_links') + return redirect(url_for('outputtourneydraw', org=org, url=url, year=year, doubles=doubles, format=format, qual=qual, compact=compact, abbr=abbr, seed_links=seed_links)) + return render_template('tourneydraw.html', form=form) @app.route('/outputranking/', methods=['GET', 'POST']) def outputranking(): #Get variables from form org = request.args.get('org', type = str) type = request.args.get('type', type = str) cut = request.args.get('cut', default='100', type = int) language = request.args.get('language', type = str) date = request.args.get('date', type = str) #Run GetWorldRanking() ranking = GetWorldRanking(org, type, cut, language, date) result = RankingOutput(ranking, org, type, cut, language, date) return render_template('outputranking.html', result=result) @app.route('/outputplayerwins/', methods=['GET', 'POST']) def outputplayerwins(): #Get variables from form org = request.args.get('org', type = str) type = request.args.get('type', type = str) player = request.args.get('player', type = str) language = request.args.get('language', type = str) level = request.args.get('level', type = int) #Run GetTournamentWins() wins = GetTournamentWins(org, player, type, level) result = TournamentWinsOutput(wins, language, type) return render_template('outputplayerwins.html', result=result) +@app.route('/outputtourneydraw/', methods=['GET', 'POST']) +def outputtourneydraw(): + #Get variables from form + language = request.args.get('language', type = int) + org = request.args.get('org', type = str) + url = request.args.get('url', type = str) + year = request.args.get('year', type = int) + doubles = request.args.get('doubles', type = int) + format = request.args.get('format', type = int) + qual = request.args.get('qual', type = int) + compact = request.args.get('compact', type = int) + abbr = request.args.get('abbr', type = int) + seed_links = request.args.get('seed_links', type = int) + if "https://event.itftennis.com/itf/web/usercontrols/tournaments/tournamentprintabledrawsheets.aspx?" in url: + # Scrape data, then create draw + data = ScrapeTournamentITF(url=url, qual=qual, doubles=doubles) + draw = TournamentDrawOutput(data=data, year=year, format=format, qual=qual, compact=compact, abbr=abbr, seed_links=seed_links) + else: # extremely basic input validation + draw = "Invalid URL, should be in format: https://event.itftennis.com/itf/web/usercontrols/tournaments/tournamentprintabledrawsheets.aspx?" + return render_template('outputtourneydraw.html', result=draw) + if __name__ == '__main__': - app.run(debug=True) \ No newline at end of file + app.run(debug=True) diff --git a/Classes/forms.py b/Classes/forms.py index a9ca978..2f65779 100644 --- a/Classes/forms.py +++ b/Classes/forms.py @@ -1,24 +1,37 @@ from flask_wtf import FlaskForm from wtforms import StringField, SubmitField, SelectField, IntegerField from wtforms.validators import DataRequired class FormPlayerWorldranking(FlaskForm): org = SelectField('Ranking Organisation', choices=[('atp', 'ATP'), ('wta', 'WTA')]) type = SelectField('Match Type', choices=[('singles', 'singles'), ('doubles', 'doubles')]) language = SelectField('Wikipedia Language', choices=[('en', 'en'), ('de', 'de'), ('cs', 'cs'), ('es', 'es'), ('fr', 'fr'), ('it', 'it'), ('ja', 'ja'), ('nl', 'nl'), ('pl', 'pl'), ('pt', 'pt'), ('ro', 'ro'), ('ru', 'ru'), ('sv', 'sv'), ('zh', 'zh')]) cut = IntegerField('Ranking Cut', validators=[DataRequired()]) date = StringField('Date', validators=[DataRequired()]) submit = SubmitField('Request') class FormPlayerTournamentwins(FlaskForm): org = SelectField('Ranking Organisation', choices=[('atp', 'ATP')]) type = SelectField('Match Type', choices=[('singles', 'singles'), ('doubles', 'doubles')]) player = StringField('Player-ID', validators=[DataRequired()]) language = SelectField('Wikipedia Language', choices=[('de', 'de'), ('nl', 'nl')]) level = SelectField('Minimum Tournament Level', choices=[(1, 'ITF Futures'), (2, 'ATP Challenger'), (3, 'ATP World Tour 250 & 500'), (4, 'ATP World Tour 1000'), (5, 'ATP Tour Finals & Grand Slams & Olympics')]) - submit = SubmitField('Request') \ No newline at end of file + submit = SubmitField('Request') + +class FormTournamentdraw(FlaskForm): + language = SelectField('Wikipedia language', choices=[('en', 'en')]) + org = SelectField('Organisation', choices=[('itf', 'ITF')]) + url = StringField('Tournament link', validators=[DataRequired()]) + year = IntegerField('Year', validators=[DataRequired()]) + doubles = SelectField('Tournament type', choices=[(0, 'singles'), (1, 'doubles')]) + format = SelectField('Match format', choices=[(3, 'best of 3'), (5, 'best of 5'), (2, 'best of 3; tiebreak deciding set'), (35, 'best of 3; best of 5 final')]) + qual = SelectField('Qualifying', choices=[(0, 'no'), (1, 'yes')]) + compact = SelectField('Compact draws', choices=[(1, 'yes'), (0, 'no')]) + abbr = SelectField('Abbreviated names (e.g. R Federer)', choices=[(1, 'yes'), (0, 'no')]) + seed_links = SelectField('Seed links', choices=[(1, 'yes'), (0, 'no')]) + submit = SubmitField('Request') diff --git a/Classes/requirements.txt b/Classes/requirements.txt index 3ad3def..5ac811b 100644 --- a/Classes/requirements.txt +++ b/Classes/requirements.txt @@ -1,18 +1,24 @@ SPARQLWrapper appdirs beautifulsoup4 certifi chardet idna isodate +itertools flask +json mypy-extensions +nameparser +num2words +os pip pycountries pyparsing qwikidata rdflib +re requests six soupsieve urllib3 diff --git a/Classes/templates/outputtourneydraw.html b/Classes/templates/outputtourneydraw.html new file mode 100644 index 0000000..5b24608 --- /dev/null +++ b/Classes/templates/outputtourneydraw.html @@ -0,0 +1,16 @@ +{% extends "base.html" %} +{% set active_page = "tourneydraw" %} + +{% block content %} +
+

Tournament draw

+
+
+ Draw: +
+
+ +
+ + +{% endblock %} diff --git a/Classes/templates/tourneydraw.html b/Classes/templates/tourneydraw.html index 01198f0..99c9593 100644 --- a/Classes/templates/tourneydraw.html +++ b/Classes/templates/tourneydraw.html @@ -1,9 +1,54 @@ {% extends "base.html" %} {% set active_page = "tourneydraw" %} {% block content %}
-

Tournament Draws

-

Under construction

+

Tournament draw generator

-{% endblock %} \ No newline at end of file +
+ {{ form.hidden_tag() }} +
+ +
{{ form.language }}
+
+
+ +
{{ form.org }}
+
+
+ +
{{ form.url }}
+
+
+ +
{{ form.year }}
+
+
+ +
{{ form.doubles }}
+
+
+ +
{{ form.format }}
+
+
+ +
{{ form.qual }}
+
+
+ +
{{ form.compact }}
+
+
+ +
{{ form.abbr }}
+
+
+ +
{{ form.seed_links }}
+
+
+ {{ form.submit() }} +
+
+{% endblock %}