Skip to content

Commit

Permalink
Merge pull request #190 from pyvec/honzajavorek/roster
Browse files Browse the repository at this point in the history
Přidání seznamu členů
  • Loading branch information
honzajavorek authored Dec 28, 2020
2 parents cfc9995 + f1e603d commit 6b894d1
Show file tree
Hide file tree
Showing 10 changed files with 188 additions and 112 deletions.
39 changes: 21 additions & 18 deletions pyvecorg/avatars.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
59 changes: 42 additions & 17 deletions pyvecorg/build.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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('''\
#
Expand All @@ -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'
Expand All @@ -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))
26 changes: 4 additions & 22 deletions pyvecorg/data/definitions/member_schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
]
}
11 changes: 11 additions & 0 deletions pyvecorg/data/members.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
15 changes: 13 additions & 2 deletions pyvecorg/data/members_list_schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
]
}
11 changes: 11 additions & 0 deletions pyvecorg/data/members_schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -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#"
}
Expand All @@ -22,6 +32,7 @@
"required": [
"heading",
"roles",
"list",
"note"
]
}
5 changes: 5 additions & 0 deletions pyvecorg/static/main.css
Original file line number Diff line number Diff line change
Expand Up @@ -325,3 +325,8 @@ a.icon, a.icon:hover, a.icon:focus,
max-width: 100%;
}
}

.note {
text-align: center;
margin: 4rem 0;
}
89 changes: 68 additions & 21 deletions pyvecorg/templates/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -126,15 +126,6 @@ <h3>{{ project.name }}</h3>
</ul>

<h2 id="supporters">{{ supporters.heading }}</h2>
<ul class="list row">
{% for supporter in supporters.entries|sort(attribute='name') %}
<li class="list-item logo col">
<a href="{{ supporter.url }}" title="{{ supporter.name }}">
<img src="{{ url_for('static', filename=supporter.logo) }}" alt="{{ supporter.name }}" height="50">
</a>
</li>
{% endfor %}
</ul>
<div class="media">
<div class="media-image">
<i class="fa fa-{{ supporters.note.icon }}" aria-hidden="true"></i>
Expand All @@ -144,6 +135,15 @@ <h3>{{ supporters.note.heading }}</h3>
{{ supporters.note.text|markdown }}
</div>
</div>
<ul class="list row">
{% for supporter in supporters.entries|sort(attribute='name') %}
<li class="list-item logo col">
<a href="{{ supporter.url }}" title="{{ supporter.name }}">
<img src="{{ url_for('static', filename=supporter.logo) }}" alt="{{ supporter.name }}" height="50">
</a>
</li>
{% endfor %}
</ul>

<h2 id="partners">{{ partners.heading }}</h2>
<ul class="list row">
Expand All @@ -157,9 +157,19 @@ <h2 id="partners">{{ partners.heading }}</h2>
</ul>

<h2 id="members">{{ members.heading }}</h2>
<div class="media">
<div class="media-image">
<i class="fa fa-{{ members.note.icon }}" aria-hidden="true"></i>
</div>
<div class="media-body">
<h3>{{ members.note.heading }}</h3>
{{ members.note.text|markdown }}
</div>
</div>
{% if members_list %}
<ul class="list row members">
{% for member in members_list.entries %}
{% for member in members_list.board %}

<li class="list-item member col">
<img src="{{ url_for('static', filename=member.avatar_filename) }}" alt="{{ member.name }}" width="100" class="rounded-circle member-avatar">
<p class="member-profiles">
Expand Down Expand Up @@ -196,23 +206,60 @@ <h2 id="members">{{ members.heading }}</h2>
{% if loop.index is divisibleby 5 %}
<div class="w-100 d-none d-md-block d-lg-block d-xl-block"></div>
{% endif %}

{% endfor %}
</ul>
<p class="note">
{{ members.list.total_count_text }}: <strong>{{ members_list.total_count }}</strong><br>
{{ members.list.gdpr_note }}
</p>
<ul class="list row members">
{% for member in members_list.public_members %}

<li class="list-item member col">
<img src="{{ url_for('static', filename=member.avatar_filename) }}" alt="{{ member.name }}" width="100" class="rounded-circle member-avatar">
<p class="member-profiles">
{% if member.github %}
<a href="https://github.com/{{ member.github }}" title="GitHub" class="icon">
<i class="fa fa-github" aria-hidden="true"></i><!--
--><span>GitHub</span><!--
--></a>
{% endif %}
{% if member.twitter %}
<a href="https://twitter.com/{{ member.twitter }}" title="Twitter" class="icon">
<i class="fa fa-twitter" aria-hidden="true"></i><!--
--><span>Twitter</span><!--
--></a>
{% endif %}
{% if member.linkedin %}
<a href="https://linkedin.com/in/{{ member.linkedin }}" title="LinkedIn" class="icon">
<i class="fa fa-linkedin" aria-hidden="true"></i><!--
--><span>LinkedIn</span><!--
--></a>
{% endif %}
</p>
<p class="member-info">
<strong class="member-name">{{ member.nickname|default(member.name) }}</strong><br>
</p>
</li>
{% if loop.index is divisibleby 2 %}
<div class="w-100 d-block d-sm-none"></div>
{% endif %}
{% if loop.index is divisibleby 3 %}
<div class="w-100 d-none d-sm-block d-md-none"></div>
{% endif %}
{% if loop.index is divisibleby 5 %}
<div class="w-100 d-none d-md-block d-lg-block d-xl-block"></div>
{% endif %}

{% endfor %}
</ul>
{% else %}
<p>
Seznam členů se nepodařilo načíst.
<p class="note">
{{ members.list.failure_text }}
<!-- To fix this, you need to run 'pipenv run build' -->
</p>
{% endif %}
<div class="media">
<div class="media-image">
<i class="fa fa-{{ members.note.icon }}" aria-hidden="true"></i>
</div>
<div class="media-body">
<h3>{{ members.note.heading }}</h3>
{{ members.note.text|markdown }}
</div>
</div>
</div>
{% endblock %}
<div class="footer">
Expand Down
Loading

0 comments on commit 6b894d1

Please sign in to comment.