User:ChristieBot/GA web.py
Appearance
''' Copyright (c) 2022 Mike Christie Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ''' import urllib.parse import re import datetime import pywikibot import pymysql import operator import GA_config import sys from dateutil.parser import parse from GA import GAN, Subtopic class Editor: def __init__(self, conn, editor, config, wbgan, name_changes): self.editor = editor cursor = conn.cursor(pymysql.cursors.DictCursor) sql = "select article_title from historical_GA_reviews where type = 'GAN' and lower(outcome) in ('promoted','pass','passed','listed') " sql += "and (nominator = '" + self.editor.replace("'","''") + "' or nominator in (select old_name from name_changes where new_name = '" + self.editor.replace("'","''") + "')) " self.GAs = 0 try: self.GAs = cursor.execute(sql) except pymysql.Error as e: pass sql = "select article_title from historical_GA_reviews where type = 'GAN' " sql += "and (nominator = '" + self.editor.replace("'","''") + "' or nominator in (select old_name from name_changes where new_name = '" + self.editor.replace("'","''") + "')) " self.nominations = 0 try: self.nominations = cursor.execute(sql) except pymysql.Error as e: pass sql = "select article_title from historical_GA_reviews where reviewer = '" + self.editor.replace("'","''") + "' and type = 'GAN'" sql = "select article_title from historical_GA_reviews where type = 'GAN' " sql += "and (reviewer = '" + self.editor.replace("'","''") + "' or reviewer in (select old_name from name_changes where new_name = '" + self.editor.replace("'","''") + "')) " self.reviews = 0 try: self.reviews = cursor.execute(sql) except pymysql.Error as e: pass self.ratio = 'Infinite' if self.GAs > 0: self.ratio = round(self.reviews/self.GAs,1) self.current_GAs = 0 if self.editor.lower() in wbgan.keys(): self.current_GAs = int(wbgan[self.editor.lower()]) if self.editor in name_changes.keys(): if name_changes[self.editor].lower() in wbgan.keys(): self.current_GAs += int(wbgan[name_changes[self.editor].lower()]) if self.editor in name_changes.values(): old_names = [x for x in name_changes.keys() if name_changes[x] == self.editor] for old_name in old_names: if old_name.lower() in wbgan.keys(): self.current_GAs += int(wbgan[old_name.lower()]) def display_summary(self): editor_text = ["<h2>Summary</h2>"] editor_text.append("<ul>") editor_text.append('<li>GA nominations: ' + str(self.nominations) + "</li>") editor_text.append('<li>Promoted GA nominations: ' + str(self.GAs) + "</li>") editor_text.append('<li>Promoted GA nominations that are still GAs: ' + str(self.current_GAs) + "</li>") editor_text.append('<li>GA reviews: ' + str(self.reviews) + "</li>") editor_text.append('<li>Ratio of reviews to successful nominations: ' + str(self.ratio) + "</li>") editor_text.append("</ul>") editor_text.append("<br />") note_text = 'Note that apart from the "Promoted GA nominations that are still GAs", which is taken from ' note_text += '<a href = "https://sdzerobot.toolforge.org/gans?user=' + urllib.parse.quote_plus(self.editor) + '" target="_blank">SDZeroBot</a>' note_text += ', these statistics only include GAs performed using GA subpages of the article talk page. They do not include GAs that were reviewed and promoted ' note_text += 'solely via discussions on the article talk page, which was the method used for GA reviews before about mid-2006.\n' editor_text.append(note_text) return("\n".join(editor_text)) class GAN_records: @classmethod def table_headings(cls, table_type=None): table_headings = "<tr>\n" columns = ["Article","Nominator","Nominated","Reviewed","Reviewer","Page","Subtopic","Outcome","Currently","Comments"] if table_type == "Nomination list": columns = ["No.","Article","Nominated","Reviewed","Reviewer","Page","Subtopic","Outcome", "Currently","Comments"] elif table_type == "Review list": columns = ["No.", "Article","Nominator","Nominated","Reviewed","Page","Subtopic","Outcome","Currently"] for c in columns: table_headings += "<th>" + c + "</th>\n" table_headings += "</tr>\n" return(table_headings) @classmethod def get_table_data(cls, conn, table_type, constraints): site = pywikibot.Site('en','wikipedia') gan_records = GAN_records(conn, constraints) ctr = 0 table_text = [] for g in gan_records.gans: ctr += 1 new_row = g.get_table_row(conn, table_type, ctr) table_text.append(new_row) return(table_text) @classmethod #def display_table(cls, conn, table_type, constraints, header, ga_cat_list): def display_table(cls, conn, table_type, constraints, header): table_text = "<h2>" + header + "</h2>\n" table_text += "<table>\n" table_text += GAN_records.table_headings(table_type) site = pywikibot.Site('en','wikipedia') #ga_pages = [] #ga_pages.append(pywikibot.Page(site, 'Wikipedia:Good articles/Agriculture, food and drink')) #ga_pages.append(pywikibot.Page(site, 'Wikipedia:Good articles/Art and architecture')) #ga_pages.append(pywikibot.Page(site, 'Wikipedia:Good articles/Engineering and technology')) #ga_pages.append(pywikibot.Page(site, 'Wikipedia:Good articles/Geography and places')) #ga_pages.append(pywikibot.Page(site, 'Wikipedia:Good articles/History')) #ga_pages.append(pywikibot.Page(site, 'Wikipedia:Good articles/Language and literature')) #ga_pages.append(pywikibot.Page(site, 'Wikipedia:Good articles/Mathematics')) #ga_pages.append(pywikibot.Page(site, 'Wikipedia:Good articles/Media and drama')) #ga_pages.append(pywikibot.Page(site, 'Wikipedia:Good articles/Music')) #ga_pages.append(pywikibot.Page(site, 'Wikipedia:Good articles/Natural sciences')) #ga_pages.append(pywikibot.Page(site, 'Wikipedia:Good articles/Philosophy and religion')) #ga_pages.append(pywikibot.Page(site, 'Wikipedia:Good articles/Social sciences and society')) #ga_pages.append(pywikibot.Page(site, 'Wikipedia:Good articles/Sports and recreation')) #ga_pages.append(pywikibot.Page(site, 'Wikipedia:Good articles/Video games')) #ga_pages.append(pywikibot.Page(site, 'Wikipedia:Good articles/Warfare')) #ga_text = "" #for gp in ga_pages: # ga_text += gp.text #ga_text = GAN_records.get_ga_text() #ga_all = (pywikibot.Page(site, 'Wikipedia:Good articles/all')) #ga_text = ga_all.text #site = pywikibot.Site('en','wikipedia') #fa_page = pywikibot.Page(site, 'Wikipedia:Featured articles') #fa_text = fa_page.text gan_records = GAN_records(conn, constraints) #gan_records = GAN_records(conn, constraints, ga_text, fa_text) #gan_records = GAN_records(conn, constraints, ga_cat_list, fa_text) #gan_records = GAN_records(conn, constraints) ctr = 0 for g in gan_records.gans: ctr += 1 new_row = g.table_row(conn, table_type, ctr) table_text += new_row table_text += "</table>\n" return(table_text) @classmethod def get_ga_text(cls): site = pywikibot.Site('en','wikipedia') ga_pages = [] ga_pages.append(pywikibot.Page(site, 'Wikipedia:Good articles/Agriculture, food and drink')) ga_pages.append(pywikibot.Page(site, 'Wikipedia:Good articles/Art and architecture')) ga_pages.append(pywikibot.Page(site, 'Wikipedia:Good articles/Engineering and technology')) ga_pages.append(pywikibot.Page(site, 'Wikipedia:Good articles/Geography and places')) ga_pages.append(pywikibot.Page(site, 'Wikipedia:Good articles/History')) ga_pages.append(pywikibot.Page(site, 'Wikipedia:Good articles/Language and literature')) ga_pages.append(pywikibot.Page(site, 'Wikipedia:Good articles/Mathematics')) ga_pages.append(pywikibot.Page(site, 'Wikipedia:Good articles/Media and drama')) ga_pages.append(pywikibot.Page(site, 'Wikipedia:Good articles/Music')) ga_pages.append(pywikibot.Page(site, 'Wikipedia:Good articles/Natural sciences')) ga_pages.append(pywikibot.Page(site, 'Wikipedia:Good articles/Philosophy and religion')) ga_pages.append(pywikibot.Page(site, 'Wikipedia:Good articles/Social sciences and society')) ga_pages.append(pywikibot.Page(site, 'Wikipedia:Good articles/Sports and recreation')) ga_pages.append(pywikibot.Page(site, 'Wikipedia:Good articles/Video games')) ga_pages.append(pywikibot.Page(site, 'Wikipedia:Good articles/Warfare')) ga_text = "" for gp in ga_pages: ga_text += gp.text return(ga_text) @classmethod def get_fa_text(cls): site = pywikibot.Site('en','wikipedia') fa_page = pywikibot.Page(site, 'Wikipedia:Featured articles') return(fa_page.text) def __init__(self, conn, constraints): #def __init__(self, conn, constraints, ga_text, fa_text): #def __init__(self, conn, constraints, ga_cat_list, fa_text): #def __init__(self, conn, constraints): self.constraints = constraints self.gans = [] cursor = conn.cursor(pymysql.cursors.DictCursor) where_clause = "" order_by_clause = " order by nomination_ts" conjunction = "where " for column in constraints.keys(): if column in ['page']: where_clause += conjunction + " " + column + " = " + constraints[column] conjunction = " and " if column in ['nominator','reviewer']: where_clause += conjunction + "(" + column + " = '" + constraints[column].replace("'","''") + "' or " + column + " in (select old_name from name_changes where new_name = '" + constraints[column].replace("'","''") +"'))" conjunction = " and " if column == 'reviewer': order_by_clause = " order by review_ts" if column in ['type']: where_clause += conjunction + column + " = '" + constraints[column] + "'" conjunction = " and " sql = "select article_title, nominator, nomination_ts, review_ts, reviewer, page, subtopic, outcome, nvl(comments,'') as comments from historical_GA_reviews " sql += where_clause sql += order_by_clause #GAN.log(conn,"GAN_records.__init__", None, "sql is " + sql) ga_text = GAN_records.get_ga_text() fa_text = GAN_records.get_fa_text() try: cursor.execute(sql) except pymysql.Error as e: return(None) for row in cursor.fetchall(): self.gans.append(GAN_record(row['article_title'], row['nominator'], row['nomination_ts'], row['review_ts'], row['reviewer'], row['page'], row['subtopic'], row['outcome'], row['comments'], ga_text, fa_text)) #self.gans.append(GAN_record(row['article_title'], row['nominator'], row['nomination_ts'], row['review_ts'], row['reviewer'], row['page'], row['subtopic'], row['outcome'], row['comments'], ga_cat_list, fa_text)) #self.gans.append(GAN_record(row['article_title'], row['nominator'], row['nomination_ts'], row['review_ts'], row['reviewer'], row['page'], row['subtopic'], row['outcome'], row['comments'])) class GAN_record: def __init__(self, article_title, nominator, nomination_ts, review_ts, reviewer, page, subtopic, outcome, comments, ga_text, fa_text): #def __init__(self, article_title, nominator, nomination_ts, review_ts, reviewer, page, subtopic, outcome, comments, ga_cat_list, fa_text): #def __init__(self, article_title, nominator, nomination_ts, review_ts, reviewer, page, subtopic, outcome, comments): self.article_title = article_title self.nominator = nominator self.nomination_ts = nomination_ts self.review_ts = review_ts self.reviewer = reviewer self.page = page self.subtopic = subtopic if subtopic in Subtopic.subtopic_var_dict.keys(): self.subtopic = Subtopic.subtopic_var_dict[subtopic] if outcome is None: self.outcome = 'Nominated' elif outcome.lower() in ['listed','pass','passed','promoted']: self.outcome = 'Promoted' elif outcome.lower() in ['fail','failed','not listed','not promoted']: self.outcome = 'Not promoted' elif outcome.lower() in ['under review']: self.outcome = 'Under review' else: self.outcome = outcome self.comments = comments #site = pywikibot.Site('en','wikipedia') #p = pywikibot.Page(site, article_title) self.state = '' #if "Talk:" + self.article_title in ga_cat_list: if '[' + self.article_title.lower() + ']' in ga_text.lower() or ('[' + self.article_title.lower() + ']').replace('_',' ') in ga_text.lower() or '[' + self.article_title.lower() + '|' in ga_text.lower() or ('[' + self.article_title.lower() + '|').replace('_',' ') in ga_text.lower(): self.state = 'Good' elif '[' + self.article_title.lower() + ']' in fa_text.lower() or ('[' + self.article_title.lower() + ']').replace('_',' ') in fa_text.lower() or '[' + self.article_title.lower() + '|' in fa_text.lower() or ('[' + self.article_title.lower() + '|').replace('_',' ') in fa_text.lower(): self.state = 'Featured' elif self.outcome == 'Promoted': self.state = 'Delisted' #if '{{good article}}' in p.text.lower(): # self.state = 'Good' #elif '{{featured article}}' in p.text.lower(): # self.state = 'Featured' #elif self.outcome == 'Promoted': # self.state = 'Delisted' def table_row(self, conn, table_type, ctr): row_text = "" #GAN.log(conn,"table_row", self.article_title, "Called with " + table_type) if table_type == "Nomination list": #GAN.log(conn,"table_row", self.article_title, "In Nomination list branch") row_text = "<tr>\n" row_text += '<td>' + str(ctr) + '</td>\n' row_text += '<td>' + GANstats.article_link(self.article_title) + '</td>\n' row_text += "<td>" + GANstats.display_date(self.nomination_ts)+ "</td>\n" row_text += "<td>" + GANstats.display_date(self.review_ts)+ "</td>\n" row_text += "<td>" + GANstats.editor_link(self.reviewer) + "</td>\n" row_text += "<td>" + GANstats.review_link(self.article_title, self.page) + "</td>\n" row_text += "<td>" + self.subtopic + "</td>\n" row_text += "<td>" + self.outcome + "</td>\n" row_text += "<td>" + self.state + "</td>\n" row_text += "<td>" + self.comments + "</td>\n" row_text += "</tr>\n" elif table_type == "Review list": #GAN.log(conn,"table_row", self.article_title, "In Review list branch") row_text = "<tr>\n" row_text += '<td>' + str(ctr) + '</td>\n' row_text += '<td>' + GANstats.article_link(self.article_title) + '</td>\n' row_text += "<td>" + GANstats.editor_link(self.nominator) + "</td>\n" row_text += "<td>" + GANstats.display_date(self.nomination_ts)+ "</td>\n" row_text += "<td>" + GANstats.display_date(self.review_ts)+ "</td>\n" row_text += "<td>" + GANstats.review_link(self.article_title, self.page) + "</td>\n" row_text += "<td>" + self.subtopic + "</td>\n" row_text += "<td>" + self.outcome + "</td>\n" row_text += "<td>" + self.state + "</td>\n" row_text += "</tr>\n" return(row_text) def get_table_row(self, conn, table_type, ctr): row = [] #GAN.log(conn,"table_row", self.article_title, "Called with " + table_type) if table_type == "Nomination list": #GAN.log(conn,"table_row", self.article_title, "In Nomination list branch") row = {} row['ctr'] = str(ctr) row['article_link_title'] = self.article_title.replace(" ","_") row['article_title'] = self.article_title row['nomination_ts'] = (GANstats.display_date(self.nomination_ts)) row['review_ts'] = (GANstats.display_date(self.review_ts)) row['reviewer_link'] = self.reviewer.replace(" ","_") row['reviewer'] = self.reviewer row['page'] = self.page row['subtopic'] = self.subtopic row['outcome'] = self.outcome row['state'] = self.state row['comments'] = self.comments elif table_type == "Review list": row = {} row['ctr'] = str(ctr) row['article_link_title'] = self.article_title.replace(" ","_") row['article_title'] = self.article_title row['nomination_ts'] = (GANstats.display_date(self.nomination_ts)) row['review_ts'] = (GANstats.display_date(self.review_ts)) row['nominator_link'] = self.nominator.replace(" ","_") row['nominator'] = self.nominator row['page'] = self.page row['subtopic'] = self.subtopic row['outcome'] = self.outcome row['state'] = self.state return(row) class GANstats: # Holds static methods that don't relate to the data @classmethod def get_wbgan(cls, config): wbgan = {} database = "s54328__goodarticles_p" wbg_conn = pymysql.connections.Connection(user=config['client']['user'], password=config['client']['password'], database="s54328__goodarticles_p", host='tools.db.svc.eqiad.wmflabs') with wbg_conn.cursor() as cursor: sql = "select lower(nominator) as nominator, count(*) as GA_count from nominators group by nominator order by count(*) desc" try: cursor.execute(sql) except pymysql.Error as e: #GAN.log(gan_conn,"editor_query:wbgan",editor_name,str(e)) pass result = cursor.fetchall() for row in result: wbgan[row[0]] = row[1] return(wbgan) @classmethod def get_name_changes(cls, config): name_changes = {} database = "s55175__ganfilter" conn = pymysql.connections.Connection(user=config['client']['user'], password=config['client']['password'], database="s55175__ganfilter", host='tools.db.svc.eqiad.wmflabs') cursor = conn.cursor(pymysql.cursors.DictCursor) sql = "select n.old_name, n.new_name from " + GA_config.strings['name changes table name'] + " n " try: cursor.execute(sql) for row in cursor.fetchall(): name_changes[row['old_name']] = row['new_name'] except pymysql.Error as e: print(sql + " : " + str(e)) return(name_changes) @classmethod def get_style(cls): style = "<style>\n" style += "table, th, td {\n" style += " border: 1px solid black;\n" style += " border-collapse: collapse;\n" style += "}\n" style += "\n" style += ".collapsible {\n" style += " cursor: pointer;\n" style += " padding: 18px;\n" style += " width: 100%;\n" style += " border: none;\n" style += " text-align: left;\n" style += " outline: none;\n" style += " font-size: 15px;\n" style += "}\n" style += "\n" style += ".active, .collapsible:hover {\n" style += " background-color: #555;\n" style += "}\n" style += ".content {\n" style += " padding: 0 18px;\n" style += " display: none;\n" style += " overflow: hidden;\n" style += " background-color: #f1f1f1;\n" style += "}\n" style += "\n" style += "</style>\n" return(style) @classmethod def get_top_bar_style(cls): top_bar_css = '<style>\n' top_bar_css += 'body {\n' top_bar_css += ' margin: 0;\n' top_bar_css += ' font-family: Arial, Helvetica, sans-serif;\n' top_bar_css += '}\n' top_bar_css += '\n' top_bar_css += '.topnav {\n' top_bar_css += ' overflow: hidden;\n' top_bar_css += ' background-color: #333;\n' top_bar_css += '}\n' top_bar_css += '\n' top_bar_css += '.topnav a {\n' top_bar_css += ' float: left;\n' top_bar_css += ' color: #f2f2f2;\n' top_bar_css += ' text-align: center;\n' top_bar_css += ' padding: 14px 16px;\n' top_bar_css += ' text-decoration: none;\n' top_bar_css += ' font-size: 17px;}\n' top_bar_css += '\n' top_bar_css += '.topnav a:hover {\n' top_bar_css += ' background-color: #ddd;\n' top_bar_css += ' color: black;\n' top_bar_css += '}\n' top_bar_css += '\n' top_bar_css += '.topnav a.active {\n' top_bar_css += ' background-color: #04AA6D;\n' top_bar_css += ' color: white;\n' top_bar_css += '}\n' top_bar_css += '</style>\n' return(top_bar_css) @classmethod def get_top_bar_div(cls): top_bar_div = '<div class="topnav">\n' top_bar_div += '<a class="active" href="fac_editor_query">FAC stats</a>\n' top_bar_div += '<a href="gan_editor_query">GAN stats</a>\n' top_bar_div += '</div>' return(top_bar_div) @classmethod def get_html_top(cls): return("<html><head></head><body>\n") @classmethod def get_html_bottom(cls): return("</body></html>\n") @classmethod def article_link(cls, article_title): if article_title is None or article_title == '': return("") else: htext = '<a href="https://wiki.riteme.site/wiki/' + article_title.replace(" ","_") + '" target="_blank">' + article_title + '</a>' return(htext) @classmethod def editor_link(cls, editor): if editor is None or editor == '': return("") else: htext = '<a href="https://wiki.riteme.site/wiki/User:' + editor.replace(" ","_") + '" target="_blank">' + editor + '</a>' return(htext) @classmethod def review_link(cls, article_title, page): if article_title is None or article_title == '': return("") else: htext = '<a href="https://wiki.riteme.site/wiki/Talk:' + article_title.replace(" ","_") + '/GA' + str(page) + '" target="_blank">' + '/GA' + str(page) + '</a>' return(htext) @classmethod def display_date(cls, date_value): if date_value is None: return("") else: return (date_value.strftime('%Y-%m-%d'))