Skip to content
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

Initial implementation of the student organization API #25

Merged
merged 1 commit into from
Aug 27, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,14 @@ for soup_name in soups:
print(soup_name)
```

Finding out who's in charge of the UMass club powerlifting team
```python3
key = student_organizations.search("Powerlifting")[0]["WebsiteKey"]
info = student_organizations.info(key)
contact = info["primaryContact"]
print("Run by {} {}".format(contact["firstName"], contact["lastName"]))
```

Contributing
------------
We'd love to have you contribute some code to this project! Patches from beginning programmers are welcome! We'll help polish up your code and everything.
Expand Down
95 changes: 95 additions & 0 deletions umass_toolkit/student_organizations.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
from urllib.parse import urljoin

import requests

URI_BASE = "https://umass.campuslabs.com"
API_BASE = "engage/api/discovery/"

# The primary endpoint used by the webapp is 'engage/api/discovery/search/
# organizations', which appears to be Microsoft's Azure Search Service REST API.
# It's some sort of RPC controlled via GET parameters. Documentation available.
# <https://docs.microsoft.com/en-us/rest/api/searchservice/search-documents>

def _request(endpoint, params={}):
uri = urljoin(urljoin(URI_BASE, API_BASE), endpoint)
return requests.get(uri, params=params).json()


# TODO: Perhaps this should raise an exception rather than returning 0?
# TODO: Also, should this maybe be exposed to the public API?
def _num_organizations():
# The necessary data is in the "@odata.count" field of every response, but
# if the webapp does this, we might as well.
resp = _request("search/organizations", {
"top": 0,
"facets[0]": "BranchId"
})
structs = resp.get("@search.facets", {}).get("BranchId", {})
if len(structs) >= 1:
return structs[0].get("count", 0)
return 0


def _category_name(category_id):
resp = _request("organization/category", {
"categoryIds[0]": category_id
})
structs = resp.get("items", [])
if len(structs) >= 1:
return structs[0].get("name")


def _category_ids():
resp = _request("search/organizations", {
"top": 0,
"facets[0]": "CategoryIds,count:{}".format(_num_organizations())
})
structs = resp.get("@search.facets", {}).get("CategoryIds", {})
return [category.get("value") for category in structs]


def _additional_fields(organization_id):
resp = _request("organization/{}/additionalFields".format(organization_id))
return resp["items"]


def categories():
"""Returns a list of valid category strings, for use in the 'categories'
parameter of 'student_organizations.search'.
"""
return [_category_name(category) for category in _category_ids()]


def info(websitekey):
"""Returns all information on the organization with the given name. A valid
'websitekey' parameter can be obtained from 'search'."""
resp = _request("organization/bykey/{}".format(websitekey))
organization_id = resp["id"]
resp["additionalFields"] = _additional_fields(organization_id)
return resp


def search(keywords="", categories=[]):
"""Return organizations matching the given keywords and categories."""
# Reverse-lookup is expensive.
filter_string = ""
if len(categories) > 0:
filter_string = []
for category_id in _category_ids():
if _category_name(category_id) in categories:
filter_string.append(
"CategoryIds/any(x: x eq '{}')".format(category_id)
)
filter_string = "(" + " or ".join(filter_string) + ")"

resp = _request("search/organizations", {
"top": _num_organizations(),
"filter": filter_string,
"query": keywords
})
return resp.get("value", [])


def all_organizations():
"""Return a list of every registered organization."""
return search()