-
Notifications
You must be signed in to change notification settings - Fork 12
Download the list of members from Google Spreadsheet, download avatars as files #85
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
b3e2814
78b0fc3
d0bd255
04854b0
246b1b8
2815334
788885f
84897f5
ef7b92c
f6e0455
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,16 +1,19 @@ | ||
| # Elsa output | ||
| _build | ||
|
|
||
| ### OSX | ||
| # OS | ||
| ._* | ||
| *.DS_Store | ||
|
|
||
| # Icon must end with two \r | ||
| Icon | ||
| # IDEs | ||
| .idea | ||
| .vscode | ||
|
|
||
| # Thumbnails | ||
| ._* | ||
| # Cache | ||
| .cache | ||
| .pytest_cache | ||
|
|
||
| # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm | ||
| # Generated | ||
| /pyvecorg/_build | ||
| /pyvecorg/data/members_list.yml | ||
| /pyvecorg/static/img/avatars | ||
|
|
||
| # User-specific stuff: | ||
| .idea | ||
| # Private | ||
| google_service_account.json |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -17,26 +17,42 @@ $ pipenv install | |||||
| The site uses [elsa](https://github.com/pyvec/elsa). | ||||||
|
|
||||||
| - Installation: `pipenv install --dev` | ||||||
| - Development server: `pipenv run serve` | ||||||
| - Download data from external sources: `pipenv run build` | ||||||
| - Tests: `pipenv run test` | ||||||
| - Development server: `pipenv run serve` | ||||||
|
|
||||||
| ### Data and tests | ||||||
|
|
||||||
| The site is just a single HTML page rendered on top of some static data. | ||||||
| However, the data can get quite complex and most of the texts need to be | ||||||
| translated into two languages. | ||||||
| However, some of the data come from external sources, the data can get quite | ||||||
| complex, and most of the texts need to be translated into two languages. | ||||||
|
|
||||||
| The data is stored in multiple YAML files. When these are read, whenever | ||||||
| an object has just `cs` and `en` properties, it is treated as a "translated text" | ||||||
| and the property corresponding to a currently selected language becomes | ||||||
| the actual value in place of the object. | ||||||
|
|
||||||
| Also, to keep the complex structure of the YAML files organized and tested, | ||||||
| there are schemas written in [JSON Schema](https://spacetelescope.github.io/understanding-json-schema/) | ||||||
| To keep the complex structure of the YAML files organized and tested, | ||||||
| there are schemas written in [JSON Schema](https://json-schema.org/understanding-json-schema/) | ||||||
| ([spec](http://json-schema.org/)). In tests, the YAML files are validated | ||||||
| against the schemas. There is also a couple of additional tests to ensure some | ||||||
| logical rules which cannot be easily expressed by JSON Schema. | ||||||
|
|
||||||
| ### External sources | ||||||
|
|
||||||
| Some data cannot be stored statically in a YAML file. There is a command | ||||||
| `pipenv run build`, which downloads them from external sources and generates | ||||||
| respective static YAML files. This is a separate step, which needs to be done | ||||||
| before developing or deploying the site, otherwise it won't work properly. | ||||||
|
|
||||||
| ### Members | ||||||
|
|
||||||
| Pyvec members are tracked in an internal Google Spreadsheet. The future | ||||||
| intention is to have the list of members public, but we're not there yet (GDPR). | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
| So far only board members are being listed publicly. The `pipenv run build` | ||||||
| command downloads the spreadsheet as CSV and generates the `members_list.yml` | ||||||
| file. It also downloads and caches avatars. | ||||||
|
|
||||||
| ### Numbers | ||||||
|
|
||||||
| There are stats numbers in [numbers.yml](pyvecorg/data/numbers.yml). They are | ||||||
|
|
@@ -50,15 +66,16 @@ as well as it clones and analyzes all relevant repositories. If you need to upda | |||||
| the number, | ||||||
|
|
||||||
| 1. ask him to run the script, | ||||||
| 2. bother him to Open Source the script code. | ||||||
| 1. bother him to Open Source the script code. | ||||||
|
|
||||||
| ### Twitter avatars | ||||||
| ### Google Drive credentials | ||||||
|
|
||||||
| To figure out correct URLs of Twitter avatars from Twitter usernames, the site | ||||||
| needs to perform an HTTP request for each of them. This is not really an issue, | ||||||
| because on production this is done only once - in the moment of deployment. | ||||||
| However, it can get very annoying during development. Set `DISABLE_TWITTER_AVATARS` | ||||||
| environment variable to truthy value to disable Twitter avatars for development. | ||||||
| 1. Follow the steps in the [gspread guide](https://gspread.readthedocs.io/en/latest/oauth2.html). Instead of Google Drive API, enable Google Sheets API. | ||||||
| 1. Save the obtained JSON file into the `pyvecorg` package as `google_service_account.json` | ||||||
| 1. Make sure it is ignored by Git | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I suppose the setup up until this point is needed for local hacking as well?
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes. But since it is decoupled, you should be able to run the site without members being populated, for editing other parts of the page. |
||||||
| 1. Run `cat pyvecorg/google_service_account.json | pbcopy` to copy the JSON into your clipboard (macOS) | ||||||
| 1. Go to [Travis CI project settings](https://travis-ci.org/pyvec/pyvec.org/settings), section Environment Variables | ||||||
| 1. Add `GOOGLE_SERVICE_ACCOUNT` variable and paste the JSON from your clipboard as a value | ||||||
|
|
||||||
| ## Deployment | ||||||
|
|
||||||
|
|
||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,92 @@ | ||
| import io | ||
| import os | ||
| import json | ||
| from textwrap import dedent | ||
| from pathlib import Path | ||
|
|
||
| import yaml | ||
| import requests | ||
| from slugify import slugify | ||
| import gspread | ||
| from oauth2client.service_account import ServiceAccountCredentials | ||
|
|
||
| from pyvecorg.avatars import get_avatar_url, create_thumbnail | ||
|
|
||
|
|
||
| PACKAGE_DIR = Path(__file__).parent | ||
| MEMBERS_LIST_YAML = PACKAGE_DIR / 'data' / 'members_list.yml' | ||
| MEMBERS_CSV_URL = 'https://docs.google.com/spreadsheets/d/e/2PACX-1vSWK18MlEy95sAGl1BM6BXWxPgJbIx2UH3tAyJjxES06hHuaXgpsmD5pRz9kkGcFupiZL_U_e7yv4t1/pub?gid=0&single=true&output=csv' # noqa | ||
| STATIC_DIR = PACKAGE_DIR / 'static' | ||
| AVATARS_DIR = STATIC_DIR / 'img' / 'avatars' | ||
|
|
||
|
|
||
| def read_spreadsheet(doc_key, sheet_name, google_service_account): | ||
| credentials = ServiceAccountCredentials.from_json_keyfile_dict( | ||
| google_service_account, | ||
| [ | ||
| 'https://spreadsheets.google.com/feeds', | ||
| 'https://www.googleapis.com/auth/drive', | ||
| ] | ||
| ) | ||
| doc = gspread.authorize(credentials).open_by_key(doc_key) | ||
| return doc.worksheet(sheet_name).get_all_values() | ||
|
|
||
|
|
||
| def parse_members(rows): | ||
| head = None | ||
| for row in rows: | ||
| if frozenset(['name', 'role', 'avatar']) < frozenset(row): | ||
| head = row | ||
| break | ||
| for row in rows: | ||
| yield {key: value for key, value in zip(head, row) if value != ''} | ||
|
|
||
|
|
||
| def generate_yaml(data): | ||
| yaml_contents = dedent('''\ | ||
| # | ||
| # This file has been generated from external sources | ||
| # using `pipenv run build`. Do not edit it manually! | ||
| # | ||
| ''') | ||
| return yaml_contents + yaml.dump(data, allow_unicode=True) | ||
|
|
||
|
|
||
| def create_member_sorting_key(member): | ||
| return ( | ||
| 0 if member.get('role') == 'chair' else 1, # chair to be first | ||
| member.get('nickname', member['name']).split(' ')[-1], # last name | ||
| ) | ||
|
|
||
|
|
||
| if __name__ == '__main__': | ||
| # Build data/members_list.yml and avatar images | ||
| gsa_path = PACKAGE_DIR / 'google_service_account.json' | ||
| gsa_json = os.getenv('GOOGLE_SERVICE_ACCOUNT') or gsa_path.read_text() | ||
| gsa = json.loads(gsa_json) | ||
|
|
||
| # Document key appears in the URL if you have the document open | ||
| # in your browser | ||
| doc_key = '1n8hzBnwZ5ANkUCvwEy8rWsXlqeAAwu-5JBodT5OJx_I' | ||
hroncok marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| rows = read_spreadsheet(doc_key, 'list', gsa) | ||
| members = sorted([ | ||
| member for member in parse_members(rows) | ||
| if member.get('role') in ('board', 'chair') | ||
| ], 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)) | ||
| MEMBERS_LIST_YAML.write_text(generate_yaml(data)) | ||
Uh oh!
There was an error while loading. Please reload this page.