diff --git a/confarchive/query.py b/confarchive/query.py deleted file mode 100644 index f64378c..0000000 --- a/confarchive/query.py +++ /dev/null @@ -1,59 +0,0 @@ -"""Database queries.""" - -import typing -from sqlalchemy.orm.query import Query -from sqlalchemy.engine.cursor import CursorResult -from sqlalchemy import func - -from . import database, model - - -def top_speakers() -> Query: - """Find people who spoke at the most conferences.""" - q: Query = ( - database.session.query(model.Person, func.count()) - .join(model.ConferencePerson) - .filter(model.Person.id != 1046) # FOSDEM Staff - .group_by(model.Person) - .order_by(func.count().desc(), model.Person.name) - ) - return q - - -def top_events() -> Query: - """Most common titles of events.""" - q: Query = ( - database.session.query(model.Event.title, func.count()) - .group_by(model.Event.title) - .order_by(func.count().desc()) - .having(func.count() > 3) - ) - return q - - -def search_for_events(search_for: str) -> Query: - """Search for events with by title.""" - q: Query = model.Event.query.filter( - model.Event.title.ilike(f"%{search_for}%") - ).order_by(model.Event.title) - return q - - -def search_for_people(search_for: str) -> Query: - """Search for people by name.""" - q: Query = model.Person.query.filter( - model.Person.name.ilike(f"%{search_for}%") - ).order_by(model.Person.name) - return q - - -def speaker_counts() -> CursorResult: - """Speaker/conference frequency distribution.""" - sql = """ -select num, count(*) -from (select person_id, count(*) as num from conference_person group by person_id) a -group by num -order by num -""" - - return typing.cast(CursorResult, database.session.execute(sql)) diff --git a/confarchive/utils.py b/confarchive/utils.py deleted file mode 100644 index ac10c61..0000000 --- a/confarchive/utils.py +++ /dev/null @@ -1,11 +0,0 @@ -"""Utility functions.""" - - -def drop_start(s: str, start: str) -> str: - """Remove text from the start of a string.""" - return s[len(start) :] if s.startswith(start) else s - - -def plural(num: int, label: str) -> str: - """Make plural version of label as appropriate.""" - return f'{num:,d} {label}{"s" if num != 1 else ""}' diff --git a/main.py b/main.py index 935d4b0..0f7a1ad 100755 --- a/main.py +++ b/main.py @@ -3,10 +3,11 @@ import os import flask +import sqlalchemy from sqlalchemy import func, or_, update from werkzeug.wrappers import Response -from confarchive import database, model, wikidata, query, utils +from confarchive import database, model, wikidata app = flask.Flask(__name__) app.debug = True @@ -15,6 +16,46 @@ app.config.from_object("config.default") database.init_app(app) +def top_speakers() -> sqlalchemy.orm.query.Query: + q = ( + database.session.query(model.Person, func.count()) + .join(model.ConferencePerson) + .filter(model.Person.id != 1046) # FOSDEM Staff + .group_by(model.Person) + .order_by(func.count().desc(), model.Person.name) + .having(func.count() > 4) + ) + return q + + +def top_speakers2() -> sqlalchemy.orm.query.Query: + q = ( + database.session.query(model.Person, func.count()) + .join(model.ConferencePerson) + .filter(model.Person.name.like("% %")) + .group_by(model.Person) + .order_by(func.count().desc()) + .having(func.count() > 2) + ) + # .order_by(func.length(model.Person.name).desc()) + return q + + +def top_events() -> sqlalchemy.orm.query.Query: + q = ( + database.session.query(model.Event.title, func.count()) + .group_by(model.Event.title) + .order_by(func.count().desc()) + .having(func.count() > 3) + ) + return q + + +def drop_start(s: str, start: str) -> str: + """Remove text from the start of a string.""" + return s[len(start) :] if s.startswith(start) else s + + @app.route("/person/", methods=["GET", "POST"]) def person(person_id: int) -> str | Response: item = model.Person.query.get(person_id) @@ -28,7 +69,7 @@ def person(person_id: int) -> str | Response: if "P18" in wd_item["claims"]: claim_p18 = wd_item["claims"]["P18"] wikidata_photo = [ - utils.drop_start(s["mainsnak"]["datavalue"]["value"], "-") + drop_start(s["mainsnak"]["datavalue"]["value"], "-") for s in claim_p18 ] for filename in wikidata_photo: @@ -72,7 +113,7 @@ def person(person_id: int) -> str | Response: "person.html", item=item, Event=model.Event, - plural=utils.plural, + plural=plural, wikidata_hits=wikidata_hits, is_admin=check_admin_mode, ) @@ -121,73 +162,85 @@ def search_people() -> str: @app.route("/merge", methods=["GET", "POST"]) def merge() -> str | Response: - """Merge speakers.""" - assert check_admin_mode() + assert app.config["ADMIN_MODE"] - if flask.request.method == "GET": + if flask.request.method == "POST": + search_for = flask.request.form["q"] + + item_ids_str = flask.request.form.getlist("person_id") + item_ids: list[int] = [int(i) for i in item_ids_str] + + merge_to_id = min(item_ids) + other_ids = [i for i in item_ids if i != merge_to_id] + + name_from_person_id = flask.request.form["name"] + + print(other_ids, "->", merge_to_id) + + with database.session.begin(): + if merge_to_id != name_from_person_id: + merge_to = model.Person.query.get(merge_to_id) + name_from_person = model.Person.query.get(name_from_person_id) + merge_to.name = name_from_person.name + + print("update ConferencePerson") + database.session.execute( + update(model.ConferencePerson) + .where(model.ConferencePerson.person_id.in_(other_ids)) + .values(person_id=merge_to_id) + ) + + print("update EventPerson") + database.session.execute( + update(model.EventPerson) + .where(model.EventPerson.person_id.in_(other_ids)) + .values(person_id=merge_to_id) + ) + + print("delete people") + for person_id in other_ids: + item = model.Person.query.get(person_id) + database.session.delete(item) + + endpoint = flask.request.endpoint + assert endpoint + return flask.redirect(flask.url_for(endpoint, q=search_for)) + + else: search_for = flask.request.args["q"] - assert search_for - search_for = search_for.strip() - q = query.search_for_people(search_for) - return flask.render_template("merge_people.html", q=q, search_for=search_for) - assert flask.request.method == "POST" - search_for = flask.request.form["q"] - - item_ids_str = flask.request.form.getlist("person_id") - item_ids: list[int] = [int(i) for i in item_ids_str] - - merge_to_id: int = min(item_ids) - other_ids = [i for i in item_ids if i != merge_to_id] - - name_from_person_id = int(flask.request.form["name"]) - - print(other_ids, "->", merge_to_id) - - with database.session.begin(): - if merge_to_id != name_from_person_id: - merge_to = model.Person.query.get(merge_to_id) - name_from_person = model.Person.query.get(name_from_person_id) - merge_to.name = name_from_person.name - - print("update ConferencePerson") - database.session.execute( - update(model.ConferencePerson) - .where(model.ConferencePerson.person_id.in_(other_ids)) - .values(person_id=merge_to_id) - ) - - print("update EventPerson") - database.session.execute( - update(model.EventPerson) - .where(model.EventPerson.person_id.in_(other_ids)) - .values(person_id=merge_to_id) - ) - - print("delete people") - for person_id in other_ids: - item = model.Person.query.get(person_id) - database.session.delete(item) - - endpoint = flask.request.endpoint - assert endpoint - return flask.redirect(flask.url_for(endpoint, q=search_for)) + assert search_for + search_for = search_for.strip() + q = model.Person.query.filter(model.Person.name.ilike(f"%{search_for}%")).order_by( + model.Person.name + ) + return flask.render_template("merge_people.html", q=q, search_for=search_for) @app.route("/events") def events_page() -> str: - """Events page.""" search_for = flask.request.args.get("q") - if search_for: - q = query.search_for_events(search_for) - return flask.render_template("search_events.html", q=q, search_for=search_for) - else: - return flask.render_template("top_events.html", top_events=query.top_events()) + if not search_for: + return flask.render_template("top_events.html", top_events=top_events()) + + q = model.Event.query.filter(model.Event.title.ilike(f"%{search_for}%")).order_by( + model.Event.title + ) + return flask.render_template("search_events.html", q=q, search_for=search_for) @app.route("/") def index() -> str: """Start page.""" + if False: + q = ( + model.Conference.query.order_by(model.Conference.start.desc()) + .add_columns( + func.count(model.Event.id), func.count(model.ConferencePerson.person_id) + ) + .group_by(model.Conference) + ) + q = model.Conference.query.order_by(model.Conference.start.desc()) count = { @@ -201,6 +254,21 @@ def index() -> str: return flask.render_template("index.html", items=q, count=count) +def plural(num: int, label: str) -> str: + return f'{num:,d} {label}{"s" if num != 1 else ""}' + + +def speaker_counts(): + sql = """ +select num, count(*) +from (select person_id, count(*) as num from conference_person group by person_id) a +group by num +order by num +""" + + return database.session.execute(sql) + + @app.route("/series") def list_series() -> str: """Page showing list of conference series.""" @@ -211,15 +279,33 @@ def list_series() -> str: @app.route("/speakers") def top_speakers_page() -> str: + top = top_speakers() + """Top speakers page.""" - top = query.top_speakers().having(func.count() > 4) + photos = [] + for person, count in top: + photo = person.photo_filename() + if photo: + photos.append((person, photo)) + + left_photos = photos[::2] + right_photos = photos[1::2] + + photo_person_ids = [person.id for person, photo in photos] + left = photo_person_ids[::2] + right = photo_person_ids[1::2] return flask.render_template( "top_speakers.html", top_speakers=top, - speaker_counts=query.speaker_counts(), - plural=utils.plural, + speaker_counts=speaker_counts(), + plural=plural, person_image_filename=person_image_filename, + # photo_person_ids=photo_person_ids, + left=left, + right=right, + left_photos=left_photos, + right_photos=right_photos, ) @@ -249,13 +335,11 @@ def add_venue(city_id: int) -> str | Response: @app.route("/wikidata") def link_to_wikidata() -> str: items = [] - top = ( - query.top_speakers() - .filter(model.Person.name.like("% %"), model.Person.wikidata_qid.is_(None)) - .having(func.count() > 2) - ) - for person, num in top: - search_hits = wikidata.search(person.name + " haswbstatement:P31=Q5") + for person, num in top_speakers2(): + if person.wikidata_qid: + continue + q = person.name + " haswbstatement:P31=Q5" + search_hits = wikidata.search(q) if not search_hits: continue @@ -368,11 +452,11 @@ def github_wikidata() -> str: """Look for speakers on Wikidata based on the GitHub property.""" items = [] for line in open("found_wikidata_github"): - person_id, person_name, qid, wd_name, github, desc = eval(line) + person_id, person_name, qid, wd_name, github, photo = eval(line) person = model.Person.query.get(person_id) if person.wikidata_qid: continue - items.append((person, qid, wd_name, desc)) + items.append((person, qid, wd_name, photo)) items.sort(key=lambda i: len(i[0].name)) diff --git a/templates/github.html b/templates/github.html index 29e6e78..58a92e0 100644 --- a/templates/github.html +++ b/templates/github.html @@ -6,13 +6,12 @@

Conference archive

{{ items | count }} matches found

- {% for person, qid, wd_name, desc in items %} + {% for person, qid, wd_name, photo in items %}
{{ person.name }} ## {{ wd_name }} ({{ qid }}) - ## - {{ desc }} + {% if photo %}📷{% endif %}
{% endfor %}