diff --git a/pyvecorg/avatars.py b/pyvecorg/avatars.py index 17fda00..4238803 100644 --- a/pyvecorg/avatars.py +++ b/pyvecorg/avatars.py @@ -7,24 +7,27 @@ def get_avatar_url(member): - no_avatar = 'https://www.gravatar.com/avatar/0000?d=mm&f=y' - try: - key = member['avatar'] - value = member[key] - except KeyError: - return no_avatar - - if key == 'email': - email_hash = hashlib.md5(value.lower().strip().encode()).hexdigest() - return f'https://www.gravatar.com/avatar/{email_hash}?size=100&d=404' - elif key == 'twitter': - username = quote(value) - url = f'https://twitter.com/{username}/profile_image' - url = requests.head(url).headers.get('location') - return url.replace('_normal', '_200x200') - if key == 'github': - username = quote(value) - return f'https://github.com/{username}.png' + key = member.get('avatar') or 'github' # defaults to 'github' + value = member.get(key) + + if value: + if key == 'email': + hash = hashlib.md5(value.lower().strip().encode()).hexdigest() + return f'https://www.gravatar.com/avatar/{hash}?size=100&d=404' + elif key == 'twitter': + username = quote(value) + url = f'https://twitter.com/{username}/profile_image' + url = requests.head(url, headers={ + # Twitter now only allows Googlebot to fetch avatars (facepalm) + 'User-Agent': 'Googlebot/42 (+http://www.google.com/bot.html)', + }).headers.get('location') + return url.replace('_normal', '_200x200') + elif key == 'github': + username = quote(value) + return f'https://github.com/{username}.png' + + # to disable avatar, specify it as 'none' or basically anything invalid + return 'https://www.gravatar.com/avatar/0000?d=mm&f=y' def create_thumbnail(file, size): diff --git a/pyvecorg/build.py b/pyvecorg/build.py index e6883be..02593f7 100644 --- a/pyvecorg/build.py +++ b/pyvecorg/build.py @@ -34,6 +34,7 @@ def read_spreadsheet(doc_key, sheet_name, google_service_account): def parse_members(rows): head = None + rows = iter(rows) for row in rows: if frozenset(['name', 'role', 'avatar']) < frozenset(row): head = row @@ -42,6 +43,17 @@ def parse_members(rows): yield {key: value for key, value in zip(head, row) if value != ''} +def coerce_member(member): + return { + 'name': member.get('nickname', member.get('name')), + 'role': member.get('role', 'member'), + 'github': member.get('github'), + 'twitter': member.get('twitter'), + 'linkedin': member.get('linkedin'), + 'avatar_filename': member.get('avatar_filename'), + } + + def generate_yaml(data): yaml_contents = dedent('''\ # @@ -59,6 +71,14 @@ def create_member_sorting_key(member): ) +def is_board(member): + return member.get('role') in ('board', 'chair') + + +def is_public_member(member): + return not is_board(member) and member.get('gdpr_consent') == 'yes' + + if __name__ == '__main__': # Build data/members_list.yml and avatar images gsa_path = PACKAGE_DIR / 'google_service_account.json' @@ -69,24 +89,29 @@ def create_member_sorting_key(member): # in your browser doc_key = '1n8hzBnwZ5ANkUCvwEy8rWsXlqeAAwu-5JBodT5OJx_I' rows = read_spreadsheet(doc_key, 'list', gsa) - members = sorted([ + members = list(sorted([ member for member in parse_members(rows) - if member.get('role') in ('board', 'chair') - ], key=create_member_sorting_key) + ], key=create_member_sorting_key)) AVATARS_DIR.mkdir(exist_ok=True) - for member in sorted(members, key=create_member_sorting_key): - avatar_url = get_avatar_url(member) - img_basename = slugify(member['name']) + '.png' - - response = requests.get(avatar_url) - response.raise_for_status() - img_bytes = create_thumbnail(io.BytesIO(response.content), 100).read() - - img_path = (AVATARS_DIR / img_basename) - img_path.write_bytes(img_bytes) - - member['avatar_filename'] = str(img_path.relative_to(STATIC_DIR)) - - data = dict(entries=list(members)) + for member in members: + if is_board(member) or is_public_member(member): + avatar_url = get_avatar_url(member) + img_basename = slugify(member['name']) + '.png' + + response = requests.get(avatar_url) + response.raise_for_status() + response_bytes = io.BytesIO(response.content) + img_bytes = create_thumbnail(response_bytes, 100).read() + + img_path = (AVATARS_DIR / img_basename) + img_path.write_bytes(img_bytes) + + member['avatar_filename'] = str(img_path.relative_to(STATIC_DIR)) + + data = dict(board=[coerce_member(member) for member in members + if is_board(member)], + public_members=[coerce_member(member) for member in members + if is_public_member(member)], + total_count=len(members)) MEMBERS_LIST_YAML.write_text(generate_yaml(data)) diff --git a/pyvecorg/data/definitions/member_schema.json b/pyvecorg/data/definitions/member_schema.json index d6b83ee..7a9a2ac 100644 --- a/pyvecorg/data/definitions/member_schema.json +++ b/pyvecorg/data/definitions/member_schema.json @@ -5,44 +5,26 @@ "name": { "type": "string" }, - "nickname": { - "type": "string" - }, "role": { "type": "string" }, "github": { - "type": "string" + "type": ["string", "null"] }, "twitter": { - "type": "string" + "type": ["string", "null"] }, "linkedin": { - "type": "string" - }, - "email": { - "type": "string", - "format": "email" - }, - "avatar": { - "type": "string" + "type": ["string", "null"] }, "avatar_filename": { "type": "string" - }, - "address": { - "type": "string" - }, - "dob": { - "type": "string" - }, - "gdpr_consent": { - "type": "string" } }, "additionalProperties": false, "required": [ "name", + "role", "avatar_filename" ] } diff --git a/pyvecorg/data/members.yml b/pyvecorg/data/members.yml index a73331b..79d3f85 100644 --- a/pyvecorg/data/members.yml +++ b/pyvecorg/data/members.yml @@ -10,6 +10,17 @@ roles: cs: člen výboru en: board member +list: + total_count_text: + cs: Celkový počet členů spolku + en: Total count of all members + failure_text: + cs: Seznam členů se nepodařilo načíst. + en: The list of members failed to load. + gdpr_note: + cs: Následují členové, kteří souhlasili s uvedením ve veřejném seznamu. + en: The following members consented with being listed publicly. + note: icon: user-circle-o heading: diff --git a/pyvecorg/data/members_list_schema.json b/pyvecorg/data/members_list_schema.json index ae4643c..46c3d83 100644 --- a/pyvecorg/data/members_list_schema.json +++ b/pyvecorg/data/members_list_schema.json @@ -2,15 +2,26 @@ "$schema": "http://json-schema.org/draft-04/schema#", "type": "object", "properties": { - "entries": { + "board": { "type": "array", "items": { "$ref": "definitions/member_schema.json#" } + }, + "public_members": { + "type": "array", + "items": { + "$ref": "definitions/member_schema.json#" + } + }, + "total_count": { + "type": "number" } }, "additionalProperties": false, "required": [ - "entries" + "board", + "public_members", + "total_count" ] } diff --git a/pyvecorg/data/members_schema.json b/pyvecorg/data/members_schema.json index 7bf7caf..37b5a54 100644 --- a/pyvecorg/data/members_schema.json +++ b/pyvecorg/data/members_schema.json @@ -14,6 +14,16 @@ }, "additionalProperties": false }, + "list": { + "properties": { + "total_count_text": { + "$ref": "definitions/translated_text_schema.json#" + }, + "failure_text": { + "$ref": "definitions/translated_text_schema.json#" + } + } + }, "note": { "$ref": "definitions/note_schema.json#" } @@ -22,6 +32,7 @@ "required": [ "heading", "roles", + "list", "note" ] } diff --git a/pyvecorg/static/main.css b/pyvecorg/static/main.css index 865ee45..466e2a2 100755 --- a/pyvecorg/static/main.css +++ b/pyvecorg/static/main.css @@ -325,3 +325,8 @@ a.icon, a.icon:hover, a.icon:focus, max-width: 100%; } } + +.note { + text-align: center; + margin: 4rem 0; +} diff --git a/pyvecorg/templates/index.html b/pyvecorg/templates/index.html index 612e783..55b3b2e 100644 --- a/pyvecorg/templates/index.html +++ b/pyvecorg/templates/index.html @@ -126,15 +126,6 @@

{{ project.name }}

{{ supporters.heading }}

-
@@ -144,6 +135,15 @@

{{ supporters.note.heading }}

{{ supporters.note.text|markdown }}
+

{{ partners.heading }}

{{ members.heading }}

+
+
+ +
+
+

{{ members.note.heading }}

+ {{ members.note.text|markdown }} +
+
{% if members_list %} +

+ {{ members.list.total_count_text }}: {{ members_list.total_count }}
+ {{ members.list.gdpr_note }} +

+ {% else %} -

- Seznam členů se nepodařilo načíst. +

+ {{ members.list.failure_text }}

{% endif %} -
-
- -
-
-

{{ members.note.heading }}

- {{ members.note.text|markdown }} -
-
{% endblock %}