Compare commits
	
		
			7 commits
		
	
	
		
			6a0e76440a
			...
			59878c16a7
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
							
							
								
									
								
								 | 
						59878c16a7 | ||
| 
							
							
								
									
								
								 | 
						c4eec7bb73 | ||
| 
							
							
								
									
								
								 | 
						f3f570063f | ||
| 
							
							
								
									
								
								 | 
						e6647a6425 | ||
| 
							
							
								
									
								
								 | 
						2cf5dbb22e | ||
| 
							
							
								
									
								
								 | 
						a7e2d17063 | ||
| 
							
							
								
									
								
								 | 
						0e37348e14 | 
| 
						 | 
				
			
			@ -5,9 +5,8 @@ import typing
 | 
			
		|||
 | 
			
		||||
import sqlalchemy
 | 
			
		||||
import sqlalchemy.orm.decl_api
 | 
			
		||||
from sqlalchemy import Index, func, text
 | 
			
		||||
from sqlalchemy import func
 | 
			
		||||
from sqlalchemy.dialects import postgresql
 | 
			
		||||
from sqlalchemy.dialects.postgresql import TSVECTOR
 | 
			
		||||
from sqlalchemy.ext.associationproxy import association_proxy
 | 
			
		||||
from sqlalchemy.ext.declarative import declarative_base
 | 
			
		||||
from sqlalchemy.ext.orderinglist import ordering_list
 | 
			
		||||
| 
						 | 
				
			
			@ -172,22 +171,6 @@ class Event(TimeStampedModel):
 | 
			
		|||
    event_type = Column(String)
 | 
			
		||||
    url = Column(String)
 | 
			
		||||
    cancelled = Column(Boolean)
 | 
			
		||||
    search_vector = Column(TSVECTOR)
 | 
			
		||||
 | 
			
		||||
    trigger = text(
 | 
			
		||||
        """
 | 
			
		||||
        CREATE TRIGGER event_vector_update BEFORE INSERT OR UPDATE
 | 
			
		||||
        ON event FOR EACH ROW EXECUTE FUNCTION
 | 
			
		||||
        tsvector_update_trigger(search_vector, 'pg_catalog.english', title, abstract, description);
 | 
			
		||||
        """
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    Index(
 | 
			
		||||
        "event_search_vector_idx",
 | 
			
		||||
        search_vector,
 | 
			
		||||
        postgresql_using="gin",
 | 
			
		||||
        postgresql_ops={"abstract_search_vector": "gin_trgm_ops"},
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    conference = relationship("Conference", back_populates="events")
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										4
									
								
								main.py
									
									
									
									
									
								
							
							
						
						
									
										4
									
								
								main.py
									
									
									
									
									
								
							| 
						 | 
				
			
			@ -182,6 +182,8 @@ def search_people() -> str:
 | 
			
		|||
 | 
			
		||||
@app.route("/merge", methods=["GET", "POST"])
 | 
			
		||||
def merge() -> str | Response:
 | 
			
		||||
    assert app.config["ADMIN_MODE"]
 | 
			
		||||
 | 
			
		||||
    if flask.request.method == "POST":
 | 
			
		||||
        search_for = flask.request.form["q"]
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -407,6 +409,8 @@ def search_everything() -> str:
 | 
			
		|||
 | 
			
		||||
@app.route("/person/<int:person_id>/delete", methods=["POST"])
 | 
			
		||||
def delete_person(person_id: int) -> str | Response:
 | 
			
		||||
    assert app.config["ADMIN_MODE"]
 | 
			
		||||
 | 
			
		||||
    item = model.Person.query.get(person_id)
 | 
			
		||||
 | 
			
		||||
    for cp in item.conferences_association:
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2,74 +2,58 @@
 | 
			
		|||
 | 
			
		||||
{% block title %}Conference archive{% endblock %}
 | 
			
		||||
 | 
			
		||||
  {% block content %}
 | 
			
		||||
  <div class="container">
 | 
			
		||||
    <div class="row">
 | 
			
		||||
    <h1>Conference archive</h1>
 | 
			
		||||
{% block content %}
 | 
			
		||||
<div>
 | 
			
		||||
  <h1>Conference archive</h1>
 | 
			
		||||
 | 
			
		||||
  <div style="margin-bottom:1rem">
 | 
			
		||||
    👥
 | 
			
		||||
    {{ "{:,d}".format(count.conference) }} conferences<br/>
 | 
			
		||||
    🌍
 | 
			
		||||
    {{ "{:,d}".format(count.country) }} countries<br/>
 | 
			
		||||
    📍
 | 
			
		||||
    {{ "{:,d}".format(count.venue) }} venues<br/>
 | 
			
		||||
    🎤
 | 
			
		||||
    {{ "{:,d}".format(count.event) }} talks<br/>
 | 
			
		||||
    👤
 | 
			
		||||
    {{ "{:,d}".format(count.person) }} speakers
 | 
			
		||||
    <a href="{{ url_for("top_speakers_page") }}">top speakers</a><br/>
 | 
			
		||||
  </div>
 | 
			
		||||
 | 
			
		||||
  <h2>Conferences</h2>
 | 
			
		||||
 | 
			
		||||
  {% for item in items %}
 | 
			
		||||
  {% if loop.first or item.start.year != loop.previtem.start.year %}
 | 
			
		||||
  <h3>{{ item.start.year }}</h3>
 | 
			
		||||
  {% endif %}
 | 
			
		||||
 | 
			
		||||
  <div style="margin-bottom:1.5rem">
 | 
			
		||||
    👥
 | 
			
		||||
    <a href="{{ url_for("conference_page", short_name=item.short_name) }}">{{ item.title }}</a>
 | 
			
		||||
    📅
 | 
			
		||||
    {{ item.start.strftime("%d %b %Y") }}
 | 
			
		||||
    <br/>
 | 
			
		||||
    {% if item.venue %}
 | 
			
		||||
      📍
 | 
			
		||||
      {{ item.venue.name }}
 | 
			
		||||
      –
 | 
			
		||||
      {{ item.venue.city.name }},
 | 
			
		||||
      {{ item.venue.city.country.name }}
 | 
			
		||||
      {{ item.venue.city.country.flag }}
 | 
			
		||||
    <br/>
 | 
			
		||||
    {% endif %}
 | 
			
		||||
    {#
 | 
			
		||||
    <form action="{{ url_for("search_people") }}">
 | 
			
		||||
      <div class="mb-3">
 | 
			
		||||
        <label for="q" class="form-label">speaker name</label>
 | 
			
		||||
        <input type="text" class="form-control" name="q" id="q">
 | 
			
		||||
      </div>
 | 
			
		||||
      <button type="submit" class="btn btn-primary">Search</button>
 | 
			
		||||
    </form>
 | 
			
		||||
    {% if item.series %}
 | 
			
		||||
    📃 Series: {{ item.series.name }}
 | 
			
		||||
    <br/>
 | 
			
		||||
    {% endif %}
 | 
			
		||||
    #}
 | 
			
		||||
 | 
			
		||||
    <div style="margin-bottom:1rem">
 | 
			
		||||
      👥
 | 
			
		||||
      {{ "{:,d}".format(count.conference) }} conferences<br/>
 | 
			
		||||
      🌍
 | 
			
		||||
      {{ "{:,d}".format(count.country) }} countries<br/>
 | 
			
		||||
      📍
 | 
			
		||||
      {{ "{:,d}".format(count.venue) }} venues<br/>
 | 
			
		||||
      🎤
 | 
			
		||||
      {{ "{:,d}".format(count.event) }} talks -
 | 
			
		||||
      <a href="{{ url_for("events_page") }}">most common titles</a><br/>
 | 
			
		||||
      👤
 | 
			
		||||
      {{ "{:,d}".format(count.person) }} speakers
 | 
			
		||||
      <a href="{{ url_for("top_speakers_page") }}">top speakers</a><br/>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <h2>Conferences</h2>
 | 
			
		||||
 | 
			
		||||
    {% for item in items %}
 | 
			
		||||
    {% if loop.first or item.start.year != loop.previtem.start.year %}
 | 
			
		||||
    <h3>{{ item.start.year }}</h3>
 | 
			
		||||
    {% endif %}
 | 
			
		||||
 | 
			
		||||
    <div style="margin-bottom:1.5rem">
 | 
			
		||||
      👥
 | 
			
		||||
      <a href="{{ url_for("conference_page", short_name=item.short_name) }}">{{ item.title }}</a>
 | 
			
		||||
      📅
 | 
			
		||||
      {{ item.start.strftime("%d %b %Y") }}
 | 
			
		||||
      <br/>
 | 
			
		||||
      {% if item.venue %}
 | 
			
		||||
        📍
 | 
			
		||||
        {{ item.venue.name }}
 | 
			
		||||
        –
 | 
			
		||||
        {{ item.venue.city.name }},
 | 
			
		||||
        {{ item.venue.city.country.name }}
 | 
			
		||||
        {{ item.venue.city.country.flag }}
 | 
			
		||||
      <br/>
 | 
			
		||||
      {% endif %}
 | 
			
		||||
      {#
 | 
			
		||||
      {% if item.series %}
 | 
			
		||||
      📃 Series: {{ item.series.name }}
 | 
			
		||||
      <br/>
 | 
			
		||||
      {% endif %}
 | 
			
		||||
      #}
 | 
			
		||||
 | 
			
		||||
      {{ (item.end - item.start).days + 1 }} days,
 | 
			
		||||
      {{ item.events.count() }} talks,
 | 
			
		||||
      {{ item.people_detail.count() }} speakers<br/>
 | 
			
		||||
    </div>
 | 
			
		||||
    {% endfor %}
 | 
			
		||||
    </table>
 | 
			
		||||
 | 
			
		||||
  </div>
 | 
			
		||||
    {{ (item.end - item.start).days + 1 }} days,
 | 
			
		||||
    {{ item.events.count() }} talks,
 | 
			
		||||
    {{ item.people_detail.count() }} speakers<br/>
 | 
			
		||||
  </div>
 | 
			
		||||
  {% endfor %}
 | 
			
		||||
 | 
			
		||||
</div>
 | 
			
		||||
{% endblock %}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,7 +1,6 @@
 | 
			
		|||
<p>
 | 
			
		||||
  <form action="{{ url_for("search_people") }}">
 | 
			
		||||
  <a href="{{ url_for("index") }}">home</a>
 | 
			
		||||
  | <a href="{{ url_for("events_page") }}">events</a>
 | 
			
		||||
  <a href="{{ url_for("index") }}">conferences</a>
 | 
			
		||||
  | <a href="{{ url_for("top_speakers_page") }}">speakers</a>
 | 
			
		||||
 | 
			
		||||
      <input type="text" class="form-control" placeholder="speaker name" name="q" id="q">
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -21,8 +21,8 @@
 | 
			
		|||
 | 
			
		||||
 | 
			
		||||
  {% block content %}
 | 
			
		||||
  <div class="container">
 | 
			
		||||
    <div class="row">
 | 
			
		||||
  <div>
 | 
			
		||||
    <div>
 | 
			
		||||
 | 
			
		||||
  {% set photo = item.photo_filename() %}
 | 
			
		||||
  {% if photo %}
 | 
			
		||||
| 
						 | 
				
			
			@ -45,7 +45,7 @@
 | 
			
		|||
  {% endif %}
 | 
			
		||||
  </p>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  {% if config.ADMIN_MODE %}
 | 
			
		||||
  {% set search_for = item.name + ' ' + " haswbstatement:P31=Q5" %}
 | 
			
		||||
  <p><a href="https://www.wikidata.org/w/index.php?search={{ search_for | urlencode }}&title=Special%3ASearch&ns0=1&ns120=1">Search for {{ item.name }} on Wikidata</a></p>
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -80,6 +80,7 @@
 | 
			
		|||
  <p>No similar names found on Wikidata</p>
 | 
			
		||||
  {% endif %}
 | 
			
		||||
  {% endif %}
 | 
			
		||||
  {% endif %}
 | 
			
		||||
 | 
			
		||||
  {% set bio_source = item.bio_source() %}
 | 
			
		||||
  {% if bio_source %}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -17,9 +17,12 @@
 | 
			
		|||
    </form>
 | 
			
		||||
 | 
			
		||||
    <p>
 | 
			
		||||
    Found {{ q.count() }} people matching '{{ search_for }}'
 | 
			
		||||
    {% set count = q.count() %}
 | 
			
		||||
    Found {{ count }} people matching '{{ search_for }}'
 | 
			
		||||
 | 
			
		||||
    {% if config.ADMIN_MODE and count %}
 | 
			
		||||
    <a href="{{ url_for("merge", q=search_for) }}">merge</a>
 | 
			
		||||
    {% endif %}
 | 
			
		||||
    </p>
 | 
			
		||||
 | 
			
		||||
<ul>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,40 +1,34 @@
 | 
			
		|||
{% extends "base.html" %}
 | 
			
		||||
 | 
			
		||||
{% block style %}
 | 
			
		||||
<script src="{{ url_for("static", filename="leader-line.min.js") }}"></script>
 | 
			
		||||
<style>
 | 
			
		||||
  .right-images {
 | 
			
		||||
  position: absolute;
 | 
			
		||||
  right: -280px;
 | 
			
		||||
  top: 0;
 | 
			
		||||
  width: 120px;
 | 
			
		||||
  .person {
 | 
			
		||||
    display: flex;
 | 
			
		||||
    align-items: top;
 | 
			
		||||
    margin-bottom: 10px;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .left-images {
 | 
			
		||||
  position: absolute;
 | 
			
		||||
  right: -140px;
 | 
			
		||||
  top: 0;
 | 
			
		||||
  width: 120px;
 | 
			
		||||
  .info {
 | 
			
		||||
    flex: 1; /* Allow text to take remaining space */
 | 
			
		||||
    padding-right: 10px; /* Add spacing between text and image */
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  .image {
 | 
			
		||||
      max-width: 100%;
 | 
			
		||||
  img.photo {
 | 
			
		||||
    max-width: 120px; /* Set max width for images */
 | 
			
		||||
    height: auto; /* Maintain image aspect ratio */
 | 
			
		||||
  }
 | 
			
		||||
</style>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  .container {
 | 
			
		||||
    position: relative;
 | 
			
		||||
  }
 | 
			
		||||
</style>
 | 
			
		||||
{% endblock %}
 | 
			
		||||
 | 
			
		||||
{% set show_images = True %}
 | 
			
		||||
{% set show_images = False %}
 | 
			
		||||
 | 
			
		||||
{% block title %}Conference archive{% endblock %}
 | 
			
		||||
 | 
			
		||||
  {% block content %}
 | 
			
		||||
  <div class="container">
 | 
			
		||||
  <div>
 | 
			
		||||
 | 
			
		||||
  {% if show_images %}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -88,65 +82,22 @@
 | 
			
		|||
    {% if loop.first or loop.previtem[1] != count %}
 | 
			
		||||
    <h4>{{ count }} conferences</h4>
 | 
			
		||||
    {% endif %}
 | 
			
		||||
    <div>
 | 
			
		||||
      <span id="person-{{ person.id }}">
 | 
			
		||||
    <div class="person">
 | 
			
		||||
      {% set photo = person.photo_filename() %}
 | 
			
		||||
      <span class="info" id="person-{{ person.id }}">
 | 
			
		||||
      👤
 | 
			
		||||
      <a href="{{ url_for("person", person_id=person.id) }}">{{ person.name }}</a>
 | 
			
		||||
      <a href="{{ url_for("person", person_id=person.id) }}">{{ person.name }}</a><br>
 | 
			
		||||
      ({{ count }} conferences, {{ person.event_count }} talks)
 | 
			
		||||
      {% if person.photo_filename() %}📷{% endif %}
 | 
			
		||||
      {% if person.wikidata_qid %}
 | 
			
		||||
      <a href="https://www.wikidata.org/wiki/{{ person.wikidata_qid }}">Wikidata</a>
 | 
			
		||||
      {% endif %}
 | 
			
		||||
      </span>
 | 
			
		||||
      {% if photo %}
 | 
			
		||||
      <img class="photo" src="{{ url_for("static", filename=photo) }}" alt="{{ person.name }}">
 | 
			
		||||
      {% endif %}
 | 
			
		||||
    </div>
 | 
			
		||||
    {% endfor %}
 | 
			
		||||
 | 
			
		||||
</div>
 | 
			
		||||
{% endblock %}
 | 
			
		||||
 | 
			
		||||
{% block script %}
 | 
			
		||||
{% if show_images %}
 | 
			
		||||
<script>
 | 
			
		||||
  {# var person_ids = {{ photo_person_ids | tojson }}; #}
 | 
			
		||||
  var left = {{ left | tojson }};
 | 
			
		||||
  var right = {{ right | tojson }};
 | 
			
		||||
var lines = {};
 | 
			
		||||
 | 
			
		||||
window.addEventListener('load', function() {
 | 
			
		||||
 | 
			
		||||
  for(var i =0; i < left.length; i++) {
 | 
			
		||||
    var id = left[i];
 | 
			
		||||
    var person = document.getElementById('person-' + id);
 | 
			
		||||
    var image = document.getElementById('image-' + id);
 | 
			
		||||
 | 
			
		||||
    var line = new LeaderLine(LeaderLine.mouseHoverAnchor(person, 'draw'), image);
 | 
			
		||||
    line.setOptions({startSocket: 'right', endSocket: 'left', path: 'fluid'});
 | 
			
		||||
 | 
			
		||||
    var line2 = new LeaderLine(LeaderLine.mouseHoverAnchor(image, 'draw'), person);
 | 
			
		||||
    line2.setOptions({startSocket: 'left', endSocket: 'right', path: 'fluid'});
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    // lines[id] = line;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  for(var i =0; i < right.length; i++) {
 | 
			
		||||
    var id = right[i];
 | 
			
		||||
    var person = document.getElementById('person-' + id);
 | 
			
		||||
    var image = document.getElementById('image-' + id);
 | 
			
		||||
 | 
			
		||||
    var line = new LeaderLine(LeaderLine.mouseHoverAnchor(person, 'draw'), image);
 | 
			
		||||
    line.setOptions({startSocket: 'right', endSocket: 'left', path: 'fluid'});
 | 
			
		||||
 | 
			
		||||
    var line2 = new LeaderLine(LeaderLine.mouseHoverAnchor(image, 'draw'), person);
 | 
			
		||||
    line2.setOptions({startSocket: 'left', endSocket: 'right', path: 'fluid'});
 | 
			
		||||
 | 
			
		||||
    // lines[id] = line;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
</script>
 | 
			
		||||
{% endif %}
 | 
			
		||||
{% endblock %}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in a new issue