diff --git a/.travis.yml b/.travis.yml index 8d28eb76..c3b47225 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,16 +1,26 @@ +sudo: false language: python +services: + - memcached python: - "2.7" install: + - if [[ $TRAVIS_PYTHON_VERSION == 2.6* ]]; then pip install -r travis-ci/python-2.6-require-django-1.6.txt; fi - pip install -r restclients/requirements.txt +env: + - DJANGO_VERSION='<1.8' + - DJANGO_VERSION='<1.10' before_script: - pip install coverage - pip install python-coveralls - pip install pep8 + - if [[ $TRAVIS_PYTHON_VERSION != 2.6* ]]; then pip install -U "Django$DJANGO_VERSION"; fi - cp travis-ci/manage.py manage.py - - python manage.py syncdb --noinput + - if [[ $TRAVIS_PYTHON_VERSION != 2.6* ]]; then python manage.py migrate --noinput; fi + - if [[ $TRAVIS_PYTHON_VERSION == 2.6* ]]; then python manage.py syncdb --noinput; fi script: - - pep8 --exclude=migrations,restclients/sws/,restclients/canvas/,restclients/util/,restclients/trumba/,restclients/dao_implementation,restclients/uwnetid/,restclients/test/,restclients/signals/,restclients/models/,restclients/hfs/,restclients/r25,restclients/nws.py,restclients/thread.py,restclients/pws.py,restclients/bookstore.py,restclients/exceptions.py,restclients/views.py,restclients/gws.py,restclients/dao.py,restclients/cache_manager.py,restclients/amazon_sqs.py,restclients/cache_implementation.py,restclients/sms.py,restclients/urls.py,restclients/library/,restclients/irws.py restclients/ + - pep8 --exclude=migrations,restclients/sws/,restclients/canvas/,restclients/uwnetid/,restclients/test/,restclients/signals/,restclients/hfs/,restclients/r25,restclients/nws.py,restclients/sms.py,restclients/library/ restclients/ + - python -m compileall restclients/ - coverage run --source=restclients manage.py test restclients after_script: - coveralls diff --git a/restclients/amazon_sqs.py b/restclients/amazon_sqs.py index 8b004649..68998242 100644 --- a/restclients/amazon_sqs.py +++ b/restclients/amazon_sqs.py @@ -5,6 +5,7 @@ from boto.sqs.message import RawMessage from django.conf import settings + class AmazonSQS(object): """ The AmazonSQS class has methods for getting/creating queues. diff --git a/restclients/bookstore.py b/restclients/bookstore.py index 23025cea..3f551b5b 100644 --- a/restclients/bookstore.py +++ b/restclients/bookstore.py @@ -9,33 +9,32 @@ import re +BOOK_PREFIX = "http://uw-seattle.verbacompare.com/m?section_id=" + + class Bookstore(object): """ Get book information for courses. """ - - def get_books_for_schedule(self, schedule): - """ - Returns a dictionary of data. SLNs are the keys, an array of Book - objects are the values. - """ + def get_books_by_quarter_sln(self, quarter, sln): dao = Book_DAO() - - url = self.get_books_url(schedule) - + sln_string = self._get_sln_string(sln) + url = "/myuw/myuw_mobile_beta.ubs?quarter=%s&%s" % ( + quarter, + sln_string, + ) response = dao.getURL(url, {"Accept": "application/json"}) if response.status != 200: raise DataFailureException(url, response.status, response.data) data = json.loads(response.data) - response = {} + books = [] - for section in schedule.sections: - response[section.sln] = [] - try: - sln_data = data[section.sln] - for book_data in sln_data: + sln_data = data[str(sln)] + + if len(sln_data) > 0: + for book_data in sln_data: book = Book() book.isbn = book_data["isbn"] book.title = book_data["title"] @@ -51,12 +50,28 @@ def get_books_for_schedule(self, schedule): author.name = author_data["name"] book.authors.append(author) - response[section.sln].append(book) - except KeyError as err: - #do nothing if bookstore has no record of book - pass + books.append(book) + return books - return response + def get_books_for_schedule(self, schedule): + """ + Returns a dictionary of data. SLNs are the keys, an array of Book + objects are the values. + """ + slns = self._get_slns(schedule) + + books = {} + + for sln in slns: + try: + section_books = self.get_books_by_quarter_sln( + schedule.term.quarter, sln + ) + books[sln] = section_books + except DataFailureException: + # do nothing if bookstore doesn't have sln + pass + return books def get_verba_link_for_schedule(self, schedule): """ @@ -76,26 +91,34 @@ def get_verba_link_for_schedule(self, schedule): for key in data: if re.match(r'^[A-Z]{2}[0-9]{5}$', key): - return "http://uw-seattle.verbacompare.com/m?section_id=%s&quarter=%s" % (key, schedule.term.quarter) - - def get_books_url(self, schedule): - sln_string = self._get_slns_string(schedule) - url = "/myuw/myuw_mobile_beta.ubs?quarter=%s&%s" % ( - schedule.term.quarter, - sln_string, - ) - - return url + return "%s%s&quarter=%s" % (BOOK_PREFIX, + key, + schedule.term.quarter) def get_verba_url(self, schedule): sln_string = self._get_slns_string(schedule) url = "/myuw/myuw_mobile_v.ubs?quarter=%s&%s" % ( - schedule.term.quarter, - sln_string, - ) + schedule.term.quarter, + sln_string, + ) return url + def _get_sln_string(self, sln): + return "sln1=%s" % sln + + def _get_slns(self, schedule): + slns = [] + # Prevent dupes - mainly for mock data + seen_slns = {} + for section in schedule.sections: + sln = section.sln + if sln not in seen_slns: + seen_slns[sln] = True + slns.append(sln) + + return slns + def _get_slns_string(self, schedule): slns = [] # Prevent dupes - mainly for mock data diff --git a/restclients/cache_implementation.py b/restclients/cache_implementation.py index 45fcdda1..f6aec145 100644 --- a/restclients/cache_implementation.py +++ b/restclients/cache_implementation.py @@ -7,6 +7,13 @@ from datetime import datetime, timedelta from django.utils.timezone import make_aware, get_current_timezone from django.conf import settings +import json +import bmemcached +import logging +import threading + + +logger = logging.getLogger(__name__) class NoCache(object): @@ -28,7 +35,8 @@ class TimedCache(object): def _response_from_cache(self, service, url, headers, max_age_in_seconds, max_error_age=60 * 5): - # If max_age_in_seconds is 0, make sure we don't get a hit from this same second. + # If max_age_in_seconds is 0, + # make sure we don't get a hit from this same second. if not max_age_in_seconds: return None now = make_aware(datetime.now(), get_current_timezone()) @@ -183,3 +191,82 @@ def processResponse(self, service, url, response): store_cache_entry(cache_entry) return + + +class MemcachedCache(object): + """ + Cache resources in memcached. + """ + client = None + + def getCache(self, service, url, headers): + client = self._get_client() + key = self._get_key(service, url) + try: + data = client.get(key) + except bmemcached.exceptions.MemcachedException as ex: + logger.warning("MemCached Err on get with key '%s' ==> '%s'", + key, str(ex)) + return + + if not data: + return + + values = json.loads(data) + response = MockHTTP() + response.status = values["status"] + response.data = values["data"] + response.headers = values["headers"] + + return {"response": response} + + def processResponse(self, service, url, response): + if response.status != 200: + # don't cache errors, at least for now... + return + + header_data = {} + for header in response.headers: + header_data[header] = response.getheader(header) + + data = json.dumps({"status": response.status, + "data": response.data, + "headers": header_data}) + + time_to_store = self.get_cache_expiration_time(service, url) + key = self._get_key(service, url) + + client = self._get_client() + try: + client.set(key, data, time=time_to_store) + logger.info("MemCached set with key '%s', %d seconds", + key, time_to_store) + except bmemcached.exceptions.MemcachedException as ex: + logger.warning("MemCached Err on set with key '%s' ==> '%s'", + key, str(ex)) + return + + def get_cache_expiration_time(self, service, url): + # Over-ride this to define your own. + return 60 * 60 * 4 + + def _get_key(self, service, url): + return "%s-%s" % (service, url) + + def _get_client(self): + thread_id = threading.current_thread().ident + if not hasattr(MemcachedCache, "_memcached_cache"): + MemcachedCache._memcached_cache = {} + + if thread_id in MemcachedCache._memcached_cache: + return MemcachedCache._memcached_cache[thread_id] + + servers = settings.RESTCLIENTS_MEMCACHED_SERVERS + username = getattr(settings, "RESTCLIENTS_MEMCACHED_USER", None) + password = getattr(settings, "RESTCLIENTS_MEMCACHED_PASS", None) + + client = bmemcached.Client(servers, username, password) + + MemcachedCache._memcached_cache[thread_id] = client + + return client diff --git a/restclients/cache_manager.py b/restclients/cache_manager.py index 887c24bf..3ad87d3c 100644 --- a/restclients/cache_manager.py +++ b/restclients/cache_manager.py @@ -4,9 +4,12 @@ innodb gap locks from deadlocking sequential inserts. """ +from django.db import IntegrityError + + __manage_bulk_inserts = False __bulk_insert_queue = [] -from django.db import IntegrityError + def store_cache_entry(entry): global __manage_bulk_inserts @@ -18,6 +21,7 @@ def store_cache_entry(entry): else: entry.save() + def save_all_queued_entries(): global __bulk_insert_queue @@ -26,7 +30,7 @@ def save_all_queued_entries(): try: for entry in __bulk_insert_queue: - if not entry.url in seen_urls: + if entry.url not in seen_urls: entry.save() seen_urls[entry.url] = True except Exception as ex: @@ -34,10 +38,12 @@ def save_all_queued_entries(): __bulk_insert_queue = [] + def enable_cache_entry_queueing(): global __manage_bulk_inserts __manage_bulk_inserts = True + def disable_cache_entry_queueing(): global __manage_bulk_inserts __manage_bulk_inserts = False diff --git a/restclients/canvas/__init__.py b/restclients/canvas/__init__.py index 49bf9ba9..f408c0da 100644 --- a/restclients/canvas/__init__.py +++ b/restclients/canvas/__init__.py @@ -31,8 +31,11 @@ def __init__(self, per_page=DEFAULT_PAGINATION, as_user=MASQUERADING_USER): Prepares for paginated responses """ self._per_page = per_page - self._as_user = as_user - self._re_canvas_id = re.compile(r'^\d+$') + self._re_canvas_id = re.compile(r'^\d{2,12}$') + + if as_user: + self._as_user = as_user if self.valid_canvas_id(as_user) \ + else self.sis_user_id(as_user) def get_courses_for_regid(self, regid): deprecation("Use restclients.canvas.courses.get_courses_for_regid") @@ -49,9 +52,9 @@ def get_term_by_sis_id(self, sis_term_id): Return a term resource for the passed SIS ID. """ params = {"workflow_state": "all", "per_page": 500} - url = "/api/v1/accounts/%s/terms%s" % ( - settings.RESTCLIENTS_CANVAS_ACCOUNT_ID, self._params(params)) - data = self._get_resource(url) + url = "/api/v1/accounts/%s/terms" % ( + settings.RESTCLIENTS_CANVAS_ACCOUNT_ID) + data = self._get_resource(url, params=params) for term in data["enrollment_terms"]: if term["sis_term_id"] == sis_term_id: @@ -75,6 +78,9 @@ def sis_section_id(self, sis_id): def sis_user_id(self, sis_id): return self._sis_id(sis_id, sis_field="user") + def sis_login_id(self, sis_id): + return self._sis_id(sis_id, sis_field="login") + def _sis_id(self, sis_id, sis_field='account'): """ generate sis_id object reference @@ -86,7 +92,7 @@ def _params(self, params): p = [] for key, val in params.iteritems(): if isinstance(val, list): - p.extend([key + '=' + str(v) for v in val]) + p.extend([key + '[]=' + str(v) for v in val]) else: p.append(key + '=' + str(val)) @@ -94,12 +100,6 @@ def _params(self, params): return "" - def _pagination(self, params): - if self._per_page != DEFAULT_PAGINATION: - params["per_page"] = self._per_page - - return params - def _next_page(self, response): """ return url path to next page of paginated data @@ -112,14 +112,11 @@ def _next_page(self, response): except: return - def _get_resource(self, url, data_key=None): + def _get_resource_url(self, url, auto_page, data_key): """ - Canvas GET method. Return representation of the requested resource, - chasing pagination links to coalesce resources. + Canvas GET method on a full url. Return representation of the requested resource, + chasing pagination links to coalesce resources if indicated. """ - if(self._as_user is not None): - url = url + "?as_user_id=" + self.sis_user_id(self._as_user) - response = Canvas_DAO().getURL(url, {"Accept": "application/json"}) if response.status != 200: @@ -127,18 +124,51 @@ def _get_resource(self, url, data_key=None): data = json.loads(response.data) - if isinstance(data, list): - next_page = self._next_page(response) - if next_page: - data.extend(self._get_resource(next_page)) - - elif isinstance(data, dict) and data_key is not None: - next_page = self._next_page(response) - if next_page: - data[data_key].extend(self._get_resource(next_page, data_key=data_key)[data_key]) + self.next_page_url = self._next_page(response) + if auto_page and self.next_page_url: + if isinstance(data, list): + data.extend(self._get_resource_url(self.next_page_url, True, data_key)) + elif isinstance(data, dict) and data_key is not None: + data[data_key].extend(self._get_resource_url( + self.next_page_url, True, data_key)[data_key]) return data + def _set_as_user(self, params): + if 'as_user_id' not in params and hasattr(self, '_as_user'): + params['as_user_id'] = self._as_user + + def _get_paged_resource(self, url, params=None, data_key=None): + """ + Canvas GET method. Return representation of the requested paged resource, + either the requested page, or chase pagination links to coalesce resources. + """ + if not params: + params = {} + + self._set_as_user(params) + + auto_page = not ('page' in params or 'per_page' in params) + + if 'per_page' not in params and self._per_page != DEFAULT_PAGINATION: + params["per_page"] = self._per_page + + full_url = '%s%s' % (url, self._params(params)) + return self._get_resource_url(full_url, auto_page, data_key) + + def _get_resource(self, url, params=None, data_key=None): + """ + Canvas GET method. Return representation of the requested resource. + """ + if not params: + params = {} + + self._set_as_user(params) + + full_url = '%s%s' % (url, self._params(params)) + + return self._get_resource_url(full_url, True, data_key) + def _put_resource(self, url, body): """ Canvas PUT method. diff --git a/restclients/canvas/accounts.py b/restclients/canvas/accounts.py index bfef293c..0b025039 100644 --- a/restclients/canvas/accounts.py +++ b/restclients/canvas/accounts.py @@ -18,18 +18,16 @@ def get_account_by_sis_id(self, sis_id): """ return self.get_account(self._sis_id(sis_id)) - def get_sub_accounts(self, account_id, params): + def get_sub_accounts(self, account_id, params={}): """ Return list of subaccounts within the account with the passed canvas id. https://canvas.instructure.com/doc/api/accounts.html#method.accounts.sub_accounts """ - params = self._pagination(params) - url = "/api/v1/accounts/%s/sub_accounts%s" % (account_id, - self._params(params)) + url = "/api/v1/accounts/%s/sub_accounts" % (account_id) accounts = [] - for datum in self._get_resource(url): + for datum in self._get_paged_resource(url, params=params): accounts.append(self._account_from_json(datum)) return accounts diff --git a/restclients/canvas/admins.py b/restclients/canvas/admins.py index c5b79672..d290c619 100644 --- a/restclients/canvas/admins.py +++ b/restclients/canvas/admins.py @@ -7,18 +7,16 @@ class Admins(Canvas): - def get_admins(self, account_id): + def get_admins(self, account_id, params={}): """ Return a list of the admins in the account. https://canvas.instructure.com/doc/api/admins.html#method.admins.index """ - params = self._pagination({}) - url = "/api/v1/accounts/%s/admins%s" % (account_id, - self._params(params)) + url = "/api/v1/accounts/%s/admins" % (account_id) admins = [] - for data in self._get_resource(url): + for data in self._get_paged_resource(url, params=params): admins.append(self._admin_from_json(data)) return admins diff --git a/restclients/canvas/authentications.py b/restclients/canvas/authentications.py index 0b12063b..8aa624ba 100644 --- a/restclients/canvas/authentications.py +++ b/restclients/canvas/authentications.py @@ -2,7 +2,11 @@ class Authentications(Canvas): def get_authentication_count_for_sis_login_id_from_start_date(self, sis_login_id, start_date): - url = "/api/v1/audit/authentication/users/sis_login_id:%s?start_time=%s" % (sis_login_id, start_date) - data = self._get_resource(url) + url = "/api/v1/audit/authentication/users/sis_login_id:%s" % (sis_login_id) + params = { + 'start_time': start_date + } + + data = self._get_resource(url, params=params) return len(data["events"]) diff --git a/restclients/canvas/conversations.py b/restclients/canvas/conversations.py index e2d3e722..2bfe66ed 100644 --- a/restclients/canvas/conversations.py +++ b/restclients/canvas/conversations.py @@ -2,8 +2,14 @@ class Conversations(Canvas): def get_conversation_ids_for_sis_login_id(self, sis_login_id): - url = "/api/v1/conversations?as_user_id=sis_login_id:%s&include_all_conversation_ids=true" % (sis_login_id) - data = self._get_resource(url) + url = "/api/v1/conversations" + + params = { + "as_user_id": self.sis_login_id(sis_login_id), + "include_all_conversation_ids": "true" + } + + data = self._get_resource(url, params=params) conversation_ids = [] for conversation_id in data["conversation_ids"]: conversation_ids.append(conversation_id) @@ -11,5 +17,9 @@ def get_conversation_ids_for_sis_login_id(self, sis_login_id): return conversation_ids def get_data_for_conversation_id_as_sis_login_id(self, conversation_id, sis_login_id): - url = "/api/v1/conversations/%s?as_user_id=sis_login_id:%s" % (conversation_id, sis_login_id) - return self._get_resource(url) + url = "/api/v1/conversations/%s" % (conversation_id) + params = { + "as_user_id": self.sis_login_id(sis_login_id) + } + + return self._get_resource(url, params=params) diff --git a/restclients/canvas/courses.py b/restclients/canvas/courses.py index d639df9d..9ea28c7d 100644 --- a/restclients/canvas/courses.py +++ b/restclients/canvas/courses.py @@ -10,13 +10,13 @@ def get_course(self, course_id, params={}): https://canvas.instructure.com/doc/api/courses.html#method.courses.show """ - include = params.get("include", None) - if include is None: - include = "term" + include = params.get("include", []) + if "term" not in include: + include.append("term") params["include"] = include - url = "/api/v1/courses/%s%s" % (course_id, self._params(params)) - return self._course_from_json(self._get_resource(url)) + url = "/api/v1/courses/%s" % (course_id) + return self._course_from_json(self._get_resource(url, params=params)) def get_course_by_sis_id(self, sis_course_id, params={}): """ @@ -34,11 +34,10 @@ def get_courses_in_account(self, account_id, params={}): if "published" in params: params["published"] = "true" if params["published"] else "" - params = self._pagination(params) - url = "/api/v1/accounts/%s/courses%s" % (account_id, - self._params(params)) + url = "/api/v1/accounts/%s/courses" % (account_id) + courses = [] - for data in self._get_resource(url): + for data in self._get_paged_resource(url, params=params): courses.append(self._course_from_json(data)) return courses @@ -73,8 +72,7 @@ def get_courses_for_regid(self, regid, params={}): """ params["as_user_id"] = self._sis_id(regid, sis_field="user") - url = "/api/v1/courses%s" % self._params(params) - data = self._get_resource(url) + data = self._get_resource("/api/v1/courses", params=params) del params["as_user_id"] courses = [] @@ -99,6 +97,19 @@ def create_course(self, account_id, course_name): return self._course_from_json(data) + def update_sis_id(self, course_id, sis_course_id): + """ + Updates the SIS ID for the course identified by the passed course ID. + + https://canvas.instructure.com/doc/api/courses.html#method.courses.update + """ + url = "/api/v1/courses/%s" % course_id + body = {"course": {"sis_course_id": sis_course_id}} + + data = self._put_resource(url, body) + + return self._course_from_json(data) + def _course_from_json(self, data): course = CanvasCourse() course.course_id = data["id"] diff --git a/restclients/canvas/enrollments.py b/restclients/canvas/enrollments.py index 70b7a950..887561f5 100644 --- a/restclients/canvas/enrollments.py +++ b/restclients/canvas/enrollments.py @@ -12,11 +12,10 @@ def get_enrollments_for_course(self, course_id, params={}): https://canvas.instructure.com/doc/api/enrollments.html#method.enrollments_api.index """ - url = "/api/v1/courses/%s/enrollments%s" % (course_id, - self._params(params)) + url = "/api/v1/courses/%s/enrollments" % (course_id) enrollments = [] - for datum in self._get_resource(url): + for datum in self._get_paged_resource(url, params=params): enrollment = self._enrollment_from_json(datum) enrollments.append(enrollment) @@ -35,11 +34,10 @@ def get_enrollments_for_section(self, section_id, params={}): https://canvas.instructure.com/doc/api/enrollments.html#method.enrollments_api.index """ - url = "/api/v1/sections/%s/enrollments%s" % (section_id, - self._params(params)) + url = "/api/v1/sections/%s/enrollments" % (section_id) enrollments = [] - for datum in self._get_resource(url): + for datum in self._get_paged_resource(url, params=params): enrollment = self._enrollment_from_json(datum) enrollments.append(enrollment) @@ -58,19 +56,21 @@ def get_enrollments_for_regid(self, regid, params={}): https://canvas.instructure.com/doc/api/enrollments.html#method.enrollments_api.index """ - url = "/api/v1/users/%s/enrollments%s" % ( - self._sis_id(regid, sis_field="user"), - self._params(params)) + url = "/api/v1/users/%s/enrollments" % ( + self._sis_id(regid, sis_field="user")) courses = Courses() enrollments = [] - for datum in self._get_resource(url): + for datum in self._get_paged_resource(url, params=params): course_id = datum["course_id"] course = courses.get_course(course_id) if course.sis_course_id is not None: enrollment = self._enrollment_from_json(datum) + enrollment.course = course + # the following 3 lines are not removed + # to be backward compatible. enrollment.course_url = course.course_url enrollment.course_name = course.name enrollment.sis_course_id = course.sis_course_id @@ -78,20 +78,40 @@ def get_enrollments_for_regid(self, regid, params={}): return enrollments - def enroll_user_in_course(self, course_id, user_id, role, status="active"): + def enroll_user(self, course_id, user_id, enrollment_type, params=None): """ Enroll a user into a course. https://canvas.instructure.com/doc/api/enrollments.html#method.enrollments_api.create """ url = "/api/v1/courses/%s/enrollments" % course_id - body = {"enrollment": {"user_id": user_id, - "type": role, - "enrollment_state": status}} - data = self._post_resource(url, body) + if not params: + params = {} + + params["user_id"] = user_id + params["type"] = enrollment_type + + data = self._post_resource(url, {"enrollment": params}) return self._enrollment_from_json(data) + def enroll_user_in_course(self, course_id, user_id, enrollment_type, + course_section_id=None, role_id=None, + status="active"): + params = { + "user_id": user_id, + "type": enrollment_type, + "enrollment_state": status + } + + if course_section_id: + params['course_section_id'] = course_section_id + + if role_id: + params['role_id'] = role_id + + return self.enroll_user(course_id, user_id, enrollment_type, params) + def _enrollment_from_json(self, data): enrollment = CanvasEnrollment() enrollment.user_id = data["user_id"] @@ -101,21 +121,27 @@ def _enrollment_from_json(self, data): enrollment.status = data["enrollment_state"] enrollment.html_url = data["html_url"] enrollment.total_activity_time = data["total_activity_time"] + enrollment.limit_privileges_to_course_section = data.get( + "limit_privileges_to_course_section", False) if data["last_activity_at"] is not None: date_str = data["last_activity_at"] enrollment.last_activity_at = dateutil.parser.parse(date_str) - if "sis_section_id" in data: - enrollment.sis_section_id = data["sis_section_id"] + + enrollment.sis_course_id = data.get("sis_course_id", None) + enrollment.sis_section_id = data.get("sis_section_id", None) + if "user" in data: - enrollment.name = data["user"]["name"] - if "login_id" in data["user"]: - enrollment.login_id = data["user"]["login_id"] - if "sis_user_id" in data["user"]: - enrollment.sis_user_id = data["user"]["sis_user_id"] + user_data = data["user"] + enrollment.name = user_data.get("name", None) + enrollment.sortable_name = user_data.get("sortable_name", None) + enrollment.login_id = user_data.get("login_id", None) + enrollment.sis_user_id = user_data.get("sis_user_id", None) + if "grades" in data: - enrollment.current_score = data["grades"]["current_score"] - enrollment.final_score = data["grades"]["final_score"] - enrollment.current_grade = data["grades"]["current_grade"] - enrollment.final_grade = data["grades"]["final_grade"] - enrollment.grade_html_url = data["grades"]["html_url"] + grade_data = data["grades"] + enrollment.current_score = grade_data.get("current_score", None) + enrollment.final_score = grade_data.get("final_score", None) + enrollment.current_grade = grade_data.get("current_grade", None) + enrollment.final_grade = grade_data.get("final_grade", None) + enrollment.grade_html_url = grade_data.get("html_url", None) return enrollment diff --git a/restclients/canvas/external_tools.py b/restclients/canvas/external_tools.py index b320bae5..65c622c8 100644 --- a/restclients/canvas/external_tools.py +++ b/restclients/canvas/external_tools.py @@ -1,9 +1,12 @@ from restclients.canvas import Canvas -#from restclients.models.canvas import ExternalTool + + +class ExternalToolsException(Exception): + pass class ExternalTools(Canvas): - def get_external_tools_in_account(self, account_id): + def get_external_tools_in_account(self, account_id, params={}): """ Return external tools for the passed canvas account id. @@ -12,7 +15,7 @@ def get_external_tools_in_account(self, account_id): url = "/api/v1/accounts/%s/external_tools" % account_id external_tools = [] - for data in self._get_resource(url): + for data in self._get_paged_resource(url, params=params): external_tools.append(self._external_tool_from_json(data)) return external_tools @@ -23,7 +26,7 @@ def get_external_tools_in_account_by_sis_id(self, sis_id): return self.get_external_tools_in_account(self._sis_id(sis_id, "account")) - def get_external_tools_in_course(self, course_id): + def get_external_tools_in_course(self, course_id, params={}): """ Return external tools for the passed canvas course id. @@ -32,7 +35,7 @@ def get_external_tools_in_course(self, course_id): url = "/api/v1/courses/%s/external_tools" % course_id external_tools = [] - for data in self._get_resource(url): + for data in self._get_paged_resource(url, params=params): external_tools.append(self._external_tool_from_json(data)) return external_tools @@ -43,5 +46,108 @@ def get_external_tools_in_course_by_sis_id(self, sis_id): return self.get_external_tools_in_course(self._sis_id(sis_id, "course")) + def add_external_tool_to_course(self, course_id, **kwargs): + return self._add_external_tool('courses', course_id, **kwargs) + + def add_external_tool_to_course_by_sis_id(self, sis_id, **kwargs): + return self._add_external_tool('courses', + self._sis_id(sis_id, "course"), + **kwargs) + + def add_external_tool_to_account(self, account_id, **kwargs): + return self._add_external_tool('accounts', account_id, **kwargs) + + def add_external_tool_to_account_by_sis_id(self, sis_id, **kwargs): + return self._add_external_tool('accounts', + self._sis_id(sis_id, "account"), + **kwargs) + + def _add_external_tool(self, type, id, **kwargs): + """ + Add the given external tool to the specified course or account + + https://canvas.instructure.com/doc/api/external_tools.html#method.external_tools.create + """ + name = kwargs.get('name'), + privacy_level = kwargs.get('privacy_level') + consumer_key = kwargs.get('consumer_key') + shared_secret = kwargs.get('shared_secret') + if not (name and privacy_level and consumer_key and shared_secret): + raise ExternalToolsException("missing required external tool parameter") + + url = "/api/v1/%s/%s/external_tools" % (type, id) + body = { + "name": kwargs.get('name'), + "privacy_level": kwargs.get('privacy_level'), + "consumer_key": kwargs.get('consumer_key'), + "shared_secret": kwargs.get('shared_secret'), + } + + if kwargs.get('config_xml'): + body['config_type'] = 'by_xml' + body['config_xml'] = kwargs.get('config_xml') + elif kwargs.get('config_url'): + body['config_type'] = 'by_url' + body['config_url'] = kwargs.get('config_url') + else: + params = ['description', 'url', 'domain', 'icon_url', 'text', + 'non_selectable', 'custom_fields', 'account_navigation', + 'user_navigation', 'course_navigation', 'editor_button', + 'resource_selection'] + + for param in params: + if param in kwargs: + body[param] = kwargs.get(param) + + data = self._post_resource(url, body) + return self._external_tool_from_json(data) + def _external_tool_from_json(self, data): return data + + def _get_sessionless_launch_url(self, tool_id, context, context_id): + """ + Get a sessionless launch url for an external tool. + + https://canvas.instructure.com/doc/api/external_tools.html#method.external_tools.generate_sessionless_launch + """ + url = "/api/v1/%ss/%s/external_tools/sessionless_launch" % (context, context_id) + params = { + "id": tool_id + } + + return self._get_resource(url, params) + + def get_sessionless_launch_url_from_account(self, tool_id, account_id): + """ + Get a sessionless launch url for an external tool. + + https://canvas.instructure.com/doc/api/external_tools.html#method.external_tools.generate_sessionless_launch + """ + return self._get_sessionless_launch_url(tool_id, "account", account_id) + + def get_sessionless_launch_url_from_account_sis_id(self, tool_id, account_sis_id): + """ + Get a sessionless launch url for an external tool. + + https://canvas.instructure.com/doc/api/external_tools.html#method.external_tools.generate_sessionless_launch + """ + return self.get_sessionless_launch_url_from_account( + tool_id, self._sis_id(account_sis_id, "account")) + + def get_sessionless_launch_url_from_course(self, tool_id, course_id): + """ + Get a sessionless launch url for an external tool. + + https://canvas.instructure.com/doc/api/external_tools.html#method.external_tools.generate_sessionless_launch + """ + return self._get_sessionless_launch_url(tool_id, "course", course_id) + + def get_sessionless_launch_url_from_course_sis_id(self, tool_id, course_sis_id): + """ + Get a sessionless launch url for an external tool. + + https://canvas.instructure.com/doc/api/external_tools.html#method.external_tools.generate_sessionless_launch + """ + return self.get_sessionless_launch_url_from_course( + tool_id, self._sis_id(course_sis_id, "course")) diff --git a/restclients/canvas/grading_standards.py b/restclients/canvas/grading_standards.py index d4ede983..dc68a494 100644 --- a/restclients/canvas/grading_standards.py +++ b/restclients/canvas/grading_standards.py @@ -3,7 +3,20 @@ class GradingStandards(Canvas): - def create_grading_standard_for_course(self, course_id, name, grading_scheme, creator): + def get_grading_standards_for_course(self, course_id): + """ + List the grading standards available to a course + https://canvas.instructure.com/doc/api/grading_standards.html#method.grading_standards_api.context_index + """ + url = "/api/v1/courses/%s/grading_standards" % course_id + + standards = [] + for data in self._get_resource(url): + standards.append(self._grading_standard_from_json(data)) + return standards + + def create_grading_standard_for_course(self, course_id, name, + grading_scheme, creator): """ Create a new grading standard for the passed course. diff --git a/restclients/canvas/page_views.py b/restclients/canvas/page_views.py index dfc34afa..09f39507 100644 --- a/restclients/canvas/page_views.py +++ b/restclients/canvas/page_views.py @@ -2,7 +2,11 @@ class PageViews(Canvas): def get_pageviews_for_sis_login_id_from_start_date(self, sis_login_id, start_date): - url = "/api/v1/users/sis_login_id:%s/page_views?start_time=%s" % (sis_login_id, start_date) - data = self._get_resource(url) + url = "/api/v1/users/sis_login_id:%s/page_views" % (sis_login_id) + params = { + "start_time": start_date + } + + data = self._get_resource(url, params=params) return data diff --git a/restclients/canvas/roles.py b/restclients/canvas/roles.py index 9577714b..58eeacc3 100644 --- a/restclients/canvas/roles.py +++ b/restclients/canvas/roles.py @@ -4,47 +4,62 @@ class Roles(Canvas): - def get_roles_in_account(self, account_id): + def get_roles_in_account(self, account_id, params={}): """ - List the roles available to an account, for the passed Canvas account ID. + List the roles for an account, for the passed Canvas account ID. https://canvas.instructure.com/doc/api/roles.html#method.role_overrides.api_index """ - url = "/api/v1/accounts/%s/roles" % account_id + url = "/api/v1/accounts/%s/roles" % (account_id) roles = [] - for datum in self._get_resource(url): + for datum in self._get_resource(url, params=params): roles.append(self._role_from_json(datum)) return roles - def get_roles_by_account_sis_id(self, account_sis_id): + def get_roles_by_account_sis_id(self, account_sis_id, params={}): """ - List the roles available to an account, for the passed account SIS ID. + List the roles for an account, for the passed account SIS ID. """ return self.get_roles_in_account(self._sis_id(account_sis_id, - sis_field="account")) + sis_field="account"), + params) - def get_role(self, account_id, role): + def get_effective_course_roles_in_account(self, account_id): + """ + List all course roles available to an account, for the passed Canvas + account ID, including course roles inherited from parent accounts. + """ + course_roles = [] + params = {"show_inherited": "1"} + for role in self.get_roles_in_account(account_id, params): + if role.base_role_type != "AccountMembership": + course_roles.append(role) + return course_roles + + def get_role(self, account_id, role_id): """ Get information about a single role, for the passed Canvas account ID. https://canvas.instructure.com/doc/api/roles.html#method.role_overrides.show """ - url = "/api/v1/accounts/%s/roles/%s" % (account_id, quote(role)) + url = "/api/v1/accounts/%s/roles/%s" % (account_id, role_id) return self._role_from_json(self._get_resource(url)) - def get_role_by_account_sis_id(self, account_sis_id, role): + def get_role_by_account_sis_id(self, account_sis_id, role_id): """ Get information about a single role, for the passed account SIS ID. """ return self.get_role(self._sis_id(account_sis_id, sis_field="account"), - role) + role_id) def _role_from_json(self, data): role = CanvasRole() - role.role = data["role"] + role.role_id = data["id"] + role.label = data["label"] role.base_role_type = data["base_role_type"] role.workflow_state = data["workflow_state"] role.permissions = data.get("permissions", {}) - role.account = CanvasAccount(data["account"]) + if "account" in data: + role.account = CanvasAccount(data["account"]) return role diff --git a/restclients/canvas/sections.py b/restclients/canvas/sections.py index b7bb8f48..b05dc73f 100644 --- a/restclients/canvas/sections.py +++ b/restclients/canvas/sections.py @@ -10,8 +10,8 @@ def get_section(self, section_id, params={}): https://canvas.instructure.com/doc/api/sections.html#method.sections.show """ - url = "/api/v1/sections/%s%s" % (section_id, self._params(params)) - return self._section_from_json(self._get_resource(url)) + url = "/api/v1/sections/%s" % (section_id) + return self._section_from_json(self._get_resource(url, params=params)) def get_section_by_sis_id(self, sis_section_id, params={}): """ @@ -26,11 +26,10 @@ def get_sections_in_course(self, course_id, params={}): https://canvas.instructure.com/doc/api/sections.html#method.sections.index """ - params = self._pagination(params) - url = "/api/v1/courses/%s/sections%s" % (course_id, self._params(params)) + url = "/api/v1/courses/%s/sections" % (course_id) sections = [] - for data in self._get_resource(url): + for data in self._get_paged_resource(url, params=params): sections.append(self._section_from_json(data)) return sections @@ -46,12 +45,10 @@ def get_sections_with_students_in_course(self, course_id, params={}): """ Return list of sections including students for the passed course ID. """ - if "include" in params and params["include"] is not None: - includes = params["include"].split(",") - if "student" not in includes: - params["include"] = ",".join(includes.append("students")) - else: - params["include"] = "students" + include = params.get("include", []) + if "students" not in include: + include.append("students") + params["include"] = include return self.get_sections_in_course(course_id, params) @@ -76,6 +73,24 @@ def create_section(self, course_id, name, sis_section_id): data = self._post_resource(url, body) return self._section_from_json(data) + def update_section(self, section_id, name, sis_section_id): + """ + Update a canvas section with the given section id. + + https://canvas.instructure.com/doc/api/sections.html#method.sections.update + """ + url = "/api/v1/sections/%s" % section_id + body = {"course_section": {}} + + if name: + body["course_section"]["name"] = name + + if sis_section_id: + body["course_section"]["sis_section_id"] = sis_section_id + + data = self._put_resource(url, body) + return self._section_from_json(data) + def _section_from_json(self, data): section = CanvasSection() section.section_id = data["id"] @@ -88,7 +103,7 @@ def _section_from_json(self, data): users = Users() section.students = [] for student_data in data["students"]: - user = users._user_from_json(data) + user = users._user_from_json(student_data) section.students.append(user) return section diff --git a/restclients/canvas/submissions.py b/restclients/canvas/submissions.py index 7fbed991..03cde049 100644 --- a/restclients/canvas/submissions.py +++ b/restclients/canvas/submissions.py @@ -30,14 +30,14 @@ def get_submissions_multiple_assignments(self, is_section, course_id, students=N course_type = "courses" if is_section: course_type = "sections" - params = "" + params = {} if students is not None: - params = "?student_ids=%s" % students + params["student_ids"] = students if assignments is not None: - params = "%s&assignments=%s" % (params, assignments) + params["assignments"] = assignments - url = "/api/v1/%s/%s/students/submissions%s" % (course_type, course_id, params) - data = self._get_resource(url) + url = "/api/v1/%s/%s/students/submissions" % (course_type, course_id) + data = self._get_resource(url, params=params) submissions = [] for submission in data: sub = self._submission_from_json(submission) diff --git a/restclients/canvas/users.py b/restclients/canvas/users.py index 3dac06a2..91dd76a3 100644 --- a/restclients/canvas/users.py +++ b/restclients/canvas/users.py @@ -1,5 +1,6 @@ from django.conf import settings from restclients.canvas import Canvas +from restclients.canvas.enrollments import Enrollments from restclients.models.canvas import CanvasUser, Login @@ -16,21 +17,29 @@ def get_user(self, user_id): def get_user_by_sis_id(self, sis_user_id): """ Returns user profile data for the passed user sis id. + + https://canvas.instructure.com/doc/api/courses.html#method.courses.users """ return self.get_user(self._sis_id(sis_user_id, sis_field="user")) - def get_users_for_sis_course_id(self, sis_course_id): + def get_users_for_course(self, course_id, params={}): """ - Returns a list of users for the given sis course id. + Returns a list of users for the given course id. """ - url = "/api/v1/courses/%s/users" % (self._sis_id(sis_course_id, sis_field="course")) - data = self._get_resource(url) + url = "/api/v1/courses/%s/users" % course_id + data = self._get_paged_resource(url, params=params) users = [] - for entry in data: - users.append(self._user_from_json(entry)) - + for datum in data: + users.append(self._user_from_json(datum)) return users + def get_users_for_sis_course_id(self, sis_course_id, params={}): + """ + Returns a list of users for the given sis course id. + """ + return self.get_users_for_course( + self._sis_id(sis_course_id, sis_field="course"), params) + def create_user(self, user, account_id=None): """ Create and return a new user and pseudonym for an account. @@ -45,7 +54,7 @@ def create_user(self, user, account_id=None): data = self._post_resource(url, user.post_data()) return self._user_from_json(data) - def get_user_logins(self, user_id): + def get_user_logins(self, user_id, params={}): """ Return a user's logins for the given user_id. @@ -53,7 +62,7 @@ def get_user_logins(self, user_id): """ url = "/api/v1/users/%s/logins" % user_id - data = self._get_resource(url) + data = self._get_paged_resource(url, params=params) logins = [] for login_data in data: @@ -93,6 +102,11 @@ def _user_from_json(self, data): user.time_zone = data["time_zone"] if "time_zone" in data else None user.locale = data["locale"] if "locale" in data else None user.avatar_url = data["avatar_url"] if "avatar_url" in data else None + if "enrollments" in data: + enrollments = Enrollments() + user.enrollments = [] + for enr_datum in data["enrollments"]: + user.enrollments.append(enrollments._enrollment_from_json(enr_datum)) return user def _login_from_json(self, data): diff --git a/restclients/dao.py b/restclients/dao.py index 966b37d9..b9bce139 100644 --- a/restclients/dao.py +++ b/restclients/dao.py @@ -1,10 +1,15 @@ -from django.utils.importlib import import_module +try: + from importlib import import_module +except: + # python 2.6 + from django.utils.importlib import import_module from django.conf import settings from django.core.exceptions import * from restclients.dao_implementation.pws import File as PWSFile from restclients.dao_implementation.sws import File as SWSFile from restclients.dao_implementation.gws import File as GWSFile from restclients.dao_implementation.irws import File as IRWSFile +from restclients.dao_implementation.kws import File as KWSFile from restclients.dao_implementation.book import File as BookFile from restclients.dao_implementation.canvas import File as CanvasFile from restclients.dao_implementation.catalyst import File as CatalystFile @@ -16,7 +21,11 @@ from restclients.dao_implementation.trumba import FileTac from restclients.dao_implementation.trumba import CalendarFile from restclients.dao_implementation.digitlib import File as DigitlibFile -from restclients.dao_implementation.libraries import File as LibrariesFile +from restclients.dao_implementation.grad import File as GradFile +from restclients.dao_implementation.library.mylibinfo import ( + File as MyLibInfoFile) +from restclients.dao_implementation.library.currics import ( + File as LibCurricsFile) from restclients.dao_implementation.myplan import File as MyPlanFile from restclients.dao_implementation.hfs import File as HfsFile from restclients.dao_implementation.uwnetid import File as UwnetidFile @@ -39,7 +48,7 @@ def _getModule(self, settings_key, default_class): config_module = getattr(mod, attr) except AttributeError: raise ImproperlyConfigured('Module "%s" does not define a ' - '"%s" class' % (module, attr)) + '"%s" class' % (module, attr)) return config_module() else: return default_class() @@ -53,7 +62,7 @@ def _getURL(self, service, url, headers): dao = self._getDAO() cache = self._getCache() cache_response = cache.getCache(service, url, headers) - if cache_response != None: + if cache_response is not None: if "response" in cache_response: return cache_response["response"] if "headers" in cache_response: @@ -63,7 +72,7 @@ def _getURL(self, service, url, headers): cache_post_response = cache.processResponse(service, url, response) - if cache_post_response != None: + if cache_post_response is not None: if "response" in cache_post_response: return cache_post_response["response"] @@ -91,7 +100,7 @@ def _getURL(self, service, url, headers, subdomain): cache = self._getCache() cache_url = subdomain + url cache_response = cache.getCache(service, cache_url, headers) - if cache_response != None: + if cache_response is not None: if "response" in cache_response: return cache_response["response"] if "headers" in cache_response: @@ -99,9 +108,11 @@ def _getURL(self, service, url, headers, subdomain): response = dao.getURL(url, headers, subdomain) - cache_post_response = cache.processResponse(service, cache_url, response) + cache_post_response = cache.processResponse(service, + cache_url, + response) - if cache_post_response != None: + if cache_post_response is not None: if "response" in cache_post_response: return cache_post_response["response"] @@ -127,6 +138,14 @@ def _getDAO(self): return self._getModule('RESTCLIENTS_PWS_DAO_CLASS', PWSFile) +class KWS_DAO(MY_DAO): + def getURL(self, url, headers): + return self._getURL('kws', url, headers) + + def _getDAO(self): + return self._getModule('RESTCLIENTS_KWS_DAO_CLASS', KWSFile) + + class GWS_DAO(MY_DAO): def getURL(self, url, headers): return self._getURL('gws', url, headers) @@ -196,6 +215,14 @@ def _getDAO(self): return self._getModule('RESTCLIENTS_DIGITLIB_DAO_CLASS', DigitlibFile) +class Grad_DAO(MY_DAO): + def getURL(self, url, headers): + return self._getURL('grad', url, headers) + + def _getDAO(self): + return self._getModule('RESTCLIENTS_GRAD_DAO_CLASS', GradFile) + + class R25_DAO(MY_DAO): def getURL(self, url, headers): return self._getURL('r25', url, headers) @@ -256,12 +283,22 @@ def _getDAO(self): return self._getModule('RESTCLIENTS_HFS_DAO_CLASS', HfsFile) -class Libraries_DAO(MY_DAO): +class MyLibInfo_DAO(MY_DAO): def getURL(self, url, headers): return self._getURL('libraries', url, headers) def _getDAO(self): - return self._getModule('RESTCLIENTS_LIBRARIES_DAO_CLASS', LibrariesFile) + return self._getModule('RESTCLIENTS_LIBRARIES_DAO_CLASS', + MyLibInfoFile) + + +class LibCurrics_DAO(MY_DAO): + def getURL(self, url, headers): + return self._getURL('libcurrics', url, headers) + + def _getDAO(self): + return self._getModule('RESTCLIENTS_LIBCURRICS_DAO_CLASS', + LibCurricsFile) class MyPlan_DAO(MY_DAO): @@ -279,6 +316,7 @@ def getURL(self, url, headers): def _getDAO(self): return self._getModule('RESTCLIENTS_UWNETID_DAO_CLASS', UwnetidFile) + class TrumbaCalendar_DAO(MY_DAO): def getURL(self, url, headers=None): return self._getURL('calendar', url, headers) @@ -300,6 +338,7 @@ def _getDAO(self): return self._getModule('RESTCLIENTS_TRUMBA_BOT_DAO_CLASS', FileBot) + class TrumbaSea_DAO(MY_DAO): service_id = FileSea().get_path_prefix() @@ -313,6 +352,7 @@ def _getDAO(self): return self._getModule('RESTCLIENTS_TRUMBA_SEA_DAO_CLASS', FileSea) + class TrumbaTac_DAO(MY_DAO): service_id = FileTac().get_path_prefix() diff --git a/restclients/dao_implementation/amazon_sqs.py b/restclients/dao_implementation/amazon_sqs.py index 59274a0e..c5eef88a 100644 --- a/restclients/dao_implementation/amazon_sqs.py +++ b/restclients/dao_implementation/amazon_sqs.py @@ -13,8 +13,8 @@ class Local(object): This implements a local queue, using django models. """ def create_queue(self, queue_name): - obj, created = MockAmazonSQSQueue.objects.get_or_create(name=queue_name) - + obj, created = MockAmazonSQSQueue.objects.get_or_create( + name=queue_name) return obj def get_queue(self, queue_name): diff --git a/restclients/dao_implementation/book.py b/restclients/dao_implementation/book.py index 26bf2c6e..17fb171c 100644 --- a/restclients/dao_implementation/book.py +++ b/restclients/dao_implementation/book.py @@ -4,6 +4,8 @@ from restclients.dao_implementation.live import get_con_pool, get_live_url from restclients.dao_implementation.mock import get_mockdata_url +from django.conf import settings + class File(object): """ @@ -15,6 +17,7 @@ class File(object): def getURL(self, url, headers): return get_mockdata_url("book", "file", url, headers) + class Live(object): """ This DAO provides real data. @@ -23,9 +26,18 @@ class Live(object): pool = None def getURL(self, url, headers): - host = 'http://www3.bookstore.washington.edu/' - if Live.pool == None: - Live.pool = get_con_pool(host, None, None) - return get_live_url (Live.pool, 'GET', - host, url, headers=headers, - service_name='book') + host = getattr(settings, + "RESTCLIENTS_BOOKSTORE_HOST", + 'http://www3.bookstore.washington.edu/') + if Live.pool is None: + cert = getattr(settings, "RESTCLIENTS_BOOKSTORE_CERT", None) + key = getattr(settings, "RESTCLIENTS_BOOKSTORE_KEY", None) + Live.pool = get_con_pool(host, key, cert) + + # For rest router... + url_prefix = getattr(settings, "RESTCLIENTS_BOOKSTORE_PREFIX", "") + url = "%s%s" % (url_prefix, url) + + return get_live_url(Live.pool, 'GET', + host, url, headers=headers, + service_name='book') diff --git a/restclients/dao_implementation/canvas.py b/restclients/dao_implementation/canvas.py index 9e90aaaf..2dc275e7 100644 --- a/restclients/dao_implementation/canvas.py +++ b/restclients/dao_implementation/canvas.py @@ -3,8 +3,8 @@ """ from restclients.dao_implementation.live import get_con_pool, get_live_url -from restclients.dao_implementation.mock import get_mockdata_url, post_mockdata_url -from restclients.dao_implementation.mock import delete_mockdata_url, put_mockdata_url +from restclients.dao_implementation.mock import get_mockdata_url, \ + post_mockdata_url, delete_mockdata_url, put_mockdata_url from django.conf import settings from os.path import abspath, dirname @@ -64,7 +64,9 @@ class Live(object): RESTCLIENTS_CANVAS_OAUTH_BEARER="..." """ pool = None - ignore_security = getattr(settings, 'RESTCLIENTS_CANVAS_IGNORE_CA_SECURITY', False) + ignore_security = getattr(settings, + 'RESTCLIENTS_CANVAS_IGNORE_CA_SECURITY', + False) verify_https = True if ignore_security: @@ -76,7 +78,7 @@ def getURL(self, url, headers): headers["Authorization"] = "Bearer %s" % bearer_key - if Live.pool == None: + if Live.pool is None: Live.pool = self._get_pool() return get_live_url(Live.pool, 'GET', host, url, headers=headers, @@ -88,7 +90,7 @@ def putURL(self, url, headers, body): headers["Authorization"] = "Bearer %s" % bearer_key - if Live.pool == None: + if Live.pool is None: Live.pool = self._get_pool() return get_live_url(Live.pool, 'PUT', host, url, headers=headers, body=body, @@ -100,7 +102,7 @@ def postURL(self, url, headers, body): headers["Authorization"] = "Bearer %s" % bearer_key - if Live.pool == None: + if Live.pool is None: Live.pool = self._get_pool() return get_live_url(Live.pool, 'POST', host, url, headers=headers, body=body, @@ -112,7 +114,7 @@ def deleteURL(self, url, headers): headers["Authorization"] = "Bearer %s" % bearer_key - if Live.pool == None: + if Live.pool is None: Live.pool = self._get_pool() return get_live_url(Live.pool, 'DELETE', host, url, headers=headers, diff --git a/restclients/dao_implementation/catalyst.py b/restclients/dao_implementation/catalyst.py index 08fcba5a..6d49a266 100644 --- a/restclients/dao_implementation/catalyst.py +++ b/restclients/dao_implementation/catalyst.py @@ -16,7 +16,8 @@ class File(object): The File DAO implementation returns generally static content. Use this DAO with this configuration: - RESTCLIENTS_CANVAS_DAO_CLASS = 'restclients.dao_implementation.catalyst.File' + RESTCLIENTS_CANVAS_DAO_CLASS = + 'restclients.dao_implementation.catalyst.File' """ def getURL(self, url, headers): return get_mockdata_url("catalyst", "file", url, headers) @@ -46,12 +47,12 @@ def getURL(self, url, headers): host = settings.RESTCLIENTS_CATALYST_HOST if hasattr(settings, "RESTCLIENTS_CATALYST_CERT_FILE"): - Live.pool = get_con_pool(host, - settings.RESTCLIENTS_CATALYST_KEY_FILE, - settings.RESTCLIENTS_CATALYST_CERT_FILE) + Live.pool = get_con_pool(host, + settings.RESTCLIENTS_CATALYST_KEY_FILE, + settings.RESTCLIENTS_CATALYST_CERT_FILE) else: - Live.pool = get_con_pool(host) + Live.pool = get_con_pool(host) if hasattr(settings, "RESTCLIENTS_CATALYST_SOL_AUTH_PRIVATE_KEY"): # Use js_rest instead of rest, to avoid certificate issues diff --git a/restclients/dao_implementation/digitlib.py b/restclients/dao_implementation/digitlib.py index 9d548bc9..02b560b0 100644 --- a/restclients/dao_implementation/digitlib.py +++ b/restclients/dao_implementation/digitlib.py @@ -6,8 +6,8 @@ import logging from django.conf import settings from restclients.dao_implementation.live import get_con_pool, get_live_url -from restclients.dao_implementation.mock import get_mockdata_url, post_mockdata_url -from restclients.dao_implementation.mock import delete_mockdata_url, put_mockdata_url +from restclients.dao_implementation.mock import get_mockdata_url,\ + post_mockdata_url, delete_mockdata_url, put_mockdata_url from restclients.mock_http import MockHTTP @@ -19,7 +19,8 @@ class File(object): The File DAO implementation returns generally static content. Use this DAO with this configuration: - RESTCLIENTS_DIGITLIB_DAO_CLASS = 'restclients.dao_implementation.digitlib.File' + RESTCLIENTS_DIGITLIB_DAO_CLASS = + 'restclients.dao_implementation.digitlib.File' """ def getURL(self, url, headers): return get_mockdata_url("digitlib", "file", url, headers) @@ -31,7 +32,8 @@ def getURL(self, url, headers): class Live(object): """ - This DAO provides real data. It requires further configuration, e.g. + This DAO provides real data. + It requires further configuration, e.g. RESTCLIENTS_DIGITLIB_HOST RESTCLIENTS_DIGITLIB_CERT_FILE RESTCLIENTS_DIGITLIB_KEY_FILE @@ -47,7 +49,7 @@ def getURL(self, url, headers): settings.RESTCLIENTS_DIGITLIB_CERT_FILE, max_pool_size=DIGITLIB_MAX_POOL_SIZE, socket_timeout=DIGITLIB_SOCKET_TIMEOUT) - redirect = getattr(settings, + redirect = getattr(settings, "RESTCLIENTS_DIGITLIB_REDIRECT", True) return get_live_url(Live.pool, diff --git a/restclients/dao_implementation/grad.py b/restclients/dao_implementation/grad.py new file mode 100644 index 00000000..0a253d26 --- /dev/null +++ b/restclients/dao_implementation/grad.py @@ -0,0 +1,57 @@ +""" +Contains Grad School DAO implementations. +""" + +import re +import logging +from django.conf import settings +from restclients.dao_implementation.live import get_con_pool, get_live_url +from restclients.dao_implementation.mock import get_mockdata_url,\ + post_mockdata_url, delete_mockdata_url, put_mockdata_url +from restclients.mock_http import MockHTTP + + +logger = logging.getLogger(__name__) + + +class File(object): + """ + The File DAO implementation returns generally static content. Use this + DAO with this configuration: + + RESTCLIENTS_GRAD_DAO_CLASS = + 'restclients.dao_implementation.grad.File' + """ + def getURL(self, url, headers): + return get_mockdata_url("grad", "file", url, headers) + + +GRAD_MAX_POOL_SIZE = 10 +GRAD_SOCKET_TIMEOUT = 15 + + +class Live(object): + """ + This DAO provides real data. + It requires further configuration, e.g. + RESTCLIENTS_GRAD_HOST + RESTCLIENTS_GRAD_CERT_FILE + RESTCLIENTS_GRAD_KEY_FILE + RESTCLIENTS_GRAD_REDIRECT + """ + pool = None + + def getURL(self, url, headers): + if Live.pool is None: + Live.pool = get_con_pool( + settings.RESTCLIENTS_GRAD_HOST, + settings.RESTCLIENTS_GRAD_KEY_FILE, + settings.RESTCLIENTS_GRAD_CERT_FILE, + max_pool_size=GRAD_MAX_POOL_SIZE, + socket_timeout=GRAD_SOCKET_TIMEOUT) + return get_live_url(Live.pool, + 'GET', + settings.RESTCLIENTS_GRAD_HOST, + url, + headers=headers, + service_name='grad') diff --git a/restclients/dao_implementation/gws.py b/restclients/dao_implementation/gws.py index 54799076..542d53fe 100644 --- a/restclients/dao_implementation/gws.py +++ b/restclients/dao_implementation/gws.py @@ -1,6 +1,8 @@ """ Contains GWS DAO implementations. """ +import re +from time import time from django.conf import settings from restclients.mock_http import MockHTTP from restclients.dao_implementation.live import get_con_pool, get_live_url @@ -30,6 +32,16 @@ def putURL(self, url, headers, body): response.status = 201 # create response.headers = {"X-Data-Source": "GWS file mock data", "Content-Type": headers["Content-Type"]} + + # insert response.data time values + now = int(round(time() * 1000)) + for t in ['createtime', 'modifytime', 'membermodifytime']: + span = '' % t + if not re.search(r'%s' % span, body): + body = re.sub(r'(
)', + r'\1\n %s%s\n' % (span, now), + body) + response.data = body else: response.status = 400 @@ -84,4 +96,4 @@ def _get_pool(self): return get_con_pool(settings.RESTCLIENTS_GWS_HOST, settings.RESTCLIENTS_GWS_KEY_FILE, settings.RESTCLIENTS_GWS_CERT_FILE, - max_pool_size = GWS_MAX_POOL_SIZE) + max_pool_size=GWS_MAX_POOL_SIZE) diff --git a/restclients/dao_implementation/iasystem.py b/restclients/dao_implementation/iasystem.py index 2339a68c..395ee3d8 100644 --- a/restclients/dao_implementation/iasystem.py +++ b/restclients/dao_implementation/iasystem.py @@ -17,12 +17,17 @@ class File(object): The File DAO implementation returns generally static content. Use this DAO with this configuration: - RESTCLIENTS_IASYSTEM_DAO_CLASS = 'restclients.dao_implementation.iasystem.File' + RESTCLIENTS_IASYSTEM_DAO_CLASS = + 'restclients.dao_implementation.iasystem.File' """ def getURL(self, url, headers, subdomain): return get_mockdata_url("iasystem", subdomain, url, headers) +IAS_MAX_POOL_SIZE = 10 +IAS_SOCKET_TIMEOUT = 5 + + class Live(object): """ This DAO provides real data. It requires further configuration, e.g. @@ -34,16 +39,24 @@ def getURL(self, url, headers, subdomain): host = self._get_host(subdomain) if subdomain not in Live.pool: - Live.pool[subdomain] = get_con_pool(host, + Live.pool[subdomain] = get_con_pool( + host, key_file=settings.RESTCLIENTS_IASYSTEM_KEY_FILE, - cert_file=settings.RESTCLIENTS_IASYSTEM_CERT_FILE) - - return get_live_url(Live.pool[subdomain], "GET", host, url, headers=headers, + cert_file=settings.RESTCLIENTS_IASYSTEM_CERT_FILE, + socket_timeout=getattr(settings, + "RESTCLIENTS_IASYSTEM_SOCKET_TIMEOUT", + IAS_SOCKET_TIMEOUT), + max_pool_size=getattr(settings, + "RESTCLIENTS_IASYSTEM_MAX_POOL_SIZE", + IAS_MAX_POOL_SIZE)) + + return get_live_url(Live.pool[subdomain], "GET", + host, url, headers=headers, service_name="iasystem") def _get_host(self, subdomain): host_setting = settings.RESTCLIENTS_IASYSTEM_HOST if "[subdomain]" not in host_setting: - raise ImproperlyConfigured("Host configuration requires a [subdomain] placeholder") + raise ImproperlyConfigured( + "Host configuration requires a [subdomain] placeholder") return host_setting.replace('[subdomain]', subdomain) - diff --git a/restclients/dao_implementation/irws.py b/restclients/dao_implementation/irws.py index 755629bd..48e17771 100644 --- a/restclients/dao_implementation/irws.py +++ b/restclients/dao_implementation/irws.py @@ -12,11 +12,12 @@ logger = logging.getLogger(__name__) -# This seemed like a good number based on a test using a class w/ 300 students. -# The range 10-50 all did well, so this seemed like the most sociable, high performing -# number to choose +# This seemed like a good number based on a test using a class w/ 300 +# students. The range 10-50 all did well, so this seemed like the +# most sociable, high performing number to choose IRWS_MAX_POOL_SIZE = 10 + class File(object): """ The File DAO implementation returns generally static content. Use this @@ -122,12 +123,12 @@ def postURL(self, url, headers, body): service_name='irws') _pool = None + @property def pool(self): if Live._pool is None: Live._pool = get_con_pool(settings.RESTCLIENTS_IRWS_HOST, - settings.RESTCLIENTS_IRWS_KEY_FILE, - settings.RESTCLIENTS_IRWS_CERT_FILE, - max_pool_size=IRWS_MAX_POOL_SIZE) + settings.RESTCLIENTS_IRWS_KEY_FILE, + settings.RESTCLIENTS_IRWS_CERT_FILE, + max_pool_size=IRWS_MAX_POOL_SIZE) return Live._pool - diff --git a/restclients/dao_implementation/kws.py b/restclients/dao_implementation/kws.py new file mode 100644 index 00000000..4fbdc4c6 --- /dev/null +++ b/restclients/dao_implementation/kws.py @@ -0,0 +1,44 @@ +""" +Contains KWS DAO implementations. +""" + +from django.conf import settings +from restclients.mock_http import MockHTTP +from restclients.dao_implementation.live import get_con_pool, get_live_url +from restclients.dao_implementation.mock import get_mockdata_url + + +KWS_MAX_POOL_SIZE = 10 + + +class File(object): + """ + The File DAO implementation returns generally static content. Use this + DAO with this configuration: + + RESTCLIENTS_KWS_DAO_CLASS = 'restclients.dao_implementation.kws.File' + """ + def getURL(self, url, headers): + return get_mockdata_url('kws', 'file', url, headers) + + +class Live(object): + """ + This DAO provides real data. It requires further configuration, e.g. + + RESTCLIENTS_KWS_CERT_FILE='/path/to/an/authorized/cert.cert', + RESTCLIENTS_KWS_KEY_FILE='/path/to/the/certs_key.key', + RESTCLIENTS_KWS_HOST='https://ucswseval1.cac.washington.edu:443', + """ + pool = None + + def getURL(self, url, headers): + if Live.pool is None: + Live.pool = get_con_pool(settings.RESTCLIENTS_KWS_HOST, + settings.RESTCLIENTS_KWS_KEY_FILE, + settings.RESTCLIENTS_KWS_CERT_FILE, + max_pool_size=KWS_MAX_POOL_SIZE) + return get_live_url(Live.pool, 'GET', + settings.RESTCLIENTS_KWS_HOST, + url, headers=headers, + service_name='kws') diff --git a/restclients/dao_implementation/library/__init__.py b/restclients/dao_implementation/library/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/restclients/dao_implementation/library/currics.py b/restclients/dao_implementation/library/currics.py new file mode 100644 index 00000000..c5e9a7ee --- /dev/null +++ b/restclients/dao_implementation/library/currics.py @@ -0,0 +1,47 @@ +""" +Contains UW Libraries Currics DAO implementations. +""" + +from django.conf import settings +from restclients.dao_implementation.live import get_con_pool, get_live_url +from restclients.dao_implementation.mock import get_mockdata_url,\ + post_mockdata_url, delete_mockdata_url, put_mockdata_url +from restclients.mock_http import MockHTTP +import re + + +class File(object): + """ + The File DAO implementation returns generally static content. Use this + DAO with this configuration: + + RESTCLIENTS_LIBCURRICS_DAO_CLASS = + 'restclients.dao_implementation.libraries.File' + """ + def getURL(self, url, headers): + return get_mockdata_url("libcurrics", "file", url, headers) + + +LIB_MAX_POOL_SIZE = 10 +LIB_SOCKET_TIMEOUT = 15 + + +class Live(object): + """ + This DAO provides real data. It requires further configuration, e.g. + RESTCLIENTS_LIBCURRICS_HOST = '...' + """ + pool = None + + def getURL(self, url, headers): + if Live.pool is None: + Live.pool = get_con_pool( + settings.RESTCLIENTS_LIBCURRICS_HOST, + max_pool_size=LIB_MAX_POOL_SIZE) + + return get_live_url(Live.pool, + 'GET', + settings.RESTCLIENTS_LIBCURRICS_HOST, + url, + headers=headers, + service_name='libcurrics') diff --git a/restclients/dao_implementation/libraries.py b/restclients/dao_implementation/library/mylibinfo.py similarity index 79% rename from restclients/dao_implementation/libraries.py rename to restclients/dao_implementation/library/mylibinfo.py index 293ec633..eb341f33 100644 --- a/restclients/dao_implementation/libraries.py +++ b/restclients/dao_implementation/library/mylibinfo.py @@ -4,8 +4,8 @@ from django.conf import settings from restclients.dao_implementation.live import get_con_pool, get_live_url -from restclients.dao_implementation.mock import get_mockdata_url, post_mockdata_url -from restclients.dao_implementation.mock import delete_mockdata_url, put_mockdata_url +from restclients.dao_implementation.mock import get_mockdata_url,\ + post_mockdata_url, delete_mockdata_url, put_mockdata_url from restclients.mock_http import MockHTTP import re @@ -15,7 +15,8 @@ class File(object): The File DAO implementation returns generally static content. Use this DAO with this configuration: - RESTCLIENTS_LIBRARIES_DAO_CLASS = 'restclients.dao_implementation.libraries.File' + RESTCLIENTS_LIBRARIES_DAO_CLASS = + 'restclients.dao_implementation.libraries.File' """ def getURL(self, url, headers): return get_mockdata_url("libraries", "file", url, headers) @@ -28,7 +29,7 @@ def getURL(self, url, headers): class Live(object): """ This DAO provides real data. It requires further configuration, e.g. - RESTCLIENTS_LIBRARIES_HOST='https://mylibinfo.lib.washington.edu/mylibinfo/v1/' + RESTCLIENTS_LIBRARIES_HOST = '...' """ pool = None @@ -39,6 +40,10 @@ def getURL(self, url, headers): settings.RESTCLIENTS_LIBRARIES_KEY_FILE, settings.RESTCLIENTS_LIBRARIES_CERT_FILE, max_pool_size=LIB_MAX_POOL_SIZE) + + # For rest router... + url_prefix = getattr(settings, "RESTCLIENTS_LIBRARIES_PREFIX", "") + url = "%s%s" % (url_prefix, url) return get_live_url(Live.pool, 'GET', settings.RESTCLIENTS_LIBRARIES_HOST, diff --git a/restclients/dao_implementation/live.py b/restclients/dao_implementation/live.py index e1c0d83b..b54b42de 100644 --- a/restclients/dao_implementation/live.py +++ b/restclients/dao_implementation/live.py @@ -69,7 +69,7 @@ def get_live_url(con_pool, """ timeout = getattr(settings, "RESTCLIENTS_TIMEOUT", con_pool.timeout) start_time = time.time() - response = con_pool.urlopen(method, url, body=body, + response = con_pool.urlopen(method, url, body=body, headers=headers, redirect=redirect, retries=retries, timeout=timeout) request_time = time.time() - start_time diff --git a/restclients/dao_implementation/mock.py b/restclients/dao_implementation/mock.py index de9c5f7a..8becacef 100644 --- a/restclients/dao_implementation/mock.py +++ b/restclients/dao_implementation/mock.py @@ -9,11 +9,16 @@ from urllib import quote, unquote, urlencode from django.conf import settings from django.core.exceptions import ImproperlyConfigured -from django.utils.importlib import import_module +try: + from importlib import import_module +except: + # python 2.6 + from django.utils.importlib import import_module from restclients.signals.rest_request import rest_request from restclients.signals.success import rest_request_passfail from restclients.mock_http import MockHTTP + """ A centralized the mock data access """ @@ -25,6 +30,7 @@ # a function. Otherwise we can be trying to load modules that are trying to # load this code, and python bails on us. + def __initialize_app_resource_dirs(): if len(app_resource_dirs) > 0: return @@ -46,6 +52,7 @@ def __initialize_app_resource_dirs(): else: app_resource_dirs.insert(0, data) + def get_mockdata_url(service_name, implementation_name, url, headers): """ @@ -58,7 +65,6 @@ def get_mockdata_url(service_name, implementation_name, dir_base = dirname(__file__) __initialize_app_resource_dirs() - RESOURCE_ROOT = abspath(dir_base + "/../resources/" + service_name + "/" + implementation_name) @@ -86,7 +92,11 @@ def get_mockdata_url(service_name, implementation_name, # If no response has been found in any installed app, return a 404 logger = logging.getLogger(__name__) - logger.info("404 for url %s, path: %s" % (url, "resources/%s/%s/%s" %(service_name, implementation_name, convert_to_platform_safe(url)))) + logger.info("404 for url %s, path: %s" % + (url, "resources/%s/%s/%s" % + (service_name, + implementation_name, + convert_to_platform_safe(url)))) rest_request_passfail.send(sender='restclients', url=url, success=False, @@ -96,12 +106,14 @@ def get_mockdata_url(service_name, implementation_name, response.status = 404 return response -def _load_resource_from_path(resource_dir, service_name, implementation_name, - url, headers): + +def _load_resource_from_path(resource_dir, service_name, + implementation_name, + url, headers): RESOURCE_ROOT = os.path.join(resource_dir['path'], - service_name, - implementation_name) + service_name, + implementation_name) app = resource_dir['app'] if url == "///": @@ -112,15 +124,15 @@ def _load_resource_from_path(resource_dir, service_name, implementation_name, orig_file_path = RESOURCE_ROOT + url unquoted = unquote(orig_file_path) paths = [ - convert_to_platform_safe(orig_file_path), - "%s/index.html" % (convert_to_platform_safe(orig_file_path)), - orig_file_path, - "%s/index.html" % orig_file_path, - convert_to_platform_safe(unquoted), - "%s/index.html" % (convert_to_platform_safe(unquoted)), - unquoted, - "%s/index.html" % unquoted, - ] + convert_to_platform_safe(orig_file_path), + "%s/index.html" % (convert_to_platform_safe(orig_file_path)), + orig_file_path, + "%s/index.html" % orig_file_path, + convert_to_platform_safe(unquoted), + "%s/index.html" % (convert_to_platform_safe(unquoted)), + unquoted, + "%s/index.html" % unquoted, + ] file_path = None handle = None @@ -141,20 +153,23 @@ def _load_resource_from_path(resource_dir, service_name, implementation_name, response = MockHTTP() response.status = 200 response.data = handle.read() - response.headers = {"X-Data-Source": service_name + " file mock data", } + response.headers = {"X-Data-Source": service_name + " file mock data", + } try: headers = open(handle.name + '.http-headers') file_values = json.loads(headers.read()) if "headers" in file_values: - response.headers = dict(response.headers.items() + file_values['headers'].items()) + response.headers = dict(response.headers.items() + + file_values['headers'].items()) if 'status' in file_values: response.status = file_values['status'] else: - response.headers = dict(response.headers.items() + file_values.items()) + response.headers = dict(response.headers.items() + + file_values.items()) except IOError: pass @@ -162,24 +177,24 @@ def _load_resource_from_path(resource_dir, service_name, implementation_name, return response - def post_mockdata_url(service_name, implementation_name, - url, headers, body, - dir_base = dirname(__file__)): + url, headers, body, + dir_base=dirname(__file__)): """ :param service_name: possible "sws", "pws", "book", "hfs", etc. :param implementation_name: possible values: "file", etc. """ - #Currently this post method does not return a response body + # Currently this post method does not return a response body response = MockHTTP() if body is not None: if "dispatch" in url: response.status = 200 else: response.status = 201 - response.headers = {"X-Data-Source": service_name + " file mock data", "Content-Type": headers['Content-Type']} + response.headers = {"X-Data-Source": service_name + " file mock data", + "Content-Type": headers['Content-Type']} else: response.status = 400 response.data = "Bad Request: no POST body" @@ -188,18 +203,19 @@ def post_mockdata_url(service_name, implementation_name, def put_mockdata_url(service_name, implementation_name, url, headers, body, - dir_base = dirname(__file__)): + dir_base=dirname(__file__)): """ :param service_name: possible "sws", "pws", "book", "hfs", etc. :param implementation_name: possible values: "file", etc. """ - #Currently this put method does not return a response body + # Currently this put method does not return a response body response = MockHTTP() if body is not None: response.status = 204 - response.headers = {"X-Data-Source": service_name + " file mock data", "Content-Type": headers['Content-Type']} + response.headers = {"X-Data-Source": service_name + " file mock data", + "Content-Type": headers['Content-Type']} else: response.status = 400 response.data = "Bad Request: no POST body" @@ -207,24 +223,26 @@ def put_mockdata_url(service_name, implementation_name, def delete_mockdata_url(service_name, implementation_name, - url, headers, - dir_base = dirname(__file__)): + url, headers, + dir_base=dirname(__file__)): """ :param service_name: possible "sws", "pws", "book", "hfs", etc. :param implementation_name: possible values: "file", etc. """ - #Http response code 204 No Content: - #The server has fulfilled the request but does not need to return an entity-body + # Http response code 204 No Content: + # The server has fulfilled the request but does not need to + # return an entity-body response = MockHTTP() response.status = 204 return response + def convert_to_platform_safe(dir_file_name): """ :param dir_file_name: a string to be processed :return: a string with all the reserved characters replaced """ - return re.sub('[\?|<>=:*,;+&"@]', '_', dir_file_name) + return re.sub('[\?|<>=:*,;+&"@]', '_', dir_file_name) diff --git a/restclients/dao_implementation/myplan.py b/restclients/dao_implementation/myplan.py index a4158260..50926e78 100644 --- a/restclients/dao_implementation/myplan.py +++ b/restclients/dao_implementation/myplan.py @@ -4,12 +4,16 @@ from django.conf import settings from restclients.dao_implementation.live import get_con_pool, get_live_url -from restclients.dao_implementation.mock import get_mockdata_url, post_mockdata_url -from restclients.dao_implementation.mock import delete_mockdata_url, put_mockdata_url +from restclients.dao_implementation.mock import get_mockdata_url,\ + post_mockdata_url, delete_mockdata_url, put_mockdata_url from restclients.mock_http import MockHTTP import re +MAX_POOL_SIZE = 10 +HOST = '' # - TBD + + class File(object): """ The File DAO implementation returns generally static content. Use this @@ -21,9 +25,6 @@ def getURL(self, url, headers): return get_mockdata_url("myplan", "file", url, headers) -MAX_POOL_SIZE = 10 -HOST = '' # - TBD - class Live(object): """ This DAO provides real data. diff --git a/restclients/dao_implementation/nws.py b/restclients/dao_implementation/nws.py index befea942..4adf4b98 100644 --- a/restclients/dao_implementation/nws.py +++ b/restclients/dao_implementation/nws.py @@ -4,8 +4,8 @@ from django.conf import settings from restclients.dao_implementation.live import get_con_pool, get_live_url -from restclients.dao_implementation.mock import get_mockdata_url, post_mockdata_url -from restclients.dao_implementation.mock import delete_mockdata_url, put_mockdata_url +from restclients.dao_implementation.mock import get_mockdata_url,\ + post_mockdata_url, delete_mockdata_url, put_mockdata_url from restclients.mock_http import MockHTTP import re @@ -18,7 +18,7 @@ class File(object): RESTCLIENTS_NWS_DAO_CLASS = 'restclients.dao_implementation.nws.File' """ def getURL(self, url, headers): - #Removes expires_after tag in channel search requests + # Removes expires_after tag in channel search requests if "v1/channel?" in url: url = re.sub('&expires_after=[^&]*', '', url) return get_mockdata_url("nws", "file", url, headers) @@ -41,7 +41,7 @@ class Live(object): pool = None def getURL(self, url, headers): - if Live.pool == None: + if Live.pool is None: Live.pool = self._get_pool() return get_live_url(Live.pool, 'GET', settings.RESTCLIENTS_NWS_HOST, @@ -49,7 +49,7 @@ def getURL(self, url, headers): service_name='nws') def deleteURL(self, url, headers): - if Live.pool == None: + if Live.pool is None: Live.pool = self._get_pool() return get_live_url(Live.pool, 'DELETE', settings.RESTCLIENTS_NWS_HOST, @@ -57,7 +57,7 @@ def deleteURL(self, url, headers): service_name='nws') def postURL(self, url, headers, body): - if Live.pool == None: + if Live.pool is None: Live.pool = self._get_pool() return get_live_url(Live.pool, 'POST', settings.RESTCLIENTS_NWS_HOST, @@ -65,21 +65,21 @@ def postURL(self, url, headers, body): service_name='nws') def putURL(self, url, headers, body): - if Live.pool == None: + if Live.pool is None: Live.pool = self._get_pool() return get_live_url(Live.pool, 'PUT', settings.RESTCLIENTS_NWS_HOST, url, headers=headers, body=body, service_name='nws') - def _get_pool(self): nws_key_file = None nws_cert_file = None - max_pool_size = 10 #default values - socket_timeout = 15 #default values + max_pool_size = 10 # default values + socket_timeout = 15 # default values - if settings.RESTCLIENTS_NWS_KEY_FILE and settings.RESTCLIENTS_NWS_CERT_FILE: + if settings.RESTCLIENTS_NWS_KEY_FILE and \ + settings.RESTCLIENTS_NWS_CERT_FILE: nws_key_file = settings.RESTCLIENTS_NWS_KEY_FILE nws_cert_file = settings.RESTCLIENTS_NWS_CERT_FILE @@ -89,7 +89,7 @@ def _get_pool(self): socket_timeout = settings.RESTCLIENTS_NWS_SOCKET_TIMEOUT return get_con_pool(settings.RESTCLIENTS_NWS_HOST, - nws_key_file, - nws_cert_file, - max_pool_size=max_pool_size, - socket_timeout=socket_timeout) + nws_key_file, + nws_cert_file, + max_pool_size=max_pool_size, + socket_timeout=socket_timeout) diff --git a/restclients/dao_implementation/pws.py b/restclients/dao_implementation/pws.py index fbbeac5a..f460abf0 100644 --- a/restclients/dao_implementation/pws.py +++ b/restclients/dao_implementation/pws.py @@ -8,11 +8,11 @@ from restclients.dao_implementation.live import get_con_pool, get_live_url from restclients.dao_implementation.mock import get_mockdata_url -# This seemed like a good number based on a test using a class w/ 300 students. -# The range 10-50 all did well, so this seemed like the most sociable, high performing -# number to choose +# This seemed a good number based on a test using a class w/ 300 students. +# The range 10-50 all did well PWS_MAX_POOL_SIZE = 10 + class File(object): """ The File DAO implementation returns generally static content. Use this @@ -23,6 +23,7 @@ class File(object): def getURL(self, url, headers): return get_mockdata_url("pws", "file", url, headers) + class AlwaysJAverage(object): """ This DAO ensures that all users have javerage's regid @@ -68,11 +69,11 @@ class Live(object): pool = None def getURL(self, url, headers): - if Live.pool == None: + if Live.pool is None: Live.pool = get_con_pool(settings.RESTCLIENTS_PWS_HOST, settings.RESTCLIENTS_PWS_KEY_FILE, settings.RESTCLIENTS_PWS_CERT_FILE, - max_pool_size= PWS_MAX_POOL_SIZE) + max_pool_size=PWS_MAX_POOL_SIZE) return get_live_url(Live.pool, 'GET', settings.RESTCLIENTS_PWS_HOST, url, headers=headers, diff --git a/restclients/dao_implementation/sms.py b/restclients/dao_implementation/sms.py index 31dee455..801594d6 100644 --- a/restclients/dao_implementation/sms.py +++ b/restclients/dao_implementation/sms.py @@ -25,7 +25,7 @@ def create_message(self, to, body): return None def send_message(self, message): - #Mock SMS Response + # Mock SMS Response response = SMSResponse() response.status = "queued" response.body = message.get_body() @@ -34,6 +34,7 @@ def send_message(self, message): return response + class Email(object): """ This implementation sends an email with the content of the SMS, for @@ -46,8 +47,11 @@ def create_message(self, to, body): return message def send_message(self, message): - content = "Phone number: %s\nMessage:\n\n%s" % (message.to, message.body) - email = EmailMessage('SMS Message', content, settings.RESTCLIENTS_SMS_EMAIL_TO) + content = "Phone number: %s\nMessage:\n\n%s" %\ + (message.to, message.body) + email = EmailMessage('SMS Message', + content, + settings.RESTCLIENTS_SMS_EMAIL_TO) email.to = [settings.RESTCLIENTS_SMS_EMAIL_TO] email.send() @@ -57,6 +61,7 @@ def send_message(self, message): response.status = "queued" response.rid = "email_sms" + class Live(object): """ This implements creating a message diff --git a/restclients/dao_implementation/sws.py b/restclients/dao_implementation/sws.py index be802591..910bd154 100644 --- a/restclients/dao_implementation/sws.py +++ b/restclients/dao_implementation/sws.py @@ -41,7 +41,8 @@ def _make_notice_date(self, response): json_data = json.loads(response.data) for notice in json_data["Notices"]: - if notice["NoticeAttributes"] and len(notice["NoticeAttributes"]) > 0: + if notice["NoticeAttributes"] and\ + len(notice["NoticeAttributes"]) > 0: for attr in notice["NoticeAttributes"]: if attr["DataType"] == "date": if attr["Value"] == "yesterday": @@ -59,7 +60,7 @@ def _make_notice_date(self, response): elif attr["Value"] == "week": attr["Value"] = week.strftime("%Y%m%d") else: - attr["Value"] = week.strftime("%Y%m%d") + pass # use original response.data = json.dumps(json_data) @@ -77,10 +78,14 @@ def getURL(self, url, headers): yesterday = now - timedelta(days=1) json_data = json.loads(response.data) - json_data["GradeSubmissionDeadline"] = tomorrow.strftime("%Y-%m-%dT17:00:00") - json_data["GradingPeriodClose"] = tomorrow.strftime("%Y-%m-%dT17:00:00") - json_data["GradingPeriodOpen"] = yesterday.strftime("%Y-%m-%dT17:00:00") - json_data["GradingPeriodOpenATerm"] = yesterday.strftime("%Y-%m-%dT17:00:00") + json_data["GradeSubmissionDeadline"] =\ + tomorrow.strftime("%Y-%m-%dT17:00:00") + json_data["GradingPeriodClose"] =\ + tomorrow.strftime("%Y-%m-%dT17:00:00") + json_data["GradingPeriodOpen"] =\ + yesterday.strftime("%Y-%m-%dT17:00:00") + json_data["GradingPeriodOpenATerm"] =\ + yesterday.strftime("%Y-%m-%dT17:00:00") response.data = json.dumps(json_data) @@ -88,14 +93,16 @@ def getURL(self, url, headers): def putURL(self, url, headers, body): # For developing against crashes in grade submission - if re.match('/student/v\d/graderoster/2013,spring,ZERROR,101,S1,', url): + if re.match('/student/v\d/graderoster/2013,spring,ZERROR,101,S1,', + url): response = MockHTTP() response.data = "No employee found for ID 1234567890" response.status = 500 return response # Submitted too late, sad. - if re.match('/student/v\d/graderoster/2013,spring,ZERROR,101,S2,', url): + if re.match('/student/v\d/graderoster/2013,spring,ZERROR,101,S2,', + url): response = MockHTTP() response.data = "grading period not active for year/quarter" response.status = 404 @@ -120,7 +127,8 @@ def _make_grade_roster_submitted(self, submitted_body): if date_graded.text is None: date_graded.text = '2013-06-01' - grade_submitter_source = item.find('.//*[@class="grade_submitter_source"]') + grade_submitter_source = item.find( + './/*[@class="grade_submitter_source"]') if grade_submitter_source.text is None: grade_submitter_source.text = 'WEBCGB' @@ -129,8 +137,10 @@ def _make_grade_roster_submitted(self, submitted_body): # Use settings.GRADEROSTER_PARTIAL_SUBMISSIONS to simulate failures status_code_text = '200' status_message_text = '' - if (getattr(settings, 'GRADEROSTER_PARTIAL_SUBMISSIONS', False) and - random.choice([True, False])): + if (getattr(settings, + 'GRADEROSTER_PARTIAL_SUBMISSIONS', + False) and random.choice([True, False])): + status_code_text = '500' status_message_text = 'Invalid grade' @@ -153,15 +163,28 @@ class Live(object): """ This DAO provides real data. It requires further configuration, e.g. - RESTCLIENTS_SWS_CERT_FILE='/path/to/an/authorized/cert.cert', - RESTCLIENTS_SWS_KEY_FILE='/path/to/the/certs_key.key', - RESTCLIENTS_SWS_HOST='https://ucswseval1.cac.washington.edu:443', + RESTCLIENTS_SWS_HOST='https://ucswseval1.cac.washington.edu:443' + RESTCLIENTS_SWS_OAUTH_BEARER='...' (read-only access) + + OR + + RESTCLIENTS_SWS_CERT_FILE='/path/to/an/authorized/cert.cert' + RESTCLIENTS_SWS_KEY_FILE='/path/to/the/certs_key.key' """ pool = None def getURL(self, url, headers): + bearer_key = getattr(settings, 'RESTCLIENTS_SWS_OAUTH_BEARER', None) + if bearer_key is not None: + headers["Authorization"] = "Bearer %s" % bearer_key + if Live.pool is None: - Live.pool = self._get_pool() + Live.pool = get_con_pool(settings.RESTCLIENTS_SWS_HOST, + settings.RESTCLIENTS_SWS_KEY_FILE if ( + bearer_key is None) else None, + settings.RESTCLIENTS_SWS_CERT_FILE if ( + bearer_key is None) else None, + max_pool_size=SWS_MAX_POOL_SIZE) return get_live_url(Live.pool, 'GET', settings.RESTCLIENTS_SWS_HOST, @@ -170,19 +193,16 @@ def getURL(self, url, headers): def putURL(self, url, headers, body): if Live.pool is None: - Live.pool = self._get_pool() + Live.pool = get_con_pool(settings.RESTCLIENTS_SWS_HOST, + settings.RESTCLIENTS_SWS_KEY_FILE, + settings.RESTCLIENTS_SWS_CERT_FILE, + max_pool_size=SWS_MAX_POOL_SIZE) return get_live_url(Live.pool, 'PUT', settings.RESTCLIENTS_SWS_HOST, url, headers=headers, body=body, service_name='sws') - def _get_pool(self): - return get_con_pool(settings.RESTCLIENTS_SWS_HOST, - settings.RESTCLIENTS_SWS_KEY_FILE, - settings.RESTCLIENTS_SWS_CERT_FILE, - max_pool_size=SWS_MAX_POOL_SIZE) - # For testing MUWM-2411 class TestBadResponse(File): @@ -190,4 +210,3 @@ def getURL(self, url, headers): if url == "/student/v5/course/2012,summer,PHYS,121/AQ.json": raise Exception("Uh oh!") return super(TestBadResponse, self).getURL(url, headers) - diff --git a/restclients/dao_implementation/trumba.py b/restclients/dao_implementation/trumba.py index 9e6377ad..ba25bf13 100644 --- a/restclients/dao_implementation/trumba.py +++ b/restclients/dao_implementation/trumba.py @@ -7,6 +7,7 @@ from restclients.dao_implementation.mock import get_mockdata_url from restclients.dao_implementation.live import get_con_pool, get_live_url + class CalendarFile(object): """ The File DAO implementation returns generally static content. Use this @@ -29,7 +30,7 @@ class CalendarLive(object): def _get_pool(self): return get_con_pool(CalendarLive.TRUMBA_HOST, - max_pool_size = CalendarLive.MAX_POOL_SIZE) + max_pool_size=CalendarLive.MAX_POOL_SIZE) def getURL(self, url, headers): if CalendarLive.pool is None: @@ -50,13 +51,13 @@ class FileSea(object): RESTCLIENTS_TRUMBA_SEA_DAO_CLASS = 'restclients.dao_implementation.trumba.FileSea' """ - #logger = logging.getLogger('restclients.dao_implementation.trumba.File') + # logger = logging.getLogger('restclients.dao_implementation.trumba.File') def get_path_prefix(self): return "trumba_sea" def getURL(self, url, headers): - #FileSea.logger.info("%s/file%s" % (self.get_path_prefix(), url)) + # FileSea.logger.info("%s/file%s" % (self.get_path_prefix(), url)) return get_mockdata_url( self.get_path_prefix(), "file", url, headers) diff --git a/restclients/dao_implementation/twilio_wrapper.py b/restclients/dao_implementation/twilio_wrapper.py index 1534193b..bcf7a071 100644 --- a/restclients/dao_implementation/twilio_wrapper.py +++ b/restclients/dao_implementation/twilio_wrapper.py @@ -10,8 +10,9 @@ class Twilio(object): def __init__(self): ''' - RESTCLIENTS_SMS_MODE needs to be configured in settings.py. RESTCLIENTS_SMS_MODE allows you - to have multiple configuration in your settings.py file, such as 'Test' + RESTCLIENTS_SMS_MODE needs to be configured in settings.py. + RESTCLIENTS_SMS_MODE allows you to have multiple configuration + in your settings.py file, such as 'Test' or 'Live'. For 'Live', your settings.py should include this: RESTCLIENTS_SMS_ACCOUNT = { @@ -22,19 +23,23 @@ def __init__(self): } } ''' - self.sid = settings.RESTCLIENTS_SMS_ACCOUNT[settings.RESTCLIENTS_SMS_MODE]['sid'] - self.token = settings.RESTCLIENTS_SMS_ACCOUNT[settings.RESTCLIENTS_SMS_MODE]['token'] - self.from_number = settings.RESTCLIENTS_SMS_ACCOUNT[settings.RESTCLIENTS_SMS_MODE]['from'] + self.sid = settings.RESTCLIENTS_SMS_ACCOUNT[ + settings.RESTCLIENTS_SMS_MODE]['sid'] + self.token = settings.RESTCLIENTS_SMS_ACCOUNT[ + settings.RESTCLIENTS_SMS_MODE]['token'] + self.from_number = settings.RESTCLIENTS_SMS_ACCOUNT[ + settings.RESTCLIENTS_SMS_MODE]['from'] def send(self, message): - #Using Twilio Python Library + # Using Twilio Python Library try: client = TwilioRestClient(self.sid, self.token) twilio_response = client.sms.messages.create( - #Twilio requries a '+1' prepended to every SMS North America phone number - to = "+1" + message.to, - from_ = self.from_number, - body = message.body) + to="+1" + message.to, + from_=self.from_number, + body=message.body) + # Twilio requries a '+1' prepended to every + # SMS North America phone number except twilio.TwilioRestException as e: raise DataFailureException(e.uri, e.status, e.msg) diff --git a/restclients/dao_implementation/uwnetid.py b/restclients/dao_implementation/uwnetid.py index 1a6fe45b..f5936288 100644 --- a/restclients/dao_implementation/uwnetid.py +++ b/restclients/dao_implementation/uwnetid.py @@ -13,7 +13,8 @@ class File(object): The File DAO implementation returns generally static content. Use this DAO with this configuration: - RESTCLIENTS_UWNETID_DAO_CLASS = 'restclients.dao_implementation.uwnetid.File' + RESTCLIENTS_UWNETID_DAO_CLASS = + 'restclients.dao_implementation.uwnetid.File' """ def getURL(self, url, headers): return get_mockdata_url("uwnetid", "file", url, headers) diff --git a/restclients/exceptions.py b/restclients/exceptions.py index cc55a7c8..406486f6 100644 --- a/restclients/exceptions.py +++ b/restclients/exceptions.py @@ -7,70 +7,92 @@ class PhoneNumberRequired(Exception): """Exception for missing phone number.""" pass + class InvalidPhoneNumber(Exception): """Exception for invalid phone numbers.""" pass + class InvalidNetID(Exception): """Exception for invalid netid.""" pass + class InvalidRegID(Exception): """Exception for invalid regid.""" pass + class InvalidEmployeeID(Exception): """Exception for invalid employee id.""" pass + +class InvalidStudentNumber(Exception): + """Exception for invalid student number.""" + pass + + class InvalidUUID(Exception): """Exception for invalid UUID.""" pass + class InvalidSectionID(Exception): """Exception for invalid section id.""" pass + class InvalidSectionURL(Exception): """Exception for invalid section url.""" pass + class InvalidGroupID(Exception): """Exception for invalid group id.""" pass + class InvalidIdCardPhotoSize(Exception): """Exception for invalid photo size.""" pass + class InvalidEndpointProtocol(Exception): """Exception for invalid endpoint protocol.""" pass + class InvalidCanvasIndependentStudyCourse(Exception): """Exception for invalid Canvas course.""" pass + class InvalidCanvasSection(Exception): """Exception for invalid Canvas section.""" pass + class InvalidGradebookID: """Exception for invalid gradebook id.""" pass + class InvalidIRWSName(Exception): """Exception for invalid IRWS name.""" pass + class InvalidIRWSPerson(Exception): """Exception for invalid IRWS name.""" pass + class IRWSPersonNotFound(Exception): """Exception for netids that don't exist""" pass + class DataFailureException(Exception): """ This exception means there was an error fetching content diff --git a/restclients/grad/__init__.py b/restclients/grad/__init__.py new file mode 100644 index 00000000..1d29c507 --- /dev/null +++ b/restclients/grad/__init__.py @@ -0,0 +1,35 @@ +""" +This is the interface for interacting with the UW Libraries Web Service. +""" + +import logging +from datetime import datetime +from restclients.dao import Grad_DAO +from restclients.exceptions import DataFailureException +from restclients.util.timer import Timer +from restclients.util.log import log_info + + +logger = logging.getLogger(__name__) + + +def get_resource(url): + dao = Grad_DAO() + timer = Timer() + response = dao.getURL(url, {}) + log_info(logger, + "%s ==status==> %s" % (url, response.status), + timer) + if response.status != 200: + logger.debug("%s ==data==> %s" % (url, response.data)) + raise DataFailureException(url, response.status, response.data) + return response.data + + +def datetime_from_string(date_string): + if date_string is None: + return None + date_format = "%Y-%m-%dT%H:%M:%S" + if len(date_string) > 20: + date_string = date_string[0:19] + return datetime.strptime(date_string, date_format) diff --git a/restclients/grad/committee.py b/restclients/grad/committee.py new file mode 100644 index 00000000..0a5fa820 --- /dev/null +++ b/restclients/grad/committee.py @@ -0,0 +1,68 @@ +""" +Interfacing with the Grad Scho Committee Request API +""" +import logging +import json +from restclients.models.grad import GradCommitteeMember, GradCommittee +from restclients.pws import PWS +from restclients.grad import get_resource, datetime_from_string + + +PREFIX = "/services/students/v1/api/committee?id=" +SUFFIX = "&status=active" + + +logger = logging.getLogger(__name__) + + +def get_committee_by_regid(regid): + """ + raise: InvalidRegID, DataFailureException + """ + person = PWS().get_person_by_regid(regid) + return get_committee_by_syskey(person.student_system_key) + + +def get_committee_by_syskey(system_key): + url = "%s%s%s" % (PREFIX, system_key, SUFFIX) + return _process_json(json.loads(get_resource(url))) + + +def _process_json(data): + """ + return a list of GradCommittee objects. + """ + requests = [] + for item in data: + committee = GradCommittee() + committee.status = item.get('status') + committee.committee_type = item.get('committeeType') + committee.dept = item.get('dept') + committee.degree_title = item.get('degreeTitle') + committee.degree_type = item.get('degreeType') + committee.major_full_name = item.get('majorFullName') + committee.start_date = datetime_from_string(item.get('startDate')) + committee.end_date = datetime_from_string(item.get('endDate')) + for member in item.get('members'): + if member.get('status') == "inactive": + continue + + com_mem = GradCommitteeMember() + com_mem.first_name = member.get('nameFirst') + com_mem.last_name = member.get('nameLast') + + if member.get('memberType') is not None and\ + len(member.get('memberType')) > 0: + com_mem.member_type = member.get('memberType').lower() + + if member.get('readingType') is not None and\ + len(member.get('readingType')) > 0: + com_mem.reading_type = member.get('readingType').lower() + + com_mem.dept = member.get('dept') + com_mem.email = member.get('email') + com_mem.status = member.get('status') + committee.members.append(com_mem) + + requests.append(committee) + return requests diff --git a/restclients/grad/degree.py b/restclients/grad/degree.py new file mode 100644 index 00000000..8f4bc8ce --- /dev/null +++ b/restclients/grad/degree.py @@ -0,0 +1,53 @@ +""" +Interfacing with the Grad Scho Degree Request API +""" +import logging +import json +from restclients.models.grad import GradDegree +from restclients.pws import PWS +from restclients.grad import get_resource, datetime_from_string + + +PREFIX = "/services/students/v1/api/request?id=" +SUFFIX = "&exclude_past_quarter=true" + + +logger = logging.getLogger(__name__) + + +def get_degree_by_regid(regid): + """ + raise: InvalidRegID, DataFailureException + """ + person = PWS().get_person_by_regid(regid) + return get_degree_by_syskey(person.student_system_key) + + +def get_degree_by_syskey(system_key): + url = "%s%s%s" % (PREFIX, system_key, SUFFIX) + return _process_json(json.loads(get_resource(url))) + + +def _process_json(json_data): + """ + return a list of GradDegree objects. + """ + requests = [] + for item in json_data: + degree = GradDegree() + degree.degree_title = item["degreeTitle"] + degree.exam_place = item["examPlace"] + degree.exam_date = datetime_from_string(item["examDate"]) + degree.req_type = item["requestType"] + degree.major_full_name = item["majorFullName"] + degree.submit_date = datetime_from_string(item["requestSubmitDate"]) + if 'decisionDate' in item and item.get('decisionDate') is not None: + degree.decision_date = datetime_from_string( + item.get('decisionDate')) + degree.status = item["status"] + degree.target_award_year = item["targetAwardYear"] + if item.get("targetAwardQuarter") is not None: + degree.target_award_quarter = item["targetAwardQuarter"].lower() + + requests.append(degree) + return requests diff --git a/restclients/grad/leave.py b/restclients/grad/leave.py new file mode 100644 index 00000000..5e43b751 --- /dev/null +++ b/restclients/grad/leave.py @@ -0,0 +1,48 @@ +""" +Interfacing with the GradScho Degree Request API +""" +import logging +import json +from restclients.models.grad import GradLeave, GradTerm +from restclients.pws import PWS +from restclients.grad import get_resource, datetime_from_string + + +PREFIX = "/services/students/v1/api/leave?id=" + + +logger = logging.getLogger(__name__) + + +def get_leave_by_regid(regid): + """ + raise: InvalidRegID, DataFailureException + """ + person = PWS().get_person_by_regid(regid) + return get_leave_by_syskey(person.student_system_key) + + +def get_leave_by_syskey(system_key): + url = "%s%s" % (PREFIX, system_key) + return _process_json(json.loads(get_resource(url))) + + +def _process_json(data): + """ + return a list of GradLeave objects. + """ + requests = [] + for item in data: + leave = GradLeave() + leave.reason = item.get('leaveReason') + leave.submit_date = datetime_from_string(item.get('submitDate')) + if item.get('status') is not None and len(item.get('status')) > 0: + leave.status = item.get('status').lower() + + for quarter in item.get('quarters'): + term = GradTerm() + term.quarter = quarter.get('quarter').lower() + term.year = quarter.get('year') + leave.terms.append(term) + requests.append(leave) + return requests diff --git a/restclients/grad/petition.py b/restclients/grad/petition.py new file mode 100644 index 00000000..3406a888 --- /dev/null +++ b/restclients/grad/petition.py @@ -0,0 +1,54 @@ +""" +Interfacing with the Grad Scho Petition Request API +""" +import logging +import json +from restclients.models.grad import GradPetition +from restclients.pws import PWS +from restclients.grad import get_resource, datetime_from_string + + +PREFIX = "/services/students/v1/api/petition?id=" + + +logger = logging.getLogger(__name__) + + +def get_petition_by_regid(regid): + """ + raise: InvalidRegID, DataFailureException + """ + person = PWS().get_person_by_regid(regid) + return get_petition_by_syskey(person.student_system_key) + + +def get_petition_by_syskey(system_key): + url = "%s%s" % (PREFIX, system_key) + return _process_json(json.loads(get_resource(url))) + + +def _process_json(data): + """ + return a list of GradPetition objects. + """ + requests = [] + for item in data: + petition = GradPetition() + petition.description = item.get('description') + petition.submit_date = datetime_from_string(item.get('submitDate')) + if 'decisionDate' in item and item.get('decisionDate') is not None: + petition.decision_date = datetime_from_string( + item.get('decisionDate')) + else: + petition.decision_date = None + + if item.get('deptRecommend') is not None and\ + len(item.get('deptRecommend')) > 0: + petition.dept_recommend = item.get('deptRecommend').lower() + + if item.get('gradSchoolDecision') is not None and\ + len(item.get('gradSchoolDecision')) > 0: + petition.gradschool_decision =\ + item.get('gradSchoolDecision').lower() + requests.append(petition) + return requests diff --git a/restclients/gws.py b/restclients/gws.py index 133d38c0..abaf16af 100644 --- a/restclients/gws.py +++ b/restclients/gws.py @@ -8,6 +8,7 @@ from restclients.exceptions import DataFailureException from restclients.models.gws import Group, CourseGroup, GroupReference from restclients.models.gws import GroupUser, GroupMember +from datetime import datetime from urllib import urlencode from lxml import etree import re @@ -19,7 +20,6 @@ class GWS(object): """ QTRS = {'win': 'winter', 'spr': 'spring', 'sum': 'summer', 'aut': 'autumn'} - def __init__(self, config={}): self.actas = config['actas'] if 'actas' in config else None @@ -45,7 +45,7 @@ def search_groups(self, **kwargs): Values are 'one' to limit results to one level of stem name and 'all' to return all groups. """ - kwargs = dict((k.lower(), v.lower()) for k,v in kwargs.iteritems()) + kwargs = dict((k.lower(), v.lower()) for k, v in kwargs.iteritems()) if 'type' in kwargs and (kwargs['type'] != 'direct' and kwargs['type'] != 'effective'): del(kwargs['type']) @@ -252,11 +252,14 @@ def _group_from_xhtml(self, data): group_id = root.find('.//*[@class="name"]').text if re.match(r'^course_', group_id): group = CourseGroup() - group.curriculum_abbr = root.find('.//*[@class="course_curr"]').text.upper() + group.curriculum_abbr = root.find( + './/*[@class="course_curr"]').text.upper() group.course_number = root.find('.//*[@class="course_no"]').text group.year = root.find('.//*[@class="course_year"]').text - group.quarter = self.QTRS[root.find('.//*[@class="course_qtr"]').text] - group.section_id = root.find('.//*[@class="course_sect"]').text.upper() + group.quarter = self.QTRS[ + root.find('.//*[@class="course_qtr"]').text] + group.section_id = root.find( + './/*[@class="course_sect"]').text.upper() group.sln = root.find('.//*[@class="course_sln"]').text group.instructors = [] @@ -273,6 +276,8 @@ def _group_from_xhtml(self, data): group.title = root.find('.//*[@class="title"]').text group.description = root.find('.//*[@class="description"]').text group.contact = root.find('.//*[@class="contact"]').text + group.membership_modified = datetime.fromtimestamp( + float(root.find('.//*[@class="membermodifytime"]').text)/1000) group.authnfactor = root.find('.//*[@class="authnfactor"]').text group.classification = root.find('.//*[@class="classification"]').text group.emailenabled = root.find('.//*[@class="emailenabled"]').text @@ -289,11 +294,13 @@ def _group_from_xhtml(self, data): group.admins.append(GroupUser(name=user.text, user_type=user.get("type"))) - for user in root.findall('.//*[@class="updaters"]/*[@class="updater"]'): + for user in\ + root.findall('.//*[@class="updaters"]/*[@class="updater"]'): group.updaters.append(GroupUser(name=user.text, user_type=user.get("type"))) - for user in root.findall('.//*[@class="creators"]/*[@class="creator"]'): + for user in\ + root.findall('.//*[@class="creators"]/*[@class="creator"]'): group.creators.append(GroupUser(name=user.text, user_type=user.get("type"))) @@ -303,7 +310,7 @@ def _group_from_xhtml(self, data): for user in root.findall('.//*[@class="optins"]/*[@class="optin"]'): group.optins.append(GroupUser(name=user.text, - user_type=user.get("type"))) + user_type=user.get("type"))) for user in root.findall('.//*[@class="optouts"]/*[@class="optout"]'): group.optouts.append(GroupUser(name=user.text, @@ -375,7 +382,7 @@ def _headers(self, headers): def _add_header(self, headers, header, value): if not headers: - return { header: value } + return {header: value} headers[header] = value return headers diff --git a/restclients/hfs/__init__.py b/restclients/hfs/__init__.py index 9cc40889..88286099 100644 --- a/restclients/hfs/__init__.py +++ b/restclients/hfs/__init__.py @@ -10,6 +10,7 @@ from restclients.util.log import log_info +ERROR_MSG = "An error has occurred" INVALID_ID_MSG = "not found in IDCard Database" INVALID_PARAM_MSG = "Input for this method must be either" logger = logging.getLogger(__name__) @@ -26,10 +27,18 @@ def get_resource(url): raise DataFailureException(url, response.status, response.data) #'Bug' with lib API causing requests with no/invalid user to return a 200 - if INVALID_ID_MSG in response.data or INVALID_PARAM_MSG in response.data: + if INVALID_PARAM_MSG in response.data: + json_data = json.loads(response.data) + raise DataFailureException(url, 400, json_data["Message"]) + + if INVALID_ID_MSG in response.data: json_data = json.loads(response.data) raise DataFailureException(url, 404, json_data["Message"]) + if ERROR_MSG in response.data: + json_data = json.loads(response.data) + raise DataFailureException(url, 500, json_data["Message"]) + try: logger.debug("%s ==data==> %s" % (url, response.data.decode('utf-8'))) except Exception as ex: diff --git a/restclients/iasystem/__init__.py b/restclients/iasystem/__init__.py index cb9bb421..29489abd 100644 --- a/restclients/iasystem/__init__.py +++ b/restclients/iasystem/__init__.py @@ -1,7 +1,12 @@ import json +import logging from restclients.dao import IASYSTEM_DAO from restclients.exceptions import DataFailureException +from restclients.util.timer import Timer +from restclients.util.log import log_info + +logger = logging.getLogger(__name__) CAMPUS_SUBDOMAIN = {'seattle': 'uw', 'tacoma': 'uwt', 'bothell': 'uwb'} @@ -18,7 +23,16 @@ def get_resource(url, subdomain): :returns: http response with content in json """ headers = {"Accept": "application/vnd.collection+json"} + timer = Timer() response = IASYSTEM_DAO().getURL(url, headers, subdomain) + + log_info(logger, + "%s ==status==> %s" % (url, response.status), + timer) + if response.status != 200: + logger.debug("%s ==data==> %s" % (url, + response.data)) raise DataFailureException(url, response.status, response.data) + return json.loads(response.data) diff --git a/restclients/iasystem/evaluation.py b/restclients/iasystem/evaluation.py index 0839e319..24117984 100644 --- a/restclients/iasystem/evaluation.py +++ b/restclients/iasystem/evaluation.py @@ -1,11 +1,15 @@ """ Interfacing with the IASytem API, Evaluation resource. """ +import pytz +import logging from urllib import urlencode from restclients.iasystem import get_resource_by_campus from restclients.models.iasystem import Evaluation from datetime import datetime -import pytz + + +IAS_PREFIX = "/api/v1/evaluation" def search_evaluations(campus, **kwargs): @@ -17,7 +21,7 @@ def search_evaluations(campus, **kwargs): section_id student_id (student number) """ - url = "/api/v1/evaluation?%s" % urlencode(kwargs) + url = "%s?%s" % (IAS_PREFIX, urlencode(kwargs)) data = get_resource_by_campus(url, campus) evaluations = _json_to_evaluation(data) @@ -26,31 +30,42 @@ def search_evaluations(campus, **kwargs): def get_evaluation_by_id(evaluation_id, campus): - url = "/api/v1/evaluation/%s" % evaluation_id + url = "%s/%s" % (IAS_PREFIX, evaluation_id) return _json_to_evaluation(get_resource_by_campus(url, campus)) def _json_to_evaluation(data): - evaluations = [] + """ + Only keep the data for online evaluations. + Two scenarios for multiple instructors: + 1) all of the co-instructors may be evaluated online as a group, + sharing the eval URL. + 2) each co-instructor may be evaluated individually, + with separate eval URLs. + """ collection_items = data.get('collection').get('items') if collection_items is None: - return evaluations + return None + evaluations = [] for item in collection_items: - type = _get_item_type(item.get('meta')) + item_meta = item.get('meta') + type = _get_item_type(item_meta) if type == "evaluation": - evaluation = Evaluation() - item_data = item.get('data') - evaluation.eval_open_date = get_open_date(item_data) - evaluation.eval_close_date = get_close_date(item_data) - evaluation.eval_status = get_value_by_name(item_data, 'status') - evaluation.eval_is_online = get_is_online(item_data) - evaluation.eval_url = get_eval_url(item.get('links')) - - section, instructor = get_section_and_instructor(item, - collection_items) - evaluation.section_sln = get_section_sln(section) - evaluation.instructor_id = get_instructor_id(instructor) - evaluations.append(evaluation) + delivery_data = item.get('data') + if get_is_online(delivery_data): + evaluation = Evaluation() + evaluation.eval_status = \ + get_value_by_name(delivery_data, 'status') + evaluation.eval_open_date = get_open_date(delivery_data) + evaluation.eval_close_date = get_close_date(delivery_data) + evaluation.eval_url = get_eval_url(item.get('links')) + section, instructors, completion =\ + _get_child_items(_get_child_ids(item_meta), + collection_items) + evaluation.section_sln = get_section_sln(section) + evaluation.instructor_ids = instructors + evaluation.is_completed = get_is_complete(completion) + evaluations.append(evaluation) return evaluations @@ -59,24 +74,32 @@ def get_section_sln(section): return int(sln) +def get_is_complete(completion): + if completion is None: + return None + return get_value_by_name(completion.get('data'), 'isCompleted') + + def get_instructor_id(instructor): id = get_value_by_name(instructor.get('data'), 'instInstructorId') return int(id) -def get_section_and_instructor(eval_data, collection_items): - instructor = None +def _get_child_items(child_ids, collection_items): section = None - child_ids = _get_child_ids(eval_data.get('meta')) + instructors = [] # array of intergers + completion = None for item in collection_items: id = get_value_by_name(item.get('meta'), 'id') if id in child_ids: type = get_value_by_name(item.get('meta'), 'type') if type == "instructor": - instructor = item + instructors.append(get_instructor_id(item)) if type == "section": section = item - return section, instructor + if type == "evaluation completion": + completion = item + return section, instructors, completion def _get_child_ids(meta_data): diff --git a/restclients/irws.py b/restclients/irws.py index 75e70c27..4d7e45ab 100644 --- a/restclients/irws.py +++ b/restclients/irws.py @@ -8,13 +8,11 @@ import logging from django.conf import settings from restclients.dao import IRWS_DAO -from restclients.exceptions import InvalidRegID, InvalidNetID, InvalidEmployeeID -from restclients.exceptions import InvalidIdCardPhotoSize +from restclients.exceptions import InvalidNetID from restclients.exceptions import DataFailureException -from restclients.exceptions import InvalidIRWSName, InvalidIRWSPerson, IRWSPersonNotFound +from restclients.exceptions import InvalidIRWSName, InvalidIRWSPerson +from restclients.exceptions import IRWSPersonNotFound from restclients.models.irws import Name, UwhrPerson, PersonIdentity -from StringIO import StringIO -from urllib import urlencode logger = logging.getLogger(__name__) @@ -27,8 +25,10 @@ def __init__(self, actas=None): self.actas = actas self._re_regid = re.compile(r'^[A-F0-9]{32}$', re.I) self._re_personal_netid = re.compile(r'^[a-z][a-z0-9]{0,7}$', re.I) - self._re_admin_netid = re.compile(r'^[a-z]adm_[a-z][a-z0-9]{0,7}$', re.I) - self._re_application_netid = re.compile(r'^a_[a-z0-9\-\_\.$.]{1,18}$', re.I) + self._re_admin_netid = re.compile( + r'^[a-z]adm_[a-z][a-z0-9]{0,7}$', re.I) + self._re_application_netid = re.compile( + r'^a_[a-z0-9\-\_\.$]{1,18}$', re.I) self._re_employee_id = re.compile(r'^\d{9}$') """ Consider adding back +, #, and % when irws stops decoding """ self._re_name_part = re.compile(r'^[\w !$&\'*\-,.?^_`{}~#+%]*$') @@ -36,9 +36,10 @@ def __init__(self, actas=None): def get_identity_by_netid(self, netid): """ - Returns a restclients.irws.PersonIdentity object for the given netid. If the - netid isn't found, nothing will be returned. If there is an error - communicating with the IRWS, a DataFailureException will be thrown. + Returns a restclients.irws.PersonIdentity object for the given + netid. If the netid isn't found, nothing will be returned. + If there is an error communicating with the IRWS, a + DataFailureException will be thrown. """ if not self.valid_uwnetid(netid): raise InvalidNetID(netid) @@ -79,7 +80,8 @@ def put_name_by_netid(self, netid, data): pd = self.valid_irws_name_from_json(data) dao = IRWS_DAO() url = "/%s/v1/name/uwnetid=%s" % (self._service_name, netid.lower()) - response = dao.putURL(url, {"Accept": "application/json"}, json.dumps(pd)) + response = dao.putURL(url, {"Accept": "application/json"}, + json.dumps(pd)) if response.status != 200: raise DataFailureException(url, response.status, response.data) @@ -88,9 +90,10 @@ def put_name_by_netid(self, netid, data): def get_hr_person_by_uri(self, uri): """ - Returns a restclients.irws.UwhrPerson object for the given uri (from identity). - If the record id isn't found, nothing will be returned. If there is an error - communicating with the IRWS, a DataFailureException will be thrown. + Returns a restclients.irws.UwhrPerson object for the given uri + (from identity). If the record id isn't found, nothing will + be returned. If there is an error communicating with the IRWS, + a DataFailureException will be thrown. """ url = "/%s/v1%s" % (self._service_name, uri) response = IRWS_DAO().getURL(url, {"Accept": "application/json"}) @@ -109,13 +112,15 @@ def get_hepps_person_by_netid(self, netid): def get_hr_person_by_netid(self, netid): """ - Returns a restclients.irws.UwhrPerson object for a given netid. Two round - trips - one to get the identity, and a second to look up a person based on - the 'hepps' or 'uwhr' uri in the payload + Returns a restclients.irws.UwhrPerson object for a given + netid. Two round trips - one to get the identity, and a second + to look up a person based on the 'hepps' or 'uwhr' uri in the + payload """ identity = self.get_identity_by_netid(netid) if not {'hepps', 'uwhr'} & set(identity.identifiers.keys()): - raise IRWSPersonNotFound('netid ' + netid + ' not a hepps/uwhr person') + raise IRWSPersonNotFound( + 'netid {} not a hepps/uwhr person'.format(netid)) source = 'uwhr' if 'uwhr' in identity.identifiers.keys() else 'hepps' return self.get_hr_person_by_uri(identity.identifiers[source]) @@ -136,23 +141,24 @@ def post_hr_person_by_netid(self, netid, data): hepps_person = self.valid_hr_person_from_json(data) identity = self.get_identity_by_netid(netid) if not {'hepps', 'uwhr'} & set(identity.identifiers.keys()): - raise IRWSPersonNotFound('netid ' + netid + ' not a hepps/uwhr person') + raise IRWSPersonNotFound( + 'netid {} not a hepps/uwhr person'.format(netid)) source = 'uwhr' if 'uwhr' in identity.identifiers.keys() else 'hepps' post_url = '/{}/v1{}'.format(self._service_name, - identity.identifiers[source]) - response = IRWS_DAO().postURL(post_url, - {'Accept': 'application/json'}, - json.dumps(hepps_person)) + identity.identifiers[source]) + response = IRWS_DAO().postURL( + post_url, {'Accept': 'application/json'}, json.dumps(hepps_person)) if response.status != 200: - raise DataFailureException(post_url, response.status, response.data) + raise DataFailureException(post_url, response.status, + response.data) return response.status def valid_uwnetid(self, netid): uwnetid = str(netid) - return (self._re_personal_netid.match(uwnetid) != None - or self._re_admin_netid.match(uwnetid) != None - or self._re_application_netid.match(uwnetid) != None) + return (self._re_personal_netid.match(uwnetid) is not None or + self._re_admin_netid.match(uwnetid) is not None or + self._re_application_netid.match(uwnetid) is not None) def valid_uwregid(self, regid): return True if self._re_regid.match(str(regid)) else False @@ -207,7 +213,7 @@ def valid_hr_person_from_json(self, data): def valid_name_part(self, name): return (len(name) <= 64 and - self._re_name_part.match(name) != None) + self._re_name_part.match(name) is not None) def _hr_person_from_json(self, data): """ @@ -217,31 +223,27 @@ def _hr_person_from_json(self, data): person = UwhrPerson() person.validid = person_data['validid'] person.regid = person_data['regid'] - if 'studentid' in person_data: person.studentid = person_data['studentid'] + if 'studentid' in person_data: + person.studentid = person_data['studentid'] person.fname = person_data['fname'] person.lname = person_data['lname'] person.category_code = person_data['category_code'] person.category_name = person_data['category_name'] - - if 'wp_publish' in person_data: person.wp_publish = person_data['wp_publish'] - else: person.wp_publish = 'N' # default to no + + setattr(person, 'wp_publish', + person_data.get('wp_publish', 'N')) return person def _name_from_json(self, data): nd = json.loads(data)['name'][0] + remap = {'formal_sname': 'formal_lname', + 'display_sname': 'display_lname'} + nd = {k if k not in remap else remap[k]: v for k, v in nd.items()} name = Name() - name.validid = nd['validid'] - if 'formal_cname' in nd: name.formal_cname = nd['formal_cname'] - if 'formal_fname' in nd: name.formal_fname = nd['formal_fname'] - if 'formal_sname' in nd: name.formal_lname = nd['formal_sname'] - if 'formal_privacy' in nd: name.formal_privacy = nd['formal_privacy'] - if 'display_cname' in nd: name.display_cname = nd['display_cname'] - if 'display_fname' in nd: name.display_fname = nd['display_fname'] - if 'display_mname' in nd: name.display_mname = nd['display_mname'] - if 'display_sname' in nd: name.display_lname = nd['display_sname'] - if 'display_privacy' in nd: name.display_privacy = nd['display_privacy'] + for attribute in nd.keys(): + setattr(name, attribute, nd[attribute]) return name def _identity_from_json(self, data): @@ -251,4 +253,3 @@ def _identity_from_json(self, data): ident.regid = idj['regid'] ident.identifiers = copy.deepcopy(idj['identifiers']) return ident - diff --git a/restclients/kws.py b/restclients/kws.py new file mode 100644 index 00000000..376fd61a --- /dev/null +++ b/restclients/kws.py @@ -0,0 +1,61 @@ +""" +This is the interface for interacting with the Key Web Service. +""" + +from restclients.dao import KWS_DAO +from restclients.exceptions import DataFailureException +from restclients.models.kws import Key +from datetime import datetime +import json + + +ENCRYPTION_KEY_PREFIX = '/key/v1' + + +class KWS(object): + """ + The KWS object has methods for getting key information. + """ + def _get_resource(self, url, headers={}): + headers["Accept"] = "application/json" + + response = KWS_DAO().getURL(url, headers) + + if response.status != 200: + raise DataFailureException(url, response.status, response.data) + + return json.loads(response.data) + + def get_key(self, key_id): + """ + Returns a restclients.Key object for the given key ID. If the + key ID isn't found, or if there is an error communicating with the + KWS, a DataFailureException will be thrown. + """ + url = "%s/encryption/%s.json" % (ENCRYPTION_KEY_PREFIX, key_id) + return self._key_from_json(self._get_resource(url)) + + def get_current_key(self, resource_name): + """ + Returns a restclients.Key object for the given resource. If the + resource isn't found, or if there is an error communicating with the + KWS, a DataFailureException will be thrown. + """ + url = "%s/type/%s/encryption/current.json" % (ENCRYPTION_KEY_PREFIX, + resource_name) + return self._key_from_json(self._get_resource(url)) + + def _key_from_json(self, data): + """ + Internal method, for creating the Key object. + """ + key = Key() + key.algorithm = data["Algorithm"] + key.cipher_mode = data["CipherMode"] + key.expiration = datetime.strptime(data["Expiration"].split(".")[0], + "%Y-%m-%dT%H:%M:%S") + key.key_id = data["ID"] + key.key = data["Key"] + key.size = data["KeySize"] + key.url = data["KeyUrl"] + return key diff --git a/restclients/library/__init__.py b/restclients/library/__init__.py index 93df58e4..e69de29b 100644 --- a/restclients/library/__init__.py +++ b/restclients/library/__init__.py @@ -1,33 +0,0 @@ -""" -This is the interface for interacting with the UW Libraries Web Service. -""" - -import logging -from restclients.dao import Libraries_DAO -from restclients.exceptions import DataFailureException -from restclients.util.timer import Timer -from restclients.util.log import log_info - - -INVALID_USER_MSG = "User not found" -logger = logging.getLogger(__name__) - - -def get_resource(url): - dao = Libraries_DAO() - timer = Timer() - response = dao.getURL(url, {}) - log_info(logger, - "%s ==status==> %s" % (url, response.status), - timer) - - if response.status != 200: - raise DataFailureException(url, response.status, response.data) - - #'Bug' with lib API causing requests with no/invalid user to return a 200 - if INVALID_USER_MSG in response.data: - raise DataFailureException(url, 404, response.data) - - logger.debug("%s ==data==> %s" % (url, response.data)) - - return response.data diff --git a/restclients/library/currics.py b/restclients/library/currics.py new file mode 100644 index 00000000..bd844c0e --- /dev/null +++ b/restclients/library/currics.py @@ -0,0 +1,91 @@ +""" +This is the interface for interacting with the UW Libraries Currics +Web Service. +""" + +import json +from urllib import quote +from restclients.dao import LibCurrics_DAO +from restclients.exceptions import DataFailureException +from restclients.models.library import SubjectGuide, Library, Librarian + + +subject_guide_url_prefix = '/currics_db/api/v1/data/course' + + +def get_subject_guide_for_section_params(year, quarter, curriculum_abbr, + course_number, section_id=None): + """ + Returns a SubjectGuide model for the passed section params: + + year: year for the section term (4-digits) + quarter: quarter (AUT, WIN, SPR, or SUM) + curriculum_abbr: curriculum abbreviation + course_number: course number + section_id: course section identifier (optional) + """ + url = '%s/%s/%s/%s/%s/%s' % ( + subject_guide_url_prefix, year, quarter.upper(), + quote(curriculum_abbr.upper()), course_number, section_id.upper()) + headers = {'Accept': 'application/json'} + + response = LibCurrics_DAO().getURL(url, headers) + + if response.status != 200: + raise DataFailureException(url, response.status, response.data) + + data = json.loads(response.data) + return _subject_guide_from_json(data['subjectGuide']) + + +def get_subject_guide_for_section(section): + """ + Returns a SubjectGuide model for the passed SWS section model. + """ + return get_subject_guide_for_section_params( + section.year, section.quarter[:3], section.curriculum_abbr, + section.course_number, section.section_id) + + +def get_subject_guide_for_canvas_course_sis_id(course_sis_id): + """ + Returns a SubjectGuide model for the passed Canvas course SIS ID. + """ + (year, quarter, curriculum_abbr, course_number, + section_id) = course_sis_id.split('-', 4) + return get_subject_guide_for_section_params( + year, quarter[:3], curriculum_abbr, course_number, section_id) + + +def _subject_guide_from_json(data): + subject_guide = SubjectGuide() + subject_guide.contact_url = data.get('askUsLink', None) + subject_guide.contact_text = data.get('askUsText', None) + subject_guide.discipline = data.get('discipline', None) + subject_guide.librarian_url = data.get('findLibrarianLink', None) + subject_guide.librarian_text = data.get('findLibrarianText', None) + subject_guide.guide_url = data.get('guideLink', None) + subject_guide.guide_text = data.get('guideText', None) + subject_guide.faq_url = data.get('howDoILink', None) + subject_guide.faq_text = data.get('howDoIText', None) + subject_guide.writing_guide_url = data.get('writingGuideLink', None) + subject_guide.writing_guide_text = data.get('writingGuideText', None) + subject_guide.libraries = [] + subject_guide.librarians = [] + + for libdata in data.get('libraries', []): + library = Library() + library.name = libdata.get('name', None) + library.description = libdata.get('description', None) + library.url = libdata.get('url', None) + subject_guide.libraries.append(library) + + for libdata in data.get('librarians', []): + librarian = Librarian() + librarian.name = libdata.get('name', None) + librarian.email = libdata.get('email', None) + librarian.phone = libdata.get('telephone', None) + librarian.url = libdata.get('url', None) + subject_guide.librarians.append(librarian) + + return subject_guide diff --git a/restclients/library/mylibinfo.py b/restclients/library/mylibinfo.py index 20e20f6b..40098b73 100644 --- a/restclients/library/mylibinfo.py +++ b/restclients/library/mylibinfo.py @@ -6,14 +6,37 @@ import logging import json from restclients.models.library import MyLibAccount -from restclients.library import get_resource +from restclients.dao import MyLibInfo_DAO +from restclients.exceptions import DataFailureException +from restclients.util.timer import Timer +from restclients.util.log import log_info +INVALID_USER_MSG = "User not found" RESPONSE_STYLES = ['html', 'json'] url_prefix = "/mylibinfo/v1/?id=" logger = logging.getLogger(__name__) +def get_resource(url): + timer = Timer() + response = MyLibInfo_DAO().getURL(url, {}) + log_info(logger, + "%s ==status==> %s" % (url, response.status), + timer) + + if response.status != 200: + raise DataFailureException(url, response.status, response.data) + + # 'Bug' with lib API causing requests with no/invalid user to return a 200 + if INVALID_USER_MSG in response.data: + raise DataFailureException(url, 404, response.data) + + logger.debug("%s ==data==> %s" % (url, response.data)) + + return response.data + + def get_account(netid, timestamp=None): """ The Libraries object has a method for getting information @@ -49,7 +72,7 @@ def _account_from_json(body): account_data = json.loads(body) except Exception as ex: raise Exception("Unable to parse library data: %s. Exception: %s" % ( - body, ex)) + body, ex)) account = MyLibAccount() account.fines = account_data["fines"] account.holds_ready = account_data["holds_ready"] diff --git a/restclients/models/__init__.py b/restclients/models/__init__.py index a1e74914..1a08825c 100644 --- a/restclients/models/__init__.py +++ b/restclients/models/__init__.py @@ -3,6 +3,7 @@ from base64 import b64encode, b64decode import warnings +from restclients.models.base import RestClientsModel from restclients.models.sws import Term as swsTerm from restclients.models.sws import Person as swsPerson from restclients.models.sws import FinalExam as swsFinalExam @@ -112,7 +113,7 @@ def CanvasEnrollment(*args, **kwargs): return canvasEnrollment(*args, **kwargs) -class CacheEntry(models.Model): +class CacheEntry(RestClientsModel): service = models.CharField(max_length=50, db_index=True) url = models.CharField(max_length=255, unique=True, db_index=True) status = models.PositiveIntegerField() @@ -120,7 +121,7 @@ class CacheEntry(models.Model): content = models.TextField() headers = None - class Meta: + class Meta(RestClientsModel.Meta): unique_together = ('service', 'url') def getHeaders(self): @@ -153,7 +154,7 @@ class CacheEntryExpires(CacheEntry): time_expires = models.DateTimeField() -class Book(models.Model): +class Book(RestClientsModel): isbn = models.CharField(max_length=15) title = models.CharField(max_length=255) price = models.DecimalField(max_digits=7, decimal_places=2) @@ -179,7 +180,7 @@ def json_data(self): return data -class BookAuthor(models.Model): +class BookAuthor(RestClientsModel): name = models.CharField(max_length=255) def json_data(self): @@ -188,7 +189,7 @@ def json_data(self): return data -class MockAmazonSQSQueue(models.Model): +class MockAmazonSQSQueue(RestClientsModel): name = models.CharField(max_length=80, unique=True, db_index=True) def new_message(self, body=""): @@ -223,7 +224,7 @@ def set_message_class(self, message_class): pass -class MockAmazonSQSMessage(models.Model): +class MockAmazonSQSMessage(RestClientsModel): body = models.CharField(max_length=8192) queue = models.ForeignKey(MockAmazonSQSQueue, on_delete=models.PROTECT) @@ -232,7 +233,7 @@ def get_body(self): return self.body -class SMSRequest(models.Model): +class SMSRequest(RestClientsModel): body = models.CharField(max_length=8192) to = models.CharField(max_length=40) from_number = models.CharField(max_length=40) @@ -247,11 +248,11 @@ def get_from_number(self): return self.from_number -class SMSResponse(models.Model): +class SMSResponse(RestClientsModel): body = models.TextField(max_length=8192) to = models.TextField(max_length=40) status = models.TextField(max_length=8192) - #all sms requests have some sort of response id + # all sms requests have some sort of response id rid = models.TextField(max_length=8192) def get_body(self): @@ -267,13 +268,13 @@ def get_rid(self): return self.id -class Notification(models.Model): +class Notification(RestClientsModel): subject = models.CharField(max_length=8192) short = models.CharField(max_length=140) # SMS max body length full = models.CharField(max_length=8192) -class CourseAvailableEvent(models.Model): +class CourseAvailableEvent(RestClientsModel): event_id = models.CharField(max_length=40) event_create_date = models.CharField(max_length=40) message_timestamp = models.CharField(max_length=40) diff --git a/restclients/models/base.py b/restclients/models/base.py new file mode 100644 index 00000000..c2370244 --- /dev/null +++ b/restclients/models/base.py @@ -0,0 +1,8 @@ +from django.db import models + + +class RestClientsModel(models.Model): + """Base class containing Meta attributes common to all models.""" + class Meta: + abstract = True + app_label = 'restclients' diff --git a/restclients/models/canvas.py b/restclients/models/canvas.py index 0a25b48b..34e37e9f 100644 --- a/restclients/models/canvas.py +++ b/restclients/models/canvas.py @@ -13,7 +13,8 @@ class Meta: class CanvasRole(models.Model): - role = models.CharField(max_length=200) + role_id = models.IntegerField(max_length=20) + label = models.CharField(max_length=200) base_role_type = models.CharField(max_length=200) workflow_state = models.CharField(max_length=50) @@ -30,12 +31,14 @@ class CanvasTerm(models.Model): def get_start_date(self): if self._start_date: return self._start_date - raise Exception("Need to fetch this from the SWS, or manually pre-populate") + raise Exception( + "Need to fetch this from the SWS, or manually pre-populate") def get_end_date(self): if self._end_date: return self._end_date - raise Exception("Need to fetch this from the SWS, or manually pre-populate") + raise Exception( + "Need to fetch this from the SWS, or manually pre-populate") class Meta: db_table = "restclients_canvas_term" @@ -53,6 +56,9 @@ class CanvasCourse(models.Model): public_syllabus = models.NullBooleanField() syllabus_body = models.TextField(null=True) + def is_unpublished(self): + return self.workflow_state.lower() == "unpublished" + def sws_course_id(self): if self.sis_course_id is None: return None @@ -122,18 +128,22 @@ class CanvasEnrollment(models.Model): role = models.CharField(max_length=80, choices=ROLE_CHOICES) status = models.CharField(max_length=100, choices=STATUS_CHOICES) name = models.CharField(max_length=100) + sortable_name = models.CharField(max_length=100) html_url = models.CharField(max_length=1000) sis_section_id = models.CharField(max_length=100, null=True) sis_course_id = models.CharField(max_length=100, null=True) course_url = models.CharField(max_length=2000, null=True) course_name = models.CharField(max_length=100, null=True) - current_score = models.DecimalField(max_digits=10, decimal_places=2, null=True) - final_score = models.DecimalField(max_digits=10, decimal_places=2, null=True) + current_score = models.DecimalField(max_digits=10, + decimal_places=2, null=True) + final_score = models.DecimalField(max_digits=10, + decimal_places=2, null=True) current_grade = models.TextField(max_length=12, null=True) final_grade = models.TextField(max_length=12, null=True) grade_html_url = models.CharField(max_length=1000) total_activity_time = models.IntegerField(max_length=10, null=True) last_activity_at = models.DateTimeField(null=True) + limit_privileges_to_course_section = models.NullBooleanField() def sws_course_id(self): if self.sis_course_id is None: @@ -322,7 +332,7 @@ class Quiz(models.Model): published = models.NullBooleanField() class Meta: - db_table ="restclients_canvas_quiz" + db_table = "restclients_canvas_quiz" class GradingStandard(models.Model): diff --git a/restclients/models/grad.py b/restclients/models/grad.py new file mode 100644 index 00000000..055b94eb --- /dev/null +++ b/restclients/models/grad.py @@ -0,0 +1,335 @@ +import datetime +from django.db import models + + +def get_datetime_str(datetime_obj): + if datetime_obj is None: + return None + return datetime_obj.isoformat() + + +class GradTerm(models.Model): + SPRING = 'spring' + SUMMER = 'summer' + AUTUMN = 'autumn' + WINTER = 'winter' + + QUARTERNAME_CHOICES = ( + (SPRING, 'Spring'), + (SUMMER, 'Summer'), + (AUTUMN, 'Autumn'), + (WINTER, 'Winter'), + ) + + quarter = models.CharField(max_length=6, + choices=QUARTERNAME_CHOICES) + year = models.PositiveSmallIntegerField() + + def __init__(self): + self.terms = [] + + def json_data(self): + return {"year": self.year, + "quarter": self.get_quarter_display(), + } + + +class GradDegree(models.Model): + AWAITING_STATUS_PREFIX = "Awaiting " + CANDIDACY_STATUS = "candidacy granted" + DEPT_RECOMMENDED_STATUS = "recommended by dept" + GRADUATED_STATUS = "graduated by grad school" + NOT_GRADUATE_STATUS = "did not graduate" + + req_type = models.CharField(max_length=100) + submit_date = models.DateTimeField() + degree_title = models.CharField(max_length=255) + major_full_name = models.CharField(max_length=255) + status = models.CharField(max_length=64) + decision_date = models.DateTimeField(null=True, default=None) + exam_place = models.CharField(max_length=255, null=True, default=None) + exam_date = models.DateTimeField(null=True, default=None) + target_award_year = models.PositiveSmallIntegerField() + target_award_quarter = models.CharField( + max_length=6, choices=GradTerm.QUARTERNAME_CHOICES) + + def is_status_graduated(self): + return self.status.lower() == self.GRADUATED_STATUS + + def is_status_candidacy(self): + return self.status.lower() == self.CANDIDACY_STATUS + + def is_status_not_graduate(self): + return self.status.lower() == self.NOT_GRADUATE_STATUS + + def is_status_await(self): + """ + return true if status is: + Awaiting Dept Action, + Awaiting Dept Action (Final Exam), + Awaiting Dept Action (General Exam) + """ + return self.status.startswith(self.AWAITING_STATUS_PREFIX) + + def is_status_recommended(self): + return self.status.lower() == self.DEPT_RECOMMENDED_STATUS + + def json_data(self): + return { + "req_type": self.req_type, + "degree_title": self.degree_title, + "exam_place": self.exam_place, + "exam_date": get_datetime_str(self.exam_date) + if self.exam_date is not None else None, + "major_full_name": self.major_full_name, + "status": self.status, + 'decision_date': get_datetime_str(self.decision_date) + if self.decision_date is not None else None, + "submit_date": get_datetime_str(self.submit_date), + "target_award_year": self.target_award_year, + "target_award_quarter": self.get_target_award_quarter_display() + if self.target_award_quarter is not None else None, + } + + +class GradCommitteeMember(models.Model): + CHAIR = "chair" + GSR = "gsr" + MEMBER = "member" + MEMBER_TYPE_CHOICES = ( + (CHAIR, "Chair"), + (GSR, "GSR"), + (MEMBER, None), + ) + READING_TYPE_CHOICES = ( + (CHAIR, "Reading Committee Chair"), + (MEMBER, "Reading Committee Member"), + ) + + first_name = models.CharField(max_length=96) + last_name = models.CharField(max_length=96) + member_type = models.CharField(max_length=64, + choices=MEMBER_TYPE_CHOICES) + reading_type = models.CharField(max_length=64, + null=True, + default=None, + choices=READING_TYPE_CHOICES) + dept = models.CharField(max_length=128, + null=True, + default=None) + email = models.CharField(max_length=255, + null=True, + default=None) + status = models.CharField(max_length=64) + + def __eq__(self, other): + return self.member_type == other.member_type and\ + self.last_name == other.last_name and\ + self.first_name == other.first_name + + def __ne__(self, other): + return self.member_type != other.member_type or\ + self.last_name != other.last_name or\ + self.first_name != other.first_name + + def __lt__(self, other): + return self.member_type == other.member_type and\ + self.last_name < other.last_name or\ + self.member_type < other.member_type + + def is_type_chair(self): + return self.member_type == self.CHAIR + + def is_type_gsr(self): + return self.member_type == self.GSR + + def is_type_member(self): + return self.member_type == self.MEMBER + + def is_reading_committee_member(self): + return self.reading_type is not None and\ + self.reading_type == self.MEMBER + + def is_reading_committee_chair(self): + return self.reading_type is not None and\ + self.reading_type == self.CHAIR + + def get_reading_display(self): + if self.is_reading_committee_chair() or\ + self.is_reading_committee_member(): + return self.get_reading_type_display() + return None + + def __str__(self): + return "%s: %s, %s: %s, %s: %s, %s: %s, %s: %s, %s: %s, %s: %s" %\ + ( + "member_type", self.member_type, + "reading_type", self.reading_type, + "last_name", self.last_name, + "first_name", self.first_name, + "dept", self.dept, + "email", self.email, + "status", self.status) + + def json_data(self): + return { + "first_name": self.first_name, + "last_name": self.last_name, + "member_type": self.get_member_type_display(), + "reading_type": self.get_reading_display(), + "dept": self.dept, + "email": self.email, + "status": self.status, + } + + +class GradCommittee(models.Model): + committee_type = models.CharField(max_length=64) + dept = models.CharField(max_length=255, null=True) + degree_title = models.CharField(max_length=255, null=True) + degree_type = models.CharField(max_length=255) + major_full_name = models.CharField(max_length=255) + status = models.CharField(max_length=64, null=True) + start_date = models.DateTimeField() + end_date = models.DateTimeField() + + def __init__(self): + self.members = [] # GradCommitteeMember + + def json_data(self): + data = { + "committee_type": self.committee_type, + "dept": self.dept, + "degree_title": self.degree_title, + "degree_type": self.degree_type, + "major_full_name": self.major_full_name, + "status": self.status, + "start_date": get_datetime_str(self.start_date) + if self.start_date is not None else None, + "end_date": get_datetime_str(self.end_date) + if self.end_date is not None else None, + "members": [], + } + for member in sorted(self.members): + data["members"].append(member.json_data()) + return data + + +class GradLeave(models.Model): + APPROVED = "approved" + DENIED = "denied" + PAID = "paid" + REQUESTED = "requested" + WITHDRAWN = "withdrawn" + + STATUS_CHOICES = ( + ("", None), + (APPROVED, "Approved"), + (DENIED, "Denied"), + (PAID, "Paid"), + (REQUESTED, "Requested"), + (WITHDRAWN, "Withdrawn"), + ) + reason = models.CharField(max_length=100, + db_index=True) + submit_date = models.DateTimeField() + status = models.CharField(max_length=50, + blank=True, + default="", + choices=STATUS_CHOICES) + + def __init__(self): + self.terms = [] + + def is_status_approved(self): + return self.status == self.APPROVED + + def is_status_denied(self): + return self.status == self.DENIED + + def is_status_paid(self): + return self.status == self.PAID + + def is_status_requested(self): + return self.status == self.REQUESTED + + def is_status_withdrawn(self): + return self.status == self.WITHDRAWN + + def json_data(self): + data = { + 'reason': self.reason, + 'submit_date': get_datetime_str(self.submit_date) + if self.submit_date is not None else None, + 'status': self.get_status_display(), + 'terms': [], + } + for term in self.terms: + data["terms"].append(term.json_data()) + return data + + +class GradPetition(models.Model): + APPROVE = "approve" + APPROVED = "approved" + DENY = "deny" + NOT_APPROVED = "not approved" + PENDING = "pending" + WITHDRAW = "withdraw" + WITHDRAWN = "withdrawn" + + RECOMMEND_CHOICES = ( + ("", None), + (APPROVE, "Approve"), + (DENY, "Deny"), + (PENDING, "Pending"), + (WITHDRAW, "Withdraw"), + ) + + DECISION_CHOICES = ( + ("", None), + (APPROVED, "Approved"), + (NOT_APPROVED, "Not approved"), + (PENDING, "Pending"), + (WITHDRAW, "Withdraw"), + (WITHDRAWN, "Withdrawn"), + ) + + description = models.CharField(max_length=100, + db_index=True) + submit_date = models.DateTimeField() + dept_recommend = models.CharField(max_length=50, + choices=RECOMMEND_CHOICES) + gradschool_decision = models.CharField(max_length=50, + null=True, + blank=True, + default="", + choices=DECISION_CHOICES) + decision_date = models.DateTimeField(null=True) + # gradschool decision date + + def is_dept_approve(self): + return self.dept_recommend == self.APPROVE + + def is_dept_deny(self): + return self.dept_recommend == self.DENY + + def is_dept_pending(self): + return self.dept_recommend == self.PENDING + + def is_dept_withdraw(self): + return self.dept_recommend == self.WITHDRAW + + def is_gs_pending(self): + return self.gradschool_decision == self.PENDING + + def json_data(self): + data = { + 'description': self.description, + 'submit_date': get_datetime_str(self.submit_date), + 'decision_date': get_datetime_str(self.decision_date) + if self.decision_date is not None else None, + 'dept_recommend': self.get_dept_recommend_display(), + 'gradschool_decision': self.get_gradschool_decision_display(), + } + return data diff --git a/restclients/models/gws.py b/restclients/models/gws.py index 7174f92e..63e08e6d 100644 --- a/restclients/models/gws.py +++ b/restclients/models/gws.py @@ -1,7 +1,8 @@ from django.db import models +from restclients.models.base import RestClientsModel -class GroupReference(models.Model): +class GroupReference(RestClientsModel): uwregid = models.CharField(max_length=32) name = models.CharField(max_length=500) title = models.CharField(max_length=500) @@ -13,7 +14,7 @@ def __str__(self): self.uwregid, self.name, self.title, self.description) -class Group(models.Model): +class Group(RestClientsModel): CLASSIFICATION_NONE = "u" CLASSIFICATION_PUBLIC = "p" CLASSIFICATION_RESTRICTED = "r" @@ -34,6 +35,7 @@ class Group(models.Model): title = models.CharField(max_length=500) description = models.CharField(max_length=2000, null=True) contact = models.CharField(max_length=120, null=True) + membership_modified = models.DateTimeField() authnfactor = models.PositiveSmallIntegerField(max_length=1, choices=((1, ""), (2, "")), default=1) @@ -49,6 +51,7 @@ class Group(models.Model): reporttoorig = models.SmallIntegerField(max_length=1, null=True, choices=((1, "Yes"), (0, "No")), default=0) + def __init__(self, *args, **kwargs): super(Group, self).__init__(*args, **kwargs) self.admins = [] @@ -65,13 +68,13 @@ def __str__(self): def has_regid(self): return self.uwregid is not None and len(self.uwregid) == 32 + class CourseGroup(Group): SPRING = "spring" SUMMER = "summer" AUTUMN = "autumn" WINTER = "winter" - QUARTERNAME_CHOICES = ( (SPRING, "Spring"), (SUMMER, "Summer"), @@ -90,7 +93,7 @@ class CourseGroup(Group): sln = models.PositiveIntegerField() -class GroupUser(models.Model): +class GroupUser(RestClientsModel): UWNETID_TYPE = "uwnetid" EPPN_TYPE = "eppn" GROUP_TYPE = "group" @@ -128,7 +131,8 @@ def __str__(self): return "{name: %s, user_type: %s}" % ( self.name, self.user_type) -class GroupMember(models.Model): + +class GroupMember(RestClientsModel): UWNETID_TYPE = "uwnetid" EPPN_TYPE = "eppn" GROUP_TYPE = "group" @@ -155,7 +159,8 @@ def is_group(self): return self.member_type == self.GROUP_TYPE def __eq__(self, other): - return self.name == other.name and self.member_type == other.member_type + return self.name == other.name and\ + self.member_type == other.member_type def __str__(self): return "{name: %s, user_type: %s}" % ( diff --git a/restclients/models/hfs.py b/restclients/models/hfs.py index 62780d1d..1184429f 100644 --- a/restclients/models/hfs.py +++ b/restclients/models/hfs.py @@ -98,15 +98,15 @@ def json_data(self, use_custom_date_format=False): } if self.student_husky_card is not None: - return_value['student_husky_card'] = self.student_husky_card.json_data( - use_custom_date_format) + return_value['student_husky_card'] =\ + self.student_husky_card.json_data(use_custom_date_format) if self.employee_husky_card is not None: - return_value['employee_husky_card'] = self.employee_husky_card.json_data( - use_custom_date_format) + return_value['employee_husky_card'] =\ + self.employee_husky_card.json_data(use_custom_date_format) if self.resident_dining is not None: - return_value['resident_dining'] = self.resident_dining.json_data( - use_custom_date_format) + return_value['resident_dining'] =\ + self.resident_dining.json_data(use_custom_date_format) return return_value diff --git a/restclients/models/iasystem.py b/restclients/models/iasystem.py index 85d6b057..392fbf77 100644 --- a/restclients/models/iasystem.py +++ b/restclients/models/iasystem.py @@ -3,9 +3,20 @@ class Evaluation(models.Model): section_sln = models.IntegerField(max_length=5) - instructor_id = models.IntegerField(max_length=9) eval_open_date = models.DateTimeField() eval_close_date = models.DateTimeField() + is_completed = models.NullBooleanField() eval_status = models.CharField(max_length=7) - eval_is_online = models.BooleanField(default=False) eval_url = models.URLField() + + def __init__(self, *args, **kwargs): + super(Evaluation, self).__init__(*args, **kwargs) + self.instructor_ids = [] + + def __str__(self): + return "{%s: %d, %s: %s, %s: %s, %s: %s, %s: %s}" % ( + "sln", self.section_sln, + "eval_open_date", self.eval_open_date, + "eval_close_date", self.eval_close_date, + "eval_url", self.eval_url, + "is_completed", self.is_completed) diff --git a/restclients/models/irws.py b/restclients/models/irws.py index b3698915..59833c7c 100644 --- a/restclients/models/irws.py +++ b/restclients/models/irws.py @@ -3,22 +3,23 @@ from django.template import Context, loader from base64 import b64encode, b64decode from datetime import datetime +from restclients.models.base import RestClientsModel from restclients.util.date_formator import abbr_week_month_day_str from restclients.exceptions import InvalidCanvasIndependentStudyCourse from restclients.exceptions import InvalidCanvasSection # IRWS Person Identity -class PersonIdentity(models.Model): +class PersonIdentity(RestClientsModel): regid = models.CharField(max_length=32) def __init__(self, *args, **kwargs): super(PersonIdentity, self).__init__(*args, **kwargs) self.identifiers = {} - + # IRWS Name -class Name(models.Model): +class Name(RestClientsModel): validid = models.CharField(max_length=32, unique=True) formal_cname = models.CharField(max_length=255) formal_fname = models.CharField(max_length=255) @@ -30,7 +31,6 @@ class Name(models.Model): display_lname = models.CharField(max_length=255) display_privacy = models.CharField(max_length=32) - def json_data(self): return {"formal_cname": self.formal_cname, "formal_fname": self.formal_fname, @@ -46,19 +46,15 @@ def json_data(self): def __eq__(self, other): return self.uwregid == other.uwregid - class Meta: - app_label = "restclients" - # IRWS Uwhr Person -class UwhrPerson(models.Model): +class UwhrPerson(RestClientsModel): validid = models.CharField(max_length=32, unique=True) regid = models.CharField(max_length=32, - db_index=True, - unique=True) + db_index=True, + unique=True) studentid = models.CharField(max_length=32) - fname = models.CharField(max_length=255) lname = models.CharField(max_length=255) category_code = models.CharField(max_length=4) @@ -118,8 +114,3 @@ def json_data(self): def __eq__(self, other): return self.uwregid == other.uwregid - - class Meta: - app_label = "restclients" - - diff --git a/restclients/models/kws.py b/restclients/models/kws.py new file mode 100644 index 00000000..2411dde9 --- /dev/null +++ b/restclients/models/kws.py @@ -0,0 +1,11 @@ +from django.db import models + + +class Key(models.Model): + algorithm = models.CharField(max_length=100) + cipher_mode = models.CharField(max_length=100) + expiration = models.DateTimeField() + key_id = models.CharField(max_length=100) + key = models.CharField(max_length=100) + size = models.SmallIntegerField(max_length=5) + url = models.CharField(max_length=1000) diff --git a/restclients/models/library.py b/restclients/models/library.py index 21b2d757..adf2979b 100644 --- a/restclients/models/library.py +++ b/restclients/models/library.py @@ -1,6 +1,37 @@ from django.db import models +class Library(models.Model): + """ Represents a library. + """ + name = models.CharField(max_length=250, null=True) + description = models.CharField(max_length=1000, null=True) + url = models.CharField(max_length=250, null=True) + + +class Librarian(models.Model): + """ Represents a librarian. + """ + name = models.CharField(max_length=250, null=True) + email = models.CharField(max_length=100, null=True) + phone = models.CharField(max_length=20, null=True) + url = models.CharField(max_length=250, null=True) + + +class SubjectGuide(models.Model): + contact_url = models.CharField(max_length=250, null=True) + contact_text = models.CharField(max_length=1000, null=True) + discipline = models.CharField(max_length=250, null=True) + librarian_url = models.CharField(max_length=250, null=True) + librarian_text = models.CharField(max_length=1000, null=True) + guide_url = models.CharField(max_length=250, null=True) + guide_text = models.CharField(max_length=1000, null=True) + faq_url = models.CharField(max_length=250, null=True) + faq_text = models.CharField(max_length=1000, null=True) + writing_guide_url = models.CharField(max_length=250, null=True) + writing_guide_text = models.CharField(max_length=1000, null=True) + + class MyLibAccount(models.Model): holds_ready = models.IntegerField(max_length=8) fines = models.DecimalField(max_digits=8, decimal_places=2) @@ -25,5 +56,6 @@ def json_data(self, } def __str__(self): - return "{next_due: %s, holds_ready: %d, fines: %.2f, items_loaned: %d}" % ( - self.next_due, self.holds_ready, self.fines, self.items_loaned) + return\ + "{next_due: %s, holds_ready: %d, fines: %.2f, items_loaned: %d}" %\ + (self.next_due, self.holds_ready, self.fines, self.items_loaned) diff --git a/restclients/models/myplan.py b/restclients/models/myplan.py index 0c6fda7a..24548e6b 100644 --- a/restclients/models/myplan.py +++ b/restclients/models/myplan.py @@ -1,16 +1,20 @@ from django.db import models + class MyPlan(models.Model): def __init__(self): self.terms = [] def json_data(self): - data = { "terms": [] } + data = { + "terms": [] + } for term in self.terms: data["terms"].append(term.json_data()) return data + class MyPlanTerm(models.Model): SPRING = 'spring' SUMMER = 'summer' @@ -43,6 +47,7 @@ def json_data(self): return data + class MyPlanCourse(models.Model): def __init__(self): self.sections = [] @@ -65,9 +70,12 @@ def json_data(self): return data + class MyPlanCourseSection(models.Model): section_id = models.CharField(max_length=2, db_index=True) def json_data(self): - return { "section_id": self.section_id } + return { + "section_id": self.section_id + } diff --git a/restclients/models/sws.py b/restclients/models/sws.py index 6a27366b..b0d801ec 100644 --- a/restclients/models/sws.py +++ b/restclients/models/sws.py @@ -3,13 +3,16 @@ from django.template import Context, loader from base64 import b64encode, b64decode from datetime import datetime -from restclients.util.date_formator import abbr_week_month_day_str from restclients.exceptions import InvalidCanvasIndependentStudyCourse from restclients.exceptions import InvalidCanvasSection +from restclients.util.date_formator import abbr_week_month_day_str +from restclients.util.datetime_convertor import convert_to_begin_of_day,\ + convert_to_end_of_day +from restclients.models.base import RestClientsModel # PWS Person -class Person(models.Model): +class Person(RestClientsModel): uwregid = models.CharField(max_length=32, db_index=True, unique=True) @@ -25,6 +28,10 @@ class Person(models.Model): full_name = models.CharField(max_length=250) display_name = models.CharField(max_length=250) + student_number = models.CharField(max_length=9) + student_system_key = models.SlugField(max_length=10) + employee_id = models.CharField(max_length=9) + is_student = models.NullBooleanField() is_staff = models.NullBooleanField() is_employee = models.NullBooleanField() @@ -43,6 +50,12 @@ class Person(models.Model): mailstop = models.CharField(max_length=255) title1 = models.CharField(max_length=255) title2 = models.CharField(max_length=255) + home_department = models.CharField(max_length=255) + + student_class = models.CharField(max_length=255) + student_department1 = models.CharField(max_length=255) + student_department2 = models.CharField(max_length=255) + student_department3 = models.CharField(max_length=255) def json_data(self): return {'uwnetid': self.uwnetid, @@ -63,17 +76,15 @@ def json_data(self): 'address1': self.address1, 'address2': self.address2, 'mailstop': self.mailstop, + 'home_department': self.home_department, } def __eq__(self, other): return self.uwregid == other.uwregid - class Meta: - app_label = "restclients" - # PWS Person -class Entity(models.Model): +class Entity(RestClientsModel): uwregid = models.CharField(max_length=32, db_index=True, unique=True) @@ -92,7 +103,7 @@ def __eq__(self, other): return self.uwregid == other.uwregid -class LastEnrolled(models.Model): +class LastEnrolled(RestClientsModel): href = models.CharField(max_length=200) quarter = models.CharField(max_length=16) year = models.PositiveSmallIntegerField() @@ -104,7 +115,7 @@ def json_data(self): } -class StudentAddress(models.Model): +class StudentAddress(RestClientsModel): city = models.CharField(max_length=255) country = models.CharField(max_length=255) street_line1 = models.CharField(max_length=255) @@ -130,7 +141,7 @@ def get_student_address_json(address): return None -class SwsPerson(models.Model): +class SwsPerson(RestClientsModel): uwregid = models.CharField(max_length=32, db_index=True, unique=True) @@ -146,18 +157,21 @@ class SwsPerson(models.Model): student_name = models.CharField(max_length=255) student_number = models.SlugField(max_length=16, null=True, blank=True) student_system_key = models.SlugField(max_length=16, null=True, blank=True) - last_enrolled = models.ForeignKey(LastEnrolled, - on_delete=models.PROTECT, - null=True) - local_address = models.ForeignKey(StudentAddress, - on_delete=models.PROTECT, - null=True, - related_name='local_address') + last_enrolled = models.ForeignKey( + LastEnrolled, + on_delete=models.PROTECT, + null=True) + local_address = models.ForeignKey( + StudentAddress, + on_delete=models.PROTECT, + null=True, + related_name='local_address') local_phone = models.CharField(max_length=64, null=True, blank=True) - permanent_address = models.ForeignKey(StudentAddress, - on_delete=models.PROTECT, - null=True, - related_name='permanent_address') + permanent_address = models.ForeignKey( + StudentAddress, + on_delete=models.PROTECT, + null=True, + related_name='permanent_address') permanent_phone = models.CharField(max_length=64, null=True, blank=True) visa_type = models.CharField(max_length=2, null=True, blank=True) @@ -173,13 +187,14 @@ def json_data(self): 'directory_release': self.directory_release, 'local_address': get_student_address_json(self.local_address), 'local_phone': self.local_phone, - 'permanent_address': get_student_address_json(self.permanent_address), + 'permanent_address': get_student_address_json( + self.permanent_address), 'permanent_phone': self.permanent_phone, 'visa_type': self.visa_type } -class Term(models.Model): +class Term(RestClientsModel): SPRING = 'spring' SUMMER = 'summer' AUTUMN = 'autumn' @@ -198,6 +213,7 @@ class Term(models.Model): last_day_add = models.DateField() last_day_drop = models.DateField() first_day_quarter = models.DateField(db_index=True) + census_day = models.DateField() last_day_instruction = models.DateField(db_index=True) aterm_last_date = models.DateField(blank=True) bterm_first_date = models.DateField(blank=True) @@ -216,8 +232,7 @@ class Term(models.Model): registration_period3_start = models.DateTimeField() registration_period3_end = models.DateTimeField() - class Meta: - app_label = "restclients" + class Meta(RestClientsModel.Meta): unique_together = ("year", "quarter") def __eq__(self, other): @@ -244,6 +259,56 @@ def get_week_of_term_for_date(self, date): return (days / 7) + def is_summer_quarter(self): + return self.quarter.lower() == "summer" + + def get_bod_first_day(self): + # returns a datetime object of the midnight at begining of day + return convert_to_begin_of_day(self.first_day_quarter) + + def get_bod_reg_period1_start(self): + return convert_to_begin_of_day(self.registration_period1_start) + + def get_bod_reg_period2_start(self): + return convert_to_begin_of_day(self.registration_period2_start) + + def get_bod_reg_period3_start(self): + return convert_to_begin_of_day(self.registration_period3_start) + + def get_eod_grade_submission(self): + # returns a datetime object of the midnight at end of day + return convert_to_end_of_day(self.grade_submission_deadline) + + def get_eod_aterm_last_day_add(self): + if not self.is_summer_quarter(): + return None + return convert_to_end_of_day(self.aterm_last_day_add) + + def get_eod_bterm_last_day_add(self): + if not self.is_summer_quarter(): + return None + return convert_to_end_of_day(self.bterm_last_day_add) + + def get_eod_last_day_add(self): + return convert_to_end_of_day(self.last_day_add) + + def get_eod_last_day_drop(self): + return convert_to_end_of_day(self.last_day_drop) + + def get_eod_census_day(self): + return convert_to_end_of_day(self.census_day) + + def get_eod_last_final_exam(self): + return convert_to_end_of_day(self.last_final_exam_date) + + def get_eod_last_instruction(self): + return convert_to_end_of_day(self.last_day_instruction) + + def get_eod_summer_aterm(self): + if not self.is_summer_quarter(): + return None + return convert_to_end_of_day(self.aterm_last_date) + def term_label(self): return "%s,%s" % (self.year, self.quarter) @@ -254,12 +319,15 @@ def json_data(self): return { 'quarter': self.get_quarter_display(), 'year': self.year, - 'last_final_exam_date': self.last_final_exam_date.strftime("%Y-%m-%d 23:59:59"), - 'grade_submission_deadline': self.grade_submission_deadline.strftime("%Y-%m-%d 23:59:59") + 'last_final_exam_date': self.last_final_exam_date.strftime( + "%Y-%m-%d 23:59:59"), + 'grade_submission_deadline': + self.grade_submission_deadline.strftime( + "%Y-%m-%d 23:59:59") } -class FinalExam(models.Model): +class FinalExam(RestClientsModel): is_confirmed = models.NullBooleanField() no_exam_or_nontraditional = models.NullBooleanField() start_date = models.DateTimeField(null=True, blank=True) @@ -267,13 +335,10 @@ class FinalExam(models.Model): building = models.CharField(max_length=20, null=True, blank=True) room_number = models.CharField(max_length=10, null=True, blank=True) - class Meta: - app_label = "restclients" - def json_data(self): data = { - "is_confirmed" : self.is_confirmed, - "no_exam_or_nontraditional" : self.no_exam_or_nontraditional, + "is_confirmed": self.is_confirmed, + "no_exam_or_nontraditional": self.no_exam_or_nontraditional, } if self.start_date: @@ -285,8 +350,10 @@ def json_data(self): return data -class Section(models.Model): - SUMMER_A_TERM = "A-term" +class Section(RestClientsModel): + SUMMER_A_TERM = "a-term" + SUMMER_B_TERM = "b-term" + SUMMER_FULL_TERM = "full-term" LMS_CANVAS = "CANVAS" LMS_CATALYST = "CATALYST" @@ -315,8 +382,8 @@ class Section(models.Model): term = models.ForeignKey(Term, on_delete=models.PROTECT) final_exam = models.ForeignKey(FinalExam, - on_delete=models.PROTECT, - null=True) + on_delete=models.PROTECT, + null=True) curriculum_abbr = models.CharField(max_length=6, db_index=True) @@ -344,16 +411,16 @@ class Section(models.Model): current_enrollment = models.IntegerField() auditors = models.IntegerField() -# These are for non-standard start/end dates - don't have those yet -# start_date = models.DateField() -# end_date = models.DateField() + # These are for non-standard start/end dates - don't have those yet + # start_date = models.DateField() + # end_date = models.DateField() -# We don't have final exam data yet :( -# final_exam_date = models.DateField() -# final_exam_start_time = models.TimeField() -# final_exam_end_time = models.TimeField() -# final_exam_building = models.CharField(max_length=5) -# final_exam_room_number = models.CharField(max_length=5) + # We don't have final exam data yet :( + # final_exam_date = models.DateField() + # final_exam_start_time = models.TimeField() + # final_exam_end_time = models.TimeField() + # final_exam_building = models.CharField(max_length=5) + # final_exam_room_number = models.CharField(max_length=5) primary_section_href = models.CharField( max_length=200, @@ -377,22 +444,27 @@ class Section(models.Model): student_grade = models.CharField(max_length=6, null=True, blank=True) grade_date = models.DateField(null=True, blank=True, default=None) - class Meta: - app_label = "restclients" + class Meta(RestClientsModel.Meta): unique_together = ('term', 'curriculum_abbr', 'course_number', 'section_id') def section_label(self): - return "%s,%s,%s,%s/%s" % (self.term.year, - self.term.quarter, self.curriculum_abbr, - self.course_number, self.section_id) + return "%s,%s,%s,%s/%s" % ( + self.term.year, + self.term.quarter, + self.curriculum_abbr, + self.course_number, + self.section_id) def primary_section_label(self): - return "%s,%s,%s,%s/%s" % (self.term.year, - self.term.quarter, self.primary_section_curriculum_abbr, - self.primary_section_course_number, self.primary_section_id) + return "%s,%s,%s,%s/%s" % ( + self.term.year, + self.term.quarter, + self.primary_section_curriculum_abbr, + self.primary_section_course_number, + self.primary_section_id) def get_instructors(self): instructors = [] @@ -414,7 +486,7 @@ def is_grade_submission_delegate(self, person): def is_grading_period_open(self): now = datetime.now() - if self.summer_term == self.SUMMER_A_TERM: + if self.is_summer_a_term(): open_date = self.term.aterm_grading_period_open else: open_date = self.term.grading_period_open @@ -423,18 +495,22 @@ def is_grading_period_open(self): def canvas_course_sis_id(self): if self.is_primary_section: - sis_id = "%s-%s-%s-%s" % (self.term.canvas_sis_id(), + sis_id = "%s-%s-%s-%s" % ( + self.term.canvas_sis_id(), self.curriculum_abbr.upper(), self.course_number, self.section_id.upper()) if self.is_independent_study: if self.independent_study_instructor_regid is None: - raise InvalidCanvasIndependentStudyCourse("Undefined " + - "instructor for independent study section: %s" % sis_id) + raise InvalidCanvasIndependentStudyCourse( + "Undefined " + + ("instructor for independent study section: %s" % + sis_id)) sis_id += "-%s" % self.independent_study_instructor_regid else: - sis_id = "%s-%s-%s-%s" % (self.term.canvas_sis_id(), + sis_id = "%s-%s-%s-%s" % ( + self.term.canvas_sis_id(), self.primary_section_curriculum_abbr.upper(), self.primary_section_course_number, self.primary_section_id.upper()) @@ -450,7 +526,8 @@ def canvas_section_sis_id(self): sis_id += "--" else: - sis_id = "%s-%s-%s-%s" % (self.term.canvas_sis_id(), + sis_id = "%s-%s-%s-%s" % ( + self.term.canvas_sis_id(), self.curriculum_abbr.upper(), self.course_number, self.section_id.upper()) @@ -466,6 +543,31 @@ def get_grade_date_str(self): return str(self.grade_date) return None + def is_summer_a_term(self): + return self.summer_term is not None and\ + len(self.summer_term) > 0 and\ + self.summer_term.lower() == self.SUMMER_A_TERM + + def is_summer_b_term(self): + return self.summer_term is not None and\ + len(self.summer_term) > 0 and\ + self.summer_term.lower() == self.SUMMER_B_TERM + + def is_half_summer_term(self): + return (self.is_summer_a_term() or + self.is_summer_b_term()) + + def is_full_summer_term(self): + return self.summer_term is not None and\ + len(self.summer_term) > 0 and\ + self.summer_term.lower() == self.SUMMER_FULL_TERM + + def is_same_summer_term(self, summer_term): + return (self.summer_term is None or len(self.summer_term) == 0) and\ + (summer_term is None or len(self.summer_term) == 0) or\ + self.summer_term is not None and summer_term is not None and\ + self.summer_term.lower() == summer_term.lower() + def json_data(self): data = { 'curriculum_abbr': self.curriculum_abbr, @@ -479,8 +581,8 @@ def json_data(self): 'summer_term': self.summer_term, 'start_date': '', 'end_date': '', - 'current_enrollment' : self.current_enrollment, - 'auditors' : self.auditors, + 'current_enrollment': self.current_enrollment, + 'auditors': self.auditors, 'meetings': [], 'credits': str(self.student_credits), 'is_auditor': self.is_auditor, @@ -497,7 +599,7 @@ def json_data(self): return data -class SectionReference(models.Model): +class SectionReference(RestClientsModel): term = models.ForeignKey(Term, on_delete=models.PROTECT) curriculum_abbr = models.CharField(max_length=6) @@ -507,12 +609,13 @@ class SectionReference(models.Model): blank=True) def section_label(self): - return "%s,%s,%s,%s/%s" % (self.term.year, - self.term.quarter, self.curriculum_abbr, - self.course_number, self.section_id) + return "%s,%s,%s,%s/%s" % ( + self.term.year, + self.term.quarter, self.curriculum_abbr, + self.course_number, self.section_id) -class SectionStatus(models.Model): +class SectionStatus(RestClientsModel): add_code_required = models.NullBooleanField() current_enrollment = models.IntegerField() current_registration_period = models.IntegerField() @@ -524,27 +627,24 @@ class SectionStatus(models.Model): space_available = models.IntegerField() is_open = models.CharField(max_length=6) - - class Meta: - app_label = "restclients" - def json_data(self): data = { - 'add_code_required' : self.add_code_required, - 'current_enrollment' : self.current_enrollment, - 'current_registration_period' : self.current_registration_period, - 'faculty_code_required' : self.faculty_code_required, - 'limit_estimated_enrollment' : self.limit_estimated_enrollment, - 'limit_estimate_enrollment_indicator' : self.limit_estimate_enrollment_indicator, - 'room_capacity' : self.room_capacity, - 'sln' : self.sln, - 'space_available' : self.space_available, - 'is_open' : self.status, + 'add_code_required': self.add_code_required, + 'current_enrollment': self.current_enrollment, + 'current_registration_period': self.current_registration_period, + 'faculty_code_required': self.faculty_code_required, + 'limit_estimated_enrollment': self.limit_estimated_enrollment, + 'limit_estimate_enrollment_indicator': + self.limit_estimate_enrollment_indicator, + 'room_capacity': self.room_capacity, + 'sln': self.sln, + 'space_available': self.space_available, + 'is_open': self.status, } return data -class Registration(models.Model): +class Registration(RestClientsModel): section = models.ForeignKey(Section, on_delete=models.PROTECT) person = models.ForeignKey(Person, @@ -552,7 +652,7 @@ class Registration(models.Model): is_active = models.NullBooleanField() -class SectionMeeting(models.Model): +class SectionMeeting(RestClientsModel): term = models.ForeignKey(Term, on_delete=models.PROTECT) section = models.ForeignKey(Section, @@ -574,10 +674,9 @@ class SectionMeeting(models.Model): meets_friday = models.NullBooleanField() meets_saturday = models.NullBooleanField() meets_sunday = models.NullBooleanField() -# instructor = models.ForeignKey(Instructor, on_delete=models.PROTECT) + # instructor = models.ForeignKey(Instructor, on_delete=models.PROTECT) - class Meta: - app_label = "restclients" + class Meta(RestClientsModel.Meta): unique_together = ('term', 'section', 'meeting_index') @@ -611,7 +710,7 @@ def json_data(self): return data -class StudentGrades(models.Model): +class StudentGrades(RestClientsModel): user = models.ForeignKey(Person) term = models.ForeignKey(Term) @@ -619,13 +718,15 @@ class StudentGrades(models.Model): credits_attempted = models.DecimalField(max_digits=3, decimal_places=1) non_grade_credits = models.DecimalField(max_digits=3, decimal_places=1) -class StudentCourseGrade(models.Model): - grade = models.CharField(max_length = 10) + +class StudentCourseGrade(RestClientsModel): + grade = models.CharField(max_length=10) credits = models.DecimalField(max_digits=3, decimal_places=1) section = models.ForeignKey(Section, on_delete=models.PROTECT) -class ClassSchedule(models.Model): + +class ClassSchedule(RestClientsModel): user = models.ForeignKey(Person) term = models.ForeignKey(Term, on_delete=models.PROTECT) @@ -643,49 +744,34 @@ def json_data(self): return data - class Meta: - app_label = "restclients" - -class Campus(models.Model): +class Campus(RestClientsModel): label = models.SlugField(max_length=15, unique=True) name = models.CharField(max_length=20) full_name = models.CharField(max_length=60) - class Meta: - app_label = "restclients" - -class College(models.Model): +class College(RestClientsModel): campus_label = models.SlugField(max_length=15) label = models.CharField(max_length=15, unique=True) name = models.CharField(max_length=60) full_name = models.CharField(max_length=60) - class Meta: - app_label = "restclients" - -class Department(models.Model): +class Department(RestClientsModel): college_label = models.CharField(max_length=15) label = models.CharField(max_length=15, unique=True) name = models.CharField(max_length=60) full_name = models.CharField(max_length=60) - class Meta: - app_label = "restclients" - -class Curriculum(models.Model): +class Curriculum(RestClientsModel): label = models.CharField(max_length=15, unique=True) name = models.CharField(max_length=60) full_name = models.CharField(max_length=60) - class Meta: - app_label = "restclients" - -class GradeRoster(models.Model): +class GradeRoster(RestClientsModel): section = models.ForeignKey(Section, on_delete=models.PROTECT) instructor = models.ForeignKey(Person, @@ -694,9 +780,12 @@ class GradeRoster(models.Model): allows_writing_credit = models.NullBooleanField() def graderoster_label(self): - return "%s,%s,%s,%s,%s,%s" % (self.section.term.year, - self.section.term.quarter, self.section.curriculum_abbr, - self.section.course_number, self.section.section_id, + return "%s,%s,%s,%s,%s,%s" % ( + self.section.term.year, + self.section.term.quarter, + self.section.curriculum_abbr, + self.section.course_number, + self.section.section_id, self.instructor.uwregid) def xhtml(self): @@ -707,11 +796,8 @@ def xhtml(self): }) return template.render(context) - class Meta: - app_label = "restclients" - -class GradeRosterItem(models.Model): +class GradeRosterItem(RestClientsModel): student_uwregid = models.CharField(max_length=32) student_first_name = models.CharField(max_length=100) student_surname = models.CharField(max_length=100) @@ -747,28 +833,19 @@ def __eq__(self, other): return (self.student_uwregid == other.student_uwregid and self.duplicate_code == other.duplicate_code) - class Meta: - app_label = "restclients" - -class GradeSubmissionDelegate(models.Model): +class GradeSubmissionDelegate(RestClientsModel): person = models.ForeignKey(Person, on_delete=models.PROTECT) delegate_level = models.CharField(max_length=20) - class Meta: - app_label = "restclients" - -class TimeScheduleConstruction(models.Model): +class TimeScheduleConstruction(RestClientsModel): campus = models.SlugField(max_length=15) is_on = models.NullBooleanField() - class Meta: - app_label = "restclients" - -class NoticeAttribute(models.Model): +class NoticeAttribute(RestClientsModel): name = models.CharField(max_length=100) data_type = models.CharField(max_length=100) @@ -790,30 +867,35 @@ def get_formatted_date_value(self): return None -class Notice(models.Model): +class Notice(RestClientsModel): notice_category = models.CharField(max_length=100) notice_content = models.TextField() notice_type = models.CharField(max_length=100) custom_category = models.CharField(max_length=100, default="Uncategorized" ) + # long_notice: if it is a short notice, this attribute + # will point to the corresponding long notice def json_data(self, include_abbr_week_month_day_format=False): attrib_data = [] for attrib in self.attributes: - if attrib.data_type == "date" and include_abbr_week_month_day_format: - attrib_data.append({'name': attrib.name, - 'data_type': attrib.data_type, - 'value': attrib.get_value(), - 'formatted_value': attrib.get_formatted_date_value() - }) + if attrib.data_type == "date" and\ + include_abbr_week_month_day_format: + attrib_data.append( + {'name': attrib.name, + 'data_type': attrib.data_type, + 'value': attrib.get_value(), + 'formatted_value': attrib.get_formatted_date_value() + }) else: - attrib_data.append({'name': attrib.name, - 'data_type': attrib.data_type, - 'value': attrib.get_value() - }) + attrib_data.append( + {'name': attrib.name, + 'data_type': attrib.data_type, + 'value': attrib.get_value() + }) data = { 'notice_content': self.notice_content, @@ -822,7 +904,7 @@ def json_data(self, include_abbr_week_month_day_format=False): return data -class Finance(models.Model): +class Finance(RestClientsModel): tuition_accbalance = models.FloatField() pce_accbalance = models.FloatField() @@ -836,19 +918,15 @@ def __str__(self): self.tuition_accbalance, self.pce_accbalance) -class Enrollment(models.Model): +class Enrollment(RestClientsModel): is_honors = models.NullBooleanField() class_level = models.CharField(max_length=100) regid = models.CharField(max_length=32, - db_index=True, - unique=True) - # majors - # minors - class Meta: - app_label = "restclients" + db_index=True, + unique=True) -class Major(models.Model): +class Major(RestClientsModel): degree_name = models.CharField(max_length=100) degree_abbr = models.CharField(max_length=100) full_name = models.CharField(max_length=100) @@ -866,7 +944,7 @@ def json_data(self): } -class Minor(models.Model): +class Minor(RestClientsModel): abbr = models.CharField(max_length=50) campus = models.CharField(max_length=8) name = models.CharField(max_length=100) diff --git a/restclients/models/trumba.py b/restclients/models/trumba.py index f8b51b6c..4eed534a 100644 --- a/restclients/models/trumba.py +++ b/restclients/models/trumba.py @@ -3,15 +3,18 @@ def is_bot(campus_code): - return campus_code is not None and campus_code == TrumbaCalendar.BOT_CAMPUS_CODE + return campus_code is not None and\ + campus_code == TrumbaCalendar.BOT_CAMPUS_CODE def is_sea(campus_code): - return campus_code is not None and campus_code == TrumbaCalendar.SEA_CAMPUS_CODE + return campus_code is not None and\ + campus_code == TrumbaCalendar.SEA_CAMPUS_CODE def is_tac(campus_code): - return campus_code is not None and campus_code == TrumbaCalendar.TAC_CAMPUS_CODE + return campus_code is not None and\ + campus_code == TrumbaCalendar.TAC_CAMPUS_CODE def is_valid_campus_code(campus_code): @@ -22,10 +25,11 @@ class TrumbaCalendar(models.Model): SEA_CAMPUS_CODE = 'sea' BOT_CAMPUS_CODE = 'bot' TAC_CAMPUS_CODE = 'tac' - CAMPUS_CHOICES = ((SEA_CAMPUS_CODE, 'Seattle'), - (BOT_CAMPUS_CODE, 'Bothell'), - (TAC_CAMPUS_CODE, 'Tacoma') - ) + CAMPUS_CHOICES = ( + (SEA_CAMPUS_CODE, 'Seattle'), + (BOT_CAMPUS_CODE, 'Bothell'), + (TAC_CAMPUS_CODE, 'Tacoma') + ) calendarid = models.PositiveIntegerField(primary_key=True) campus = models.CharField(max_length=3, choices=CAMPUS_CHOICES, @@ -45,6 +49,10 @@ def is_tac(self): def __eq__(self, other): return self.calendarid == other.calendarid + def __lt__(self, other): + return self.campus == other.campus and\ + self.name < other.name + def __str__(self): return "{name: %s, campus: %s, calendarid: %s}" % ( self.name, self.campus, self.calendarid) @@ -70,11 +78,18 @@ def make_group_title(calendar_name, gtype): return "%s calendar %s group" % (calendar_name, gtype) +editor_group_desc =\ + "Specifying the editors who can add/edit/delete events on this calendar" +showon_group_desc =\ + "Specifying the editor groups whose members have the showon permissions" +\ + " on this calendar" + + def make_group_desc(gtype): if is_editor_group(gtype): - return "Specifying the editors who can add/edit/delete events on this calendar" + return editor_group_desc else: - return "Specifying the editor groups whose members have the showon permissions on this calendar" + return showon_group_desc class UwcalGroup(models.Model): @@ -150,13 +165,30 @@ def is_republish_permission(level): return level is not None and level == Permission.REPUBLISH +def is_view_permission(level): + return level is not None and level == Permission.VIEW + + +def is_higher_permission(level1, level2): + """ + Return True if the level1 is higher than level2 + """ + return (is_publish_permission(level1) and + not is_publish_permission(level2) or + (is_edit_permission(level1) and + not is_publish_permission(level2) and + not is_edit_permission(level2)) or + (is_showon_permission(level1) and + is_view_permission(level2))) + + class Permission(models.Model): - EDIT = 'EDIT' - NONE = 'NONE' PUBLISH = 'PUBLISH' + EDIT = 'EDIT' REPUBLISH = 'REPUBLISH' SHOWON = 'SHOWON' VIEW = 'VIEW' + NONE = 'NONE' LEVEL_CHOICES = ((EDIT, 'Can add, delete and change content'), (PUBLISH, 'Can view, edit and publish'), (REPUBLISH, 'Can view, edit and republish'), @@ -175,11 +207,18 @@ class Permission(models.Model): def get_trumba_userid(self): return "%s@washington.edu" % self.uwnetid + def is_publish(self): + return is_publish_permission(self.level) + def is_edit(self): - return is_edit_permission(self.level) or is_publish_permission(self.level) + return is_edit_permission(self.level) or self.is_publish() def is_showon(self): - return is_showon_permission(self.level) or is_republish_permission(self.level) + return is_showon_permission(self.level) or\ + is_republish_permission(self.level) + + def is_gt_level(self, perm_level): + return is_higher_permission(self.level, perm_level) def is_bot(self): return is_bot(self.campus) @@ -191,13 +230,20 @@ def is_tac(self): return is_tac(self.campus) def __eq__(self, other): - # noinspection PyPep8,PyPep8 - return self.calendarid == other.calendarid and self.uwnetid == other.uwnetid and self.name == other.name and self.level == other.level + return self.calendarid == other.calendarid and\ + self.uwnetid == other.uwnetid and\ + self.name == other.name and self.level == other.level + + def __lt__(self, other): + return self.calendarid == other.calendarid and\ + (is_higher_permission(self.level, other.level) or + self.level == other.level and + self.uwnetid < other.uwnetid) def __str__(self): return "{calendarid: %s, campus: %s, uwnetid: %s, level: %s}" % ( self.calendarid, self.campus, self.uwnetid, self.level) def __unicode__(self): - return u'{calendarid: %s, campus: %s, uwnetid: %s, name: %s, level: %s}' % ( - self.calendarid, self.campus, self.uwnetid, self.name, self.level) + return u'{calendarid: %s, campus: %s, uwnetid: %s, level: %s}' % ( + self.calendarid, self.campus, self.uwnetid, self.level) diff --git a/restclients/models/uwnetid.py b/restclients/models/uwnetid.py index 4847ea26..62346cae 100644 --- a/restclients/models/uwnetid.py +++ b/restclients/models/uwnetid.py @@ -1,5 +1,6 @@ from django.db import models + class UwEmailForwarding(models.Model): fwd = models.CharField(max_length=64, null=True) permitted = models.NullBooleanField() diff --git a/restclients/pws.py b/restclients/pws.py index ed9bd9c3..f0abb3a1 100644 --- a/restclients/pws.py +++ b/restclients/pws.py @@ -3,9 +3,9 @@ """ from restclients.dao import PWS_DAO -from restclients.exceptions import InvalidRegID, InvalidNetID, InvalidEmployeeID -from restclients.exceptions import InvalidIdCardPhotoSize -from restclients.exceptions import DataFailureException +from restclients.exceptions import InvalidRegID, InvalidNetID,\ + InvalidEmployeeID, InvalidStudentNumber, InvalidIdCardPhotoSize,\ + DataFailureException from restclients.models.sws import Person, Entity from StringIO import StringIO from urllib import urlencode @@ -13,6 +13,10 @@ import re +PERSON_PREFIX = '/identity/v1/person' +ENTITY_PREFIX = '/identity/v1/entity' + + class PWS(object): """ The PWS object has methods for getting person information. @@ -21,21 +25,24 @@ def __init__(self, actas=None): self.actas = actas self._re_regid = re.compile(r'^[A-F0-9]{32}$', re.I) self._re_personal_netid = re.compile(r'^[a-z][a-z0-9]{0,7}$', re.I) - self._re_admin_netid = re.compile(r'^[a-z]adm_[a-z][a-z0-9]{0,7}$', re.I) - self._re_application_netid = re.compile(r'^a_[a-z0-9\-\_\.$.]{1,18}$', re.I) + self._re_admin_netid = re.compile(r'^[a-z]adm_[a-z][a-z0-9]{0,7}$', + re.I) + self._re_application_netid = re.compile(r'^a_[a-z0-9\-\_\.$.]{1,18}$', + re.I) self._re_employee_id = re.compile(r'^\d{9}$') + self._re_student_number = re.compile(r'^\d{7}$') def get_person_by_regid(self, regid): """ Returns a restclients.Person object for the given regid. If the - regid isn't found, nothing will be returned. If there is an error - communicating with the PWS, a DataFailureException will be thrown. + regid isn't found, or if there is an error communicating with the PWS, + a DataFailureException will be thrown. """ if not self.valid_uwregid(regid): raise InvalidRegID(regid) dao = PWS_DAO() - url = "/identity/v1/person/%s/full.json" % regid.upper() + url = "%s/%s/full.json" % (PERSON_PREFIX, regid.upper()) response = dao.getURL(url, {"Accept": "application/json"}) if response.status != 200: @@ -46,14 +53,14 @@ def get_person_by_regid(self, regid): def get_person_by_netid(self, netid): """ Returns a restclients.Person object for the given netid. If the - netid isn't found, nothing will be returned. If there is an error - communicating with the PWS, a DataFailureException will be thrown. + netid isn't found, or if there is an error communicating with the PWS, + a DataFailureException will be thrown. """ if not self.valid_uwnetid(netid): raise InvalidNetID(netid) dao = PWS_DAO() - url = "/identity/v1/person/%s/full.json" % netid.lower() + url = "%s/%s/full.json" % (PERSON_PREFIX, netid.lower()) response = dao.getURL(url, {"Accept": "application/json"}) if response.status != 200: @@ -64,13 +71,14 @@ def get_person_by_netid(self, netid): def get_person_by_employee_id(self, employee_id): """ Returns a restclients.Person object for the given employee id. If the - employee id isn't found, nothing will be returned. If there is an error - communicating with the PWS, a DataFailureException will be thrown. + employee id isn't found, or if there is an error communicating with the + PWS, a DataFailureException will be thrown. """ if not self.valid_employee_id(employee_id): raise InvalidEmployeeID(employee_id) - url = "/identity/v1/person.json?%s" % urlencode({"employee_id": employee_id}) + url = "%s.json?%s" % (PERSON_PREFIX, + urlencode({"employee_id": employee_id})) response = PWS_DAO().getURL(url, {"Accept": "application/json"}) if response.status != 200: @@ -78,21 +86,47 @@ def get_person_by_employee_id(self, employee_id): # Search does not return a full person resource data = json.loads(response.data) - if len(data["Persons"]): - regid = data["Persons"][0]["PersonURI"]["UWRegID"] - return self.get_person_by_regid(regid) + if not len(data["Persons"]): + raise DataFailureException(url, 404, "No person found") + + regid = data["Persons"][0]["PersonURI"]["UWRegID"] + return self.get_person_by_regid(regid) + + def get_person_by_student_number(self, student_number): + """ + Returns a restclients.Person object for the given student number. If + the student number isn't found, or if there is an error communicating + with the PWS, a DataFailureException will be thrown. + """ + if not self.valid_student_number(student_number): + raise InvalidStudentNumber(student_number) + + url = "%s.json?%s" % (PERSON_PREFIX, + urlencode({"student_number": student_number})) + response = PWS_DAO().getURL(url, {"Accept": "application/json"}) + + if response.status != 200: + raise DataFailureException(url, response.status, response.data) + + # Search does not return a full person resource + data = json.loads(response.data) + if not len(data["Persons"]): + raise DataFailureException(url, 404, "No person found") + + regid = data["Persons"][0]["PersonURI"]["UWRegID"] + return self.get_person_by_regid(regid) def get_entity_by_regid(self, regid): """ Returns a restclients.Entity object for the given regid. If the - regid isn't found, nothing will be returned. If there is an error - communicating with the PWS, a DataFailureException will be thrown. + regid isn't found, or if there is an error communicating with the PWS, + a DataFailureException will be thrown. """ if not self.valid_uwregid(regid): raise InvalidRegID(regid) dao = PWS_DAO() - url = "/identity/v1/entity/%s.json" % regid.upper() + url = "%s/%s.json" % (ENTITY_PREFIX, regid.upper()) response = dao.getURL(url, {"Accept": "application/json"}) if response.status != 200: @@ -103,14 +137,14 @@ def get_entity_by_regid(self, regid): def get_entity_by_netid(self, netid): """ Returns a restclients.Entity object for the given netid. If the - netid isn't found, nothing will be returned. If there is an error - communicating with the PWS, a DataFailureException will be thrown. + netid isn't found, or if there is an error communicating with the PWS, + a DataFailureException will be thrown. """ if not self.valid_uwnetid(netid): raise InvalidNetID(netid) dao = PWS_DAO() - url = "/identity/v1/entity/%s.json" % netid.lower() + url = "%s/%s.json" % (ENTITY_PREFIX, netid.lower()) response = dao.getURL(url, {"Accept": "application/json"}) if response.status != 200: @@ -126,7 +160,7 @@ def get_contact(self, regid): raise InvalidRegID(regid) dao = PWS_DAO() - url = "/identity/v1/person/%s/full.json" % regid.upper() + url = "%s/%s/full.json" % (PERSON_PREFIX, regid.upper()) response = dao.getURL(url, {"Accept": "application/json"}) if response.status == 404: @@ -171,9 +205,9 @@ def get_idcard_photo(self, regid, size="medium"): def valid_uwnetid(self, netid): uwnetid = str(netid) - return (self._re_personal_netid.match(uwnetid) != None - or self._re_admin_netid.match(uwnetid) != None - or self._re_application_netid.match(uwnetid) != None) + return (self._re_personal_netid.match(uwnetid) is not None or + self._re_admin_netid.match(uwnetid) is not None or + self._re_application_netid.match(uwnetid) is not None) def valid_uwregid(self, regid): return True if self._re_regid.match(str(regid)) else False @@ -181,6 +215,10 @@ def valid_uwregid(self, regid): def valid_employee_id(self, employee_id): return True if self._re_employee_id.match(str(employee_id)) else False + def valid_student_number(self, student_number): + return True if ( + self._re_student_number.match(str(student_number))) else False + def _person_from_json(self, data): """ Internal method, for creating the Person object. @@ -196,39 +234,69 @@ def _person_from_json(self, data): person.full_name = person_data["RegisteredName"] person.display_name = person_data["DisplayName"] + person_affiliations = person_data.get('PersonAffiliations') + if person_affiliations is not None: + student_affiliations = (person_affiliations + .get('StudentPersonAffiliation')) + if student_affiliations is not None: + person.student_number = (student_affiliations + .get('StudentNumber')) + person.student_system_key = (student_affiliations + .get('StudentSystemKey')) + employee_affiliations = (person_affiliations + .get('EmployeePersonAffiliation')) + if employee_affiliations is not None: + person.employee_id = (employee_affiliations + .get('EmployeeID')) + for affiliation in person_data["EduPersonAffiliations"]: if affiliation == "student": person.is_student = True - if affiliation == "staff": + elif affiliation == "alum": + person.is_alum = True + elif affiliation == "staff": person.is_staff = True - if affiliation == "faculty": + elif affiliation == "faculty": person.is_faculty = True - if affiliation == "employee": + elif affiliation == "employee": person.is_employee = True - # This is for MUWM-417 - affiliations = person_data["PersonAffiliations"] - if "EmployeePersonAffiliation" in affiliations: - employee = affiliations["EmployeePersonAffiliation"] - white_pages = employee["EmployeeWhitePages"] - - if not white_pages["PublishInDirectory"]: - person.whitepages_publish = False - else: - person.email1 = white_pages["Email1"] - person.email2 = white_pages["Email2"] - person.phone1 = white_pages["Phone1"] - person.phone2 = white_pages["Phone2"] - person.title1 = white_pages["Title1"] - person.title2 = white_pages["Title2"] - person.voicemail = white_pages["VoiceMail"] - person.fax = white_pages["Fax"] - person.touchdial = white_pages["TouchDial"] - person.address1 = white_pages["Address1"] - person.address2 = white_pages["Address2"] - person.mailstop = employee["MailStop"] - if affiliation == "alum": - person.is_alum = True + affiliations = person_data["PersonAffiliations"] + if "EmployeePersonAffiliation" in affiliations: + employee = affiliations["EmployeePersonAffiliation"] + person.mailstop = employee["MailStop"] + person.home_department = employee["HomeDepartment"] + + white_pages = employee["EmployeeWhitePages"] + if not white_pages["PublishInDirectory"]: + person.whitepages_publish = False + else: + person.email1 = white_pages["Email1"] + person.email2 = white_pages["Email2"] + person.phone1 = white_pages["Phone1"] + person.phone2 = white_pages["Phone2"] + person.title1 = white_pages["Title1"] + person.title2 = white_pages["Title2"] + person.voicemail = white_pages["VoiceMail"] + person.fax = white_pages["Fax"] + person.touchdial = white_pages["TouchDial"] + person.address1 = white_pages["Address1"] + person.address2 = white_pages["Address2"] + if "StudentPersonAffiliation" in affiliations and person.is_student: + student = affiliations["StudentPersonAffiliation"] + if "StudentWhitePages" in student: + white_pages = student["StudentWhitePages"] + if "Class" in white_pages: + person.student_class = white_pages["Class"] + if "Department1" in white_pages: + person.student_department1 = (white_pages + .get('Department1')) + if "Department2" in white_pages: + person.student_department2 = (white_pages + .get('Department2')) + if "Department3" in white_pages: + person.student_department3 = (white_pages + .get('Department3')) return person diff --git a/restclients/resources/book/file/myuw/myuw_mobile_beta.ubs_quarter_autumn_sln1_19187 b/restclients/resources/book/file/myuw/myuw_mobile_beta.ubs_quarter_autumn_sln1_19187 new file mode 100644 index 00000000..ec8366a5 --- /dev/null +++ b/restclients/resources/book/file/myuw/myuw_mobile_beta.ubs_quarter_autumn_sln1_19187 @@ -0,0 +1,32 @@ +{ + "19187": [ + { + "isbn": "9780878935970", + "title": "Principles Of Conservation Biology (3e 06)", + "authors": [ + { + "name": "Groom" + } + ], + "price": null, + "used_price": null, + "required": true, + "notes": "required", + "cover_image": "www7.bookstore.washington.edu/MyUWImage.taf?isbn=9780878935970&key=46c9ef715edb2ec69517e2c8e6ec9c18" + }, + { + "isbn": "9781934931400", + "title": "Response Card Rf Lcd Radio Frequency ( Clicker )", + "authors": [ + { + "name": "Turning Technologies" + } + ], + "price": 44, + "used_price": null, + "required": true, + "notes": "required", + "cover_image": "www7.bookstore.washington.edu/MyUWImage.taf?isbn=9781934931400&key=f66964a1341ff6e6518530eee30209a4" + } + ] +} \ No newline at end of file diff --git a/restclients/resources/book/file/myuw/myuw_mobile_beta.ubs_quarter_autumn_sln1_19187_sln2_19222_sln3_19223_sln4_19224_sln5_19226 b/restclients/resources/book/file/myuw/myuw_mobile_beta.ubs_quarter_autumn_sln1_19187_sln2_19222_sln3_19223_sln4_19224_sln5_19226 deleted file mode 100644 index 733912f9..00000000 --- a/restclients/resources/book/file/myuw/myuw_mobile_beta.ubs_quarter_autumn_sln1_19187_sln2_19222_sln3_19223_sln4_19224_sln5_19226 +++ /dev/null @@ -1,72 +0,0 @@ -{ - "19187": [], - - "19222": [], - - "19223": [], - - "19224": [{ - "isbn":"9780878935970", - "title":"Principles Of Conservation Biology (3e 06)", - "authors": [ - {"name":"Groom" } - ], - "price":null, - "used_price":null, - "required":true, - "notes":"required", - "cover_image":"www7.bookstore.washington.edu/MyUWImage.taf?isbn=9780878935970&key=46c9ef715edb2ec69517e2c8e6ec9c18" - }, - { - "isbn":"9781934931400", - "title":"Response Card Rf Lcd Radio Frequency ( Clicker )", - "authors": [ - {"name":"Turning Technologies" } - ], - "price":44.00, - "used_price":null, - "required":true, - "notes":"required", - "cover_image":"www7.bookstore.washington.edu/MyUWImage.taf?isbn=9781934931400&key=f66964a1341ff6e6518530eee30209a4" - }], - - "19226": [{ - "isbn":"9780312666781", - "title":"2 P/s Brief Ed, Acts Of Inquiry W/ Getting The Picture", - "authors": [ - {"name":"Bawarshi" } - ], - "price":36.00, - "used_price":null, - "required":true, - "notes":"required", - "cover_image":"www7.bookstore.washington.edu/MyUWImage.taf?isbn=9780312666781&key=ba57b128a107b6e1a20471cceb9d771f" - }, - - { - "isbn":"9781457600043", - "title":"Everyday Writer (5e 13)", - "authors": [ - {"name":"Lunsford" } - ], - "price":null, - "used_price":null, - "required":false, - "notes":"", - "cover_image":"www7.bookstore.washington.edu/MyUWImage.taf?isbn=9781457600043&key=37cf9eee8ed81537adeae52f206bef0f" - }, - - { - "isbn":"9780312591564", - "title":"Four Year Access Card Writers Help ( Writer's )", - "authors": [ - {"name":"Hacker" } - ], - "price":39.50, - "used_price":null, - "required":false, - "notes":"", - "cover_image":"www7.bookstore.washington.edu/MyUWImage.taf?isbn=9780312591564&key=3267f72397de9087518530eee30209a4" - }] - } - diff --git a/restclients/resources/book/file/myuw/myuw_mobile_beta.ubs_quarter_spring_sln1_13830 b/restclients/resources/book/file/myuw/myuw_mobile_beta.ubs_quarter_spring_sln1_13830 new file mode 100644 index 00000000..286bb0f0 --- /dev/null +++ b/restclients/resources/book/file/myuw/myuw_mobile_beta.ubs_quarter_spring_sln1_13830 @@ -0,0 +1,32 @@ +{ + "13830": [ + { + "isbn": "9780878935970", + "title": "Principles Of Conservation Biology (3e 06)", + "authors": [ + { + "name": "Groom" + } + ], + "price": null, + "used_price": null, + "required": true, + "notes": "required", + "cover_image": "www7.bookstore.washington.edu/MyUWImage.taf?isbn=9780878935970&key=46c9ef715edb2ec69517e2c8e6ec9c18" + }, + { + "isbn": "9781934931400", + "title": "Response Card Rf Lcd Radio Frequency ( Clicker )", + "authors": [ + { + "name": "Turning Technologies" + } + ], + "price": 44, + "used_price": null, + "required": true, + "notes": "required", + "cover_image": "www7.bookstore.washington.edu/MyUWImage.taf?isbn=9781934931400&key=f66964a1341ff6e6518530eee30209a4" + } + ] +} \ No newline at end of file diff --git a/restclients/resources/book/file/myuw/myuw_mobile_beta.ubs_quarter_spring_sln1_13830_sln2_13833_sln3_18529_sln4_18532_sln5_18545 b/restclients/resources/book/file/myuw/myuw_mobile_beta.ubs_quarter_spring_sln1_13830_sln2_13833_sln3_18529_sln4_18532_sln5_18545 deleted file mode 100644 index 7dc55717..00000000 --- a/restclients/resources/book/file/myuw/myuw_mobile_beta.ubs_quarter_spring_sln1_13830_sln2_13833_sln3_18529_sln4_18532_sln5_18545 +++ /dev/null @@ -1,74 +0,0 @@ -{ - "13830": [], - - "13833": [], - - "18529": [], - - "18532": [], - - "18545": [{ - "isbn":"9780878935970", - "title":"Principles Of Conservation Biology (3e 06)", - "authors": [ - {"name":"Groom" } - ], - "price":null, - "used_price":null, - "required":true, - "notes":"required", - "cover_image":"www7.bookstore.washington.edu/MyUWImage.taf?isbn=9780878935970&key=46c9ef715edb2ec69517e2c8e6ec9c18" - }, - { - "isbn":"9781934931400", - "title":"Response Card Rf Lcd Radio Frequency ( Clicker )", - "authors": [ - {"name":"Turning Technologies" } - ], - "price":44.00, - "used_price":null, - "required":true, - "notes":"required", - "cover_image":"www7.bookstore.washington.edu/MyUWImage.taf?isbn=9781934931400&key=f66964a1341ff6e6518530eee30209a4" - }], - - "19226": [{ - "isbn":"9780312666781", - "title":"2 P/s Brief Ed, Acts Of Inquiry W/ Getting The Picture", - "authors": [ - {"name":"Bawarshi" } - ], - "price":36.00, - "used_price":null, - "required":true, - "notes":"required", - "cover_image":"www7.bookstore.washington.edu/MyUWImage.taf?isbn=9780312666781&key=ba57b128a107b6e1a20471cceb9d771f" - }, - - { - "isbn":"9781457600043", - "title":"Everyday Writer (5e 13)", - "authors": [ - {"name":"Lunsford" } - ], - "price":null, - "used_price":null, - "required":false, - "notes":"", - "cover_image":"www7.bookstore.washington.edu/MyUWImage.taf?isbn=9781457600043&key=37cf9eee8ed81537adeae52f206bef0f" - }, - - { - "isbn":"9780312591564", - "title":"Four Year Access Card Writers Help ( Writer's )", - "authors": [ - {"name":"Hacker" } - ], - "price":39.50, - "used_price":null, - "required":false, - "notes":"", - "cover_image":"www7.bookstore.washington.edu/MyUWImage.taf?isbn=9780312591564&key=3267f72397de9087518530eee30209a4" - }] - } - diff --git a/restclients/resources/book/file/myuw/myuw_mobile_beta.ubs_quarter_spring_sln1_13833 b/restclients/resources/book/file/myuw/myuw_mobile_beta.ubs_quarter_spring_sln1_13833 new file mode 100644 index 00000000..3a1d8e1d --- /dev/null +++ b/restclients/resources/book/file/myuw/myuw_mobile_beta.ubs_quarter_spring_sln1_13833 @@ -0,0 +1,3 @@ +{ + "13833": [] +} \ No newline at end of file diff --git a/restclients/resources/book/file/myuw/myuw_mobile_beta.ubs_quarter_summer_sln1_13830_sln2_13833_sln3_18529_sln4_18532_sln5_18545 b/restclients/resources/book/file/myuw/myuw_mobile_beta.ubs_quarter_summer_sln1_13830_sln2_13833_sln3_18529_sln4_18532_sln5_18545 deleted file mode 100644 index 05efd888..00000000 --- a/restclients/resources/book/file/myuw/myuw_mobile_beta.ubs_quarter_summer_sln1_13830_sln2_13833_sln3_18529_sln4_18532_sln5_18545 +++ /dev/null @@ -1,54 +0,0 @@ -{ - "13830": [], - "18529": [{ - "isbn":"9781256396062", - "title":"2 P/S Tutorials In Introductory Physics", - "authors": [{"name":"Mcdermott"}], - "price":58.00, - "used_price":null, - "required":true, - "notes":null, - "cover_image": null - },{ - "isbn":"9781464144554", - "title":"2 P/S V.1 Loose Leaf Physics F/ Scientists & Engineers (6 E 09) (Custom) W/ Smartphysics2", - "authors": [{"name":"Tipler"}], - "price":69.00, - "used_price":null, - "required":true, - "notes":null, - "cover_image": null - },{ - "isbn":"9780738050423", - "title":"Phys 121, Mechanics Lab (Custom)", - "authors": [{"name":"Hayden Mcneil"}], - "price":18.50, - "used_price":62.45, - "required":true, - "notes":null, - "cover_image": null - },{ - "isbn":"9788123231006", - "title":"Tx3100 Rf Transceiver", - "authors": [{"name":"H Itt"}], - "price":47.25, - "used_price":35.45, - "required":true, - "notes":null, - "cover_image": null - }], - "18532": [], - "18545": [], - "13833": [ - { - "isbn":"978-0136131069", - "title":"Quantum Chemistry (6th Edition)", - "authors": [{"name":"Ira N. Levine"}], - "price":175.00, - "used_price":null, - "required":true, - "notes":"Not sure what would go here, other than required", - "cover_image": "http://s3.amazonaws.com/semesterly/covers/92093d45-7a04-5d77-8f33-c098e1635a76.jpg" - } - ] -} diff --git a/restclients/resources/canvas/file/api/v1/accounts/12345/external_tools/index.html b/restclients/resources/canvas/file/api/v1/accounts/12345/external_tools/index.html new file mode 100644 index 00000000..62d2e733 --- /dev/null +++ b/restclients/resources/canvas/file/api/v1/accounts/12345/external_tools/index.html @@ -0,0 +1,10 @@ +[{"consumer_key":"AAAAAAAAAAAAAAAAAAAA","created_at":"2012-12-27T16:23:53Z","description":"A very handy tool.","domain":null,"id":1234,"name":"Tool","updated_at":"2018-01-31T21:25:08Z","url":"https://hostname.org/launch","privacy_level":"public","custom_fields":{},"workflow_state":"public","vendor_help_link":null,"user_navigation":null,"course_navigation":{"url":"https://other-hostname.com/launch","text":"Tool","visibility":"admins","label":"Tool","selection_width":800,"selection_height":400},"account_navigation":{"url":"https://other-hostname.com/launch","text":"Tool","visibility":"admins","label":"Tool","selection_width":800,"selection_height":400},"resource_selection":null,"editor_button":null,"homework_submission":null,"migration_selection":null,"course_home_sub_navigation":null,"course_settings_sub_navigation":null,"global_navigation":null,"assignment_menu":null,"file_menu":null,"discussion_topic_menu":null,"module_menu":null,"quiz_menu":null,"wiki_page_menu":null,"tool_configuration":null,"link_selection":null,"assignment_selection":null,"post_grades":null,"not_selectable":false}, +{"consumer_key":"AAAAAAAAAAAAAAAAAAAA","created_at":"2012-12-27T16:23:53Z","description":"A very handy tool.","domain":null,"id":1234,"name":"Tool","updated_at":"2018-01-31T21:25:08Z","url":"https://hostname.org/launch","privacy_level":"public","custom_fields":{},"workflow_state":"public","vendor_help_link":null,"user_navigation":null,"course_navigation":{"url":"https://other-hostname.com/launch","text":"Tool","visibility":"admins","label":"Tool","selection_width":800,"selection_height":400},"account_navigation":{"url":"https://other-hostname.com/launch","text":"Tool","visibility":"admins","label":"Tool","selection_width":800,"selection_height":400},"resource_selection":null,"editor_button":null,"homework_submission":null,"migration_selection":null,"course_home_sub_navigation":null,"course_settings_sub_navigation":null,"global_navigation":null,"assignment_menu":null,"file_menu":null,"discussion_topic_menu":null,"module_menu":null,"quiz_menu":null,"wiki_page_menu":null,"tool_configuration":null,"link_selection":null,"assignment_selection":null,"post_grades":null,"not_selectable":false}, +{"consumer_key":"AAAAAAAAAAAAAAAAAAAA","created_at":"2012-12-27T16:23:53Z","description":"A very handy tool.","domain":null,"id":1234,"name":"Tool","updated_at":"2018-01-31T21:25:08Z","url":"https://hostname.org/launch","privacy_level":"public","custom_fields":{},"workflow_state":"public","vendor_help_link":null,"user_navigation":null,"course_navigation":{"url":"https://other-hostname.com/launch","text":"Tool","visibility":"admins","label":"Tool","selection_width":800,"selection_height":400},"account_navigation":{"url":"https://other-hostname.com/launch","text":"Tool","visibility":"admins","label":"Tool","selection_width":800,"selection_height":400},"resource_selection":null,"editor_button":null,"homework_submission":null,"migration_selection":null,"course_home_sub_navigation":null,"course_settings_sub_navigation":null,"global_navigation":null,"assignment_menu":null,"file_menu":null,"discussion_topic_menu":null,"module_menu":null,"quiz_menu":null,"wiki_page_menu":null,"tool_configuration":null,"link_selection":null,"assignment_selection":null,"post_grades":null,"not_selectable":false}, +{"consumer_key":"AAAAAAAAAAAAAAAAAAAA","created_at":"2012-12-27T16:23:53Z","description":"A very handy tool.","domain":null,"id":1234,"name":"Tool","updated_at":"2018-01-31T21:25:08Z","url":"https://hostname.org/launch","privacy_level":"public","custom_fields":{},"workflow_state":"public","vendor_help_link":null,"user_navigation":null,"course_navigation":{"url":"https://other-hostname.com/launch","text":"Tool","visibility":"admins","label":"Tool","selection_width":800,"selection_height":400},"account_navigation":{"url":"https://other-hostname.com/launch","text":"Tool","visibility":"admins","label":"Tool","selection_width":800,"selection_height":400},"resource_selection":null,"editor_button":null,"homework_submission":null,"migration_selection":null,"course_home_sub_navigation":null,"course_settings_sub_navigation":null,"global_navigation":null,"assignment_menu":null,"file_menu":null,"discussion_topic_menu":null,"module_menu":null,"quiz_menu":null,"wiki_page_menu":null,"tool_configuration":null,"link_selection":null,"assignment_selection":null,"post_grades":null,"not_selectable":false}, +{"consumer_key":"AAAAAAAAAAAAAAAAAAAA","created_at":"2012-12-27T16:23:53Z","description":"A very handy tool.","domain":null,"id":1234,"name":"Tool","updated_at":"2018-01-31T21:25:08Z","url":"https://hostname.org/launch","privacy_level":"public","custom_fields":{},"workflow_state":"public","vendor_help_link":null,"user_navigation":null,"course_navigation":{"url":"https://other-hostname.com/launch","text":"Tool","visibility":"admins","label":"Tool","selection_width":800,"selection_height":400},"account_navigation":{"url":"https://other-hostname.com/launch","text":"Tool","visibility":"admins","label":"Tool","selection_width":800,"selection_height":400},"resource_selection":null,"editor_button":null,"homework_submission":null,"migration_selection":null,"course_home_sub_navigation":null,"course_settings_sub_navigation":null,"global_navigation":null,"assignment_menu":null,"file_menu":null,"discussion_topic_menu":null,"module_menu":null,"quiz_menu":null,"wiki_page_menu":null,"tool_configuration":null,"link_selection":null,"assignment_selection":null,"post_grades":null,"not_selectable":false}, +{"consumer_key":"AAAAAAAAAAAAAAAAAAAA","created_at":"2012-12-27T16:23:53Z","description":"A very handy tool.","domain":null,"id":1234,"name":"Tool","updated_at":"2018-01-31T21:25:08Z","url":"https://hostname.org/launch","privacy_level":"public","custom_fields":{},"workflow_state":"public","vendor_help_link":null,"user_navigation":null,"course_navigation":{"url":"https://other-hostname.com/launch","text":"Tool","visibility":"admins","label":"Tool","selection_width":800,"selection_height":400},"account_navigation":{"url":"https://other-hostname.com/launch","text":"Tool","visibility":"admins","label":"Tool","selection_width":800,"selection_height":400},"resource_selection":null,"editor_button":null,"homework_submission":null,"migration_selection":null,"course_home_sub_navigation":null,"course_settings_sub_navigation":null,"global_navigation":null,"assignment_menu":null,"file_menu":null,"discussion_topic_menu":null,"module_menu":null,"quiz_menu":null,"wiki_page_menu":null,"tool_configuration":null,"link_selection":null,"assignment_selection":null,"post_grades":null,"not_selectable":false}, +{"consumer_key":"AAAAAAAAAAAAAAAAAAAA","created_at":"2012-12-27T16:23:53Z","description":"A very handy tool.","domain":null,"id":1234,"name":"Tool","updated_at":"2018-01-31T21:25:08Z","url":"https://hostname.org/launch","privacy_level":"public","custom_fields":{},"workflow_state":"public","vendor_help_link":null,"user_navigation":null,"course_navigation":{"url":"https://other-hostname.com/launch","text":"Tool","visibility":"admins","label":"Tool","selection_width":800,"selection_height":400},"account_navigation":{"url":"https://other-hostname.com/launch","text":"Tool","visibility":"admins","label":"Tool","selection_width":800,"selection_height":400},"resource_selection":null,"editor_button":null,"homework_submission":null,"migration_selection":null,"course_home_sub_navigation":null,"course_settings_sub_navigation":null,"global_navigation":null,"assignment_menu":null,"file_menu":null,"discussion_topic_menu":null,"module_menu":null,"quiz_menu":null,"wiki_page_menu":null,"tool_configuration":null,"link_selection":null,"assignment_selection":null,"post_grades":null,"not_selectable":false}, +{"consumer_key":"AAAAAAAAAAAAAAAAAAAA","created_at":"2012-12-27T16:23:53Z","description":"A very handy tool.","domain":null,"id":1234,"name":"Tool","updated_at":"2018-01-31T21:25:08Z","url":"https://hostname.org/launch","privacy_level":"public","custom_fields":{},"workflow_state":"public","vendor_help_link":null,"user_navigation":null,"course_navigation":{"url":"https://other-hostname.com/launch","text":"Tool","visibility":"admins","label":"Tool","selection_width":800,"selection_height":400},"account_navigation":{"url":"https://other-hostname.com/launch","text":"Tool","visibility":"admins","label":"Tool","selection_width":800,"selection_height":400},"resource_selection":null,"editor_button":null,"homework_submission":null,"migration_selection":null,"course_home_sub_navigation":null,"course_settings_sub_navigation":null,"global_navigation":null,"assignment_menu":null,"file_menu":null,"discussion_topic_menu":null,"module_menu":null,"quiz_menu":null,"wiki_page_menu":null,"tool_configuration":null,"link_selection":null,"assignment_selection":null,"post_grades":null,"not_selectable":false}, +{"consumer_key":"AAAAAAAAAAAAAAAAAAAA","created_at":"2012-12-27T16:23:53Z","description":"A very handy tool.","domain":null,"id":1234,"name":"Tool","updated_at":"2018-01-31T21:25:08Z","url":"https://hostname.org/launch","privacy_level":"public","custom_fields":{},"workflow_state":"public","vendor_help_link":null,"user_navigation":null,"course_navigation":{"url":"https://other-hostname.com/launch","text":"Tool","visibility":"admins","label":"Tool","selection_width":800,"selection_height":400},"account_navigation":{"url":"https://other-hostname.com/launch","text":"Tool","visibility":"admins","label":"Tool","selection_width":800,"selection_height":400},"resource_selection":null,"editor_button":null,"homework_submission":null,"migration_selection":null,"course_home_sub_navigation":null,"course_settings_sub_navigation":null,"global_navigation":null,"assignment_menu":null,"file_menu":null,"discussion_topic_menu":null,"module_menu":null,"quiz_menu":null,"wiki_page_menu":null,"tool_configuration":null,"link_selection":null,"assignment_selection":null,"post_grades":null,"not_selectable":false}, +{"consumer_key":"AAAAAAAAAAAAAAAAAAAA","created_at":"2012-12-27T16:23:53Z","description":"A very handy tool.","domain":null,"id":1234,"name":"Tool","updated_at":"2018-01-31T21:25:08Z","url":"https://hostname.org/launch","privacy_level":"public","custom_fields":{},"workflow_state":"public","vendor_help_link":null,"user_navigation":null,"course_navigation":{"url":"https://other-hostname.com/launch","text":"Tool","visibility":"admins","label":"Tool","selection_width":800,"selection_height":400},"account_navigation":{"url":"https://other-hostname.com/launch","text":"Tool","visibility":"admins","label":"Tool","selection_width":800,"selection_height":400},"resource_selection":null,"editor_button":null,"homework_submission":null,"migration_selection":null,"course_home_sub_navigation":null,"course_settings_sub_navigation":null,"global_navigation":null,"assignment_menu":null,"file_menu":null,"discussion_topic_menu":null,"module_menu":null,"quiz_menu":null,"wiki_page_menu":null,"tool_configuration":null,"link_selection":null,"assignment_selection":null,"post_grades":null,"not_selectable":false}] diff --git a/restclients/resources/canvas/file/api/v1/accounts/12345/external_tools/index.html.http-headers b/restclients/resources/canvas/file/api/v1/accounts/12345/external_tools/index.html.http-headers new file mode 100644 index 00000000..923919e4 --- /dev/null +++ b/restclients/resources/canvas/file/api/v1/accounts/12345/external_tools/index.html.http-headers @@ -0,0 +1,3 @@ +{ + "Link" : "; rel=\"current\",; rel=\"next\",; rel=\"first\",; rel=\"last\"" +} \ No newline at end of file diff --git a/restclients/resources/canvas/file/api/v1/accounts/12345/external_tools/sessionless_launch_id_54321 b/restclients/resources/canvas/file/api/v1/accounts/12345/external_tools/sessionless_launch_id_54321 new file mode 100644 index 00000000..4db823ec --- /dev/null +++ b/restclients/resources/canvas/file/api/v1/accounts/12345/external_tools/sessionless_launch_id_54321 @@ -0,0 +1 @@ +{"id":54321,"name":"Canvas Data Portal","url":"https://test.instructure.com/accounts/12345/external_tools/sessionless_launch?verifier=3814320fb74f7c7fe0a6bacfa6199872eb7848ea771e8d2d40862f0d25ff2700164e2fd1da0e3e4cc62e6e02530b7fabefd9e9a16a063e1297fb45f72dccc3bc"} \ No newline at end of file diff --git a/restclients/resources/canvas/file/api/v1/accounts/12345/external_tools_page_2_per_page_10 b/restclients/resources/canvas/file/api/v1/accounts/12345/external_tools_page_2_per_page_10 new file mode 100644 index 00000000..7ac93780 --- /dev/null +++ b/restclients/resources/canvas/file/api/v1/accounts/12345/external_tools_page_2_per_page_10 @@ -0,0 +1,2 @@ +[{"consumer_key":"AAAAAAAAAAAAAAAAAAAA","created_at":"2012-12-27T16:23:53Z","description":"A very handy tool.","domain":null,"id":1234,"name":"Tool","updated_at":"2018-01-31T21:25:08Z","url":"https://hostname.org/launch","privacy_level":"public","custom_fields":{},"workflow_state":"public","vendor_help_link":null,"user_navigation":null,"course_navigation":{"url":"https://other-hostname.com/launch","text":"Tool","visibility":"admins","label":"Tool","selection_width":800,"selection_height":400},"account_navigation":{"url":"https://other-hostname.com/launch","text":"Tool","visibility":"admins","label":"Tool","selection_width":800,"selection_height":400},"resource_selection":null,"editor_button":null,"homework_submission":null,"migration_selection":null,"course_home_sub_navigation":null,"course_settings_sub_navigation":null,"global_navigation":null,"assignment_menu":null,"file_menu":null,"discussion_topic_menu":null,"module_menu":null,"quiz_menu":null,"wiki_page_menu":null,"tool_configuration":null,"link_selection":null,"assignment_selection":null,"post_grades":null,"not_selectable":false}, +{"consumer_key":"AAAAAAAAAAAAAAAAAAAA","created_at":"2012-12-27T16:23:53Z","description":"A very handy tool.","domain":null,"id":1234,"name":"Tool","updated_at":"2018-01-31T21:25:08Z","url":"https://hostname.org/launch","privacy_level":"public","custom_fields":{},"workflow_state":"public","vendor_help_link":null,"user_navigation":null,"course_navigation":{"url":"https://other-hostname.com/launch","text":"Tool","visibility":"admins","label":"Tool","selection_width":800,"selection_height":400},"account_navigation":{"url":"https://other-hostname.com/launch","text":"Tool","visibility":"admins","label":"Tool","selection_width":800,"selection_height":400},"resource_selection":null,"editor_button":null,"homework_submission":null,"migration_selection":null,"course_home_sub_navigation":null,"course_settings_sub_navigation":null,"global_navigation":null,"assignment_menu":null,"file_menu":null,"discussion_topic_menu":null,"module_menu":null,"quiz_menu":null,"wiki_page_menu":null,"tool_configuration":null,"link_selection":null,"assignment_selection":null,"post_grades":null,"not_selectable":false}] \ No newline at end of file diff --git a/restclients/resources/canvas/file/api/v1/accounts/12345/external_tools_page_2_per_page_10.http-headers b/restclients/resources/canvas/file/api/v1/accounts/12345/external_tools_page_2_per_page_10.http-headers new file mode 100644 index 00000000..f3d20c9f --- /dev/null +++ b/restclients/resources/canvas/file/api/v1/accounts/12345/external_tools_page_2_per_page_10.http-headers @@ -0,0 +1,3 @@ +{ + "Link": "; rel=\"current\",; rel=\"prev\",; rel=\"first\",; rel=\"last\"" +} \ No newline at end of file diff --git a/restclients/resources/canvas/file/api/v1/accounts/12345/roles/999 b/restclients/resources/canvas/file/api/v1/accounts/12345/roles/999 new file mode 100644 index 00000000..c18db75a --- /dev/null +++ b/restclients/resources/canvas/file/api/v1/accounts/12345/roles/999 @@ -0,0 +1 @@ +{"account":{"id":12345,"name":"University of Washington","parent_account_id":null,"root_account_id":null},"role":"Course Access","label":"Course Access","base_role_type":"AccountMembership","id":999,"workflow_state":"active","permissions":{"view_analytics":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_wiki":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"read_forum":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"post_to_forum":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"moderate_forum":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"send_messages":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"send_messages_all":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_outcomes":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"create_conferences":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"create_collaborations":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"read_roster":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"view_all_grades":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_grades":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"comment_on_others_submissions":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_students":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_admin_users":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_role_overrides":{"enabled":false,"locked":true,"readonly":false,"explicit":false},"manage_account_memberships":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_account_settings":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_groups":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"view_group_pages":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_files":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_assignments":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"read_question_banks":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_calendar":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"read_reports":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_courses":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_user_logins":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_alerts":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"become_user":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_sis":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"read_sis":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"read_course_list":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"view_statistics":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_user_notes":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"read_course_content":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_content":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_interaction_alerts":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"change_course_state":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_sections":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_frozen_assignments":{"enabled":false,"locked":false,"readonly":false,"explicit":false}}} diff --git a/restclients/resources/canvas/file/api/v1/accounts/12345/roles/Course%20Access b/restclients/resources/canvas/file/api/v1/accounts/12345/roles/Course%20Access deleted file mode 100644 index 9e241016..00000000 --- a/restclients/resources/canvas/file/api/v1/accounts/12345/roles/Course%20Access +++ /dev/null @@ -1 +0,0 @@ -{"account":{"id":12345,"name":"University of Washington","parent_account_id":null,"root_account_id":null},"role":"Course Access","label":"Course Access","base_role_type":"AccountMembership","workflow_state":"active","permissions":{"view_analytics":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_wiki":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"read_forum":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"post_to_forum":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"moderate_forum":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"send_messages":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"send_messages_all":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_outcomes":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"create_conferences":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"create_collaborations":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"read_roster":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"view_all_grades":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_grades":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"comment_on_others_submissions":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_students":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_admin_users":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_role_overrides":{"enabled":false,"locked":true,"readonly":false,"explicit":false},"manage_account_memberships":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_account_settings":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_groups":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"view_group_pages":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_files":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_assignments":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"read_question_banks":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_calendar":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"read_reports":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_courses":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_user_logins":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_alerts":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"become_user":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_sis":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"read_sis":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"read_course_list":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"view_statistics":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_user_notes":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"read_course_content":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_content":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_interaction_alerts":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"change_course_state":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_sections":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_frozen_assignments":{"enabled":false,"locked":false,"readonly":false,"explicit":false}}} \ No newline at end of file diff --git a/restclients/resources/canvas/file/api/v1/accounts/12345/roles/index.html b/restclients/resources/canvas/file/api/v1/accounts/12345/roles/index.html index 25cb5e8e..238e0f7c 100644 --- a/restclients/resources/canvas/file/api/v1/accounts/12345/roles/index.html +++ b/restclients/resources/canvas/file/api/v1/accounts/12345/roles/index.html @@ -1 +1 @@ -[{"account":{"id":12345,"name":"University of Washington","parent_account_id":null,"root_account_id":null},"role":"AccountAdmin","label":"Account Admin","base_role_type":"AccountMembership","workflow_state":"active","permissions":{"view_analytics":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"manage_wiki":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"read_forum":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"post_to_forum":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"moderate_forum":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"send_messages":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"send_messages_all":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"manage_outcomes":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"create_conferences":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"create_collaborations":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"read_roster":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"view_all_grades":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"manage_grades":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"comment_on_others_submissions":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"manage_students":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"manage_admin_users":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"manage_role_overrides":{"enabled":true,"locked":true,"readonly":true,"explicit":false},"manage_account_memberships":{"enabled":true,"locked":true,"readonly":true,"explicit":false},"manage_account_settings":{"enabled":true,"locked":true,"readonly":true,"explicit":false},"manage_groups":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"view_group_pages":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"manage_files":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"manage_assignments":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"read_question_banks":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"manage_calendar":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"read_reports":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"manage_courses":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"manage_user_logins":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"manage_alerts":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"become_user":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"manage_sis":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"read_sis":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"read_course_list":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"view_statistics":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"manage_user_notes":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"read_course_content":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"manage_content":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"manage_interaction_alerts":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"change_course_state":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"manage_sections":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"manage_frozen_assignments":{"enabled":true,"locked":false,"readonly":false,"explicit":false}}},{"account":{"id":12345,"name":"University of Washington","parent_account_id":null,"root_account_id":null},"role":"TeacherEnrollment","label":"Teacher","base_role_type":"TeacherEnrollment","workflow_state":"active","permissions":{"view_analytics":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"manage_wiki":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"read_forum":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"post_to_forum":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"moderate_forum":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"send_messages":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"send_messages_all":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"manage_outcomes":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"create_conferences":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"create_collaborations":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"read_roster":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"view_all_grades":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"manage_grades":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"comment_on_others_submissions":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"manage_students":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"manage_admin_users":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"manage_role_overrides":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"manage_account_memberships":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"manage_account_settings":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"manage_groups":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"view_group_pages":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"manage_files":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"manage_assignments":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"read_question_banks":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"manage_calendar":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"read_reports":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"manage_courses":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"manage_user_logins":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"manage_alerts":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"become_user":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"manage_sis":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"read_sis":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":true},"read_course_list":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"view_statistics":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"manage_user_notes":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"read_course_content":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"manage_content":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"manage_interaction_alerts":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"change_course_state":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"manage_sections":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"manage_frozen_assignments":{"enabled":false,"locked":true,"readonly":true,"explicit":false}}},{"account":{"id":12345,"name":"University of Washington","parent_account_id":null,"root_account_id":null},"role":"TaEnrollment","label":"TA","base_role_type":"TaEnrollment","workflow_state":"active","permissions":{"view_analytics":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"manage_wiki":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"read_forum":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"post_to_forum":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"moderate_forum":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"send_messages":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"send_messages_all":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"manage_outcomes":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"create_conferences":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"create_collaborations":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"read_roster":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"view_all_grades":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"manage_grades":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"comment_on_others_submissions":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"manage_students":{"enabled":false,"locked":false,"readonly":false,"explicit":true,"prior_default":true},"manage_admin_users":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_role_overrides":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"manage_account_memberships":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"manage_account_settings":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"manage_groups":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"view_group_pages":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"manage_files":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"manage_assignments":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"read_question_banks":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"manage_calendar":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"read_reports":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"manage_courses":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"manage_user_logins":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"manage_alerts":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"become_user":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"manage_sis":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"read_sis":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"read_course_list":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"view_statistics":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"manage_user_notes":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"read_course_content":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"manage_content":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"manage_interaction_alerts":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"change_course_state":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_sections":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_frozen_assignments":{"enabled":false,"locked":true,"readonly":true,"explicit":false}}},{"account":{"id":12345,"name":"University of Washington","parent_account_id":null,"root_account_id":null},"role":"DesignerEnrollment","label":"Designer","base_role_type":"DesignerEnrollment","workflow_state":"active","permissions":{"view_analytics":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"manage_wiki":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"read_forum":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"post_to_forum":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"moderate_forum":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"send_messages":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"send_messages_all":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"manage_outcomes":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"create_conferences":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"create_collaborations":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"read_roster":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"view_all_grades":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_grades":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"comment_on_others_submissions":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"manage_students":{"enabled":false,"locked":false,"readonly":false,"explicit":true,"prior_default":true},"manage_admin_users":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_role_overrides":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"manage_account_memberships":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"manage_account_settings":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"manage_groups":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"view_group_pages":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"manage_files":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"manage_assignments":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"read_question_banks":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"manage_calendar":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"read_reports":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"manage_courses":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"manage_user_logins":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"manage_alerts":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"become_user":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"manage_sis":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"read_sis":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"read_course_list":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"view_statistics":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"manage_user_notes":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"read_course_content":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"manage_content":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"manage_interaction_alerts":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"change_course_state":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"manage_sections":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"manage_frozen_assignments":{"enabled":false,"locked":true,"readonly":true,"explicit":false}}},{"account":{"id":12345,"name":"University of Washington","parent_account_id":null,"root_account_id":null},"role":"StudentEnrollment","label":"Student","base_role_type":"StudentEnrollment","workflow_state":"active","permissions":{"view_analytics":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_wiki":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"read_forum":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"post_to_forum":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"moderate_forum":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"send_messages":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"send_messages_all":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_outcomes":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"create_conferences":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"create_collaborations":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"read_roster":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"view_all_grades":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"manage_grades":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"comment_on_others_submissions":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_students":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"manage_admin_users":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"manage_role_overrides":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"manage_account_memberships":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"manage_account_settings":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"manage_groups":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"view_group_pages":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_files":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"manage_assignments":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"read_question_banks":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"manage_calendar":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"read_reports":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_courses":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"manage_user_logins":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"manage_alerts":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"become_user":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"manage_sis":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"read_sis":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"read_course_list":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"view_statistics":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"manage_user_notes":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"read_course_content":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"manage_content":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"manage_interaction_alerts":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"change_course_state":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"manage_sections":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"manage_frozen_assignments":{"enabled":false,"locked":true,"readonly":true,"explicit":false}}},{"account":{"id":12345,"name":"University of Washington","parent_account_id":null,"root_account_id":null},"role":"ObserverEnrollment","label":"Observer","base_role_type":"ObserverEnrollment","workflow_state":"active","permissions":{"view_analytics":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"manage_wiki":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"read_forum":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"post_to_forum":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"moderate_forum":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"send_messages":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"send_messages_all":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_outcomes":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"create_conferences":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"create_collaborations":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"read_roster":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"view_all_grades":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"manage_grades":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"comment_on_others_submissions":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"manage_students":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"manage_admin_users":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"manage_role_overrides":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"manage_account_memberships":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"manage_account_settings":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"manage_groups":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"view_group_pages":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_files":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_assignments":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"read_question_banks":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_calendar":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"read_reports":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"manage_courses":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"manage_user_logins":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"manage_alerts":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"become_user":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"manage_sis":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"read_sis":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"read_course_list":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"view_statistics":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"manage_user_notes":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"read_course_content":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"manage_content":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_interaction_alerts":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"change_course_state":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"manage_sections":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"manage_frozen_assignments":{"enabled":false,"locked":true,"readonly":true,"explicit":false}}},{"account":{"id":12345,"name":"University of Washington","parent_account_id":null,"root_account_id":null},"role":"Support","label":"Support","base_role_type":"AccountMembership","workflow_state":"active","permissions":{"view_analytics":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_wiki":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"read_forum":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"post_to_forum":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"moderate_forum":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"send_messages":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"send_messages_all":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_outcomes":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"create_conferences":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"create_collaborations":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"read_roster":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"view_all_grades":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_grades":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"comment_on_others_submissions":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_students":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_admin_users":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_role_overrides":{"enabled":false,"locked":true,"readonly":false,"explicit":false},"manage_account_memberships":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_account_settings":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_groups":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"view_group_pages":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_files":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_assignments":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"read_question_banks":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_calendar":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"read_reports":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_courses":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_user_logins":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_alerts":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"become_user":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_sis":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"read_sis":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"read_course_list":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"view_statistics":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_user_notes":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"read_course_content":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_content":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_interaction_alerts":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"change_course_state":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_sections":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_frozen_assignments":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false}}},{"account":{"id":12345,"name":"University of Washington","parent_account_id":null,"root_account_id":null},"role":"Sub Account Admin","label":"Sub Account Admin","base_role_type":"AccountMembership","workflow_state":"active","permissions":{"view_analytics":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_wiki":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"read_forum":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"post_to_forum":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"moderate_forum":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"send_messages":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"send_messages_all":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_outcomes":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"create_conferences":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"create_collaborations":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"read_roster":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"view_all_grades":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_grades":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"comment_on_others_submissions":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_students":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_admin_users":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_role_overrides":{"enabled":false,"locked":true,"readonly":false,"explicit":false},"manage_account_memberships":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_account_settings":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_groups":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"view_group_pages":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_files":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_assignments":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"read_question_banks":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_calendar":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"read_reports":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_courses":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_user_logins":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_alerts":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"become_user":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_sis":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"read_sis":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"read_course_list":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"view_statistics":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_user_notes":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"read_course_content":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_content":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_interaction_alerts":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"change_course_state":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_sections":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_frozen_assignments":{"enabled":false,"locked":false,"readonly":false,"explicit":false}}},{"account":{"id":12345,"name":"University of Washington","parent_account_id":null,"root_account_id":null},"role":"Become users only (dept. admin)","label":"Become users only (dept. admin)","base_role_type":"AccountMembership","workflow_state":"active","permissions":{"view_analytics":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_wiki":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"read_forum":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"post_to_forum":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"moderate_forum":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"send_messages":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"send_messages_all":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_outcomes":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"create_conferences":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"create_collaborations":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"read_roster":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"view_all_grades":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_grades":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"comment_on_others_submissions":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_students":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_admin_users":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_role_overrides":{"enabled":false,"locked":true,"readonly":false,"explicit":false},"manage_account_memberships":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_account_settings":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_groups":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"view_group_pages":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_files":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_assignments":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"read_question_banks":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_calendar":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"read_reports":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_courses":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_user_logins":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_alerts":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"become_user":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_sis":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"read_sis":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"read_course_list":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"view_statistics":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_user_notes":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"read_course_content":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_content":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_interaction_alerts":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"change_course_state":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_sections":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_frozen_assignments":{"enabled":false,"locked":false,"readonly":false,"explicit":false}}},{"account":{"id":12345,"name":"University of Washington","parent_account_id":null,"root_account_id":null},"role":"UW-IT Support","label":"UW-IT Support","base_role_type":"AccountMembership","workflow_state":"active","permissions":{"view_analytics":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_wiki":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"read_forum":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"post_to_forum":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"moderate_forum":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"send_messages":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"send_messages_all":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_outcomes":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"create_conferences":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"create_collaborations":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"read_roster":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"view_all_grades":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_grades":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"comment_on_others_submissions":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_students":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_admin_users":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_role_overrides":{"enabled":false,"locked":true,"readonly":false,"explicit":true,"prior_default":false},"manage_account_memberships":{"enabled":false,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_account_settings":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_groups":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"view_group_pages":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_files":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_assignments":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"read_question_banks":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_calendar":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"read_reports":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_courses":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_user_logins":{"enabled":false,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_alerts":{"enabled":false,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"become_user":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_sis":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"read_sis":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"read_course_list":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"view_statistics":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_user_notes":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"read_course_content":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_content":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_interaction_alerts":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"change_course_state":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_sections":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_frozen_assignments":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false}}}] \ No newline at end of file +[{"account":{"id":12345,"name":"University of Washington","parent_account_id":null,"root_account_id":null},"role":"AccountAdmin","label":"Account Admin","id":924,"base_role_type":"AccountMembership","workflow_state":"active","permissions":{"view_analytics":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"manage_wiki":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"read_forum":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"post_to_forum":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"moderate_forum":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"send_messages":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"send_messages_all":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"manage_outcomes":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"create_conferences":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"create_collaborations":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"read_roster":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"view_all_grades":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"manage_grades":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"comment_on_others_submissions":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"manage_students":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"manage_admin_users":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"manage_role_overrides":{"enabled":true,"locked":true,"readonly":true,"explicit":false},"manage_account_memberships":{"enabled":true,"locked":true,"readonly":true,"explicit":false},"manage_account_settings":{"enabled":true,"locked":true,"readonly":true,"explicit":false},"manage_groups":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"view_group_pages":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"manage_files":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"manage_assignments":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"read_question_banks":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"manage_calendar":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"read_reports":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"manage_courses":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"manage_user_logins":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"manage_alerts":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"become_user":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"manage_sis":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"read_sis":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"read_course_list":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"view_statistics":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"manage_user_notes":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"read_course_content":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"manage_content":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"manage_interaction_alerts":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"change_course_state":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"manage_sections":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"manage_frozen_assignments":{"enabled":true,"locked":false,"readonly":false,"explicit":false}}},{"account":{"id":12345,"name":"University of Washington","parent_account_id":null,"root_account_id":null},"role":"TeacherEnrollment","label":"Teacher","id":923,"base_role_type":"TeacherEnrollment","workflow_state":"active","permissions":{"view_analytics":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"manage_wiki":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"read_forum":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"post_to_forum":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"moderate_forum":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"send_messages":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"send_messages_all":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"manage_outcomes":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"create_conferences":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"create_collaborations":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"read_roster":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"view_all_grades":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"manage_grades":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"comment_on_others_submissions":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"manage_students":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"manage_admin_users":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"manage_role_overrides":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"manage_account_memberships":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"manage_account_settings":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"manage_groups":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"view_group_pages":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"manage_files":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"manage_assignments":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"read_question_banks":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"manage_calendar":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"read_reports":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"manage_courses":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"manage_user_logins":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"manage_alerts":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"become_user":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"manage_sis":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"read_sis":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":true},"read_course_list":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"view_statistics":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"manage_user_notes":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"read_course_content":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"manage_content":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"manage_interaction_alerts":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"change_course_state":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"manage_sections":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"manage_frozen_assignments":{"enabled":false,"locked":true,"readonly":true,"explicit":false}}},{"account":{"id":12345,"name":"University of Washington","parent_account_id":null,"root_account_id":null},"role":"TaEnrollment","label":"TA","id":922,"base_role_type":"TaEnrollment","workflow_state":"active","permissions":{"view_analytics":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"manage_wiki":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"read_forum":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"post_to_forum":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"moderate_forum":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"send_messages":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"send_messages_all":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"manage_outcomes":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"create_conferences":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"create_collaborations":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"read_roster":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"view_all_grades":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"manage_grades":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"comment_on_others_submissions":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"manage_students":{"enabled":false,"locked":false,"readonly":false,"explicit":true,"prior_default":true},"manage_admin_users":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_role_overrides":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"manage_account_memberships":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"manage_account_settings":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"manage_groups":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"view_group_pages":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"manage_files":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"manage_assignments":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"read_question_banks":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"manage_calendar":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"read_reports":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"manage_courses":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"manage_user_logins":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"manage_alerts":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"become_user":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"manage_sis":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"read_sis":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"read_course_list":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"view_statistics":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"manage_user_notes":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"read_course_content":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"manage_content":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"manage_interaction_alerts":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"change_course_state":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_sections":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_frozen_assignments":{"enabled":false,"locked":true,"readonly":true,"explicit":false}}},{"account":{"id":12345,"name":"University of Washington","parent_account_id":null,"root_account_id":null},"role":"DesignerEnrollment","label":"Designer","id":921,"base_role_type":"DesignerEnrollment","workflow_state":"active","permissions":{"view_analytics":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"manage_wiki":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"read_forum":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"post_to_forum":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"moderate_forum":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"send_messages":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"send_messages_all":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"manage_outcomes":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"create_conferences":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"create_collaborations":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"read_roster":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"view_all_grades":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_grades":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"comment_on_others_submissions":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"manage_students":{"enabled":false,"locked":false,"readonly":false,"explicit":true,"prior_default":true},"manage_admin_users":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_role_overrides":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"manage_account_memberships":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"manage_account_settings":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"manage_groups":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"view_group_pages":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"manage_files":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"manage_assignments":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"read_question_banks":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"manage_calendar":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"read_reports":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"manage_courses":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"manage_user_logins":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"manage_alerts":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"become_user":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"manage_sis":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"read_sis":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"read_course_list":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"view_statistics":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"manage_user_notes":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"read_course_content":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"manage_content":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"manage_interaction_alerts":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"change_course_state":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"manage_sections":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"manage_frozen_assignments":{"enabled":false,"locked":true,"readonly":true,"explicit":false}}},{"account":{"id":12345,"name":"University of Washington","parent_account_id":null,"root_account_id":null},"role":"StudentEnrollment","label":"Student","id":920,"base_role_type":"StudentEnrollment","workflow_state":"active","permissions":{"view_analytics":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_wiki":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"read_forum":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"post_to_forum":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"moderate_forum":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"send_messages":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"send_messages_all":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_outcomes":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"create_conferences":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"create_collaborations":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"read_roster":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"view_all_grades":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"manage_grades":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"comment_on_others_submissions":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_students":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"manage_admin_users":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"manage_role_overrides":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"manage_account_memberships":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"manage_account_settings":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"manage_groups":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"view_group_pages":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_files":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"manage_assignments":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"read_question_banks":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"manage_calendar":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"read_reports":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_courses":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"manage_user_logins":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"manage_alerts":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"become_user":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"manage_sis":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"read_sis":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"read_course_list":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"view_statistics":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"manage_user_notes":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"read_course_content":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"manage_content":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"manage_interaction_alerts":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"change_course_state":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"manage_sections":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"manage_frozen_assignments":{"enabled":false,"locked":true,"readonly":true,"explicit":false}}},{"account":{"id":12345,"name":"University of Washington","parent_account_id":null,"root_account_id":null},"role":"ObserverEnrollment","label":"Observer","id":919,"base_role_type":"ObserverEnrollment","workflow_state":"active","permissions":{"view_analytics":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"manage_wiki":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"read_forum":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"post_to_forum":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"moderate_forum":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"send_messages":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"send_messages_all":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_outcomes":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"create_conferences":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"create_collaborations":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"read_roster":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"view_all_grades":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"manage_grades":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"comment_on_others_submissions":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"manage_students":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"manage_admin_users":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"manage_role_overrides":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"manage_account_memberships":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"manage_account_settings":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"manage_groups":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"view_group_pages":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_files":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_assignments":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"read_question_banks":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_calendar":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"read_reports":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"manage_courses":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"manage_user_logins":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"manage_alerts":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"become_user":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"manage_sis":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"read_sis":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"read_course_list":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"view_statistics":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"manage_user_notes":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"read_course_content":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"manage_content":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_interaction_alerts":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"change_course_state":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"manage_sections":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"manage_frozen_assignments":{"enabled":false,"locked":true,"readonly":true,"explicit":false}}},{"account":{"id":12345,"name":"University of Washington","parent_account_id":null,"root_account_id":null},"role":"Support","label":"Support","id":918,"base_role_type":"AccountMembership","workflow_state":"active","permissions":{"view_analytics":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_wiki":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"read_forum":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"post_to_forum":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"moderate_forum":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"send_messages":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"send_messages_all":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_outcomes":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"create_conferences":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"create_collaborations":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"read_roster":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"view_all_grades":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_grades":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"comment_on_others_submissions":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_students":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_admin_users":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_role_overrides":{"enabled":false,"locked":true,"readonly":false,"explicit":false},"manage_account_memberships":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_account_settings":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_groups":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"view_group_pages":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_files":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_assignments":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"read_question_banks":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_calendar":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"read_reports":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_courses":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_user_logins":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_alerts":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"become_user":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_sis":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"read_sis":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"read_course_list":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"view_statistics":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_user_notes":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"read_course_content":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_content":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_interaction_alerts":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"change_course_state":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_sections":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_frozen_assignments":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false}}},{"account":{"id":12345,"name":"University of Washington","parent_account_id":null,"root_account_id":null},"role":"Sub Account Admin","label":"Sub Account Admin","id":917,"base_role_type":"AccountMembership","workflow_state":"active","permissions":{"view_analytics":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_wiki":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"read_forum":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"post_to_forum":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"moderate_forum":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"send_messages":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"send_messages_all":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_outcomes":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"create_conferences":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"create_collaborations":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"read_roster":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"view_all_grades":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_grades":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"comment_on_others_submissions":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_students":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_admin_users":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_role_overrides":{"enabled":false,"locked":true,"readonly":false,"explicit":false},"manage_account_memberships":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_account_settings":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_groups":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"view_group_pages":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_files":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_assignments":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"read_question_banks":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_calendar":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"read_reports":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_courses":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_user_logins":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_alerts":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"become_user":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_sis":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"read_sis":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"read_course_list":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"view_statistics":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_user_notes":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"read_course_content":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_content":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_interaction_alerts":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"change_course_state":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_sections":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_frozen_assignments":{"enabled":false,"locked":false,"readonly":false,"explicit":false}}},{"account":{"id":12345,"name":"University of Washington","parent_account_id":null,"root_account_id":null},"role":"Become users only (dept. admin)","label":"Become users only (dept. admin)","id":916,"base_role_type":"AccountMembership","workflow_state":"active","permissions":{"view_analytics":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_wiki":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"read_forum":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"post_to_forum":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"moderate_forum":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"send_messages":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"send_messages_all":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_outcomes":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"create_conferences":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"create_collaborations":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"read_roster":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"view_all_grades":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_grades":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"comment_on_others_submissions":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_students":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_admin_users":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_role_overrides":{"enabled":false,"locked":true,"readonly":false,"explicit":false},"manage_account_memberships":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_account_settings":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_groups":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"view_group_pages":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_files":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_assignments":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"read_question_banks":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_calendar":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"read_reports":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_courses":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_user_logins":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_alerts":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"become_user":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_sis":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"read_sis":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"read_course_list":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"view_statistics":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_user_notes":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"read_course_content":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_content":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_interaction_alerts":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"change_course_state":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_sections":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_frozen_assignments":{"enabled":false,"locked":false,"readonly":false,"explicit":false}}},{"account":{"id":12345,"name":"University of Washington","parent_account_id":null,"root_account_id":null},"role":"UW-IT Support","label":"UW-IT Support","id":915,"base_role_type":"AccountMembership","workflow_state":"active","permissions":{"view_analytics":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_wiki":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"read_forum":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"post_to_forum":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"moderate_forum":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"send_messages":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"send_messages_all":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_outcomes":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"create_conferences":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"create_collaborations":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"read_roster":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"view_all_grades":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_grades":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"comment_on_others_submissions":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_students":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_admin_users":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_role_overrides":{"enabled":false,"locked":true,"readonly":false,"explicit":true,"prior_default":false},"manage_account_memberships":{"enabled":false,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_account_settings":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_groups":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"view_group_pages":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_files":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_assignments":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"read_question_banks":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_calendar":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"read_reports":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_courses":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_user_logins":{"enabled":false,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_alerts":{"enabled":false,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"become_user":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_sis":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"read_sis":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"read_course_list":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"view_statistics":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_user_notes":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"read_course_content":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_content":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_interaction_alerts":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"change_course_state":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_sections":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_frozen_assignments":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false}}}] diff --git a/restclients/resources/canvas/file/api/v1/accounts/12345/roles_page_2_per_page_10 b/restclients/resources/canvas/file/api/v1/accounts/12345/roles_page_2_per_page_10 index 88dbb959..8b2a66b1 100644 --- a/restclients/resources/canvas/file/api/v1/accounts/12345/roles_page_2_per_page_10 +++ b/restclients/resources/canvas/file/api/v1/accounts/12345/roles_page_2_per_page_10 @@ -1 +1 @@ -[{"account":{"id":12345,"name":"University of Washington","parent_account_id":null,"root_account_id":null},"role":"Course Access","label":"Course Access","base_role_type":"AccountMembership","workflow_state":"active","permissions":{"view_analytics":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_wiki":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"read_forum":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"post_to_forum":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"moderate_forum":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"send_messages":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"send_messages_all":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_outcomes":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"create_conferences":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"create_collaborations":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"read_roster":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"view_all_grades":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_grades":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"comment_on_others_submissions":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_students":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_admin_users":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_role_overrides":{"enabled":false,"locked":true,"readonly":false,"explicit":false},"manage_account_memberships":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_account_settings":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_groups":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"view_group_pages":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_files":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_assignments":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"read_question_banks":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_calendar":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"read_reports":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_courses":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_user_logins":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_alerts":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"become_user":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_sis":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"read_sis":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"read_course_list":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"view_statistics":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_user_notes":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"read_course_content":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_content":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_interaction_alerts":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"change_course_state":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_sections":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_frozen_assignments":{"enabled":false,"locked":false,"readonly":false,"explicit":false}}},{"account":{"id":12345,"name":"University of Washington","parent_account_id":null,"root_account_id":null},"role":"UW-IT Research","label":"UW-IT Research","base_role_type":"AccountMembership","workflow_state":"active","permissions":{"view_analytics":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_wiki":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"read_forum":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"post_to_forum":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"moderate_forum":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"send_messages":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"send_messages_all":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_outcomes":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"create_conferences":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"create_collaborations":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"read_roster":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"view_all_grades":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_grades":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"comment_on_others_submissions":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_students":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_admin_users":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_role_overrides":{"enabled":false,"locked":true,"readonly":false,"explicit":false},"manage_account_memberships":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_account_settings":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_groups":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"view_group_pages":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_files":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_assignments":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"read_question_banks":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_calendar":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"read_reports":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_courses":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_user_logins":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_alerts":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"become_user":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_sis":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"read_sis":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"read_course_list":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"view_statistics":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_user_notes":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"read_course_content":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_content":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_interaction_alerts":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"change_course_state":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_sections":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_frozen_assignments":{"enabled":false,"locked":false,"readonly":false,"explicit":false}}},{"account":{"id":12345,"name":"University of Washington","parent_account_id":null,"root_account_id":null},"role":"Temp-UW-Tier2","label":"Temp-UW-Tier2","base_role_type":"AccountMembership","workflow_state":"active","permissions":{"view_analytics":{"enabled":false,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_wiki":{"enabled":false,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"read_forum":{"enabled":false,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"post_to_forum":{"enabled":false,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"moderate_forum":{"enabled":false,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"send_messages":{"enabled":false,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"send_messages_all":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_outcomes":{"enabled":false,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"create_conferences":{"enabled":false,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"create_collaborations":{"enabled":false,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"read_roster":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"view_all_grades":{"enabled":false,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_grades":{"enabled":false,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"comment_on_others_submissions":{"enabled":false,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_students":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_admin_users":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_role_overrides":{"enabled":false,"locked":true,"readonly":false,"explicit":true,"prior_default":false},"manage_account_memberships":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_account_settings":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_groups":{"enabled":false,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"view_group_pages":{"enabled":false,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_files":{"enabled":false,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_assignments":{"enabled":false,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"read_question_banks":{"enabled":false,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_calendar":{"enabled":false,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"read_reports":{"enabled":false,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_courses":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_user_logins":{"enabled":false,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_alerts":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"become_user":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_sis":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"read_sis":{"enabled":false,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"read_course_list":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"view_statistics":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_user_notes":{"enabled":false,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"read_course_content":{"enabled":false,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_content":{"enabled":false,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_interaction_alerts":{"enabled":false,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"change_course_state":{"enabled":false,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_sections":{"enabled":false,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_frozen_assignments":{"enabled":false,"locked":false,"readonly":false,"explicit":true,"prior_default":false}}},{"account":{"id":12345,"name":"University of Washington","parent_account_id":null,"root_account_id":null},"role":"UW-IT Support Tier 2","label":"UW-IT Support Tier 2","base_role_type":"AccountMembership","workflow_state":"active","permissions":{"view_analytics":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_wiki":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"read_forum":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"post_to_forum":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"moderate_forum":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"send_messages":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"send_messages_all":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_outcomes":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"create_conferences":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"create_collaborations":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"read_roster":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"view_all_grades":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_grades":{"enabled":false,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"comment_on_others_submissions":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_students":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_admin_users":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_role_overrides":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_account_memberships":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_account_settings":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_groups":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"view_group_pages":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_files":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_assignments":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"read_question_banks":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_calendar":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"read_reports":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_courses":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_user_logins":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_alerts":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"become_user":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_sis":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"read_sis":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"read_course_list":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"view_statistics":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_user_notes":{"enabled":false,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"read_course_content":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_content":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_interaction_alerts":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"change_course_state":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_sections":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_frozen_assignments":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false}}},{"account":{"id":12345,"name":"University of Washington","parent_account_id":null,"root_account_id":null},"role":"UW-IT Support Tier 1","label":"UW-IT Support Tier 1","base_role_type":"AccountMembership","workflow_state":"active","permissions":{"view_analytics":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_wiki":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"read_forum":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"post_to_forum":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"moderate_forum":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"send_messages":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"send_messages_all":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_outcomes":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"create_conferences":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"create_collaborations":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"read_roster":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"view_all_grades":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_grades":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"comment_on_others_submissions":{"enabled":false,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_students":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_admin_users":{"enabled":false,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_role_overrides":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_account_memberships":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_account_settings":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_groups":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"view_group_pages":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_files":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_assignments":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"read_question_banks":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_calendar":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"read_reports":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_courses":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_user_logins":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_alerts":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"become_user":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_sis":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"read_sis":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"read_course_list":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"view_statistics":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_user_notes":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"read_course_content":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_content":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_interaction_alerts":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"change_course_state":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_sections":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_frozen_assignments":{"enabled":false,"locked":false,"readonly":false,"explicit":false}}}] \ No newline at end of file +[{"account":{"id":12345,"name":"University of Washington","parent_account_id":null,"root_account_id":null},"role":"Course Access","label":"Course Access","id":999,"base_role_type":"AccountMembership","workflow_state":"active","permissions":{"view_analytics":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_wiki":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"read_forum":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"post_to_forum":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"moderate_forum":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"send_messages":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"send_messages_all":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_outcomes":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"create_conferences":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"create_collaborations":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"read_roster":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"view_all_grades":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_grades":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"comment_on_others_submissions":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_students":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_admin_users":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_role_overrides":{"enabled":false,"locked":true,"readonly":false,"explicit":false},"manage_account_memberships":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_account_settings":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_groups":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"view_group_pages":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_files":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_assignments":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"read_question_banks":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_calendar":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"read_reports":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_courses":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_user_logins":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_alerts":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"become_user":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_sis":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"read_sis":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"read_course_list":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"view_statistics":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_user_notes":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"read_course_content":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_content":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_interaction_alerts":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"change_course_state":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_sections":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_frozen_assignments":{"enabled":false,"locked":false,"readonly":false,"explicit":false}}},{"account":{"id":12345,"name":"University of Washington","parent_account_id":null,"root_account_id":null},"role":"UW-IT Research","label":"UW-IT Research","id":902,"base_role_type":"AccountMembership","workflow_state":"active","permissions":{"view_analytics":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_wiki":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"read_forum":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"post_to_forum":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"moderate_forum":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"send_messages":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"send_messages_all":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_outcomes":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"create_conferences":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"create_collaborations":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"read_roster":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"view_all_grades":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_grades":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"comment_on_others_submissions":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_students":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_admin_users":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_role_overrides":{"enabled":false,"locked":true,"readonly":false,"explicit":false},"manage_account_memberships":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_account_settings":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_groups":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"view_group_pages":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_files":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_assignments":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"read_question_banks":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_calendar":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"read_reports":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_courses":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_user_logins":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_alerts":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"become_user":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_sis":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"read_sis":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"read_course_list":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"view_statistics":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_user_notes":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"read_course_content":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_content":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_interaction_alerts":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"change_course_state":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_sections":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_frozen_assignments":{"enabled":false,"locked":false,"readonly":false,"explicit":false}}},{"account":{"id":12345,"name":"University of Washington","parent_account_id":null,"root_account_id":null},"role":"Temp-UW-Tier2","label":"Temp-UW-Tier2","id":901,"base_role_type":"AccountMembership","workflow_state":"active","permissions":{"view_analytics":{"enabled":false,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_wiki":{"enabled":false,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"read_forum":{"enabled":false,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"post_to_forum":{"enabled":false,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"moderate_forum":{"enabled":false,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"send_messages":{"enabled":false,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"send_messages_all":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_outcomes":{"enabled":false,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"create_conferences":{"enabled":false,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"create_collaborations":{"enabled":false,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"read_roster":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"view_all_grades":{"enabled":false,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_grades":{"enabled":false,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"comment_on_others_submissions":{"enabled":false,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_students":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_admin_users":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_role_overrides":{"enabled":false,"locked":true,"readonly":false,"explicit":true,"prior_default":false},"manage_account_memberships":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_account_settings":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_groups":{"enabled":false,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"view_group_pages":{"enabled":false,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_files":{"enabled":false,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_assignments":{"enabled":false,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"read_question_banks":{"enabled":false,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_calendar":{"enabled":false,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"read_reports":{"enabled":false,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_courses":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_user_logins":{"enabled":false,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_alerts":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"become_user":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_sis":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"read_sis":{"enabled":false,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"read_course_list":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"view_statistics":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_user_notes":{"enabled":false,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"read_course_content":{"enabled":false,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_content":{"enabled":false,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_interaction_alerts":{"enabled":false,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"change_course_state":{"enabled":false,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_sections":{"enabled":false,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_frozen_assignments":{"enabled":false,"locked":false,"readonly":false,"explicit":true,"prior_default":false}}},{"account":{"id":12345,"name":"University of Washington","parent_account_id":null,"root_account_id":null},"role":"UW-IT Support Tier 2","label":"UW-IT Support Tier 2","id":900,"base_role_type":"AccountMembership","workflow_state":"active","permissions":{"view_analytics":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_wiki":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"read_forum":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"post_to_forum":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"moderate_forum":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"send_messages":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"send_messages_all":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_outcomes":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"create_conferences":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"create_collaborations":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"read_roster":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"view_all_grades":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_grades":{"enabled":false,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"comment_on_others_submissions":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_students":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_admin_users":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_role_overrides":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_account_memberships":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_account_settings":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_groups":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"view_group_pages":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_files":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_assignments":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"read_question_banks":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_calendar":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"read_reports":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_courses":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_user_logins":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_alerts":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"become_user":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_sis":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"read_sis":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"read_course_list":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"view_statistics":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_user_notes":{"enabled":false,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"read_course_content":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_content":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_interaction_alerts":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"change_course_state":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_sections":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_frozen_assignments":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false}}},{"account":{"id":12345,"name":"University of Washington","parent_account_id":null,"root_account_id":null},"role":"UW-IT Support Tier 1","label":"UW-IT Support Tier 1","id":910,"base_role_type":"AccountMembership","workflow_state":"active","permissions":{"view_analytics":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_wiki":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"read_forum":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"post_to_forum":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"moderate_forum":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"send_messages":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"send_messages_all":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_outcomes":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"create_conferences":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"create_collaborations":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"read_roster":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"view_all_grades":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_grades":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"comment_on_others_submissions":{"enabled":false,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_students":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_admin_users":{"enabled":false,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_role_overrides":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_account_memberships":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_account_settings":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_groups":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"view_group_pages":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_files":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_assignments":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"read_question_banks":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_calendar":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"read_reports":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_courses":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_user_logins":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_alerts":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"become_user":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_sis":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"read_sis":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"read_course_list":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"view_statistics":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_user_notes":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"read_course_content":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_content":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_interaction_alerts":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"change_course_state":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_sections":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_frozen_assignments":{"enabled":false,"locked":false,"readonly":false,"explicit":false}}}] diff --git a/restclients/resources/canvas/file/api/v1/accounts/12345/roles_show_inherited_1 b/restclients/resources/canvas/file/api/v1/accounts/12345/roles_show_inherited_1 new file mode 100644 index 00000000..238e0f7c --- /dev/null +++ b/restclients/resources/canvas/file/api/v1/accounts/12345/roles_show_inherited_1 @@ -0,0 +1 @@ +[{"account":{"id":12345,"name":"University of Washington","parent_account_id":null,"root_account_id":null},"role":"AccountAdmin","label":"Account Admin","id":924,"base_role_type":"AccountMembership","workflow_state":"active","permissions":{"view_analytics":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"manage_wiki":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"read_forum":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"post_to_forum":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"moderate_forum":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"send_messages":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"send_messages_all":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"manage_outcomes":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"create_conferences":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"create_collaborations":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"read_roster":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"view_all_grades":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"manage_grades":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"comment_on_others_submissions":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"manage_students":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"manage_admin_users":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"manage_role_overrides":{"enabled":true,"locked":true,"readonly":true,"explicit":false},"manage_account_memberships":{"enabled":true,"locked":true,"readonly":true,"explicit":false},"manage_account_settings":{"enabled":true,"locked":true,"readonly":true,"explicit":false},"manage_groups":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"view_group_pages":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"manage_files":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"manage_assignments":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"read_question_banks":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"manage_calendar":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"read_reports":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"manage_courses":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"manage_user_logins":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"manage_alerts":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"become_user":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"manage_sis":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"read_sis":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"read_course_list":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"view_statistics":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"manage_user_notes":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"read_course_content":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"manage_content":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"manage_interaction_alerts":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"change_course_state":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"manage_sections":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"manage_frozen_assignments":{"enabled":true,"locked":false,"readonly":false,"explicit":false}}},{"account":{"id":12345,"name":"University of Washington","parent_account_id":null,"root_account_id":null},"role":"TeacherEnrollment","label":"Teacher","id":923,"base_role_type":"TeacherEnrollment","workflow_state":"active","permissions":{"view_analytics":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"manage_wiki":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"read_forum":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"post_to_forum":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"moderate_forum":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"send_messages":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"send_messages_all":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"manage_outcomes":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"create_conferences":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"create_collaborations":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"read_roster":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"view_all_grades":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"manage_grades":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"comment_on_others_submissions":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"manage_students":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"manage_admin_users":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"manage_role_overrides":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"manage_account_memberships":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"manage_account_settings":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"manage_groups":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"view_group_pages":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"manage_files":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"manage_assignments":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"read_question_banks":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"manage_calendar":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"read_reports":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"manage_courses":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"manage_user_logins":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"manage_alerts":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"become_user":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"manage_sis":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"read_sis":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":true},"read_course_list":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"view_statistics":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"manage_user_notes":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"read_course_content":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"manage_content":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"manage_interaction_alerts":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"change_course_state":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"manage_sections":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"manage_frozen_assignments":{"enabled":false,"locked":true,"readonly":true,"explicit":false}}},{"account":{"id":12345,"name":"University of Washington","parent_account_id":null,"root_account_id":null},"role":"TaEnrollment","label":"TA","id":922,"base_role_type":"TaEnrollment","workflow_state":"active","permissions":{"view_analytics":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"manage_wiki":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"read_forum":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"post_to_forum":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"moderate_forum":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"send_messages":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"send_messages_all":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"manage_outcomes":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"create_conferences":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"create_collaborations":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"read_roster":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"view_all_grades":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"manage_grades":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"comment_on_others_submissions":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"manage_students":{"enabled":false,"locked":false,"readonly":false,"explicit":true,"prior_default":true},"manage_admin_users":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_role_overrides":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"manage_account_memberships":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"manage_account_settings":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"manage_groups":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"view_group_pages":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"manage_files":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"manage_assignments":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"read_question_banks":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"manage_calendar":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"read_reports":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"manage_courses":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"manage_user_logins":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"manage_alerts":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"become_user":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"manage_sis":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"read_sis":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"read_course_list":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"view_statistics":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"manage_user_notes":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"read_course_content":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"manage_content":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"manage_interaction_alerts":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"change_course_state":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_sections":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_frozen_assignments":{"enabled":false,"locked":true,"readonly":true,"explicit":false}}},{"account":{"id":12345,"name":"University of Washington","parent_account_id":null,"root_account_id":null},"role":"DesignerEnrollment","label":"Designer","id":921,"base_role_type":"DesignerEnrollment","workflow_state":"active","permissions":{"view_analytics":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"manage_wiki":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"read_forum":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"post_to_forum":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"moderate_forum":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"send_messages":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"send_messages_all":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"manage_outcomes":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"create_conferences":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"create_collaborations":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"read_roster":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"view_all_grades":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_grades":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"comment_on_others_submissions":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"manage_students":{"enabled":false,"locked":false,"readonly":false,"explicit":true,"prior_default":true},"manage_admin_users":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_role_overrides":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"manage_account_memberships":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"manage_account_settings":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"manage_groups":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"view_group_pages":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"manage_files":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"manage_assignments":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"read_question_banks":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"manage_calendar":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"read_reports":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"manage_courses":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"manage_user_logins":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"manage_alerts":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"become_user":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"manage_sis":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"read_sis":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"read_course_list":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"view_statistics":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"manage_user_notes":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"read_course_content":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"manage_content":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"manage_interaction_alerts":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"change_course_state":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"manage_sections":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"manage_frozen_assignments":{"enabled":false,"locked":true,"readonly":true,"explicit":false}}},{"account":{"id":12345,"name":"University of Washington","parent_account_id":null,"root_account_id":null},"role":"StudentEnrollment","label":"Student","id":920,"base_role_type":"StudentEnrollment","workflow_state":"active","permissions":{"view_analytics":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_wiki":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"read_forum":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"post_to_forum":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"moderate_forum":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"send_messages":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"send_messages_all":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_outcomes":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"create_conferences":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"create_collaborations":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"read_roster":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"view_all_grades":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"manage_grades":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"comment_on_others_submissions":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_students":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"manage_admin_users":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"manage_role_overrides":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"manage_account_memberships":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"manage_account_settings":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"manage_groups":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"view_group_pages":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_files":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"manage_assignments":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"read_question_banks":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"manage_calendar":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"read_reports":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_courses":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"manage_user_logins":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"manage_alerts":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"become_user":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"manage_sis":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"read_sis":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"read_course_list":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"view_statistics":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"manage_user_notes":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"read_course_content":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"manage_content":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"manage_interaction_alerts":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"change_course_state":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"manage_sections":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"manage_frozen_assignments":{"enabled":false,"locked":true,"readonly":true,"explicit":false}}},{"account":{"id":12345,"name":"University of Washington","parent_account_id":null,"root_account_id":null},"role":"ObserverEnrollment","label":"Observer","id":919,"base_role_type":"ObserverEnrollment","workflow_state":"active","permissions":{"view_analytics":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"manage_wiki":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"read_forum":{"enabled":true,"locked":false,"readonly":false,"explicit":false},"post_to_forum":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"moderate_forum":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"send_messages":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"send_messages_all":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_outcomes":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"create_conferences":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"create_collaborations":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"read_roster":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"view_all_grades":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"manage_grades":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"comment_on_others_submissions":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"manage_students":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"manage_admin_users":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"manage_role_overrides":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"manage_account_memberships":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"manage_account_settings":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"manage_groups":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"view_group_pages":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_files":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_assignments":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"read_question_banks":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_calendar":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"read_reports":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"manage_courses":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"manage_user_logins":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"manage_alerts":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"become_user":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"manage_sis":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"read_sis":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"read_course_list":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"view_statistics":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"manage_user_notes":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"read_course_content":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"manage_content":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_interaction_alerts":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"change_course_state":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"manage_sections":{"enabled":false,"locked":true,"readonly":true,"explicit":false},"manage_frozen_assignments":{"enabled":false,"locked":true,"readonly":true,"explicit":false}}},{"account":{"id":12345,"name":"University of Washington","parent_account_id":null,"root_account_id":null},"role":"Support","label":"Support","id":918,"base_role_type":"AccountMembership","workflow_state":"active","permissions":{"view_analytics":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_wiki":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"read_forum":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"post_to_forum":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"moderate_forum":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"send_messages":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"send_messages_all":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_outcomes":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"create_conferences":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"create_collaborations":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"read_roster":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"view_all_grades":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_grades":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"comment_on_others_submissions":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_students":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_admin_users":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_role_overrides":{"enabled":false,"locked":true,"readonly":false,"explicit":false},"manage_account_memberships":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_account_settings":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_groups":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"view_group_pages":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_files":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_assignments":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"read_question_banks":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_calendar":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"read_reports":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_courses":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_user_logins":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_alerts":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"become_user":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_sis":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"read_sis":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"read_course_list":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"view_statistics":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_user_notes":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"read_course_content":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_content":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_interaction_alerts":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"change_course_state":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_sections":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_frozen_assignments":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false}}},{"account":{"id":12345,"name":"University of Washington","parent_account_id":null,"root_account_id":null},"role":"Sub Account Admin","label":"Sub Account Admin","id":917,"base_role_type":"AccountMembership","workflow_state":"active","permissions":{"view_analytics":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_wiki":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"read_forum":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"post_to_forum":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"moderate_forum":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"send_messages":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"send_messages_all":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_outcomes":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"create_conferences":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"create_collaborations":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"read_roster":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"view_all_grades":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_grades":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"comment_on_others_submissions":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_students":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_admin_users":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_role_overrides":{"enabled":false,"locked":true,"readonly":false,"explicit":false},"manage_account_memberships":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_account_settings":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_groups":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"view_group_pages":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_files":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_assignments":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"read_question_banks":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_calendar":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"read_reports":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_courses":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_user_logins":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_alerts":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"become_user":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_sis":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"read_sis":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"read_course_list":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"view_statistics":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_user_notes":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"read_course_content":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_content":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_interaction_alerts":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"change_course_state":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_sections":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_frozen_assignments":{"enabled":false,"locked":false,"readonly":false,"explicit":false}}},{"account":{"id":12345,"name":"University of Washington","parent_account_id":null,"root_account_id":null},"role":"Become users only (dept. admin)","label":"Become users only (dept. admin)","id":916,"base_role_type":"AccountMembership","workflow_state":"active","permissions":{"view_analytics":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_wiki":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"read_forum":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"post_to_forum":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"moderate_forum":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"send_messages":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"send_messages_all":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_outcomes":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"create_conferences":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"create_collaborations":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"read_roster":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"view_all_grades":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_grades":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"comment_on_others_submissions":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_students":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_admin_users":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_role_overrides":{"enabled":false,"locked":true,"readonly":false,"explicit":false},"manage_account_memberships":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_account_settings":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_groups":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"view_group_pages":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_files":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_assignments":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"read_question_banks":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_calendar":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"read_reports":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_courses":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_user_logins":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_alerts":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"become_user":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_sis":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"read_sis":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"read_course_list":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"view_statistics":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_user_notes":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"read_course_content":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_content":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_interaction_alerts":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"change_course_state":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_sections":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_frozen_assignments":{"enabled":false,"locked":false,"readonly":false,"explicit":false}}},{"account":{"id":12345,"name":"University of Washington","parent_account_id":null,"root_account_id":null},"role":"UW-IT Support","label":"UW-IT Support","id":915,"base_role_type":"AccountMembership","workflow_state":"active","permissions":{"view_analytics":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_wiki":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"read_forum":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"post_to_forum":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"moderate_forum":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"send_messages":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"send_messages_all":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_outcomes":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"create_conferences":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"create_collaborations":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"read_roster":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"view_all_grades":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_grades":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"comment_on_others_submissions":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_students":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_admin_users":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_role_overrides":{"enabled":false,"locked":true,"readonly":false,"explicit":true,"prior_default":false},"manage_account_memberships":{"enabled":false,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_account_settings":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"manage_groups":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"view_group_pages":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_files":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_assignments":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"read_question_banks":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_calendar":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"read_reports":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_courses":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_user_logins":{"enabled":false,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_alerts":{"enabled":false,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"become_user":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_sis":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"read_sis":{"enabled":false,"locked":false,"readonly":false,"explicit":false},"read_course_list":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"view_statistics":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_user_notes":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"read_course_content":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_content":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_interaction_alerts":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"change_course_state":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_sections":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false},"manage_frozen_assignments":{"enabled":true,"locked":false,"readonly":false,"explicit":true,"prior_default":false}}}] diff --git a/restclients/resources/canvas/file/api/v1/courses/149650.PUT b/restclients/resources/canvas/file/api/v1/courses/149650.PUT new file mode 100644 index 00000000..3c6832e0 --- /dev/null +++ b/restclients/resources/canvas/file/api/v1/courses/149650.PUT @@ -0,0 +1 @@ +{"sis_course_id":"NEW_SIS_ID","hide_final_grades":false,"course_code":"PHYS 121","end_at":null,"id":149650,"default_view":"syllabus","account_id":84378,"name":"MECHANICS","workflow_state":"unpublished","public_syllabus":false,"enrollments":[{"type":"teacher"}],"calendar":{"ics":"https://canvas.uw.edu/feeds/calendars/course_UdeVj2RQpRVlZDSJdWYn3xFQ1EjDNojfypjM9h1h.ics"},"start_at":null,"syllabus_body":"Syllabus"} diff --git a/restclients/resources/canvas/file/api/v1/courses/149650_include_syllabus_body b/restclients/resources/canvas/file/api/v1/courses/149650_include[]_syllabus_body_include[]_term similarity index 100% rename from restclients/resources/canvas/file/api/v1/courses/149650_include_syllabus_body rename to restclients/resources/canvas/file/api/v1/courses/149650_include[]_syllabus_body_include[]_term diff --git a/restclients/resources/canvas/file/api/v1/courses/149650_include_term b/restclients/resources/canvas/file/api/v1/courses/149650_include[]_term similarity index 100% rename from restclients/resources/canvas/file/api/v1/courses/149650_include_term rename to restclients/resources/canvas/file/api/v1/courses/149650_include[]_term diff --git a/restclients/resources/canvas/file/api/v1/courses/862539/users_search_term_jav_include[]_enrollments b/restclients/resources/canvas/file/api/v1/courses/862539/users_search_term_jav_include[]_enrollments new file mode 100644 index 00000000..b3d0e86b --- /dev/null +++ b/restclients/resources/canvas/file/api/v1/courses/862539/users_search_term_jav_include[]_enrollments @@ -0,0 +1 @@ +[{"id":"3589026","name":"JAMES AVERAGE","sortable_name":"AVERAGE, JAMES","short_name":"JAMES AVG","sis_user_id":"15AE3883B6EC44C349E04E5FE089ABEB","integration_id":null,"sis_login_id":"javerage","sis_import_id":"5110542","login_id":"javerage","avatar_url":"","enrollments":[{"associated_user_id":null,"course_id":"862539","course_section_id":"1150574","created_at":"2015-06-24T02:11:11Z","end_at":null,"id":"18259018","limit_privileges_to_course_section":false,"root_account_id":"83919","start_at":null,"type":"DesignerEnrollment","updated_at":"2015-06-24T02:11:11Z","user_id":"3529076","enrollment_state":"active","role":"Librarian","role_id":"992","last_activity_at":null,"total_activity_time":0,"sis_import_id":"5228871","sis_source_id":":15AE3883B6EC44C349E04E5FE089ABEB:992:UW Group members","sis_course_id":"2015-summer-TRAIN-100-A","course_integration_id":null,"sis_section_id":"2015-summer-TRAIN-100-A-groups","section_integration_id":null,"html_url":"https://uw.test.instructure.com/courses/974686/users/3529076","can_be_removed":true}],"email":"javerage@uw.edu"},{"id":"496168","name":"JOHN AVERAGE","sortable_name":"AVERAGE, JOHN","short_name":"JOHN AVERAGE","sis_user_id":"C0A2BBFA6A7D11D5A4AE0004AC444EEE","integration_id":null,"sis_login_id":"javg11","sis_import_id":"5145333","login_id":"javg11","avatar_url":"","enrollments":[{"associated_user_id":null,"course_id":"862539","course_section_id":"1150574","created_at":"2015-06-24T02:11:11Z","end_at":null,"id":"18339443","limit_privileges_to_course_section":false,"root_account_id":"83919","start_at":null,"type":"DesignerEnrollment","updated_at":"2015-06-24T02:11:11Z","user_id":"496244","enrollment_state":"active","role":"Librarian","role_id":"992","last_activity_at":null,"total_activity_time":0,"sis_import_id":"5228871","sis_source_id":":C0A2BBFA6A7D11D5A4AE0004AC444EEE:992:UW Group members","sis_course_id":"2015-summer-TRAIN-100-A","course_integration_id":null,"sis_section_id":"2015-summer-TRAIN-100-A-groups","section_integration_id":null,"html_url":"https://uw.test.instructure.com/courses/974686/users/496333","can_be_removed":true}],"email":"javg11@uw.edu"},{"id":"496333","name":"JOE AVERAGE","sortable_name":"AVERAGE, JOE","short_name":"JOE AVERAGE","sis_user_id":"D6AAC7406A7C11D5A4AE9934AC494BBC","integration_id":null,"sis_login_id":"jave2","sis_import_id":"5253858","login_id":"jave2","avatar_url":"","enrollments":[{"associated_user_id":null,"course_id":"974686","course_section_id":"1150574","created_at":"2015-06-24T02:11:11Z","end_at":null,"id":"18259019","limit_privileges_to_course_section":false,"root_account_id":"83919","start_at":null,"type":"DesignerEnrollment","updated_at":"2015-06-24T02:11:11Z","user_id":"495321","enrollment_state":"active","role":"Librarian","role_id":"992","last_activity_at":null,"total_activity_time":0,"sis_import_id":"5228871","sis_source_id":":D6AAC7406A7C11D5A4AE9934AC494BBC:UW Group members","sis_course_id":"2015-summer-TRAIN-100-A","course_integration_id":null,"sis_section_id":"2015-summer-TRAIN-100-A-groups","section_integration_id":null,"html_url":"https://uw.test.instructure.com/courses/974686/users/123456","can_be_removed":true}],"email":"jave2@u.washington.edu"}] diff --git a/restclients/resources/canvas/file/api/v1/courses/sis_course_id%3A2013-spring-CSE-142-A/sections_include_students b/restclients/resources/canvas/file/api/v1/courses/sis_course_id%3A2013-spring-CSE-142-A/sections_include[]_students similarity index 100% rename from restclients/resources/canvas/file/api/v1/courses/sis_course_id%3A2013-spring-CSE-142-A/sections_include_students rename to restclients/resources/canvas/file/api/v1/courses/sis_course_id%3A2013-spring-CSE-142-A/sections_include[]_students diff --git a/restclients/resources/canvas/file/api/v1/courses/sis_course_id%3A2015-autumn-UWBW-301-A/external_tools/index.html b/restclients/resources/canvas/file/api/v1/courses/sis_course_id%3A2015-autumn-UWBW-301-A/external_tools/index.html new file mode 100644 index 00000000..978de5f9 --- /dev/null +++ b/restclients/resources/canvas/file/api/v1/courses/sis_course_id%3A2015-autumn-UWBW-301-A/external_tools/index.html @@ -0,0 +1,2 @@ +[{"consumer_key":"AAAAAAAAAAAAAAAAAAAA","created_at":"2012-12-27T16:23:53Z","description":"A very handy tool.","domain":null,"id":1234,"name":"Tool","updated_at":"2018-01-31T21:25:08Z","url":"https://hostname.org/launch","privacy_level":"public","custom_fields":{},"workflow_state":"public","vendor_help_link":null,"user_navigation":null,"course_navigation":{"url":"https://other-hostname.com/launch","text":"Tool","visibility":"admins","label":"Tool","selection_width":800,"selection_height":400},"account_navigation":{"url":"https://other-hostname.com/launch","text":"Tool","visibility":"admins","label":"Tool","selection_width":800,"selection_height":400},"resource_selection":null,"editor_button":null,"homework_submission":null,"migration_selection":null,"course_home_sub_navigation":null,"course_settings_sub_navigation":null,"global_navigation":null,"assignment_menu":null,"file_menu":null,"discussion_topic_menu":null,"module_menu":null,"quiz_menu":null,"wiki_page_menu":null,"tool_configuration":null,"link_selection":null,"assignment_selection":null,"post_grades":null,"not_selectable":false}, +{"consumer_key":"AAAAAAAAAAAAAAAAAAAA","created_at":"2012-12-27T16:23:53Z","description":"A very handy tool.","domain":null,"id":1234,"name":"Course Tool","updated_at":"2018-01-31T21:25:08Z","url":"https://hostname.org/launch","privacy_level":"public","custom_fields":{},"workflow_state":"public","vendor_help_link":null,"user_navigation":null,"course_navigation":{"url":"https://other-hostname.com/launch","text":"Tool","visibility":"admins","label":"Tool","selection_width":800,"selection_height":400},"account_navigation":{"url":"https://other-hostname.com/launch","text":"Tool","visibility":"admins","label":"Tool","selection_width":800,"selection_height":400},"resource_selection":null,"editor_button":null,"homework_submission":null,"migration_selection":null,"course_home_sub_navigation":null,"course_settings_sub_navigation":null,"global_navigation":null,"assignment_menu":null,"file_menu":null,"discussion_topic_menu":null,"module_menu":null,"quiz_menu":null,"wiki_page_menu":null,"tool_configuration":null,"link_selection":null,"assignment_selection":null,"post_grades":null,"not_selectable":false}] \ No newline at end of file diff --git a/restclients/resources/canvas/file/api/v1/courses/sis_course_id%3A2015-autumn-UWBW-301-A/external_tools/sessionless_launch_id_54321 b/restclients/resources/canvas/file/api/v1/courses/sis_course_id%3A2015-autumn-UWBW-301-A/external_tools/sessionless_launch_id_54321 new file mode 100644 index 00000000..4db823ec --- /dev/null +++ b/restclients/resources/canvas/file/api/v1/courses/sis_course_id%3A2015-autumn-UWBW-301-A/external_tools/sessionless_launch_id_54321 @@ -0,0 +1 @@ +{"id":54321,"name":"Canvas Data Portal","url":"https://test.instructure.com/accounts/12345/external_tools/sessionless_launch?verifier=3814320fb74f7c7fe0a6bacfa6199872eb7848ea771e8d2d40862f0d25ff2700164e2fd1da0e3e4cc62e6e02530b7fabefd9e9a16a063e1297fb45f72dccc3bc"} \ No newline at end of file diff --git a/restclients/resources/grad/file/services/students/v1/api/committee_id_000083856_status_active b/restclients/resources/grad/file/services/students/v1/api/committee_id_000083856_status_active new file mode 100644 index 00000000..f121940d --- /dev/null +++ b/restclients/resources/grad/file/services/students/v1/api/committee_id_000083856_status_active @@ -0,0 +1,85 @@ +[ + {"committeeType":"Advisor", + "dept":"Anthropology", + "degreeType":"MASTER OF PUBLIC HEALTH (EPIDEMIOLOGY)", + "majorFullName":"ANTH", + "status":"active", + "startDate":"2012-12-07T08:26:14.77", + "endDate":null, + "members":[{"nameFirst":"Bet", + "nameLast":"Duncan", + "memberType":"member", + "readingType":"member", + "status":"active", + "dept":"Anthropology", + "email":"bbb@u.washington.edu"} + ] + }, + {"committeeType":"Master's Committee", + "dept":"Epidemiology - Public Health", + "degreeType":"MASTER OF PUBLIC HEALTH (EPIDEMIOLOGY)", + "majorFullName":"EPI", + "status":"active", + "startDate":"2012-11-14T00:00:00", + "endDate":null, + "members":[{"nameFirst":"Nina L.", + "nameLast":"Patrick", + "memberType":"chair", + "readingType":"chair", + "status":"active", + "dept":"Epidemiology - Public Health", + "email":"nnn@u.washington.edu"}, + {"nameFirst":"Louis", + "nameLast":"Vivian", + "memberType":"member", + "readingType":"member", + "status":"active", + "dept":"Ministry of Health, Peru", + "email":"lll@oge.sld.pe"}, + {"nameFirst":"Bet", + "nameLast":"Duncan", + "memberType":"gsr", + "readingType":"member", + "status":"active", + "dept":"Anthropology", + "email":"bbb@u.washington.edu"} + ] + }, + {"committeeType":"Doctoral Supervisory Committee", + "dept":"Anthropology", + "degreeType":"DOCTOR OF PHILOSOPHY (ANTHROPOLOGY)", + "majorFullName":"ANTH", + "status":"active", + "startDate":"2013-06-07T00:00:00", + "endDate":null, + "members":[{"nameFirst":"Bet", + "nameLast":"Duncan", + "memberType":"chair", + "readingType":null, + "status":"active", + "dept":"Anthropology", + "email":"bbb@u.washington.edu"}, + {"nameFirst":"Malinda", + "nameLast":"Korry", + "memberType":"gsr", + "readingType":null, + "status":"active", + "dept":"Health Services - Public Health", + "email":""}, + {"nameFirst":"Steve M.", + "nameLast":"Goodman", + "memberType":"chair", + "readingType":null, + "status":"active", + "dept":"Anthropology", + "email":"sss@u.washington.edu"}, + {"nameFirst":"James T.", + "nameLast":"Pfeiffer", + "memberType":"member", + "readingType":null, + "status":"active", + "dept":"Global Health", + "email":"jjj@uw.edu"} + ] + } +] diff --git a/restclients/resources/grad/file/services/students/v1/api/leave_id_000083856 b/restclients/resources/grad/file/services/students/v1/api/leave_id_000083856 new file mode 100644 index 00000000..86b729b6 --- /dev/null +++ b/restclients/resources/grad/file/services/students/v1/api/leave_id_000083856 @@ -0,0 +1,29 @@ +[ + { + "leaveReason":"Dissertation/Thesis research/writing", + "submitDate":"2012-09-10T09:40:03.36", + "status":"paid", + "quarters":[{"year":2012, + "quarter":"autumn"}]}, + {"leaveReason":"Dissertation/Thesis research/writing", + "submitDate":"2013-01-02T12:05:14.37", + "status":"paid", + "quarters":[{"year":2013, + "quarter":"winter"}]}, + {"leaveReason":"Dissertation/Thesis research/writing", + "submitDate":"2013-09-19T08:55:36.517", + "status":"paid", + "quarters":[{"year":2013, + "quarter":"autumn"}]}, + {"leaveReason":"Dissertation/Thesis research/writing", + "submitDate":"2014-01-02T15:03:28.01", + "status":"paid", + "quarters":[{"year":2014, + "quarter":"winter"}]}, + {"leaveReason":"Dissertation/Thesis research/writing", + "submitDate":"2014-03-31T11:32:48.583", + "status":"paid", + "quarters":[{"year":2014, + "quarter":"spring"}] + } +] diff --git a/restclients/resources/grad/file/services/students/v1/api/petition_id_000083856 b/restclients/resources/grad/file/services/students/v1/api/petition_id_000083856 new file mode 100644 index 00000000..9765bcc2 --- /dev/null +++ b/restclients/resources/grad/file/services/students/v1/api/petition_id_000083856 @@ -0,0 +1,27 @@ +[ + { + "description":"Master's degree - Extend six year limit", + "submitDate":"2013-05-11T11:25:35.917", + "decisionDate":"2013-06-10T16:32:28.64", + "deptRecommend":"Approve", + "gradSchoolDecision":"Approved" + }, + { + "description":"Master's degree - Extend six year limit", + "submitDate":"2013-07-11T11:25:35.917", + "deptRecommend":"Approve", + "gradSchoolDecision":"Pending" + }, + { + "description":"Master's degree - Extend six year limit", + "submitDate":"2013-07-11T11:25:35.917", + "deptRecommend":"Withdraw", + "gradSchoolDecision":"Withdraw" + }, + { + "description":"Master's degree - Extend six year limit", + "submitDate":"2013-07-11T11:25:35.917", + "deptRecommend":"Approve", + "gradSchoolDecision":"Withdrawn" + } +] diff --git a/restclients/resources/grad/file/services/students/v1/api/request_id_000083856_exclude_past_quarter_true b/restclients/resources/grad/file/services/students/v1/api/request_id_000083856_exclude_past_quarter_true new file mode 100644 index 00000000..33d4849a --- /dev/null +++ b/restclients/resources/grad/file/services/students/v1/api/request_id_000083856_exclude_past_quarter_true @@ -0,0 +1,92 @@ +[ + {"syskey":83856, + "requestId":101502, + "requestType":"Masters Request", + "degreeTitle":"MASTER OF LANDSCAPE ARCHITECTURE/MASTER OF ARCHITECTURE", + "majorFullName":"Landscape Arch/Architecture (Concurrent)", + "status":"Awaiting Dept Action (Final Exam)", + "examPlace":null, + "examDate":null, + "targetAwardYear":2015, + "targetAwardQuarter":"winter", + "requestSubmitDate":"2015-03-11T20:53:32.733", + "major":"LA ARC", + "pathway":0, + "degreeLevel":2, + "degreeType":7}, + {"syskey":83856, + "requestId":101518, + "requestType":"Masters Request", + "degreeTitle":"MASTER OF ARCHITECTURE", + "majorFullName":"Architecture", + "status":"Withdrawn", + "examPlace":null, + "examDate":null, + "targetAwardYear":2015, + "targetAwardQuarter":"winter", + "requestSubmitDate":"2015-03-12T17:17:23.183", + "major":"ARCH", + "pathway":0, + "degreeLevel":2, + "degreeType":7}, + {"syskey":83856, + "requestId":101519, + "requestType":"Masters Request", + "degreeTitle":"MASTER OF ARCHITECTURE", + "majorFullName":"Architecture", + "status":"Withdrawn", + "examPlace":null, + "examDate":null, + "targetAwardYear":2015, + "targetAwardQuarter":"winter", + "requestSubmitDate":"2015-03-12T17:24:04.813", + "major":"ARCH", + "pathway":0, + "degreeLevel":2, + "degreeType":7}, + {"syskey":83856, + "requestId":101520, + "requestType":"Masters Request", + "degreeTitle":"MASTER OF LANDSCAPE ARCHITECTURE", + "majorFullName":"Landscape Architecture", + "status":"Not Recommended by Dept", + "examPlace":null, + "examDate":null, + "targetAwardYear":2015, + "targetAwardQuarter":"winter", + "requestSubmitDate":"2015-03-12T17:30:19.37", + "major":"L ARCH", + "pathway":0, + "degreeLevel":2, + "degreeType":7}, + {"syskey":83856, + "requestId":102782, + "requestType":"Masters Request", + "degreeTitle":"MASTER OF ARCHITECTURE", + "majorFullName":"Architecture", + "status":"Recommended by Dept", + "examPlace":null, + "examDate":null, + "targetAwardYear":2015, + "targetAwardQuarter":"spring", + "requestSubmitDate":"2015-03-30T15:38:12.907", + "major":"ARCH", + "pathway":0, + "degreeLevel":2, + "degreeType":7}, + {"syskey":83856, + "requestId":102785, + "requestType":"Masters Request", + "degreeTitle":"MASTER OF LANDSCAPE ARCHITECTURE", + "majorFullName":"Landscape Architecture", + "status":"Did Not Graduate", + "examPlace":null, + "examDate":null, + "targetAwardYear":2015, + "targetAwardQuarter":"spring", + "requestSubmitDate":"2015-03-30T15:40:25.007", + "major":"L ARCH", + "pathway":0, + "degreeLevel":2, + "degreeType":7} +] diff --git a/restclients/resources/hfs/file/myuw/v1/eight b/restclients/resources/hfs/file/myuw/v1/eight index c14379e3..8cc6b732 100644 --- a/restclients/resources/hfs/file/myuw/v1/eight +++ b/restclients/resources/hfs/file/myuw/v1/eight @@ -3,11 +3,11 @@ "student_husky_card": { "balance": 100.23, "last_transaction_date": "2014-06-02T15:17:16.789", - "add_funds_url": "https://www.hfs.washington.edu/olco" + "add_funds_url": "https://www.hfs.washington.edu/olco/Secure/AccountSummary.aspx" }, "resident_dining": { "balance": 15.1, "last_transaction_date": "2014-06-01T13:15:36.573", - "add_funds_url": "https://www.hfs.washington.edu/olco" + "add_funds_url": "https://www.hfs.washington.edu/olco/Secure/AccountSummary.aspx" } } diff --git a/restclients/resources/hfs/file/myuw/v1/javerage b/restclients/resources/hfs/file/myuw/v1/javerage index 1ea8a313..a1894c05 100644 --- a/restclients/resources/hfs/file/myuw/v1/javerage +++ b/restclients/resources/hfs/file/myuw/v1/javerage @@ -2,17 +2,17 @@ "student_husky_card": { "balance": 1.23, "last_transaction_date": "2014-06-02T15:17:16.789", - "add_funds_url": "https://www.hfs.washington.edu/olco" + "add_funds_url": "https://www.hfs.washington.edu/olco/Secure/AccountSummary.aspx" }, "employee_husky_card": { "balance": 0.56, "last_transaction_date": "2014-05-19T14:16:26.931", "last_tansaction_location": "The Farm Café", - "add_funds_url": "https://www.hfs.washington.edu/olco" + "add_funds_url": "https://www.hfs.washington.edu/olco/Secure/AccountSummary.aspx" }, "resident_dining": { "balance": 7.89, "last_transaction_date": "2014-06-01T13:15:36.573", - "add_funds_url": "https://www.hfs.washington.edu/olco" + "add_funds_url": "https://www.hfs.washington.edu/olco/Secure/AccountSummary.aspx" } } diff --git a/restclients/resources/hfs/file/myuw/v1/jbothell b/restclients/resources/hfs/file/myuw/v1/jbothell index c3b1c6fe..d61a4cf0 100644 --- a/restclients/resources/hfs/file/myuw/v1/jbothell +++ b/restclients/resources/hfs/file/myuw/v1/jbothell @@ -2,7 +2,7 @@ "student_husky_card": { "balance": 5.1, "last_transaction_date": "2013-09-15T15:17:16.789", - "add_funds_url": "https://www.hfs.washington.edu/olco" + "add_funds_url": "https://www.hfs.washington.edu/olco/Secure/AccountSummary.aspx" }, "employee_husky_card":null, "resident_dining":null diff --git a/restclients/resources/hfs/file/myuw/v1/jerror b/restclients/resources/hfs/file/myuw/v1/jerror new file mode 100644 index 00000000..556b16c8 --- /dev/null +++ b/restclients/resources/hfs/file/myuw/v1/jerror @@ -0,0 +1,3 @@ +{ + "Message": "An error has occurred." +} diff --git a/restclients/resources/hfs/file/myuw/v1/jinternational b/restclients/resources/hfs/file/myuw/v1/jinternational index 23a7be4b..d6ae129c 100644 --- a/restclients/resources/hfs/file/myuw/v1/jinternational +++ b/restclients/resources/hfs/file/myuw/v1/jinternational @@ -3,7 +3,7 @@ "employee_husky_card": { "balance": 200.00, "last_transaction_date": "2014-01-15T15:17:16.789", - "add_funds_url": "https://www.hfs.washington.edu/olco" + "add_funds_url": "https://www.hfs.washington.edu/olco/Secure/AccountSummary.aspx" }, "resident_dining": null, } diff --git a/restclients/resources/hfs/file/myuw/v1/jnew b/restclients/resources/hfs/file/myuw/v1/jnew index 5a205239..a35491fd 100644 --- a/restclients/resources/hfs/file/myuw/v1/jnew +++ b/restclients/resources/hfs/file/myuw/v1/jnew @@ -1,12 +1,12 @@ { "student_husky_card":{ "balance": 0.0, - "add_funds_url": "https://www.hfs.washington.edu/olco" + "add_funds_url": "https://www.hfs.washington.edu/olco/Secure/AccountSummary.aspx" }, "employee_husky_card":null, "resident_dining": { "balance": 777.89, "last_transaction_date": "2014-05-17T13:15:36.573", - "add_funds_url": "https://www.hfs.washington.edu/olco" + "add_funds_url": "https://www.hfs.washington.edu/olco/Secure/AccountSummary.aspx" } } diff --git a/restclients/resources/iasystem/uw/api/v1/evaluation/141412 b/restclients/resources/iasystem/uw/api/v1/evaluation/141412 new file mode 100644 index 00000000..b4714025 --- /dev/null +++ b/restclients/resources/iasystem/uw/api/v1/evaluation/141412 @@ -0,0 +1,97 @@ +{"collection":{ +"href":"https://uw.iasystem.org/api/v1/evaluation", +"links":[{"href":"https://uw.iasystem.org/api/v1/evaluation", + "rel":"home", + "prompt":"IASystem v1 Web API"}, + {"href":"https://uw.iasystem.org/api/v1/evaluation/141412", + "rel":"self", + "prompt":"IASystem Evaluation '141412'"}], +"version":"1.0", +"items":[{ + "links":[{"prompt":"Evaluation URL", + "href":"https://uw.iasystem.org/survey/141412", + "rel":"publishedto"}], + "href":"https://uw.iasystem.org/api/v1/evaluation/141412", + "data":[{"name":"id", + "prompt":"Id", + "value":"141412"}, + {"name":"year", + "value":"2015", + "prompt":"Year"}, + {"name":"termName", + "value":"Winter", + "prompt":"Term"}, + {"value":"Online", + "prompt":"Delivery Method", + "name":"deliveryMethod"}, + {"prompt":"Status", + "value":"Closed", + "name":"status"}, + {"prompt":"Open Date", + "value":"2015-03-13T14:00:00Z", + "name":"openDate"}, + {"value":"2015-03-21T06:59:59Z", + "prompt":"Close Date", + "name":"closeDate"}], + "meta":[ {"name":"type", + "value":"evaluation"}, + {"value":"1", + "name":"id"}, + {"name":"childId", + "value":"2"}, + {"name":"childId", + "value":"3"}, + {"value":"4", + "name":"childId"}, + {"name":"childId", + "value":"5"} ] }, + + {"meta":[{"name":"type", + "value":"section"}, + {"value":"2", + "name":"id"}], + "data":[{"value":"13180", + "name":"instCourseId"}, + {"value":"2015", + "name":"year"}, + {"value":"Winter", + "name":"termName"}, + {"name":"curriculumAbbreviation", + "value":"DRAMA"}, + {"name":"courseNumber", + "value":"401"}, + {"name":"sectionId", + "value":"A"}, + {"name":"courseTitle", + "value":"Majors Seminar"}]}, + {"meta":[{"name":"type", + "value":"instructor"}, + {"value":"3", + "name":"id"}], + "data":[{"value":"849004282", + "name":"instInstructorId"}, + {"name":"firstName", + "value":"Caitlin"}, + {"name":"lastName", + "value":"Goldbaum"}]}, + {"meta":[{"value":"instructor", + "name":"type"}, + {"name":"id", + "value":"4"}], + "data":[{"name":"instInstructorId", + "value":"849007339"}, + {"name":"firstName", + "value":"Todd"}, + {"value":"London", + "name":"lastName"}]}, + {"data":[{"name":"instInstructorId", + "value":"859003192"}, + {"name":"firstName", + "value":"Andrew"}, + {"name":"lastName", + "value":"Tsao"}], + "meta":[{"value":"instructor", + "name":"type"}, + {"name":"id", + "value":"5"}]} +]}} diff --git a/restclients/resources/iasystem/uw/api/v1/evaluation_term_name_Spring_curriculum_abbreviation_PHYS_student_id_1033334_section_id_AQ_course_number_121_year_2013 b/restclients/resources/iasystem/uw/api/v1/evaluation_term_name_Spring_curriculum_abbreviation_PHYS_student_id_1033334_section_id_AQ_course_number_121_year_2013 new file mode 100644 index 00000000..9accbeb2 --- /dev/null +++ b/restclients/resources/iasystem/uw/api/v1/evaluation_term_name_Spring_curriculum_abbreviation_PHYS_student_id_1033334_section_id_AQ_course_number_121_year_2013 @@ -0,0 +1,228 @@ +{ + "collection": { + "queries": [ + { + "rel": "collection filter", + "prompt": "Evaluation Search Resource", + "href": "https://uw.iasysdev.org/api/v1/evaluation", + "data": [ + { + "required": true, + "name": "year", + "prompt": "Year" + }, + { + "required": true, + "prompt": "Term Name", + "name": "term_name" + }, + { + "prompt": "Curriculum Abbreviation", + "name": "curriculum_abbreviation" + }, + { + "name": "course_number", + "prompt": "Course Number" + }, + { + "name": "section_id", + "prompt": "Section ID" + }, + { + "name": "student_id", + "prompt": "Student ID" + } + ] + } + ], + "items": + [ + { + "meta": + [ + { + "value": "evaluation", + "name": "type" + }, + { + "value": "1", + "name": "id" + }, + { + "value": "2", + "name": "childId" + }, + { + "value": "3", + "name": "childId" + }, + { + "value": "4", + "name": "childId" + } + + ], + "data": + [ + { + "prompt": "Id", + "name": "id", + "value": "136617" + }, + { + "name": "year", + "prompt": "Year", + "value": "2013" + }, + { + "name": "termName", + "prompt": "Term", + "value": "Spring" + }, + { + "prompt": "Delivery Method", + "name": "deliveryMethod", + "value": "Online" + }, + { + "prompt": "Status", + "name": "status", + "value": "Closed" + }, + { + "name": "openDate", + "prompt": "Open Date", + "value": "2013-06-07T15:00:00Z" + }, + { + "prompt": "Close Date", + "name": "closeDate", + "value": "2013-06-14T07:59:59Z" + } + ], + "links": + [ + { + "href": "https://uw.iasysdev.org/survey/136617", + "rel": "publishedto", + "prompt": "Evaluation URL" + } + ], + "href": "https://uw.iasysdev.org/api/v1/evaluation/136617" + }, + { + "meta": + [ + { + "value": "section", + "name": "type" + }, + { + "name": "id", + "value": "2" + } + ], + "data": + [ + { + "value": "18545", + "name": "instCourseId" + }, + { + "name": "year", + "value": "2013" + }, + { + "name": "termName", + "value": "Spring" + }, + { + "value": "PHYS", + "name": "curriculumAbbreviation" + }, + { + "name": "courseNumber", + "value": "121" + }, + { + "name": "sectionId", + "value": "AQ" + }, + { + "name": "courseTitle", + "value": "MECHANICS" + } + ] + }, + { + "data": + [ + { + "name": "instInstructorId", + "value": "123456789" + }, + { + "name": "firstName", + "value": "Steven" + }, + { + "value": "Herbert", + "name": "lastName" + } + ], + "meta": + [ + { + "name": "type", + "value": "instructor" + }, + { + "name": "id", + "value": "3" + } + ] + }, + { "meta": + [ + { + "value": "4", + "name": "id" + }, + { + "value" : "evaluation completion", + "name" : "type" + } + ], + "data" : + [ + { + "name" : "evaluationId", + "value" : "136617" + }, + { "value" : "1033334", + "name" : "studentId" + }, + { + "name" : "isCompleted", + "value" : true + } + ] + } + ], + "links": + [ + { + "rel": "home", + "prompt": "IASystem v1 Web API", + "href": "https://uw.iasysdev.org/api/v1" + }, + { + "prompt": "IASystem Evaluation Listing", + "rel": "self", + "href": "https://uw.iasysdev.org/api/v1/evaluation?section_id=AQ&curriculum_abbreviation=PHYS&course_number=121&term_name=Spring&year=2013" + } + ], + "href": "https://uw.iasysdev.org/api/v1/evaluation", + "version": "1.0" + } +} diff --git a/restclients/resources/iasystem/uw/api/v1/evaluation_term_name_Spring_curriculum_abbreviation_PHYS_student_id_1233334_section_id_AQ_course_number_121_year_2013 b/restclients/resources/iasystem/uw/api/v1/evaluation_term_name_Spring_curriculum_abbreviation_PHYS_student_id_1233334_section_id_AQ_course_number_121_year_2013 new file mode 100644 index 00000000..462443a8 --- /dev/null +++ b/restclients/resources/iasystem/uw/api/v1/evaluation_term_name_Spring_curriculum_abbreviation_PHYS_student_id_1233334_section_id_AQ_course_number_121_year_2013 @@ -0,0 +1,259 @@ +{ + "collection": { + "queries": [ + { + "rel": "collection filter", + "prompt": "Evaluation Search Resource", + "href": "https://uw.iasysdev.org/api/v1/evaluation", + "data": [ + { + "required": true, + "name": "year", + "prompt": "Year" + }, + { + "required": true, + "prompt": "Term Name", + "name": "term_name" + }, + { + "prompt": "Curriculum Abbreviation", + "name": "curriculum_abbreviation" + }, + { + "name": "course_number", + "prompt": "Course Number" + }, + { + "name": "section_id", + "prompt": "Section ID" + }, + { + "name": "student_id", + "prompt": "Student ID" + } + ] + } + ], + "items": + [ + { + "meta": + [ + { + "value": "evaluation", + "name": "type" + }, + { + "value": "1", + "name": "id" + }, + { + "value": "2", + "name": "childId" + }, + { + "value": "3", + "name": "childId" + }, + { + "value": "4", + "name": "childId" + }, + { + "value": "5", + "name": "childId" + } + ], + "data": + [ + { + "prompt": "Id", + "name": "id", + "value": "136617" + }, + { + "name": "year", + "prompt": "Year", + "value": "2013" + }, + { + "name": "termName", + "prompt": "Term", + "value": "Spring" + }, + { + "prompt": "Delivery Method", + "name": "deliveryMethod", + "value": "Online" + }, + { + "prompt": "Status", + "name": "status", + "value": "Closed" + }, + { + "name": "openDate", + "prompt": "Open Date", + "value": "2013-06-07T15:00:00Z" + }, + { + "prompt": "Close Date", + "name": "closeDate", + "value": "2013-06-14T07:59:59Z" + } + ], + "links": + [ + { + "href": "https://uw.iasysdev.org/survey/136617", + "rel": "publishedto", + "prompt": "Evaluation URL" + } + ], + "href": "https://uw.iasysdev.org/api/v1/evaluation/136617" + }, + { + "meta": + [ + { + "value": "section", + "name": "type" + }, + { + "name": "id", + "value": "2" + } + ], + "data": + [ + { + "value": "18545", + "name": "instCourseId" + }, + { + "name": "year", + "value": "2013" + }, + { + "name": "termName", + "value": "Spring" + }, + { + "value": "PHYS", + "name": "curriculumAbbreviation" + }, + { + "name": "courseNumber", + "value": "121" + }, + { + "name": "sectionId", + "value": "AQ" + }, + { + "name": "courseTitle", + "value": "MECHANICS" + } + ] + }, + { + "data": + [ + { + "name": "instInstructorId", + "value": "123456789" + }, + { + "name": "firstName", + "value": "Steven" + }, + { + "value": "Herbert", + "name": "lastName" + } + ], + "meta": + [ + { + "name": "type", + "value": "instructor" + }, + { + "name": "id", + "value": "3" + } + ] + }, + { + "data": + [ + { + "name": "instInstructorId", + "value": "987654321" + }, + { + "name": "firstName", + "value": "Steven1" + }, + { + "value": "Herbert1", + "name": "lastName" + } + ], + "meta": + [ + { + "name": "type", + "value": "instructor" + }, + { + "name": "id", + "value": "4" + } + ] + }, + { "meta": + [ + { + "value": "5", + "name": "id" + }, + { + "value" : "evaluation completion", + "name" : "type" + } + ], + "data" : + [ + { + "name" : "evaluationId", + "value" : "136617" + }, + { "value" : "1233334", + "name" : "studentId" + }, + { + "name" : "isCompleted", + "value" : false + } + ] + } + ], + "links": + [ + { + "rel": "home", + "prompt": "IASystem v1 Web API", + "href": "https://uw.iasysdev.org/api/v1" + }, + { + "prompt": "IASystem Evaluation Listing", + "rel": "self", + "href": "https://uw.iasysdev.org/api/v1/evaluation?section_id=AQ&curriculum_abbreviation=PHYS&course_number=121&term_name=Spring&year=2013" + } + ], + "href": "https://uw.iasysdev.org/api/v1/evaluation", + "version": "1.0" + } +} diff --git a/restclients/resources/iasystem/uw/api/v1/evaluation_term_name_Spring_curriculum_abbreviation_TRAIN_student_id_1033334_section_id_A_course_number_100_year_2013 b/restclients/resources/iasystem/uw/api/v1/evaluation_term_name_Spring_curriculum_abbreviation_TRAIN_student_id_1033334_section_id_A_course_number_100_year_2013 new file mode 100644 index 00000000..833f87a4 --- /dev/null +++ b/restclients/resources/iasystem/uw/api/v1/evaluation_term_name_Spring_curriculum_abbreviation_TRAIN_student_id_1033334_section_id_A_course_number_100_year_2013 @@ -0,0 +1,330 @@ +{ + "collection": { + "queries": + [ { + "rel": "collection filter", + "prompt": "Evaluation Search Resource", + "href": "https://uw.iasysdev.org/api/v1/evaluation", + "data": [ + { + "required": true, + "name": "year", + "prompt": "Year" + }, + { + "required": true, + "prompt": "Term Name", + "name": "term_name" + }, + { + "prompt": "Curriculum Abbreviation", + "name": "curriculum_abbreviation" + }, + { + "name": "course_number", + "prompt": "Course Number" + }, + { + "name": "section_id", + "prompt": "Section ID" + }, + { + "name": "student_id", + "prompt": "Student ID" + } ] + } ], + "items": + [ {"meta":[ + { + "value": "evaluation", + "name": "type" + }, + { + "value": "1", + "name": "id" + }, + { + "value": "2", + "name": "childId" + }, + { + "value": "3", + "name": "childId" + }, + { + "value": "4", + "name": "childId" + } ], + "data":[ + { + "prompt": "Id", + "name": "id", + "value": "136617" + }, + { + "name": "year", + "prompt": "Year", + "value": "2013" + }, + { + "name": "termName", + "prompt": "Term", + "value": "Spring" + }, + { + "prompt": "Delivery Method", + "name": "deliveryMethod", + "value": "Online" + }, + { + "prompt": "Status", + "name": "status", + "value": "Closed" + }, + { + "name": "openDate", + "prompt": "Open Date", + "value": "2013-05-30T15:00:00Z" + }, + { + "prompt": "Close Date", + "name": "closeDate", + "value": "2013-07-01T07:59:59Z" + } + ], + "links": [ + { + "href": "https://uw.iasysdev.org/survey/136617", + "rel": "publishedto", + "prompt": "Evaluation URL" + } ], + "href": "https://uw.iasysdev.org/api/v1/evaluation/136617" + }, + {"meta":[ + { + "value": "section", + "name": "type" + }, + { + "name": "id", + "value": "2" + } + ], + "data":[ + { + "value": "17169", + "name": "instCourseId" + }, + { + "name": "year", + "value": "2013" + }, + { + "name": "termName", + "value": "Spring" + }, + { + "value": "TRAIN", + "name": "curriculumAbbreviation" + }, + { + "name": "courseNumber", + "value": "100" + }, + { + "name": "sectionId", + "value": "A" + }, + { + "name": "courseTitle", + "value": "Train One Hundred" + } + ] + }, + {"data":[ + { + "name": "instInstructorId", + "value": "123456789" + }, + { + "name": "firstName", + "value": "Steven" + }, + { + "value": "Herbert", + "name": "lastName" + } + ], + "meta": [ + { + "name": "type", + "value": "instructor" + }, + { + "name": "id", + "value": "3" + }] + }, + {"meta":[ + { + "value": "4", + "name": "id" + }, + { + "value" : "evaluation completion", + "name" : "type" + }], + "data":[ + { + "name" : "evaluationId", + "value" : "136617" + }, + { "value" : "1033334", + "name" : "studentId" + }, + { + "name" : "isCompleted", + "value" : false + }] + }, + + {"href":"https://demo.iasystem.org/api/v1/evaluation/96956", + "meta":[{"value":"evaluation", + "name":"type"}, + {"value":"5", + "name":"id"}, + {"value":"2", + "name":"childId"}, + {"value":"6", + "name":"childId"}, + {"value":"7", + "name":"childId"}], + "links":[{"prompt":"Evaluation URL", + "rel":"publishedto", + "href":"https://demo.iasystem.org/survey/96956"}], + "data":[{"prompt":"Id", + "value":"96956", + "name":"id"}, + {"value":"2013", + "prompt":"Year", + "name":"year"}, + {"prompt":"Term", + "value":"Spring", + "name":"termName"}, + {"prompt":"Delivery Method", + "value":"Online", + "name":"deliveryMethod"}, + {"name":"status", + "value":"Pending", + "prompt":"Status"}, + {"name":"openDate", + "prompt":"Open Date", + "value":"2013-06-05T07:00:00Z"}, + {"name":"closeDate", + "prompt":"Close Date", + "value":"2013-06-17T06:59:59Z"}] + }, + {"meta":[{"name":"type", + "value":"instructor"}, + {"value":"6", + "name":"id"}], + "data":[{"name":"instInstructorId", + "value":"123456782"}, + {"value":"see pws", + "name":"firstName"}, + {"name":"lastName", + "value":"see pws"}]}, + {"data":[{"name":"evaluationId", + "value":"96956"}, + {"name":"studentId", + "value":"1033334"}, + {"value": false, + "name":"isCompleted"}], + "meta":[{"value":"evaluation completion", + "name":"type"}, + {"value":"7", + "name":"id"}] + }, + {"links":[{"rel":"publishedto", + "prompt":"Evaluation URL", + "href":"https://uw.iasystem.org/survey/96957"}], + "data":[{"name":"id", + "value":"96957", + "prompt":"Id"}, + {"prompt":"Year", + "value":"2013", + "name":"year"}, + {"prompt":"Term", + "value":"Spring", + "name":"termName"}, + {"prompt":"Delivery Method", + "value":"Online", + "name":"deliveryMethod"}, + {"value":"Pending", + "prompt":"Status", + "name":"status"}, + {"name":"openDate", + "value":"2013-06-10T07:00:00Z", + "prompt":"Open Date"}, + {"name":"closeDate", + "value":"2013-06-19T06:59:59Z", + "prompt":"Close Date"}], + "meta":[{"name":"type", + "value":"evaluation"}, + {"value":"8", + "name":"id"}, + {"value":"2", + "name":"childId"}, + {"name":"childId", + "value":"9"}, + {"value":"10", + "name":"childId"}], + "href":"https://uw.iasystem.org/api/v1/evaluation/96957"}, + {"data":[{"name":"instInstructorId", + "value":"123456798"}, + {"value":"see pws", + "name":"firstName"}, + {"value":"see pws", + "name":"lastName"}], + "meta":[{"value":"instructor", + "name":"type"}, + {"value":"9", + "name":"id"}] + }, + {"meta": [ { + "value": "10", + "name": "id" + }, + { + "value" : "evaluation completion", + "name" : "type" + }], + "data": [ { + "name" : "evaluationId", + "value" : "96957" + }, + { "value" : "1033334", + "name" : "studentId" + }, + { + "name" : "isCompleted", + "value" : true + }] + } + ], + "links": [ + { + "rel": "home", + "prompt": "IASystem v1 Web API", + "href": "https://uw.iasysdev.org/api/v1" + }, + { + "prompt": "IASystem Evaluation Listing", + "rel": "self", + "href": "https://uw.iasysdev.org/api/v1/evaluation?section_id=A&curriculum_abbreviation=LSJ&course_number=200&term_name=Autumn&year=2014" + } + ], + "href": "https://uw.iasysdev.org/api/v1/evaluation", + "version": "1.0" + } +} diff --git a/restclients/resources/iasystem/uwb/api/v1/evaluation_student_id_1033334_term_name_Autumn_year_2014 b/restclients/resources/iasystem/uwb/api/v1/evaluation_student_id_1033334_term_name_Autumn_year_2014 index 30b5d09a..9bf09f52 100644 --- a/restclients/resources/iasystem/uwb/api/v1/evaluation_student_id_1033334_term_name_Autumn_year_2014 +++ b/restclients/resources/iasystem/uwb/api/v1/evaluation_student_id_1033334_term_name_Autumn_year_2014 @@ -2,7 +2,7 @@ "collection": { "queries": [ { - "href": "https://uw.iasysdev.org/api/v1/evaluation", + "href": "https://uwb.iasysdev.org/api/v1/evaluation", "data": [ { "required": true, @@ -95,11 +95,11 @@ "links": [ { "rel": "publishedto", - "href": "https://uw.iasysdev.org/survey/132068", + "href": "https://uwb.iasysdev.org/survey/132068", "prompt": "Evaluation URL" } ], - "href": "https://uw.iasysdev.org/api/v1/evaluation/132068" + "href": "https://uwb.iasysdev.org/api/v1/evaluation/132068" }, { "meta": [ @@ -233,11 +233,11 @@ "value": "2014-12-03T07:59:59Z" } ], - "href": "https://uw.iasysdev.org/api/v1/evaluation/132136", + "href": "https://uwb.iasysdev.org/api/v1/evaluation/132136", "links": [ { "rel": "publishedto", - "href": "https://uw.iasysdev.org/survey/132136", + "href": "https://uwb.iasysdev.org/survey/132136", "prompt": "Evaluation URL" } ] @@ -469,11 +469,11 @@ "links": [ { "prompt": "Evaluation URL", - "href": "https://uw.iasysdev.org/survey/132167", + "href": "https://uwb.iasysdev.org/survey/132167", "rel": "publishedto" } ], - "href": "https://uw.iasysdev.org/api/v1/evaluation/132167" + "href": "https://uwb.iasysdev.org/api/v1/evaluation/132167" }, { "meta": [ @@ -699,16 +699,16 @@ "links": [ { "rel": "home", - "href": "https://uw.iasysdev.org/api/v1", + "href": "https://uwb.iasysdev.org/api/v1", "prompt": "IASystem v1 Web API" }, { "rel": "self", - "href": "https://uw.iasysdev.org/api/v1/evaluation?student_id=1230430&term_name=Autumn&year=2014", + "href": "https://uwb.iasysdev.org/api/v1/evaluation?student_id=1230430&term_name=Autumn&year=2014", "prompt": "IASystem Evaluation Listing" } ], - "href": "https://uw.iasysdev.org/api/v1/evaluation", + "href": "https://uwb.iasysdev.org/api/v1/evaluation", "version": "1.0" } -} \ No newline at end of file +} diff --git a/restclients/resources/iasystem/uwt/api/v1/evaluation_student_id_1033334_term_name_Autumn_year_2014 b/restclients/resources/iasystem/uwt/api/v1/evaluation_student_id_1033334_term_name_Autumn_year_2014 index 30b5d09a..41012556 100644 --- a/restclients/resources/iasystem/uwt/api/v1/evaluation_student_id_1033334_term_name_Autumn_year_2014 +++ b/restclients/resources/iasystem/uwt/api/v1/evaluation_student_id_1033334_term_name_Autumn_year_2014 @@ -454,23 +454,6 @@ "prompt": "Status", "name": "status", "value": "Closed" - }, - { - "value": "2014-11-24T15:00:00Z", - "prompt": "Open Date", - "name": "openDate" - }, - { - "value": "2014-12-03T07:59:59Z", - "prompt": "Close Date", - "name": "closeDate" - } - ], - "links": [ - { - "prompt": "Evaluation URL", - "href": "https://uw.iasysdev.org/survey/132167", - "rel": "publishedto" } ], "href": "https://uw.iasysdev.org/api/v1/evaluation/132167" diff --git a/restclients/resources/kws/file/key/v1/encryption/ee99defd-baee-43b0-9e1e-f8238dd106bb.json b/restclients/resources/kws/file/key/v1/encryption/ee99defd-baee-43b0-9e1e-f8238dd106bb.json new file mode 100644 index 00000000..fb3a2cec --- /dev/null +++ b/restclients/resources/kws/file/key/v1/encryption/ee99defd-baee-43b0-9e1e-f8238dd106bb.json @@ -0,0 +1,15 @@ +{"Algorithm":"AES128CBC", +"AppliesTo":[ + {"DateCreated":"2013-01-11T13:44:33.36", + "Description":"UW Student Registration Event", + "ID":"b917bbd0-c7fb-42bc-acc7-c73dab23ccaa", + "Name":"uw-student-registration", + "Url":"https://it-wseval1.s.uw.edu/key/v1/type/uw-student-registration.json"} +], +"CipherMode":"CBC", +"DateCreated":"2013-01-11T13:44:33.36", +"Expiration":"2013-04-11T13:44:33.36", +"ID":"ee99defd-baee-43b0-9e1e-f8238dd106bb", +"Key":"Uv2JsxggfxF9OQNzIxAzDQ==", +"KeySize":128, +"KeyUrl":"https://it-wseval1.s.uw.edu/key/v1/encryption/ee99defd-baee-43b0-9e1e-f8238dd106bb.json"} diff --git a/restclients/resources/kws/file/key/v1/type/uw-student-registration/encryption/current.json b/restclients/resources/kws/file/key/v1/type/uw-student-registration/encryption/current.json new file mode 100644 index 00000000..fb3a2cec --- /dev/null +++ b/restclients/resources/kws/file/key/v1/type/uw-student-registration/encryption/current.json @@ -0,0 +1,15 @@ +{"Algorithm":"AES128CBC", +"AppliesTo":[ + {"DateCreated":"2013-01-11T13:44:33.36", + "Description":"UW Student Registration Event", + "ID":"b917bbd0-c7fb-42bc-acc7-c73dab23ccaa", + "Name":"uw-student-registration", + "Url":"https://it-wseval1.s.uw.edu/key/v1/type/uw-student-registration.json"} +], +"CipherMode":"CBC", +"DateCreated":"2013-01-11T13:44:33.36", +"Expiration":"2013-04-11T13:44:33.36", +"ID":"ee99defd-baee-43b0-9e1e-f8238dd106bb", +"Key":"Uv2JsxggfxF9OQNzIxAzDQ==", +"KeySize":128, +"KeyUrl":"https://it-wseval1.s.uw.edu/key/v1/encryption/ee99defd-baee-43b0-9e1e-f8238dd106bb.json"} diff --git a/restclients/resources/libcurrics/file/currics_db/api/v1/data/course/2015/AUT/MATH/309/A b/restclients/resources/libcurrics/file/currics_db/api/v1/data/course/2015/AUT/MATH/309/A new file mode 100644 index 00000000..d3faf623 --- /dev/null +++ b/restclients/resources/libcurrics/file/currics_db/api/v1/data/course/2015/AUT/MATH/309/A @@ -0,0 +1,36 @@ +{ + "subjectGuide": { + "askUsLink": "http://www.lib.washington.edu/about/contact", + "askUsText": "UW librarians are also available to help answer your general questions by email, phone, text, or 24/7 real-time chat.", + "discipline": "Mathematics", + "findLibrarianLink": "http://guides.lib.uw.edu/research/subject-librarians", + "findLibrarianText": "At UW Libraries, there's a dedicated librarian for Mathematics who can help you with your research needs. Get in touch with them by phone or email.", + "guideLink": "http://guides.lib.uw.edu/friendly.php?s=research/math", + "guideText": "Mathematics, Pure and Applied", + "howDoILink": "http://guides.lib.uw.edu/research/faq", + "howDoIText": "This online guide addresses common research and library tasks with short video tutorials and links to other UW help guides.", + "librarians": [ + { + "email": "javerage@uw.edu", + "name": "J Average", + "telephone": "111-111-1111", + "url": "http://guides.lib.washington.edu/Javerage" + }, + { + "email": "baverage@uw.edu", + "name": "B Average", + "telephone": "111-111-1112", + "url": "http://guides.lib.washington.edu/Baverage" + } + ], + "libraries": [ + { + "description": "The Mathematics Research Library provides research help and access to materials in Applied Mathematics, Mathematics, and Statistics.", + "name": "Mathematics Research Library", + "url": "http://www.lib.washington.edu/math" + } + ], + "writingGuideLink": "http://guides.lib.uw.edu/research/citations", + "writingGuideText": "Find citation style guides, citation management tools, and writing/grammar help." + } +} diff --git a/restclients/resources/pws/file/identity/v1/person.json_employee_id_123456789 b/restclients/resources/pws/file/identity/v1/person.json_employee_id_123456789 index e797c107..3da32e29 100644 --- a/restclients/resources/pws/file/identity/v1/person.json_employee_id_123456789 +++ b/restclients/resources/pws/file/identity/v1/person.json_employee_id_123456789 @@ -15,7 +15,7 @@ "RegisteredFirstMiddleName": null, "RegisteredSurname": null, "Start": "1", - "StudentNumber": null, + "StudentNumber": "1234567", "StudentSystemKey": null, "UWNetID": null, "UWRegID": null diff --git a/restclients/resources/pws/file/identity/v1/person.json_employee_id_999999999 b/restclients/resources/pws/file/identity/v1/person.json_employee_id_999999999 new file mode 100644 index 00000000..3ea68de9 --- /dev/null +++ b/restclients/resources/pws/file/identity/v1/person.json_employee_id_999999999 @@ -0,0 +1,27 @@ +{ + "Current": { + "DevelopmentID": null, + "EduPersonAffiliationAffiliate": null, + "EduPersonAffiliationAlum": null, + "EduPersonAffiliationEmployee": null, + "EduPersonAffiliationFaculty": null, + "EduPersonAffiliationMember": null, + "EduPersonAffiliationStaff": null, + "EduPersonAffiliationStudent": null, + "EmployeeID": "999999999", + "Href": "/identity/v1/person.json?uwregid=&uwnetid=&employee_id=999999999&student_number=&student_system_key=&development_id=®istered_surname=®istered_first_middle_name=&edupersonaffiliation_student=&edupersonaffiliation_staff=&edupersonaffiliation_faculty=&edupersonaffiliation_employee=&edupersonaffiliation_member=&edupersonaffiliation_alum=&edupersonaffiliation_affiliate=&page_size=10&page_start=1", + "PageSize": "10", + "PageStart": "1", + "RegisteredFirstMiddleName": null, + "RegisteredSurname": null, + "Start": "1", + "StudentNumber": null, + "StudentSystemKey": null, + "UWNetID": null, + "UWRegID": null + }, + "MaxResultSize": 500, + "Next": null, + "Persons": [], + "Previous": null +} diff --git a/restclients/resources/pws/file/identity/v1/person.json_student_number_1234567 b/restclients/resources/pws/file/identity/v1/person.json_student_number_1234567 new file mode 100644 index 00000000..3da32e29 --- /dev/null +++ b/restclients/resources/pws/file/identity/v1/person.json_student_number_1234567 @@ -0,0 +1,42 @@ +{ + "Current": { + "DevelopmentID": null, + "EduPersonAffiliationAffiliate": null, + "EduPersonAffiliationAlum": null, + "EduPersonAffiliationEmployee": null, + "EduPersonAffiliationFaculty": null, + "EduPersonAffiliationMember": null, + "EduPersonAffiliationStaff": null, + "EduPersonAffiliationStudent": null, + "EmployeeID": "123456789", + "Href": "/identity/v1/person.json?uwregid=&uwnetid=&employee_id=123456789&student_number=&student_system_key=&development_id=®istered_surname=®istered_first_middle_name=&edupersonaffiliation_student=&edupersonaffiliation_staff=&edupersonaffiliation_faculty=&edupersonaffiliation_employee=&edupersonaffiliation_member=&edupersonaffiliation_alum=&edupersonaffiliation_affiliate=&page_size=10&page_start=1", + "PageSize": "10", + "PageStart": "1", + "RegisteredFirstMiddleName": null, + "RegisteredSurname": null, + "Start": "1", + "StudentNumber": "1234567", + "StudentSystemKey": null, + "UWNetID": null, + "UWRegID": null + }, + "MaxResultSize": 500, + "Next": null, + "Persons": [ + { + "PersonFullURI": { + "DisplayName": "James Average Student", + "Href": "/identity/v1/person/9136CCB8F66711D5BE060004AC494FFE/full.json", + "UWNetID": "javerage", + "UWRegID": "9136CCB8F66711D5BE060004AC494FFE" + }, + "PersonURI": { + "DisplayName": "James Average Student", + "Href": "/identity/v1/person/9136CCB8F66711D5BE060004AC494FFE.json", + "UWNetID": "javerage", + "UWRegID": "9136CCB8F66711D5BE060004AC494FFE" + } + } + ], + "Previous": null +} diff --git a/restclients/resources/pws/file/identity/v1/person.json_student_number_9999999 b/restclients/resources/pws/file/identity/v1/person.json_student_number_9999999 new file mode 100644 index 00000000..6fea6963 --- /dev/null +++ b/restclients/resources/pws/file/identity/v1/person.json_student_number_9999999 @@ -0,0 +1,27 @@ +{ + "Current": { + "DevelopmentID": null, + "EduPersonAffiliationAffiliate": null, + "EduPersonAffiliationAlum": null, + "EduPersonAffiliationEmployee": null, + "EduPersonAffiliationFaculty": null, + "EduPersonAffiliationMember": null, + "EduPersonAffiliationStaff": null, + "EduPersonAffiliationStudent": null, + "EmployeeID": null, + "Href": "/identity/v1/person.json?uwregid=&uwnetid=&employee_id=&student_number=9999999&student_system_key=&development_id=®istered_surname=®istered_first_middle_name=&edupersonaffiliation_student=&edupersonaffiliation_staff=&edupersonaffiliation_faculty=&edupersonaffiliation_employee=&edupersonaffiliation_member=&edupersonaffiliation_alum=&edupersonaffiliation_affiliate=&page_size=10&page_start=1", + "PageSize": "10", + "PageStart": "1", + "RegisteredFirstMiddleName": null, + "RegisteredSurname": null, + "Start": "1", + "StudentNumber": "9999999", + "StudentSystemKey": null, + "UWNetID": null, + "UWRegID": null + }, + "MaxResultSize": 500, + "Next": null, + "Persons": [], + "Previous": null +} diff --git a/restclients/resources/pws/file/identity/v1/person/javerage/full.json b/restclients/resources/pws/file/identity/v1/person/javerage/full.json index af88f1bd..6c36cb54 100644 --- a/restclients/resources/pws/file/identity/v1/person/javerage/full.json +++ b/restclients/resources/pws/file/identity/v1/person/javerage/full.json @@ -1 +1 @@ -{"DisplayName":"James Student","EduPersonAffiliations":["member","student","alum","staff","employee"],"IsTestEntity":true,"PriorUWNetIDs":[],"PriorUWRegIDs":[],"RegisteredFirstMiddleName":"JAMES AVERAGE","RegisteredName":"JAMES AVERAGE STUDENT","RegisteredSurname":"STUDENT","UIDNumber":"35443","UWNetID":"javerage","UWRegID":"9136CCB8F66711D5BE060004AC494FFE","WhitepagesPublish":false,"PersonAffiliations":{"AlumPersonAffiliation":{"DevelopmentID":"0000773877"},"EmployeePersonAffiliation":{"EmployeeID":"123456789","EmployeeWhitePages":{"Address1":null,"Address2":null,"Department1":null,"Department2":null,"Email1":null,"Email2":null,"Fax":null,"Name":null,"Phone1":null,"Phone2":null,"PublishInDirectory":false,"Title1":null,"Title2":null,"TouchDial":null,"VoiceMail":null},"HomeDepartment":"C&C TEST BUDGET","MailStop":null},"StudentPersonAffiliation":{"StudentNumber":"1033334","StudentSystemKey":"000083856","StudentWhitePages":{"Class":null,"Department1":null,"Department2":null,"Department3":null,"Email":null,"Name":null,"Phone":null,"PublishInDirectory":false}}}} +{"DisplayName":"James Student","EduPersonAffiliations":["member","student","alum","staff","employee"],"IsTestEntity":true,"PriorUWNetIDs":[],"PriorUWRegIDs":[],"RegisteredFirstMiddleName":"JAMES AVERAGE","RegisteredName":"JAMES AVERAGE STUDENT","RegisteredSurname":"STUDENT","UIDNumber":"35443","UWNetID":"javerage","UWRegID":"9136CCB8F66711D5BE060004AC494FFE","WhitepagesPublish":false,"PersonAffiliations":{"AlumPersonAffiliation":{"DevelopmentID":"0000773877"},"EmployeePersonAffiliation":{"EmployeeID":"123456789","EmployeeWhitePages":{"Address1":null,"Address2":null,"Department1":null,"Department2":null,"Email1":null,"Email2":null,"Fax":null,"Name":null,"Phone1":null,"Phone2":null,"PublishInDirectory":false,"Title1":null,"Title2":null,"TouchDial":null,"VoiceMail":null},"HomeDepartment":"C&C TEST BUDGET","MailStop":null},"StudentPersonAffiliation":{"StudentNumber":"1033334","StudentSystemKey":"000083856","StudentWhitePages":{"Class":"Junior","Department1":"Informatics","Department2":null,"Department3":null,"Email":null,"Name":null,"Phone":null,"PublishInDirectory":false}}}} diff --git a/restclients/resources/sws/file/student/v5/notice/9136CCB8F66711D5BE060004AC494FFE.json b/restclients/resources/sws/file/student/v5/notice/9136CCB8F66711D5BE060004AC494FFE.json index 70d628c9..f21aec29 100644 --- a/restclients/resources/sws/file/student/v5/notice/9136CCB8F66711D5BE060004AC494FFE.json +++ b/restclients/resources/sws/file/student/v5/notice/9136CCB8F66711D5BE060004AC494FFE.json @@ -188,6 +188,51 @@ "NoticeCategory": "StudentDAD", "NoticeContent": "Spring quarter ends June 13, 2014", "NoticeType": "QtrEnd" + }, + { + "NoticeAttributes": [], + "NoticeCategory": "StudentFinAid", + "NoticeType": "DirectDeposit", + "NoticeContent": "Enroll in Direct Deposit for Aid Funds Sign up for direct deposit of your Financial Aid Funds" + }, + { + "NoticeAttributes": [], + "NoticeCategory": "StudentFinAid", + "NoticeType": "DirectDepositShort", + "NoticeContent": "Enroll in Direct Deposit" + }, + { + "NoticeAttributes": [ + { + "Name": "Year", + "DataType": "string", + "Value": "2016" + }, + { + "Name": "Date", + "DataType": "date", + "Value": "future" + }, + { + "Name": "Begin", + "DataType": "date", + "Value": "week" + }, + { + "Name": "End", + "DataType": "date", + "Value": "future" + } + ], + "NoticeCategory": "StudentFinAid", + "NoticeType": "AidPriorityDate", + "NoticeContent": "Submit [year] FAFSA by Feb 28th If you haven't already, complete your [year] Financial Aid Application before Feb 28th for priority consideration" + }, + { + "NoticeAttributes": [], + "NoticeCategory": "StudentFinAid", + "NoticeType": "AidPriorityDateShort", + "NoticeContent": "Submit [year] FAFSA by Feb 28th" } ], "Person": { diff --git a/restclients/resources/sws/file/student/v5/person/9136CCB8F66711D5BE060004AC494F31.json b/restclients/resources/sws/file/student/v5/person/9136CCB8F66711D5BE060004AC494F31.json index cd3cd0de..7c7af9a3 100644 --- a/restclients/resources/sws/file/student/v5/person/9136CCB8F66711D5BE060004AC494F31.json +++ b/restclients/resources/sws/file/student/v5/person/9136CCB8F66711D5BE060004AC494F31.json @@ -28,7 +28,7 @@ "Resident":null, "StudentName":"Chakrabarti,Japendra", "StudentNumber":"1233334", -"StudentSystemKey":"000083856", +"StudentSystemKey":"000018235", "TestScore":{"Href":"\/student\/v5\/testscore\/9136CCB8F66711D5BE060004AC494F31.json", "RegID":"9136CCB8F66711D5BE060004AC494F31"}, "UWNetID":"jinter", diff --git a/restclients/resources/trumba_bot/file/service/calendars.asmx/SetPermissions_CalendarID_2_Email__washington.edu_Level_EDIT b/restclients/resources/trumba_bot/file/service/calendars.asmx/SetPermissions_CalendarID_2_Email__washington.edu_Level_EDIT new file mode 100644 index 00000000..c64fb9ba --- /dev/null +++ b/restclients/resources/trumba_bot/file/service/calendars.asmx/SetPermissions_CalendarID_2_Email__washington.edu_Level_EDIT @@ -0,0 +1,4 @@ + + + + diff --git a/restclients/resources/trumba_bot/file/service/calendars.asmx/SetPermissions_CalendarID_2_Email_test10_washington.edu_Level_EDIT b/restclients/resources/trumba_bot/file/service/calendars.asmx/SetPermissions_CalendarID_2_Email_test10_washington.edu_Level_EDIT new file mode 100644 index 00000000..681ec373 --- /dev/null +++ b/restclients/resources/trumba_bot/file/service/calendars.asmx/SetPermissions_CalendarID_2_Email_test10_washington.edu_Level_EDIT @@ -0,0 +1,4 @@ + + + + diff --git a/restclients/resources/trumba_bot/file/service/calendars.asmx/SetPermissions_CalendarID_2_Email_test10_washington.edu_Level_NONE b/restclients/resources/trumba_bot/file/service/calendars.asmx/SetPermissions_CalendarID_2_Email_test10_washington.edu_Level_NONE new file mode 100644 index 00000000..681ec373 --- /dev/null +++ b/restclients/resources/trumba_bot/file/service/calendars.asmx/SetPermissions_CalendarID_2_Email_test10_washington.edu_Level_NONE @@ -0,0 +1,4 @@ + + + + diff --git a/restclients/resources/trumba_bot/file/service/calendars.asmx/SetPermissions_CalendarID_2_Email_test10_washington.edu_Level_PUBLISH b/restclients/resources/trumba_bot/file/service/calendars.asmx/SetPermissions_CalendarID_2_Email_test10_washington.edu_Level_PUBLISH new file mode 100644 index 00000000..468657ed --- /dev/null +++ b/restclients/resources/trumba_bot/file/service/calendars.asmx/SetPermissions_CalendarID_2_Email_test10_washington.edu_Level_PUBLISH @@ -0,0 +1,4 @@ + + + + diff --git a/restclients/resources/trumba_bot/file/service/calendars.asmx/SetPermissions_CalendarID_2_Email_test10_washington.edu_Level_SHOWON b/restclients/resources/trumba_bot/file/service/calendars.asmx/SetPermissions_CalendarID_2_Email_test10_washington.edu_Level_SHOWON new file mode 100644 index 00000000..681ec373 --- /dev/null +++ b/restclients/resources/trumba_bot/file/service/calendars.asmx/SetPermissions_CalendarID_2_Email_test10_washington.edu_Level_SHOWON @@ -0,0 +1,4 @@ + + + + diff --git a/restclients/resources/trumba_sea/file/service/calendars.asmx/SetPermissions_CalendarID_1_Email_test10_washington.edu_Level_NONE b/restclients/resources/trumba_sea/file/service/calendars.asmx/SetPermissions_CalendarID_1_Email_test10_washington.edu_Level_NONE new file mode 100644 index 00000000..681ec373 --- /dev/null +++ b/restclients/resources/trumba_sea/file/service/calendars.asmx/SetPermissions_CalendarID_1_Email_test10_washington.edu_Level_NONE @@ -0,0 +1,4 @@ + + + + diff --git a/restclients/resources/trumba_tac/file/service/calendars.asmx/SetPermissions_CalendarID_3_Email__washington.edu_Level_EDIT b/restclients/resources/trumba_tac/file/service/calendars.asmx/SetPermissions_CalendarID_3_Email__washington.edu_Level_EDIT new file mode 100644 index 00000000..c64fb9ba --- /dev/null +++ b/restclients/resources/trumba_tac/file/service/calendars.asmx/SetPermissions_CalendarID_3_Email__washington.edu_Level_EDIT @@ -0,0 +1,4 @@ + + + + diff --git a/restclients/resources/trumba_tac/file/service/calendars.asmx/SetPermissions_CalendarID_3_Email_test10_washington.edu_Level_EDIT b/restclients/resources/trumba_tac/file/service/calendars.asmx/SetPermissions_CalendarID_3_Email_test10_washington.edu_Level_EDIT new file mode 100644 index 00000000..681ec373 --- /dev/null +++ b/restclients/resources/trumba_tac/file/service/calendars.asmx/SetPermissions_CalendarID_3_Email_test10_washington.edu_Level_EDIT @@ -0,0 +1,4 @@ + + + + diff --git a/restclients/resources/trumba_tac/file/service/calendars.asmx/SetPermissions_CalendarID_3_Email_test10_washington.edu_Level_NONE b/restclients/resources/trumba_tac/file/service/calendars.asmx/SetPermissions_CalendarID_3_Email_test10_washington.edu_Level_NONE new file mode 100644 index 00000000..681ec373 --- /dev/null +++ b/restclients/resources/trumba_tac/file/service/calendars.asmx/SetPermissions_CalendarID_3_Email_test10_washington.edu_Level_NONE @@ -0,0 +1,4 @@ + + + + diff --git a/restclients/resources/trumba_tac/file/service/calendars.asmx/SetPermissions_CalendarID_3_Email_test10_washington.edu_Level_PUBLISH b/restclients/resources/trumba_tac/file/service/calendars.asmx/SetPermissions_CalendarID_3_Email_test10_washington.edu_Level_PUBLISH new file mode 100644 index 00000000..468657ed --- /dev/null +++ b/restclients/resources/trumba_tac/file/service/calendars.asmx/SetPermissions_CalendarID_3_Email_test10_washington.edu_Level_PUBLISH @@ -0,0 +1,4 @@ + + + + diff --git a/restclients/resources/trumba_tac/file/service/calendars.asmx/SetPermissions_CalendarID_3_Email_test10_washington.edu_Level_SHOWON b/restclients/resources/trumba_tac/file/service/calendars.asmx/SetPermissions_CalendarID_3_Email_test10_washington.edu_Level_SHOWON new file mode 100644 index 00000000..681ec373 --- /dev/null +++ b/restclients/resources/trumba_tac/file/service/calendars.asmx/SetPermissions_CalendarID_3_Email_test10_washington.edu_Level_SHOWON @@ -0,0 +1,4 @@ + + + + diff --git a/restclients/sws/section.py b/restclients/sws/section.py index 40d0c1b1..65bc72fe 100644 --- a/restclients/sws/section.py +++ b/restclients/sws/section.py @@ -6,3 +6,19 @@ from restclients.sws.v5.section import get_section_by_label from restclients.sws.v5.section import get_linked_sections from restclients.sws.v5.section import get_joint_sections +from restclients.models.sws import Section + + +def is_a_term(str): + return str is not None and len(str) > 0 and\ + str.lower() == Section.SUMMER_A_TERM + + +def is_b_term(str): + return str is not None and len(str) > 0 and\ + str.lower() == Section.SUMMER_B_TERM + + +def is_full_summer_term(str): + return str is not None and len(str) > 0 and\ + str.lower() == Section.SUMMER_FULL_TERM diff --git a/restclients/sws/term.py b/restclients/sws/term.py index c098bd51..f023b3d3 100644 --- a/restclients/sws/term.py +++ b/restclients/sws/term.py @@ -5,3 +5,29 @@ from restclients.sws.v5.term import get_term_before from restclients.sws.v5.term import get_term_after from restclients.sws.v5.term import get_term_by_date + + +def get_specific_term(year, quarter): + """ + Rename the get_term_by_year_and_quarter to a short name. + """ + return get_term_by_year_and_quarter(year, quarter.lower()) + + +def get_next_autumn_term(term): + """ + Return the Term object for the next autumn quarter + in the same year as the given term + """ + return get_specific_term(term.year, 'autumn') + + +def get_next_non_summer_term(term): + """ + Return the Term object for the quarter after + as the given term (skip the summer quarter) + """ + next_term = get_term_after(term) + if next_term.is_summer_quarter(): + return get_next_autumn_term(next_term) + return next_term diff --git a/restclients/sws/v5/graderoster.py b/restclients/sws/v5/graderoster.py index 17f3a7ad..5f390063 100644 --- a/restclients/sws/v5/graderoster.py +++ b/restclients/sws/v5/graderoster.py @@ -7,8 +7,10 @@ from lxml import etree import re + graderoster_url = "/student/v5/graderoster" + def get_graderoster(section, instructor): """ Returns a restclients.GradeRoster for the passed Section model and @@ -18,7 +20,9 @@ def get_graderoster(section, instructor): instructor=instructor).graderoster_label() url = "%s/%s" % (graderoster_url, encode_section_label(label)) headers = {"Accept": "text/xhtml", + "Connection": "keep-alive", "X-UW-Act-as": instructor.uwnetid} + response = SWS_DAO().getURL(url, headers) if response.status != 200: @@ -38,6 +42,7 @@ def update_graderoster(graderoster): label = graderoster.graderoster_label() url = "%s/%s" % (graderoster_url, encode_section_label(label)) headers = {"Content-Type": "application/xhtml+xml", + "Connection": "keep-alive", "X-UW-Act-as": graderoster.instructor.uwnetid} body = graderoster.xhtml() diff --git a/restclients/sws/v5/notice.py b/restclients/sws/v5/notice.py index ced466b7..705779fc 100644 --- a/restclients/sws/v5/notice.py +++ b/restclients/sws/v5/notice.py @@ -2,6 +2,7 @@ Interfaceing with the Student Web Service, for notice resource """ +import copy import logging from restclients.models.sws import Notice, NoticeAttribute from restclients.sws import get_resource @@ -24,43 +25,65 @@ def get_notices_by_regid(regid): def _notices_from_json(notice_data): - notices = [] notices_list = notice_data.get("Notices") - if notices_list is not None: - for notice in notices_list: - notice_obj = Notice() - notice_obj.notice_category = notice.get("NoticeCategory") - notice_obj.notice_content = notice.get("NoticeContent") - notice_obj.notice_type = notice.get("NoticeType") + if notices_list is None: + return None + notices = [] + for notice in notices_list: + notice_obj = Notice() + notice_obj.notice_category = notice.get("NoticeCategory") + notice_obj.notice_content = notice.get("NoticeContent") + notice_obj.notice_type = notice.get("NoticeType") + + notice_attribs = [] + try: + for notice_attrib in notice.get("NoticeAttributes"): + attribute = NoticeAttribute() + attribute.data_type = notice_attrib.get("DataType") + attribute.name = notice_attrib.get("Name") + + # Currently known data types + if attribute.data_type == "url": + attribute._url_value = notice_attrib.get("Value") + elif attribute.data_type == "date": + # Convert to UTC datetime + date = parser.parse(notice_attrib.get("Value")) + localtz = pytz.timezone('America/Los_Angeles') + local_dt = localtz.localize(date) + utc_dt = local_dt.astimezone(pytz.utc) - notice_attribs = [] - try: - for notice_attrib in notice.get("NoticeAttributes"): - attribute = NoticeAttribute() - attribute.data_type = notice_attrib.get("DataType") - attribute.name = notice_attrib.get("Name") + attribute._date_value = utc_dt + elif attribute.data_type == "string": + attribute._string_value = notice_attrib.get("Value") + else: + logger.warn("Unkown attribute type: %s\nWith Value:%s" % + (attribute.data_type, + notice_attrib.get("value"))) + continue + notice_attribs.append(attribute) + except TypeError: + pass + notice_obj.attributes = notice_attribs + notices.append(notice_obj) + return _associate_short_long(notices) + + +def _associate_short_long(notices): + """ + If a notice is type ${1}Short, associate with its Long notice + in an attribute called long_notice. + """ + for notice in notices: + if notice.notice_type is not None and\ + notice.notice_category == "StudentFinAid" and\ + notice.notice_type.endswith("Short"): + notice.long_notice = _find_notice_by_type(notices, + notice.notice_type[:-5]) + return notices - #Currently known data types - if attribute.data_type == "url": - attribute._url_value = notice_attrib.get("Value") - elif attribute.data_type == "date": - #Convert to UTC datetime - date = parser.parse(notice_attrib.get("Value")) - localtz = pytz.timezone('America/Los_Angeles') - local_dt = localtz.localize(date) - utc_dt = local_dt.astimezone(pytz.utc) - attribute._date_value = utc_dt - elif attribute.data_type == "string": - attribute._string_value = notice_attrib.get("Value") - else: - logger.warn("Unkown attribute type: %s\nWith Value:%s" % - (attribute.data_type, - notice_attrib.get("value"))) - continue - notice_attribs.append(attribute) - except TypeError: - pass - notice_obj.attributes = notice_attribs - notices.append(notice_obj) - return notices +def _find_notice_by_type(notices, type): + for notice in notices: + if notice.notice_type == type: + return notice + return None diff --git a/restclients/sws/v5/term.py b/restclients/sws/v5/term.py index 0af8a3aa..4ff1852f 100644 --- a/restclients/sws/v5/term.py +++ b/restclients/sws/v5/term.py @@ -139,6 +139,8 @@ def _json_to_term_model(term_data): term.last_day_drop = parse_sws_date(term_data["LastDropDay"]) + term.census_day = parse_sws_date(term_data["CensusDay"]) + if term_data["ATermLastDay"] is not None: term.aterm_last_date = parse_sws_date(term_data["ATermLastDay"]) diff --git a/restclients/templates/proxy/grad/index.html b/restclients/templates/proxy/grad/index.html new file mode 100644 index 00000000..66194f0f --- /dev/null +++ b/restclients/templates/proxy/grad/index.html @@ -0,0 +1,22 @@ +

MyGrad Search (Proxy)

+
    + {% url 'restclients.views.proxy' 'grad' 'services/students/v1/api/committee.html' as committee_url %} + {% if committee_url %} +
  • Committee
  • + {% endif %} + + {% url 'restclients.views.proxy' 'grad' 'services/students/v1/api/request.html' as degree_url %} + {% if degree_url %} +
  • Degree
  • + {% endif %} + + {% url 'restclients.views.proxy' 'grad' 'services/students/v1/api/leave.html' as leave_url %} + {% if leave_url %} +
  • Leave
  • + {% endif %} + + {% url 'restclients.views.proxy' 'grad' 'services/students/v1/api/petition.html' as petition_url %} + {% if petition_url %} +
  • Petition
  • + {% endif %} +
diff --git a/restclients/templates/proxy/grad/services/students/v1/api/committee.html b/restclients/templates/proxy/grad/services/students/v1/api/committee.html new file mode 100644 index 00000000..efca2db8 --- /dev/null +++ b/restclients/templates/proxy/grad/services/students/v1/api/committee.html @@ -0,0 +1,12 @@ +

Committee Request (Proxy)

+
+
+ + {% include 'proxy/grad/services/students/v1/api/id.html' %} + + +

+ +

+
+
diff --git a/restclients/templates/proxy/grad/services/students/v1/api/id.html b/restclients/templates/proxy/grad/services/students/v1/api/id.html new file mode 100644 index 00000000..b0f9c11d --- /dev/null +++ b/restclients/templates/proxy/grad/services/students/v1/api/id.html @@ -0,0 +1,3 @@ +
+ +
diff --git a/restclients/templates/proxy/grad/services/students/v1/api/leave.html b/restclients/templates/proxy/grad/services/students/v1/api/leave.html new file mode 100644 index 00000000..391c9f12 --- /dev/null +++ b/restclients/templates/proxy/grad/services/students/v1/api/leave.html @@ -0,0 +1,11 @@ +

Leave Request (Proxy)

+
+
+ + {% include 'proxy/grad/services/students/v1/api/id.html' %} + +

+ +

+
+
diff --git a/restclients/templates/proxy/grad/services/students/v1/api/petition.html b/restclients/templates/proxy/grad/services/students/v1/api/petition.html new file mode 100644 index 00000000..e940a755 --- /dev/null +++ b/restclients/templates/proxy/grad/services/students/v1/api/petition.html @@ -0,0 +1,11 @@ +

Petition Request (Proxy)

+
+
+ + {% include 'proxy/grad/services/students/v1/api/id.html' %} + +

+ +

+
+
diff --git a/restclients/templates/proxy/grad/services/students/v1/api/request.html b/restclients/templates/proxy/grad/services/students/v1/api/request.html new file mode 100644 index 00000000..dbc8420e --- /dev/null +++ b/restclients/templates/proxy/grad/services/students/v1/api/request.html @@ -0,0 +1,12 @@ +

Degree Request (Proxy)

+
+
+ + {% include 'proxy/grad/services/students/v1/api/id.html' %} + + +

+ +

+
+
diff --git a/restclients/templates/proxy/iasystem/evaluation.html b/restclients/templates/proxy/iasystem/evaluation.html new file mode 100644 index 00000000..81e8594d --- /dev/null +++ b/restclients/templates/proxy/iasystem/evaluation.html @@ -0,0 +1,37 @@ +

Iasystem Search (Proxy)

+
+
+

+ + +

+

+ + +

+

+ + +

+

+ + +

+

+ + +

+ + +
+
diff --git a/restclients/templates/proxy/iasystem/index.html b/restclients/templates/proxy/iasystem/index.html new file mode 100644 index 00000000..59e6166e --- /dev/null +++ b/restclients/templates/proxy/iasystem/index.html @@ -0,0 +1,19 @@ +

Iasystem Search (Proxy)

+
    + {% url 'restclients.views.proxy' 'iasystem' 'uw/api/v1/evaluation.html' as uw_url %} + {% if uw_url %} +
  • Seattle Domain
  • + {% endif %} + + {% url 'restclients.views.proxy' 'iasystem' 'uwb/api/v1/evaluation.html' as uwb_url %} + {% if uwb_url %} +
  • Bothell Domain
  • + {% endif %} + + {% url 'restclients.views.proxy' 'iasystem' 'uwt/api/v1/evaluation.html' as uwt_url %} + {% if uwt_url %} +
  • Tacoma Domain
  • + {% endif %} + +
+ diff --git a/restclients/templates/proxy/iasystem/uw/api/v1/evaluation.html b/restclients/templates/proxy/iasystem/uw/api/v1/evaluation.html new file mode 100644 index 00000000..c32b8955 --- /dev/null +++ b/restclients/templates/proxy/iasystem/uw/api/v1/evaluation.html @@ -0,0 +1 @@ +{% include "proxy/iasystem/evaluation.html" %} diff --git a/restclients/templates/proxy/iasystem/uwb/api/v1/evaluation.html b/restclients/templates/proxy/iasystem/uwb/api/v1/evaluation.html new file mode 100644 index 00000000..c32b8955 --- /dev/null +++ b/restclients/templates/proxy/iasystem/uwb/api/v1/evaluation.html @@ -0,0 +1 @@ +{% include "proxy/iasystem/evaluation.html" %} diff --git a/restclients/templates/proxy/iasystem/uwt/api/v1/evaluation.html b/restclients/templates/proxy/iasystem/uwt/api/v1/evaluation.html new file mode 100644 index 00000000..c32b8955 --- /dev/null +++ b/restclients/templates/proxy/iasystem/uwt/api/v1/evaluation.html @@ -0,0 +1 @@ +{% include "proxy/iasystem/evaluation.html" %} diff --git a/restclients/templates/proxy/sws/student/v5/course.html b/restclients/templates/proxy/sws/student/v5/course.html index 4becc61b..7f4ddd6a 100644 --- a/restclients/templates/proxy/sws/student/v5/course.html +++ b/restclients/templates/proxy/sws/student/v5/course.html @@ -5,6 +5,8 @@

Course Search (Proxy)

+{% include 'proxy/sws/student/v5/includes/transcriptable_course.html' %} +{% include 'proxy/sws/student/v5/includes/changed_since_date.html' %} {% include 'proxy/sws/student/v5/includes/page_size.html' %} diff --git a/restclients/templates/proxy/sws/student/v5/includes/changed_since_date.html b/restclients/templates/proxy/sws/student/v5/includes/changed_since_date.html new file mode 100644 index 00000000..1f8bdf4e --- /dev/null +++ b/restclients/templates/proxy/sws/student/v5/includes/changed_since_date.html @@ -0,0 +1 @@ +
diff --git a/restclients/templates/proxy/sws/student/v5/includes/transcriptable_course.html b/restclients/templates/proxy/sws/student/v5/includes/transcriptable_course.html new file mode 100644 index 00000000..19ef3e0d --- /dev/null +++ b/restclients/templates/proxy/sws/student/v5/includes/transcriptable_course.html @@ -0,0 +1 @@ +
diff --git a/restclients/templates/proxy/sws/student/v5/registration.html b/restclients/templates/proxy/sws/student/v5/registration.html index 9a9532df..68788972 100644 --- a/restclients/templates/proxy/sws/student/v5/registration.html +++ b/restclients/templates/proxy/sws/student/v5/registration.html @@ -4,7 +4,7 @@

Registration Search (Proxy)

{% include 'proxy/sws/student/v5/includes/course.html' %} {% include 'proxy/sws/student/v5/includes/section_id.html' %} {% include 'proxy/sws/student/v5/includes/reg_id.html' %} -
+
{% include 'proxy/sws/student/v5/includes/page_size.html' %} diff --git a/restclients/templates/proxy/sws/student/v5/section.html b/restclients/templates/proxy/sws/student/v5/section.html index 5bc39de6..28324346 100644 --- a/restclients/templates/proxy/sws/student/v5/section.html +++ b/restclients/templates/proxy/sws/student/v5/section.html @@ -4,13 +4,15 @@

Course Section Search (Proxy)

{% include 'proxy/sws/student/v5/includes/course.html' %} {% include 'proxy/sws/student/v5/includes/reg_id.html' %}
- + - +
-
+
+{% include 'proxy/sws/student/v5/includes/transcriptable_course.html' %} +{% include 'proxy/sws/student/v5/includes/changed_since_date.html' %} {% include 'proxy/sws/student/v5/includes/page_size.html' %} diff --git a/restclients/test/book/by_schedule.py b/restclients/test/book/by_schedule.py index 0196d93a..fe87fdfa 100644 --- a/restclients/test/book/by_schedule.py +++ b/restclients/test/book/by_schedule.py @@ -5,10 +5,10 @@ from unittest2 import skipIf from restclients.sws.term import get_current_term from restclients.sws.registration import get_schedule_by_regid_and_term +from restclients.exceptions import DataFailureException class BookstoreScheduleTest(TestCase): - @skipIf(True, "Bookstore structure still in flux") - def test_sched(self): + def test_get_book(self): with self.settings( RESTCLIENTS_BOOK_DAO_CLASS='restclients.dao_implementation.book.File', RESTCLIENTS_SWS_DAO_CLASS='restclients.dao_implementation.sws.File', @@ -17,20 +17,30 @@ def test_sched(self): books = Bookstore() - term = get_current_term() - schedule = get_schedule_by_regid_and_term('AA36CCB8F66711D5BE060004AC494FFE', term) + result = books.get_books_by_quarter_sln('autumn', 19187) + self.assertEqual(len(result), 2) + self.assertEqual(result[0].isbn, '9780878935970') - book_data = books.get_books_for_schedule(schedule) + with self.assertRaises(DataFailureException): + books.get_books_by_quarter_sln('autumn', 00000) - self.assertEquals(len(book_data), 2, "Has data for 2 sections") - self.assertEquals(len(book_data["13830"]), 0, "No books for sln 13830") - self.assertEquals(len(book_data["13833"]), 1, "one book for sln 13833") - book = book_data["13833"][0] + def test_get_books_by_schedule(self): + with self.settings( + RESTCLIENTS_BOOK_DAO_CLASS='restclients.dao_implementation.book.File', + RESTCLIENTS_SWS_DAO_CLASS='restclients.dao_implementation.sws.File', + RESTCLIENTS_PWS_DAO_CLASS='restclients.dao_implementation.pws.File', + ): - self.assertEquals(book.price, 175.00, "Has the right book price") + books = Bookstore() + term = get_current_term() + schedule = get_schedule_by_regid_and_term('AA36CCB8F66711D5BE060004AC494FFE', term) + schedule_books = books.get_books_for_schedule(schedule) + self.assertEquals(len(schedule_books), 2) + self.assertEqual(len(schedule_books['13833']), 0) + self.assertEqual(len(schedule_books['13830']), 2) def test_verba_link(self): with self.settings( @@ -69,5 +79,6 @@ def test_dupe_slns(self): self.assertEquals(verba_link, '/myuw/myuw_mobile_v.ubs?quarter=spring&sln1=13830&sln2=13833') - books_link = books.get_books_url(schedule) - self.assertEquals(books_link, '/myuw/myuw_mobile_beta.ubs?quarter=spring&sln1=13830&sln2=13833') + schedule_books = books.get_books_for_schedule(schedule) + self.assertEquals(len(schedule_books), 2) + diff --git a/restclients/test/cache/memcached.py b/restclients/test/cache/memcached.py new file mode 100644 index 00000000..d008e60a --- /dev/null +++ b/restclients/test/cache/memcached.py @@ -0,0 +1,60 @@ +from django.test import TestCase +from django.conf import settings +from restclients.dao import SWS_DAO +from restclients.cache_implementation import MemcachedCache +from restclients.mock_http import MockHTTP +from unittest2 import skipIf +import re + + +class MemcachedCacheTest(TestCase): + @skipIf(not getattr(settings, 'RESTCLIENTS_TEST_MEMCACHED', False), "Needs configuration to test memcached cache") + def test_memcached(self): + with self.settings(RESTCLIENTS_SWS_DAO_CLASS='restclients.dao_implementation.sws.File', + RESTCLIENTS_DAO_CACHE_CLASS='restclients.cache_implementation.MemcachedCache'): + + cache = MemcachedCache() + sws = SWS_DAO() + sws.getURL('/student/', {}) + + hit = cache.getCache('sws', '/student/', {}) + response = hit["response"] + + self.assertEquals(response.status, 200) + + html = response.data + + if not re.search('student/v4', html): + self.fail("Doesn't contains a link to v4") + + hit = cache.getCache('sws', '/student/', {}) + self.assertEquals(response.status, 200) + # Make sure there's nothing for pws there after the get + response = cache.getCache('pws', '/student', {}) + self.assertEquals(response, None) + + @skipIf(not getattr(settings, 'RESTCLIENTS_TEST_MEMCACHED', False), "Needs configuration to test memcached cache") + def test_longkeys(self): + with self.settings(RESTCLIENTS_SWS_DAO_CLASS='restclients.dao_implementation.sws.File', + RESTCLIENTS_DAO_CACHE_CLASS='restclients.cache_implementation.MemcachedCache'): + + cache = MemcachedCache() + url = "".join("X" for i in range(300)) + + + ok_response = MockHTTP() + ok_response.status = 200 + ok_response.data = "valid" + cache.processResponse('ok', url, ok_response) + + # This makes sure we don't actually cache anything when the url + # is too long + response = cache.getCache('ok', url, {}) + self.assertEquals(response, None) + + # But the get doesn't raise the exception w/ the set before it, + # so this redundant-looking code is testing to make sure that we + # catch the exception on the get as well. + url = "".join("Y" for i in range(300)) + response = cache.getCache('ok', url, {}) + self.assertEquals(response, None) diff --git a/restclients/test/canvas/courses.py b/restclients/test/canvas/courses.py index 9cead1c0..0fcecba2 100644 --- a/restclients/test/canvas/courses.py +++ b/restclients/test/canvas/courses.py @@ -19,21 +19,21 @@ def test_course(self): self.assertEquals(course.term.term_id, 810, "Term id") self.assertEquals(course.public_syllabus, False, "public_syllabus") self.assertEquals(course.workflow_state, "unpublished", "workflow_state") + self.assertTrue(course.is_unpublished) def test_course_with_params(self): with self.settings( RESTCLIENTS_CANVAS_DAO_CLASS='restclients.dao_implementation.canvas.File'): canvas = Courses() - course1 = canvas.get_course(149650, params={"include":"term"}) + course1 = canvas.get_course(149650, params={"include":["term"]}) self.assertEquals(course1.term.term_id, 810, "Course contains term data") self.assertEquals(course1.syllabus_body, None, "Course doesn't contain syllabus_body") - course2 = canvas.get_course(149650, params={"include":"syllabus_body"}) + course2 = canvas.get_course(149650, params={"include":["syllabus_body"]}) self.assertEquals(course2.syllabus_body, "Syllabus", "Course contains syllabus_body") - self.assertEquals(course2.term, None, "Course doesn't contain term") - + self.assertEquals(course1.term.term_id, 810, "Course contains term data") def test_courses(self): with self.settings( @@ -104,3 +104,14 @@ def test_create_course(self): self.assertEquals(course.course_id, 18881, "Correct course ID") self.assertEquals(course.name, name, "Correct course name") self.assertEquals(course.account_id, account_id, "Correct account ID") + + def test_update_sis_id(self): + with self.settings( + RESTCLIENTS_CANVAS_DAO_CLASS='restclients.dao_implementation.canvas.File'): + canvas = Courses() + + course = canvas.update_sis_id(149650, "NEW_SIS_ID") + + self.assertEquals(course.course_id, 149650, "Has proper course id") + self.assertEquals(course.course_url, "https://canvas.uw.edu/courses/149650", "Has proper course url") + self.assertEquals(course.sis_course_id, "NEW_SIS_ID") diff --git a/restclients/test/canvas/enrollments.py b/restclients/test/canvas/enrollments.py index a7c3e3dc..09a83d93 100644 --- a/restclients/test/canvas/enrollments.py +++ b/restclients/test/canvas/enrollments.py @@ -47,7 +47,7 @@ def test_enrollments_by_regid(self): enrollment = enrollments[0] - self.assertEquals(enrollment.course_url, "https://canvas.uw.edu/courses/149650", "Has proper course url") + self.assertEquals(enrollment.course.course_url, "https://canvas.uw.edu/courses/149650", "Has proper course url") self.assertEquals(enrollment.sis_course_id, "2013-spring-PHYS-121-A") self.assertEquals(enrollment.sws_course_id(), "2013,spring,PHYS,121/A") @@ -57,6 +57,7 @@ def test_enrollments_by_regid(self): self.assertEquals(stu_enrollment.login_id, "javerage", "Login ID") self.assertEquals(stu_enrollment.sis_user_id, "9136CCB8F66711D5BE060004AC494FFE", "SIS User ID") self.assertEquals(stu_enrollment.name, "JAMES AVERAGE STUDENT", "Name") + self.assertEquals(enrollment.sortable_name, "STUDENT, JAMES AVERAGE", "Sortable Name") self.assertEquals(str(stu_enrollment.last_activity_at), "2012-08-18 23:08:51-06:00", "Last activity datetime") self.assertEquals(stu_enrollment.total_activity_time, 100, "Total activity time") self.assertEquals(stu_enrollment.status, CanvasEnrollment.STATUS_ACTIVE, "Status") @@ -72,6 +73,7 @@ def test_pending_enrollments(self): enrollment = enrollments[0] self.assertEquals(enrollment.name, "j.average@gmail.com", "Name") + self.assertEquals(enrollment.sortable_name, "j.average@gmail.com", "Sortable Name") self.assertEquals(enrollment.login_id, None) self.assertEquals(enrollment.status, CanvasEnrollment.STATUS_INVITED, "Status") diff --git a/restclients/test/canvas/external_tools.py b/restclients/test/canvas/external_tools.py new file mode 100644 index 00000000..359077ba --- /dev/null +++ b/restclients/test/canvas/external_tools.py @@ -0,0 +1,43 @@ +from django.test import TestCase +from restclients.canvas.external_tools import ExternalTools + + +class CanvasTestExternalTools(TestCase): + def test_get_external_tools_in_account(self): + with self.settings( + RESTCLIENTS_CANVAS_DAO_CLASS='restclients.dao_implementation.canvas.File'): + canvas = ExternalTools() + + tools = canvas.get_external_tools_in_account('12345') + + self.assertEquals(len(tools), 12, "Correct tools length") + self.assertEquals(tools[10]['name'], "Tool", "Name is Correct") + + def test_get_external_tools_in_course_by_sis_id(self): + with self.settings( + RESTCLIENTS_CANVAS_DAO_CLASS='restclients.dao_implementation.canvas.File'): + canvas = ExternalTools() + + tools = canvas.get_external_tools_in_course_by_sis_id('2015-autumn-UWBW-301-A') + + self.assertEquals(len(tools), 2, "Correct tools length") + self.assertEquals(tools[1]['name'], 'Course Tool', "Has correct tool name") + + def test_get_sessionless_launch_from_account_sis_id(self): + with self.settings( + RESTCLIENTS_CANVAS_DAO_CLASS='restclients.dao_implementation.canvas.File'): + canvas = ExternalTools() + + launch = canvas.get_sessionless_launch_url_from_account('54321', '12345') + + self.assertEquals(launch['id'], 54321, "Has correct tool id") + + def test_get_sessionless_launch_from_course_sis_id(self): + with self.settings( + RESTCLIENTS_CANVAS_DAO_CLASS='restclients.dao_implementation.canvas.File'): + canvas = ExternalTools() + + launch = canvas.get_sessionless_launch_url_from_course_sis_id('54321', '2015-autumn-UWBW-301-A') + + self.assertEquals(launch['id'], 54321, "Has correct tool id") + diff --git a/restclients/test/canvas/roles.py b/restclients/test/canvas/roles.py index abaeb37c..88558e61 100644 --- a/restclients/test/canvas/roles.py +++ b/restclients/test/canvas/roles.py @@ -17,15 +17,30 @@ def test_roles(self): role = roles[10] - self.assertEquals(role.role, "Course Access") + self.assertEquals(role.base_role_type, "AccountMembership") + self.assertEquals(role.label, "Course Access") self.assertEquals(role.permissions.get('read_course_list').get('enabled'), True) + def test_course_roles(self): + with self.settings( + RESTCLIENTS_CANVAS_DAO_CLASS='restclients.dao_implementation.canvas.File'): + canvas = Roles() + + roles = canvas.get_effective_course_roles_in_account(12345) + + self.assertEquals(len(roles), 5, "Course roles only") + + role = roles[0] + self.assertEquals(role.base_role_type, "TeacherEnrollment") + self.assertEquals(role.label, "Teacher") + def test_role(self): with self.settings( RESTCLIENTS_CANVAS_DAO_CLASS='restclients.dao_implementation.canvas.File'): canvas = Roles() - role = canvas.get_role(12345, 'Course Access') + role = canvas.get_role(12345, 999) - self.assertEquals(role.role, "Course Access") + self.assertEquals(role.role_id, 999) + self.assertEquals(role.label, "Course Access") self.assertEquals(role.permissions.get('read_course_list').get('enabled'), True) diff --git a/restclients/test/canvas/sections.py b/restclients/test/canvas/sections.py index cac2cd7f..4c78f493 100644 --- a/restclients/test/canvas/sections.py +++ b/restclients/test/canvas/sections.py @@ -8,7 +8,7 @@ def test_sections(self): RESTCLIENTS_CANVAS_DAO_CLASS='restclients.dao_implementation.canvas.File'): canvas = Sections() - sections = canvas.get_sections_in_course_by_sis_id('2013-spring-CSE-142-A', {'include': 'students'}) + sections = canvas.get_sections_in_course_by_sis_id('2013-spring-CSE-142-A', {'include': ['students']}) self.assertEquals(len(sections), 16, "Too few sections") diff --git a/restclients/test/canvas/users.py b/restclients/test/canvas/users.py index 9932bb01..a11443c8 100644 --- a/restclients/test/canvas/users.py +++ b/restclients/test/canvas/users.py @@ -48,6 +48,25 @@ def test_create_user(self): "Has correct sis id") self.assertEquals(user.email, "testid99@foo.com", "Has correct email") + def test_get_users_for_course_id(self): + with self.settings( + RESTCLIENTS_CANVAS_DAO_CLASS='restclients.dao_implementation.canvas.File'): + canvas = Users() + + users = canvas.get_users_for_course("862539", + params={"search_term": "jav", "include": ["enrollments"]}) + + self.assertEquals(len(users), 3, "Found 3 canvas users") + + user = users[0] + self.assertEquals(user.login_id, "javerage", "Login ID") + self.assertEquals(user.sis_user_id, "15AE3883B6EC44C349E04E5FE089ABEB", "SIS User ID") + self.assertEquals(user.name, "JAMES AVERAGE", "Name") + self.assertEquals(user.sortable_name, "AVERAGE, JAMES", "Sortable Name") + enrollment = user.enrollments[0] + self.assertEquals(enrollment.sis_course_id, "2015-summer-TRAIN-100-A") + self.assertEquals(enrollment.role, "DesignerEnrollment", "Role") + def test_get_logins(self): with self.settings( RESTCLIENTS_CANVAS_DAO_CLASS='restclients.dao_implementation.canvas.File'): diff --git a/restclients/test/grad/__init__.py b/restclients/test/grad/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/restclients/test/grad/committee.py b/restclients/test/grad/committee.py new file mode 100644 index 00000000..205b1d7e --- /dev/null +++ b/restclients/test/grad/committee.py @@ -0,0 +1,100 @@ +import datetime +from django.test import TestCase +from django.conf import settings +from restclients.exceptions import DataFailureException +from restclients.grad.committee import get_committee_by_regid + + +class CommitteeTest(TestCase): + + def test_get_committee_by_regid(self): + with self.settings( + RESTCLIENTS_GRAD_DAO_CLASS=\ + 'restclients.dao_implementation.grad.File', + RESTCLIENTS_SWS_DAO_CLASS=\ + 'restclients.dao_implementation.sws.File'): + requests = get_committee_by_regid( + "9136CCB8F66711D5BE060004AC494FFE") + + self.assertEqual(len(requests), 3) + + committee = requests[0] + self.assertIsNotNone(committee.json_data()) + self.assertEqual(committee.committee_type, + "Advisor") + self.assertEqual(committee.status, "active") + self.assertEqual(committee.dept, "Anthropology") + self.assertEqual(committee.degree_title, None) + self.assertEqual(committee.degree_type, + "MASTER OF PUBLIC HEALTH (EPIDEMIOLOGY)") + self.assertEqual(committee.major_full_name, "ANTH") + self.assertEqual(committee.start_date, + datetime.datetime(2012, 12, 7, 8, 26, 14)) + self.assertEqual(len(committee.members), 1) + + committee = requests[1] + self.assertEqual(committee.committee_type, + "Master's Committee") + members = committee.members + self.assertEqual(len(members), 3) + self.assertEqual(members[0].first_name, "Nina L.") + self.assertEqual(members[0].last_name, "Patrick") + self.assertTrue(members[0].is_type_chair()) + self.assertTrue(members[0].is_reading_committee_chair()) + self.assertEqual(members[0].dept, "Epidemiology - Public Health") + self.assertEqual(members[0].email, "nnn@u.washington.edu") + self.assertEqual(members[0].status, "active") + + self.assertFalse(members[1].is_type_chair()) + self.assertFalse(members[1].is_reading_committee_chair()) + self.assertTrue(members[1].is_type_member()) + self.assertTrue(members[1].is_reading_committee_member()) + self.assertTrue(members[2].is_type_gsr()) + self.assertTrue(members[2].is_reading_committee_member()) + + json_data = committee.json_data() + self.assertEqual(len(json_data["members"]), 3) + member_json = json_data["members"][0] + self.assertEqual(member_json["member_type"], "Chair") + self.assertEqual(member_json["reading_type"], + "Reading Committee Chair") + member_json = json_data["members"][1] + self.assertEqual(member_json["member_type"], "GSR") + member_json = json_data["members"][2] + self.assertEqual(member_json["member_type"], None) + + committee = requests[2] + self.assertEqual(committee.committee_type, + "Doctoral Supervisory Committee") + members = committee.members + self.assertEqual(len(members), 4) + self.assertFalse(members[0].__eq__(members[1])) + self.assertFalse(members[0] == members[1]) + self.assertTrue(members[0].__ne__(members[1])) + self.assertTrue(members[0] != members[1]) + json_data = committee.json_data() + self.assertEqual(len(json_data["members"]), 4) + member_json = json_data["members"][0] + self.assertEqual(member_json["member_type"], + "Chair") + self.assertEqual(member_json["last_name"], + "Duncan") + member_json = json_data["members"][1] + self.assertEqual(member_json["member_type"], + "Chair") + self.assertEqual(member_json["last_name"], + "Goodman") + member_json = json_data["members"][2] + self.assertEqual(member_json["member_type"], + "GSR") + member_json = json_data["members"][3] + self.assertEqual(member_json["member_type"], + None) + + def test_error(self): + with self.settings( + RESTCLIENTS_SWS_DAO_CLASS=\ + 'restclients.dao_implementation.sws.File'): + self.assertRaises(DataFailureException, + get_committee_by_regid, + "00000000000000000000000000000001") diff --git a/restclients/test/grad/degree.py b/restclients/test/grad/degree.py new file mode 100644 index 00000000..f9a8f85a --- /dev/null +++ b/restclients/test/grad/degree.py @@ -0,0 +1,51 @@ +import datetime +from django.test import TestCase +from django.conf import settings +from restclients.exceptions import DataFailureException +from restclients.grad.degree import get_degree_by_regid + + +class DegreeTest(TestCase): + + def test_get_request_by_regid(self): + with self.settings( + RESTCLIENTS_GRAD_DAO_CLASS=\ + 'restclients.dao_implementation.grad.File', + RESTCLIENTS_SWS_DAO_CLASS=\ + 'restclients.dao_implementation.sws.File'): + requests = get_degree_by_regid( + "9136CCB8F66711D5BE060004AC494FFE") + + self.assertEqual(len(requests), 6) + degree = requests[0] + self.assertIsNotNone(degree.json_data()) + self.assertEqual(degree.req_type, "Masters Request") + self.assertEqual(degree.submit_date, + datetime.datetime(2015, 3, 11, 20, 53, 32)) + self.assertEqual( + degree.degree_title, + "MASTER OF LANDSCAPE ARCHITECTURE/MASTER OF ARCHITECTURE") + self.assertEqual(degree.major_full_name, + "Landscape Arch/Architecture (Concurrent)") + self.assertEqual(degree.status, + "Awaiting Dept Action (Final Exam)") + self.assertTrue(degree.is_status_await()) + self.assertFalse(degree.is_status_graduated()) + self.assertFalse(degree.is_status_candidacy()) + self.assertFalse(degree.is_status_recommended()) + self.assertIsNone(degree.exam_place) + self.assertIsNone(degree.exam_date) + self.assertEqual(degree.target_award_year, 2015) + self.assertEqual(degree.target_award_quarter, "winter") + degree = requests[5] + self.assertEqual(degree.status, + "Did Not Graduate") + self.assertTrue(degree.is_status_not_graduate()) + + def test_error(self): + with self.settings( + RESTCLIENTS_SWS_DAO_CLASS=\ + 'restclients.dao_implementation.sws.File'): + self.assertRaises(DataFailureException, + get_degree_by_regid, + "00000000000000000000000000000001") diff --git a/restclients/test/grad/leave.py b/restclients/test/grad/leave.py new file mode 100644 index 00000000..6a79672b --- /dev/null +++ b/restclients/test/grad/leave.py @@ -0,0 +1,43 @@ +import datetime +from django.test import TestCase +from django.conf import settings +from restclients.exceptions import DataFailureException +from restclients.grad.leave import get_leave_by_regid + + +class LeaveTest(TestCase): + + def test_get_leave_by_regid(self): + with self.settings( + RESTCLIENTS_GRAD_DAO_CLASS=\ + 'restclients.dao_implementation.grad.File', + RESTCLIENTS_SWS_DAO_CLASS=\ + 'restclients.dao_implementation.sws.File'): + requests = get_leave_by_regid( + "9136CCB8F66711D5BE060004AC494FFE") + + self.assertEqual(len(requests), 5) + leave = requests[0] + self.assertIsNotNone(leave.json_data()) + self.assertEqual(leave.reason, + "Dissertation/Thesis research/writing") + self.assertEqual(leave.submit_date, + datetime.datetime(2012, 9, 10, 9, 40, 03)) + self.assertEqual(leave.status, "paid") + self.assertTrue(leave.is_status_paid()) + self.assertFalse(leave.is_status_approved()) + self.assertFalse(leave.is_status_denied()) + self.assertFalse(leave.is_status_requested()) + self.assertFalse(leave.is_status_withdrawn()) + self.assertEqual(len(leave.terms), 1) + self.assertEqual(leave.terms[0].quarter, "autumn") + self.assertEqual(leave.terms[0].year, 2012) + + + def test_error(self): + with self.settings( + RESTCLIENTS_SWS_DAO_CLASS=\ + 'restclients.dao_implementation.sws.File'): + self.assertRaises(DataFailureException, + get_leave_by_regid, + "00000000000000000000000000000001") diff --git a/restclients/test/grad/petition.py b/restclients/test/grad/petition.py new file mode 100644 index 00000000..a8165991 --- /dev/null +++ b/restclients/test/grad/petition.py @@ -0,0 +1,88 @@ +import datetime +from django.test import TestCase +from django.conf import settings +from restclients.exceptions import DataFailureException +from restclients.grad.petition import get_petition_by_regid + + +class PetitionTest(TestCase): + + def test_get_petition_by_regid(self): + with self.settings( + RESTCLIENTS_GRAD_DAO_CLASS=\ + 'restclients.dao_implementation.grad.File', + RESTCLIENTS_SWS_DAO_CLASS=\ + 'restclients.dao_implementation.sws.File'): + requests = get_petition_by_regid( + "9136CCB8F66711D5BE060004AC494FFE") + + self.assertEqual(len(requests), 4) + petition = requests[0] + self.assertIsNotNone(petition.json_data()) + self.assertEqual(petition.description, + "Master's degree - Extend six year limit") + self.assertEqual(petition.submit_date, + datetime.datetime(2013, 5, 11, 11, 25, 35)) + self.assertEqual(petition.decision_date, + datetime.datetime(2013, 6, 10, 16, 32, 28)) + self.assertEqual(petition.dept_recommend, "approve") + self.assertEqual(petition.gradschool_decision, "approved") + self.assertTrue(petition.is_dept_approve()) + self.assertFalse(petition.is_dept_deny()) + self.assertFalse(petition.is_dept_pending()) + self.assertFalse(petition.is_dept_withdraw()) + self.assertFalse(petition.is_gs_pending()) + json_data = petition.json_data() + self.assertIsNotNone(json_data) + self.assertEqual(json_data['submit_date'], + "2013-05-11T11:25:35") + self.assertEqual(json_data['decision_date'], + "2013-06-10T16:32:28") + self.assertEqual(json_data['dept_recommend'], + "Approve") + self.assertEqual(json_data['gradschool_decision'], + "Approved") + + petition = requests[1] + self.assertEqual(petition.gradschool_decision, "pending") + self.assertIsNone(petition.decision_date) + self.assertTrue(petition.is_gs_pending()) + json_data = petition.json_data() + self.assertIsNotNone(json_data) + self.assertEqual(json_data['dept_recommend'], + "Approve") + self.assertEqual(json_data['gradschool_decision'], + "Pending") + + + petition = requests[2] + self.assertEqual(petition.dept_recommend, "withdraw") + self.assertEqual(petition.gradschool_decision, "withdraw") + self.assertIsNone(petition.decision_date) + self.assertTrue(petition.is_dept_withdraw()) + json_data = petition.json_data() + self.assertIsNotNone(json_data) + self.assertEqual(json_data['dept_recommend'], + "Withdraw") + self.assertEqual(json_data['gradschool_decision'], + "Withdraw") + + + petition = requests[3] + self.assertEqual(petition.gradschool_decision, "withdrawn") + self.assertIsNone(petition.decision_date) + self.assertTrue(petition.is_dept_approve()) + json_data = petition.json_data() + self.assertIsNotNone(json_data) + self.assertEqual(json_data['dept_recommend'], + "Approve") + self.assertEqual(json_data['gradschool_decision'], + "Withdrawn") + + def test_error(self): + with self.settings( + RESTCLIENTS_SWS_DAO_CLASS=\ + 'restclients.dao_implementation.sws.File'): + self.assertRaises(DataFailureException, + get_petition_by_regid, + "00000000000000000000000000000001") diff --git a/restclients/test/hfs/idcard.py b/restclients/test/hfs/idcard.py index 3d7ff9d8..5827dd2d 100644 --- a/restclients/test/hfs/idcard.py +++ b/restclients/test/hfs/idcard.py @@ -5,6 +5,11 @@ from restclients.exceptions import DataFailureException from decimal import * + +ADD_FUND_URL =\ + "https://www.hfs.washington.edu/olco/Secure/AccountSummary.aspx" + + class HfsTest(TestCase): def test_get_hfs_accounts(self): @@ -15,21 +20,24 @@ def test_get_hfs_accounts(self): hfs_acc = get_hfs_accounts("javerage") self.assertEquals(hfs_acc.student_husky_card.last_updated, datetime(2014, 6, 2, 15, 17, 16)) - self.assertEquals(hfs_acc.student_husky_card.balance, Decimal('1.23')) - self.assertEquals(hfs_acc.student_husky_card.add_funds_url, - "https://www.hfs.washington.edu/olco") + self.assertEquals(hfs_acc.student_husky_card.balance, + Decimal('1.23')) + self.assertEquals( + hfs_acc.student_husky_card.add_funds_url, + ADD_FUND_URL) self.assertEquals(hfs_acc.employee_husky_card.last_updated, datetime(2014, 5, 19, 14, 16, 26)) - self.assertEquals(hfs_acc.employee_husky_card.balance, Decimal('0.56')) + self.assertEquals(hfs_acc.employee_husky_card.balance, + Decimal('0.56')) self.assertEquals(hfs_acc.employee_husky_card.add_funds_url, - "https://www.hfs.washington.edu/olco") + ADD_FUND_URL) self.assertEquals(hfs_acc.resident_dining.last_updated, datetime(2014, 6, 1, 13, 15, 36)) self.assertEquals(hfs_acc.resident_dining.balance, Decimal('7.89')) self.assertEquals(hfs_acc.resident_dining.add_funds_url, - "https://www.hfs.washington.edu/olco") + ADD_FUND_URL) def test_get_hfs_empty_account(self): @@ -51,15 +59,17 @@ def test_get_hfs_partially_empty_account(self): hfs_acc = get_hfs_accounts("jnew") self.assertIsNotNone(hfs_acc.student_husky_card) self.assertIsNone(hfs_acc.student_husky_card.last_updated) - self.assertEquals(hfs_acc.student_husky_card.balance, Decimal('0.0')) + self.assertEquals(hfs_acc.student_husky_card.balance, + Decimal('0.0')) self.assertIsNone(hfs_acc.employee_husky_card) - self.assertEquals(hfs_acc.resident_dining.balance, Decimal('777.89')) + self.assertEquals(hfs_acc.resident_dining.balance, + Decimal('777.89')) self.assertEquals(hfs_acc.resident_dining.last_updated, datetime(2014, 5, 17, 13, 15, 36)) self.assertEquals(hfs_acc.resident_dining.add_funds_url, - "https://www.hfs.washington.edu/olco") + ADD_FUND_URL) def test_invalid_user(self): @@ -68,24 +78,39 @@ def test_invalid_user(self): 'restclients.dao_implementation.hfs.File'): #Testing error message in a 200 response - self.assertRaises(DataFailureException, get_hfs_accounts, "invalidnetid") - self.assertRaises(DataFailureException, get_hfs_accounts, "invalidnetid123") + self.assertRaises(DataFailureException, + get_hfs_accounts, "invalidnetid") + self.assertRaises(DataFailureException, + get_hfs_accounts, "invalidnetid123") + try: + get_hfs_accounts("jerror") + except DataFailureException as ex: + self.assertEquals(ex.status, 500) + self.assertEquals(ex.msg, + "An error has occurred.") try: get_hfs_accounts("none") except DataFailureException as ex: - self.assertEquals(ex.msg, "UWNetID none not found in IDCard Database.") - - + self.assertEquals(ex.status, 404) + self.assertEquals(ex.msg, + "UWNetID none not found in IDCard Database.") + + MSG = "%s%s%s" % ( + "Input for this method must be either a valid UWNetID ", + "or two nine-digit Student and ", + "Faculty/Staff/Employee ID numbers, comma-separated.") try: get_hfs_accounts("invalidnetid") except DataFailureException as ex: - self.assertEquals(ex.msg, "Input for this method must be either a valid UWNetID or two nine-digit Student and Faculty/Staff/Employee ID numbers, comma-separated.") - + self.assertEquals(ex.status, 400) + self.assertEquals( + ex.msg, MSG) def test_float_parsing(self): with self.settings( RESTCLIENTS_HFS_DAO_CLASS= 'restclients.dao_implementation.hfs.File'): hfs_acc = get_hfs_accounts("jbothell") - self.assertEquals(hfs_acc.student_husky_card.balance, Decimal('5.1')) + self.assertEquals(hfs_acc.student_husky_card.balance, + Decimal('5.1')) diff --git a/restclients/test/iasystem/evaluation.py b/restclients/test/iasystem/evaluation.py index 38b20db8..f3dac102 100644 --- a/restclients/test/iasystem/evaluation.py +++ b/restclients/test/iasystem/evaluation.py @@ -1,35 +1,154 @@ -from django.test import TestCase -from django.conf import settings -from restclients.iasystem.evaluation import search_evaluations, get_evaluation_by_id import datetime import pytz +from django.test import TestCase +from django.conf import settings +from restclients.iasystem.evaluation import search_evaluations,\ + get_evaluation_by_id + + +FDAO_SWS = 'restclients.dao_implementation.sws.File' +FDAO_PWS = 'restclients.dao_implementation.pws.File' +FDAO_IAS = 'restclients.dao_implementation.iasystem.File' + class IASystemTest(TestCase): def test_search_eval(self): with self.settings( - RESTCLIENTS_PWS_DAO_CLASS='restclients.dao_implementation.iasystem.File'): - evals = search_evaluations("seattle", year=2014, term_name='Autumn', student_id=1033334) + RESTCLIENTS_PWS_DAO_CLASS=FDAO_PWS): + evals = search_evaluations("seattle", + year=2014, + term_name='Autumn', + student_id=1033334) self.assertEqual(evals[0].section_sln, 15314) - self.assertEqual(evals[0].instructor_id, 851006409) - self.assertEqual(evals[0].eval_open_date, datetime.datetime(2014, 11, 24, 15, 0, tzinfo=pytz.utc)) - self.assertEqual(evals[0].eval_close_date, datetime.datetime(2051, 12, 3, 7, 59, 59, tzinfo=pytz.utc)) + self.assertIsNotNone(evals[0].instructor_ids) + self.assertEqual(len(evals[0].instructor_ids), 1) + self.assertEqual(evals[0].instructor_ids[0], 851006409) + self.assertEqual(evals[0].eval_open_date, + datetime.datetime(2014, 11, 24, + 15, 0, + tzinfo=pytz.utc)) + self.assertEqual(evals[0].eval_close_date, + datetime.datetime(2051, 12, 3, + 7, 59, 59, + tzinfo=pytz.utc)) self.assertEqual(evals[0].eval_status, "Open") - self.assertEqual(evals[0].eval_is_online, True) - self.assertEqual(evals[0].eval_url, "https://uw.iasysdev.org/survey/132068") + self.assertEqual(evals[0].eval_url, + "https://uw.iasysdev.org/survey/132068") + self.assertIsNone(evals[0].is_completed) self.assertEqual(evals[1].eval_status, "Closed") - self.assertEqual(evals[2].eval_is_online, False) + self.assertIsNone(evals[1].is_completed) def test_all_campuses(self): - evals = search_evaluations("seattle", year=2014, term_name='Autumn', student_id=1033334) - self.assertEqual(len(evals), 3) + evals = search_evaluations("seattle", year=2014, + term_name='Autumn', student_id=1033334) + self.assertEqual(len(evals), 2) - evals = search_evaluations("bothell", year=2014, term_name='Autumn', student_id=1033334) - self.assertEqual(len(evals), 3) + evals = search_evaluations("bothell", year=2014, + term_name='Autumn', student_id=1033334) + self.assertEqual(len(evals), 2) - evals = search_evaluations("tacoma", year=2014, term_name='Autumn', student_id=1033334) - self.assertEqual(len(evals), 3) + evals = search_evaluations("tacoma", year=2014, + term_name='Autumn', student_id=1033334) + self.assertEqual(len(evals), 2) def test_get_by_id(self): evals = get_evaluation_by_id(132136, "seattle") self.assertEqual(len(evals), 1) + + def test_multiple_instructor(self): + evals = get_evaluation_by_id(141412, "seattle") + self.assertEqual(len(evals), 1) + self.assertEqual(len(evals[0].instructor_ids), 3) + self.assertEqual(evals[0].instructor_ids[0], 849004282) + self.assertEqual(evals[0].instructor_ids[1], 849007339) + self.assertEqual(evals[0].instructor_ids[2], 859003192) + self.assertEqual(evals[0].eval_open_date, + datetime.datetime(2015, 3, 13, + 14, 0, + tzinfo=pytz.utc)) + self.assertEqual(evals[0].eval_close_date, + datetime.datetime(2015, 3, 21, + 6, 59, 59, + tzinfo=pytz.utc)) + self.assertEqual(evals[0].eval_url, + "https://uw.iasystem.org/survey/141412") + self.assertIsNone(evals[0].is_completed) + + def test_evaluation_completion(self): + with self.settings(RESTCLIENTS_SWS_DAO_CLASS=FDAO_SWS, + RESTCLIENTS_PWS_DAO_CLASS=FDAO_PWS, + RESTCLIENTS_IASYSTEM_DAO_CLASS=FDAO_IAS): + + regid = "9136CCB8F66711D5BE060004AC494FFE" + evals = search_evaluations('seattle', + term_name='Spring', + curriculum_abbreviation="PHYS", + student_id=1033334, + section_id="AQ", + course_number=121, + year=2013) + self.assertEqual(evals[0].section_sln, 18545) + self.assertIsNotNone(evals[0].instructor_ids) + self.assertEqual(len(evals[0].instructor_ids), 1) + self.assertEqual(evals[0].instructor_ids[0], 123456789) + self.assertTrue(evals[0].is_completed) + + regid = "9136CCB8F66711D5BE060004AC494F31" + evals = search_evaluations('seattle', + term_name='Spring', + curriculum_abbreviation="PHYS", + student_id=1233334, + section_id="AQ", + course_number=121, + year=2013) + self.assertEqual(evals[0].section_sln, 18545) + self.assertIsNotNone(evals[0].instructor_ids) + self.assertEqual(len(evals[0].instructor_ids), 2) + self.assertEqual(evals[0].instructor_ids[0], 123456789) + self.assertEqual(evals[0].instructor_ids[1], 987654321) + self.assertFalse(evals[0].is_completed) + + def test_multiple_evals(self): + with self.settings(RESTCLIENTS_SWS_DAO_CLASS=FDAO_SWS, + RESTCLIENTS_PWS_DAO_CLASS=FDAO_PWS, + RESTCLIENTS_IASYSTEM_DAO_CLASS=FDAO_IAS): + + regid = "9136CCB8F66711D5BE060004AC494FFE" + evals = search_evaluations('seattle', + term_name='Spring', + curriculum_abbreviation="TRAIN", + student_id=1033334, + section_id="A", + course_number=100, + year=2013) + self.assertIsNotNone(evals) + self.assertEqual(len(evals), 3) + self.assertEqual(evals[0].section_sln, 17169) + self.assertEqual(evals[0].eval_open_date, + datetime.datetime(2013, 5, 30, + 15, 0, 0, + tzinfo=pytz.utc)) + self.assertEqual(evals[0].eval_close_date, + datetime.datetime(2013, 7, 1, + 7, 59, 59, + tzinfo=pytz.utc)) + self.assertFalse(evals[0].is_completed) + self.assertEqual(evals[1].eval_open_date, + datetime.datetime(2013, 6, 5, + 7, 0, 0, + tzinfo=pytz.utc)) + self.assertEqual(evals[1].eval_close_date, + datetime.datetime(2013, 6, 17, + 6, 59, 59, + tzinfo=pytz.utc)) + self.assertFalse(evals[1].is_completed) + self.assertEqual(evals[2].eval_open_date, + datetime.datetime(2013, 6, 10, + 7, 0, 0, + tzinfo=pytz.utc)) + self.assertEqual(evals[2].eval_close_date, + datetime.datetime(2013, 6, 19, + 6, 59, 59, + tzinfo=pytz.utc)) + self.assertTrue(evals[2].is_completed) diff --git a/restclients/test/kws/__init__.py b/restclients/test/kws/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/restclients/test/kws/key.py b/restclients/test/kws/key.py new file mode 100644 index 00000000..bb492fe7 --- /dev/null +++ b/restclients/test/kws/key.py @@ -0,0 +1,34 @@ +from django.test import TestCase +from django.conf import settings +from restclients.kws import KWS +from restclients.exceptions import DataFailureException + + +class KWSTestKeyData(TestCase): + def test_key_by_id(self): + with self.settings( + RESTCLIENTS_KWS_DAO_CLASS='restclients.dao_implementation.kws.File'): + + kws = KWS() + key = kws.get_key('ee99defd-baee-43b0-9e1e-f8238dd106bb') + self.assertEquals(key.algorithm, 'AES128CBC', 'Correct algorithm') + self.assertEquals(key.cipher_mode, 'CBC', 'Correct cipher mode') + self.assertEquals(key.expiration.isoformat(), '2013-04-11T13:44:33', 'Correct expiration') + self.assertEquals(key.key_id, 'ee99defd-baee-43b0-9e1e-f8238dd106bb', 'Correct key ID') + self.assertEquals(key.key, 'Uv2JsxggfxF9OQNzIxAzDQ==', 'Correct key') + self.assertEquals(key.size, 128, 'Correct key size') + self.assertEquals(key.url, 'https://it-wseval1.s.uw.edu/key/v1/encryption/ee99defd-baee-43b0-9e1e-f8238dd106bb.json', 'Correct key URL') + + def test_current_key(self): + with self.settings( + RESTCLIENTS_KWS_DAO_CLASS='restclients.dao_implementation.kws.File'): + + kws = KWS() + key = kws.get_current_key('uw-student-registration') + self.assertEquals(key.algorithm, 'AES128CBC', 'Correct algorithm') + self.assertEquals(key.cipher_mode, 'CBC', 'Correct cipher mode') + self.assertEquals(key.expiration.isoformat(), '2013-04-11T13:44:33', 'Correct expiration') + self.assertEquals(key.key_id, 'ee99defd-baee-43b0-9e1e-f8238dd106bb', 'Correct key ID') + self.assertEquals(key.key, 'Uv2JsxggfxF9OQNzIxAzDQ==', 'Correct key') + self.assertEquals(key.size, 128, 'Correct key size') + self.assertEquals(key.url, 'https://it-wseval1.s.uw.edu/key/v1/encryption/ee99defd-baee-43b0-9e1e-f8238dd106bb.json', 'Correct key URL') diff --git a/restclients/test/library/currics.py b/restclients/test/library/currics.py new file mode 100644 index 00000000..538c1ef0 --- /dev/null +++ b/restclients/test/library/currics.py @@ -0,0 +1,106 @@ +from datetime import date +from django.test import TestCase +from django.conf import settings +from restclients.library.currics import get_subject_guide_for_section_params,\ + get_subject_guide_for_section, get_subject_guide_for_canvas_course_sis_id +from restclients.exceptions import DataFailureException + +class CurricsTest(TestCase): + def test_subject_guide_with_bad_section_params(self): + with self.settings( + RESTCLIENTS_LIBCURRICS_DAO_CLASS = + 'restclients.dao_implementation.library.currics.File'): + + # Valid curriculum, with no file + self.assertRaises(DataFailureException, get_subject_guide_for_section_params, + year=2012, quarter='aut', curriculum_abbr='B ARTS', + course_number='197', section_id='A') + + # Missing params + self.assertRaises(TypeError, get_subject_guide_for_section_params) + + # URL capitalization and quoting + try: + # Using a non-existant section so we can inspect the url + guide = get_subject_guide_for_section_params( + year=1990, quarter='aut', curriculum_abbr='A B&C', + course_number='101', section_id='a') + except DataFailureException as ex: + self.assertEquals(ex.url, + '/currics_db/api/v1/data/course/1990/AUT/A%20B%26C/101/A', + 'Quoted curriculum abbr') + + def test_subject_guide_for_section_params(self): + with self.settings( + RESTCLIENTS_LIBCURRICS_DAO_CLASS = + 'restclients.dao_implementation.library.currics.File'): + + guide = get_subject_guide_for_section_params( + year=2015, quarter='aut', curriculum_abbr='MATH', + course_number='309', section_id='A') + + self.assertEquals(guide.discipline, 'Mathematics') + self.assertEquals(guide.contact_url, 'http://www.lib.washington.edu/about/contact') + self.assertEquals(guide.librarian_url, 'http://guides.lib.uw.edu/research/subject-librarians') + self.assertEquals(guide.guide_url, 'http://guides.lib.uw.edu/friendly.php?s=research/math') + self.assertEquals(guide.faq_url, 'http://guides.lib.uw.edu/research/faq') + self.assertEquals(len(guide.libraries), 1) + self.assertEquals(guide.libraries[0].name, 'Mathematics Research Library') + self.assertEquals(guide.libraries[0].url, 'http://www.lib.washington.edu/math') + self.assertEquals(len(guide.librarians), 2) + self.assertEquals(guide.librarians[0].email, 'javerage@uw.edu') + self.assertEquals(guide.librarians[0].name, 'J Average') + self.assertEquals(guide.librarians[0].url, 'http://guides.lib.washington.edu/Javerage') + self.assertEquals(guide.librarians[1].email, 'baverage@uw.edu') + self.assertEquals(guide.librarians[1].name, 'B Average') + self.assertEquals(guide.librarians[1].url, 'http://guides.lib.washington.edu/Baverage') + + def get_subject_guide_for_section(self): + with self.settings( + RESTCLIENTS_LIBCURRICS_DAO_CLASS = + 'restclients.dao_implementation.library.currics.File'): + + section = Section(year=2015, quarter='autumn', + curriculum_abbr='MATH', course_number='309', section_id='A') + + guide = get_subject_guide_for_section(section) + + self.assertEquals(guide.discipline, 'Mathematics') + self.assertEquals(guide.contact_url, 'http://www.lib.washington.edu/about/contact') + self.assertEquals(guide.librarian_url, 'http://guides.lib.uw.edu/research/subject-librarians') + self.assertEquals(guide.guide_url, 'http://guides.lib.uw.edu/friendly.php?s=research/math') + self.assertEquals(guide.faq_url, 'http://guides.lib.uw.edu/research/faq') + self.assertEquals(len(guide.libraries), 1) + self.assertEquals(guide.libraries[0].name, 'Mathematics Research Library') + self.assertEquals(guide.libraries[0].url, 'http://www.lib.washington.edu/math') + self.assertEquals(len(guide.librarians), 2) + self.assertEquals(guide.librarians[0].email, 'javerage@uw.edu') + self.assertEquals(guide.librarians[0].name, 'J Average') + self.assertEquals(guide.librarians[0].url, 'http://guides.lib.washington.edu/Javerage') + self.assertEquals(guide.librarians[1].email, 'baverage@uw.edu') + self.assertEquals(guide.librarians[1].name, 'B Average') + self.assertEquals(guide.librarians[1].url, 'http://guides.lib.washington.edu/Baverage') + + def get_subject_guide_for_canvas_course_sis_id(self): + with self.settings( + RESTCLIENTS_LIBCURRICS_DAO_CLASS = + 'restclients.dao_implementation.library.currics.File'): + + sis_id = '2015-autumn-MATH-309-A' + guide = get_subject_guide_for_canvas_course_sis_id(sis_id) + + self.assertEquals(guide.discipline, 'Mathematics') + self.assertEquals(guide.contact_url, 'http://www.lib.washington.edu/about/contact') + self.assertEquals(guide.librarian_url, 'http://guides.lib.uw.edu/research/subject-librarians') + self.assertEquals(guide.guide_url, 'http://guides.lib.uw.edu/friendly.php?s=research/math') + self.assertEquals(guide.faq_url, 'http://guides.lib.uw.edu/research/faq') + self.assertEquals(len(guide.libraries), 1) + self.assertEquals(guide.libraries[0].name, 'Mathematics Research Library') + self.assertEquals(guide.libraries[0].url, 'http://www.lib.washington.edu/math') + self.assertEquals(len(guide.librarians), 2) + self.assertEquals(guide.librarians[0].email, 'javerage@uw.edu') + self.assertEquals(guide.librarians[0].name, 'J Average') + self.assertEquals(guide.librarians[0].url, 'http://guides.lib.washington.edu/Javerage') + self.assertEquals(guide.librarians[1].email, 'baverage@uw.edu') + self.assertEquals(guide.librarians[1].name, 'B Average') + self.assertEquals(guide.librarians[1].url, 'http://guides.lib.washington.edu/Baverage') diff --git a/restclients/test/library/mylibinfo.py b/restclients/test/library/mylibinfo.py index ff969cd0..10a6ac36 100644 --- a/restclients/test/library/mylibinfo.py +++ b/restclients/test/library/mylibinfo.py @@ -9,7 +9,7 @@ class MyLibInfoTest(TestCase): def test_get_account(self): with self.settings( RESTCLIENTS_LIBRARIES_DAO_CLASS = - 'restclients.dao_implementation.libraries.File'): + 'restclients.dao_implementation.library.mylibinfo.File'): account = get_account("javerage") self.assertEquals(account.next_due, date(2014, 5, 27)) @@ -29,7 +29,7 @@ def test_get_account(self): def test_html_response(self): with self.settings( RESTCLIENTS_LIBRARIES_DAO_CLASS = - 'restclients.dao_implementation.libraries.File'): + 'restclients.dao_implementation.library.mylibinfo.File'): response = get_account_html("javerage") self.assertEquals(response, '

You have 7 items checked out.
\nYou have items due back on 2014-04-29.
\nYou don\'t owe any fines.

\nGo to your account') @@ -37,7 +37,7 @@ def test_html_response(self): def test_bad_json(self): with self.settings( RESTCLIENTS_LIBRARIES_DAO_CLASS = - 'restclients.dao_implementation.libraries.File'): + 'restclients.dao_implementation.library.mylibinfo.File'): self.assertRaises(Exception, get_account, "badjsonuser") @@ -52,7 +52,7 @@ def test_bad_json(self): def test_invalid_user(self): with self.settings( RESTCLIENTS_LIBRARIES_DAO_CLASS = - 'restclients.dao_implementation.libraries.File'): + 'restclients.dao_implementation.library.mylibinfo.File'): #Testing error message in a 200 response self.assertRaises(DataFailureException, get_account, "invalidnetid") @@ -68,7 +68,7 @@ def test_invalid_user(self): def test_with_timestamp(self): with self.settings( RESTCLIENTS_LIBRARIES_DAO_CLASS= - 'restclients.dao_implementation.libraries.File'): + 'restclients.dao_implementation.library.mylibinfo.File'): response = get_account_html('javerage', timestamp=1391122522900) self.assertEquals(response, '

You have 7 items checked out.
\n You have items due back on 2014-04-29.
\n You don\'t owe any fines.

\n Go to your account') diff --git a/restclients/test/pws/person.py b/restclients/test/pws/person.py index 097c5c3e..8661de1a 100644 --- a/restclients/test/pws/person.py +++ b/restclients/test/pws/person.py @@ -1,9 +1,11 @@ from django.test import TestCase from django.conf import settings from restclients.pws import PWS -from restclients.exceptions import InvalidRegID, InvalidNetID, InvalidEmployeeID +from restclients.exceptions import InvalidRegID, InvalidNetID,\ + InvalidEmployeeID, InvalidStudentNumber from restclients.exceptions import DataFailureException + class PWSTestPersonData(TestCase): def test_by_regid(self): @@ -27,6 +29,32 @@ def test_by_employeeid(self): self.assertEquals(person.uwnetid, 'javerage', "Correct netid") self.assertEquals(person.uwregid, '9136CCB8F66711D5BE060004AC494FFE', "Correct regid") + self.assertRaises(InvalidEmployeeID, + pws.get_person_by_employee_id, + '12345') + + # Valid non-existent employee ID + self.assertRaises(DataFailureException, + pws.get_person_by_employee_id, + '999999999') + + def test_by_student_number(self): + with self.settings( + RESTCLIENTS_PWS_DAO_CLASS='restclients.dao_implementation.pws.File'): + pws = PWS() + person = pws.get_person_by_student_number('1234567') + self.assertEquals(person.uwnetid, 'javerage', "Correct netid") + self.assertEquals(person.uwregid, '9136CCB8F66711D5BE060004AC494FFE', "Correct regid") + + # Valid non-existent student number + self.assertRaises(DataFailureException, + pws.get_person_by_student_number, + '9999999') + + self.assertRaises(InvalidStudentNumber, + pws.get_person_by_student_number, + '123456') + def test_names(self): with self.settings( RESTCLIENTS_PWS_DAO_CLASS='restclients.dao_implementation.pws.File'): @@ -36,6 +64,9 @@ def test_names(self): self.assertEquals(person.first_name, 'JAMES AVERAGE') self.assertEquals(person.full_name, 'JAMES AVERAGE STUDENT') self.assertEquals(person.display_name, 'James Student') + self.assertEquals(person.student_number, "1033334") + self.assertEquals(person.employee_id, "123456789") + self.assertEquals(person.student_class, "Junior") def test_bad_netids(self): with self.settings( @@ -93,6 +124,52 @@ def test_compare_persons(self): self.assertEquals(person1 == person2, True, "persons are equal") self.assertEquals(person1 == person3, False, "persons are inequal") + def test_affiliation_data(self): + with self.settings( + RESTCLIENTS_PWS_DAO_CLASS='restclients.dao_implementation.pws.File'): + pws = PWS() + + person1 = pws.get_person_by_netid("javerage") + self.assertEquals(person1.is_student, True) + self.assertEquals(person1.is_alum, True) + self.assertEquals(person1.is_staff, True) + self.assertEquals(person1.is_faculty, None) + self.assertEquals(person1.is_employee, True) + + self.assertEquals(person1.mailstop, None, "MailStop") + self.assertEquals(person1.home_department, "C&C TEST BUDGET", + "HomeDepartment") + self.assertEquals(person1.student_number, "1033334") + self.assertEquals(person1.employee_id, "123456789") + self.assertEquals(person1.student_department1, "Informatics") + self.assertEquals(person1.student_department2, None) + self.assertEquals(person1.student_department3, None) + + person2 = pws.get_person_by_netid("finals1") + self.assertEquals(person2.is_student, True) + self.assertEquals(person2.is_alum, True) + self.assertEquals(person2.is_staff, True) + self.assertEquals(person2.is_faculty, None) + self.assertEquals(person2.is_employee, True) + + self.assertEquals(person2.home_department, "C&C TEST BUDGET", + "HomeDepartment") + self.assertEquals(person2.student_number, "1033334") + self.assertEquals(person2.employee_id, "123456789") + self.assertEquals(person2.student_class, None) + self.assertEquals(person2.student_department1, None) + self.assertEquals(person2.student_department2, None) + self.assertEquals(person2.student_department3, None) + + def test_missing_person_affiliations(self): + with self.settings( + RESTCLIENTS_PWS_DAO_CLASS='restclients.dao_implementation.pws.File'): + pws = PWS() + person = pws.get_person_by_netid("bill") + self.assertEquals(person.employee_id, u'') + self.assertEquals(person.student_number, u'') + self.assertEquals(person.student_class, u'') + def _test_regid(self, netid, regid): with self.settings( RESTCLIENTS_PWS_DAO_CLASS='restclients.dao_implementation.pws.File'): diff --git a/restclients/test/sws/notice.py b/restclients/test/sws/notice.py index 7844002d..c61e2475 100644 --- a/restclients/test/sws/notice.py +++ b/restclients/test/sws/notice.py @@ -6,11 +6,11 @@ class SWSNotice(TestCase): def test_notice_resource(self): with self.settings( - RESTCLIENTS_SWS_DAO_CLASS='restclients.dao_implementation.sws.File', - RESTCLIENTS_PWS_DAO_CLASS='restclients.dao_implementation.pws.File'): + RESTCLIENTS_SWS_DAO_CLASS='restclients.dao_implementation.sws.File', + RESTCLIENTS_PWS_DAO_CLASS='restclients.dao_implementation.pws.File'): notices = get_notices_by_regid("9136CCB8F66711D5BE060004AC494FFE") - self.assertEquals(len(notices), 13) + self.assertEquals(len(notices), 17) today = date.today() yesterday = today - timedelta(days=1) @@ -120,3 +120,21 @@ def test_notice_resource(self): self.assertEquals(attribute.name, "Date") self.assertEquals(attribute.data_type, "date") self.assertEquals(attribute.get_value(), future_end.strftime("%Y-%m-%d")) + + notice = notices[13] + self.assertEquals(notice.notice_category, "StudentFinAid") + self.assertEquals(notice.notice_type, "DirectDeposit") + + notice = notices[14] + self.assertEquals(notice.notice_category, "StudentFinAid") + self.assertEquals(notice.notice_type, "DirectDepositShort") + self.assertEquals(notice.long_notice.notice_type, "DirectDeposit") + + notice = notices[15] + self.assertEquals(notice.notice_category, "StudentFinAid") + self.assertEquals(notice.notice_type, "AidPriorityDate") + + notice = notices[16] + self.assertEquals(notice.notice_category, "StudentFinAid") + self.assertEquals(notice.notice_type, "AidPriorityDateShort") + self.assertEquals(notice.long_notice.notice_type, "AidPriorityDate") diff --git a/restclients/test/sws/section.py b/restclients/test/sws/section.py index 107ba1c5..da29e644 100644 --- a/restclients/test/sws/section.py +++ b/restclients/test/sws/section.py @@ -1,35 +1,53 @@ +from datetime import datetime from django.test import TestCase from django.conf import settings from restclients.models.sws import Term, Curriculum, Person from restclients.exceptions import DataFailureException from restclients.exceptions import InvalidSectionID, InvalidSectionURL -from restclients.exceptions import InvalidCanvasIndependentStudyCourse, InvalidCanvasSection -import restclients.sws.section as SectionSws +from restclients.exceptions import InvalidCanvasIndependentStudyCourse,\ + InvalidCanvasSection from restclients.sws import use_v5_resources -from datetime import datetime +from restclients.sws.section import get_section_by_label,\ + get_joint_sections, get_linked_sections,\ + get_sections_by_instructor_and_term,\ + get_sections_by_curriculum_and_term,\ + get_changed_sections_by_term,\ + get_sections_by_delegate_and_term,\ + is_a_term, is_b_term, is_full_summer_term + + +SWSF = 'restclients.dao_implementation.sws.File' +PWSF = 'restclients.dao_implementation.pws.File' class SWSTestSectionData(TestCase): def test_final_exams(self): with self.settings( - RESTCLIENTS_SWS_DAO_CLASS='restclients.dao_implementation.sws.File', - RESTCLIENTS_PWS_DAO_CLASS='restclients.dao_implementation.pws.File'): + RESTCLIENTS_SWS_DAO_CLASS=SWSF, + RESTCLIENTS_PWS_DAO_CLASS=PWSF): - section = SectionSws.get_section_by_label('2013,summer,B BIO,180/A') - self.assertEquals(section.final_exam, None, "No final exam for B BIO 180") + section = get_section_by_label('2013,summer,B BIO,180/A') + self.assertEquals(section.final_exam, None, + "No final exam for B BIO 180") - section = SectionSws.get_section_by_label('2013,summer,MATH,125/G') + section = get_section_by_label('2013,summer,MATH,125/G') final_exam = section.final_exam - self.assertEquals(final_exam.is_confirmed, False, "Final exam for Math 125 isn't confirmed") - self.assertEquals(final_exam.no_exam_or_nontraditional, False, "Final exam for Math 125 isn't non-traditional") - section = SectionSws.get_section_by_label('2013,summer,TRAIN,101/A') + self.assertEquals(final_exam.is_confirmed, False, + "Final exam for Math 125 isn't confirmed") + self.assertEquals(final_exam.no_exam_or_nontraditional, False, + "Final exam for Math 125 isn't non-traditional") + section = get_section_by_label('2013,summer,TRAIN,101/A') final_exam = section.final_exam - self.assertEquals(final_exam.is_confirmed, True, "Final exam for Train 101 is confirmed") - self.assertEquals(final_exam.no_exam_or_nontraditional, False, "Final exam for Train 101 isn't non-traditional") - self.assertEquals(final_exam.building, "KNE", "Has right final building") - self.assertEquals(final_exam.room_number, "012", "Has right room #") + self.assertEquals(final_exam.is_confirmed, True, + "Final exam for Train 101 is confirmed") + self.assertEquals(final_exam.no_exam_or_nontraditional, False, + "Final exam for Train 101 isn't non-traditional") + self.assertEquals(final_exam.building, "KNE", + "Has right final building") + self.assertEquals(final_exam.room_number, "012", + "Has right room #") start = final_exam.start_date end = final_exam.end_date @@ -46,360 +64,415 @@ def test_final_exams(self): self.assertEquals(end.hour, 16) self.assertEquals(end.minute, 20) - - def test_section_by_label(self): with self.settings( - RESTCLIENTS_SWS_DAO_CLASS='restclients.dao_implementation.sws.File', - RESTCLIENTS_PWS_DAO_CLASS='restclients.dao_implementation.pws.File'): + RESTCLIENTS_SWS_DAO_CLASS=SWSF, + RESTCLIENTS_PWS_DAO_CLASS=PWSF): #Valid data, shouldn't throw any exceptions - SectionSws.get_section_by_label('2013,summer,TRAIN,100/A') + get_section_by_label('2013,summer,TRAIN,100/A') #Invalid data, should throw exceptions self.assertRaises(InvalidSectionID, - SectionSws.get_section_by_label, + get_section_by_label, '') self.assertRaises(InvalidSectionID, - SectionSws.get_section_by_label, + get_section_by_label, ' ') self.assertRaises(InvalidSectionID, - SectionSws.get_section_by_label, + get_section_by_label, '2012') self.assertRaises(InvalidSectionID, - SectionSws.get_section_by_label, + get_section_by_label, '2012,summer') self.assertRaises(InvalidSectionID, - SectionSws.get_section_by_label, + get_section_by_label, '2012,summer,TRAIN') self.assertRaises(InvalidSectionID, - SectionSws.get_section_by_label, + get_section_by_label, '2012, summer, TRAIN, 100') self.assertRaises(InvalidSectionID, - SectionSws.get_section_by_label, + get_section_by_label, 'summer, TRAIN, 100/A') self.assertRaises(InvalidSectionID, - SectionSws.get_section_by_label, + get_section_by_label, '2012,fall,TRAIN,100/A') self.assertRaises(InvalidSectionID, - SectionSws.get_section_by_label, + get_section_by_label, '-2012,summer,TRAIN,100/A') self.assertRaises(DataFailureException, - SectionSws.get_section_by_label, + get_section_by_label, '9999,summer,TRAIN,100/A') #Valid section labels, no files for them self.assertRaises(DataFailureException, - SectionSws.get_section_by_label, + get_section_by_label, '2012,summer,TRAIN,110/A') self.assertRaises(DataFailureException, - SectionSws.get_section_by_label, + get_section_by_label, '2012,summer,TRAIN,100/B') self.assertRaises(DataFailureException, - SectionSws.get_section_by_label, + get_section_by_label, '2012,summer,PHYS,121/B') self.assertRaises(DataFailureException, - SectionSws.get_section_by_label, + get_section_by_label, '2012,summer,PHYS,121/BB') self.assertRaises(DataFailureException, - SectionSws.get_section_by_label, + get_section_by_label, '2010,autumn,G H,201/A') self.assertRaises(DataFailureException, - SectionSws.get_section_by_label, + get_section_by_label, '2010,autumn,CS&SS,221/A') self.assertRaises(DataFailureException, - SectionSws.get_section_by_label, + get_section_by_label, '2010,autumn,KOREAN,101/A') self.assertRaises(DataFailureException, - SectionSws.get_section_by_label, + get_section_by_label, '2010,autumn,CM,101/A') def test_instructors_in_section(self): with self.settings( - RESTCLIENTS_SWS_DAO_CLASS='restclients.dao_implementation.sws.File', - RESTCLIENTS_PWS_DAO_CLASS='restclients.dao_implementation.pws.File'): + RESTCLIENTS_SWS_DAO_CLASS=SWSF, + RESTCLIENTS_PWS_DAO_CLASS=PWSF): - section = SectionSws.get_section_by_label('2013,winter,ASIAN,203/A') + section = get_section_by_label('2013,winter,ASIAN,203/A') - self.assertEquals(len(section.get_instructors()), 1, "Correct number of instructors") + self.assertEquals(len(section.get_instructors()), 1, + "Correct number of instructors") person1 = Person(uwregid="FBB38FE46A7C11D5A4AE0004AC494FFE") - self.assertEquals(section.is_instructor(person1), False, "Person is not instructor") + self.assertEquals(section.is_instructor(person1), False, + "Person is not instructor") person2 = Person(uwregid="6DF0A9206A7D11D5A4AE0004AC494FFE") - self.assertEquals(section.is_instructor(person2), True, "Person is instructor") + self.assertEquals(section.is_instructor(person2), True, + "Person is instructor") - section2 = SectionSws.get_section_by_label('2013,summer,TRAIN,101/A') - self.assertEquals(len(section2.get_instructors()), 2, "Correct number of instructors") + section2 = get_section_by_label('2013,summer,TRAIN,101/A') + self.assertEquals(len(section2.get_instructors()), 2, + "Correct number of instructors") - section3 = SectionSws.get_section_by_label('2013,spring,PHYS,121/A') + section3 = get_section_by_label('2013,spring,PHYS,121/A') self.assertEquals(len(section3.get_instructors()), 5, "Correct number of all instructors") - section3 = SectionSws.get_section_by_label('2013,spring,PHYS,121/A', False) + section3 = get_section_by_label('2013,spring,PHYS,121/A', False) self.assertEquals(len(section3.get_instructors()), 4, "Correct number of TSPrinted instructors") def test_delegates_in_section(self): with self.settings( - RESTCLIENTS_SWS_DAO_CLASS='restclients.dao_implementation.sws.File', - RESTCLIENTS_PWS_DAO_CLASS='restclients.dao_implementation.pws.File'): + RESTCLIENTS_SWS_DAO_CLASS=SWSF, + RESTCLIENTS_PWS_DAO_CLASS=PWSF): - section = SectionSws.get_section_by_label('2013,winter,ASIAN,203/A') + section = get_section_by_label('2013,winter,ASIAN,203/A') self.assertEquals(len(section.grade_submission_delegates), 3, "Correct number of delegates") person1 = Person(uwregid="6DF0A9206A7D11D5A4AE0004AC494FFE") - self.assertEquals(section.is_grade_submission_delegate(person1), False, "Person is not delegate") + self.assertEquals(section.is_grade_submission_delegate(person1), + False, "Person is not delegate") person2 = Person(uwregid="FBB38FE46A7C11D5A4AE0004AC494FFE") - self.assertEquals(section.is_grade_submission_delegate(person2), True, "Person is delegate") + self.assertEquals(section.is_grade_submission_delegate(person2), + True, "Person is delegate") def test_joint_sections(self): with self.settings( - RESTCLIENTS_SWS_DAO_CLASS='restclients.dao_implementation.sws.File', - RESTCLIENTS_PWS_DAO_CLASS='restclients.dao_implementation.pws.File'): + RESTCLIENTS_SWS_DAO_CLASS=SWSF, + RESTCLIENTS_PWS_DAO_CLASS=PWSF): - section = SectionSws.get_section_by_label('2013,winter,ASIAN,203/A') - joint_sections = SectionSws.get_joint_sections(section) + section = get_section_by_label('2013,winter,ASIAN,203/A') + joint_sections = get_joint_sections(section) self.assertEquals(len(joint_sections), 1) - section = SectionSws.get_section_by_label('2013,winter,EMBA,503/A') - joint_sections = SectionSws.get_joint_sections(section) + section = get_section_by_label('2013,winter,EMBA,503/A') + joint_sections = get_joint_sections(section) self.assertEquals(len(joint_sections), 0) - #Failing because linked section json files haven't been made (Train 100 AA/AB) + # Failing because linked section json files haven't been made + # (Train 100 AA/AB) def test_linked_sections(self): with self.settings( - RESTCLIENTS_SWS_DAO_CLASS='restclients.dao_implementation.sws.File', - RESTCLIENTS_PWS_DAO_CLASS='restclients.dao_implementation.pws.File'): + RESTCLIENTS_SWS_DAO_CLASS=SWSF, + RESTCLIENTS_PWS_DAO_CLASS=PWSF): #Valid data, shouldn't throw any exceptions - section = SectionSws.get_section_by_label('2013,summer,TRAIN,100/A') - SectionSws.get_linked_sections(section) + section = get_section_by_label('2013,summer,TRAIN,100/A') + get_linked_sections(section) #Invalid data, should throw exceptions section.linked_section_urls = [''] self.assertRaises(InvalidSectionURL, - SectionSws.get_linked_sections, section) + get_linked_sections, section) section.linked_section_urls = [' '] self.assertRaises(InvalidSectionURL, - SectionSws.get_linked_sections, section) + get_linked_sections, section) section.linked_section_urls = ['2012,summer,TRAIN,100/A'] self.assertRaises(InvalidSectionURL, - SectionSws.get_linked_sections, section) + get_linked_sections, section) if use_v5_resources(): - section.linked_section_urls = ['/student/v5/course/2012,summer,PHYS,121/B.json'] + section.linked_section_urls =\ + ['/student/v5/course/2012,summer,PHYS,121/B.json'] else: - section.linked_section_urls = ['/student/v4/course/2012,summer,PHYS,121/B.json'] + section.linked_section_urls =\ + ['/student/v4/course/2012,summer,PHYS,121/B.json'] self.assertRaises(DataFailureException, - SectionSws.get_linked_sections, section) + get_linked_sections, section) if use_v5_resources(): - section.linked_section_urls = ['/student/v5/course/2010,autumn,CS&SS,221/A.json'] + section.linked_section_urls =\ + ['/student/v5/course/2010,autumn,CS&SS,221/A.json'] else: - section.linked_section_urls = ['/student/v4/course/2010,autumn,CS&SS,221/A.json'] + section.linked_section_urls =\ + ['/student/v4/course/2010,autumn,CS&SS,221/A.json'] self.assertRaises(DataFailureException, - SectionSws.get_linked_sections, section) + get_linked_sections, section) if use_v5_resources(): - section.linked_section_urls = ['/student/v5/course/2010,autumn,KOREAN,101/A.json'] + section.linked_section_urls =\ + ['/student/v5/course/2010,autumn,KOREAN,101/A.json'] else: - section.linked_section_urls = ['/student/v4/course/2010,autumn,KOREAN,101/A.json'] + section.linked_section_urls =\ + ['/student/v4/course/2010,autumn,KOREAN,101/A.json'] self.assertRaises(DataFailureException, - SectionSws.get_linked_sections, section) + get_linked_sections, section) if use_v5_resources(): - section.linked_section_urls = ['/student/v5/course/2010,autumn,G H,201/A.json'] + section.linked_section_urls =\ + ['/student/v5/course/2010,autumn,G H,201/A.json'] else: - section.linked_section_urls = ['/student/v4/course/2010,autumn,G H,201/A.json'] + section.linked_section_urls =\ + ['/student/v4/course/2010,autumn,G H,201/A.json'] self.assertRaises(DataFailureException, - SectionSws.get_linked_sections, section) + get_linked_sections, section) if use_v5_resources(): - section.linked_section_urls = ['/student/v5/course/2010,autumn,CM,101/A.json'] + section.linked_section_urls =\ + ['/student/v5/course/2010,autumn,CM,101/A.json'] else: - section.linked_section_urls = ['/student/v4/course/2010,autumn,CM,101/A.json'] + section.linked_section_urls =\ + ['/student/v4/course/2010,autumn,CM,101/A.json'] self.assertRaises(DataFailureException, - SectionSws.get_linked_sections, section) + get_linked_sections, section) if use_v5_resources(): - section.linked_section_urls = ['/student/v5/course/2012,autumn,PHYS,121/A.json', - '/student/v5/course/2012,autumn,PHYS,121/AC.json', - '/student/v5/course/2012,autumn,PHYS,121/BT.json'] + section.linked_section_urls = [ + '/student/v5/course/2012,autumn,PHYS,121/A.json', + '/student/v5/course/2012,autumn,PHYS,121/AC.json', + '/student/v5/course/2012,autumn,PHYS,121/BT.json'] else: - section.linked_section_urls = ['/student/v4/course/2012,autumn,PHYS,121/A.json', - '/student/v4/course/2012,autumn,PHYS,121/AC.json', - '/student/v4/course/2012,autumn,PHYS,121/BT.json'] + section.linked_section_urls = [ + '/student/v4/course/2012,autumn,PHYS,121/A.json', + '/student/v4/course/2012,autumn,PHYS,121/AC.json', + '/student/v4/course/2012,autumn,PHYS,121/BT.json'] self.assertRaises(DataFailureException, - SectionSws.get_linked_sections, section) + get_linked_sections, section) if use_v5_resources(): - section.linked_section_urls = ['/student/v5/course/2012,autumn,PHYS,121/A.json', - '/student/v5/course/2012,autumn,PHYS,121/AC.json', - '/student/v5/course/2012,autumn,PHYS,121/AAA.json'] + section.linked_section_urls = [ + '/student/v5/course/2012,autumn,PHYS,121/A.json', + '/student/v5/course/2012,autumn,PHYS,121/AC.json', + '/student/v5/course/2012,autumn,PHYS,121/AAA.json'] else: - section.linked_section_urls = ['/student/v4/course/2012,autumn,PHYS,121/A.json', - '/student/v4/course/2012,autumn,PHYS,121/AC.json', - '/student/v4/course/2012,autumn,PHYS,121/AAA.json'] + section.linked_section_urls = [ + '/student/v4/course/2012,autumn,PHYS,121/A.json', + '/student/v4/course/2012,autumn,PHYS,121/AC.json', + '/student/v4/course/2012,autumn,PHYS,121/AAA.json'] self.assertRaises(DataFailureException, - SectionSws.get_linked_sections, section) + get_linked_sections, section) def test_sections_by_instructor_and_term(self): with self.settings( - RESTCLIENTS_SWS_DAO_CLASS='restclients.dao_implementation.sws.File', - RESTCLIENTS_PWS_DAO_CLASS='restclients.dao_implementation.pws.File'): + RESTCLIENTS_SWS_DAO_CLASS=SWSF, + RESTCLIENTS_PWS_DAO_CLASS=PWSF): term = Term(quarter="summer", year=2013) instructor = Person(uwregid="FBB38FE46A7C11D5A4AE0004AC494FFE") - sections = SectionSws.get_sections_by_instructor_and_term(instructor, term) + sections = get_sections_by_instructor_and_term(instructor, term) self.assertEquals(len(sections), 1) def test_sections_by_delegate_and_term(self): with self.settings( - RESTCLIENTS_SWS_DAO_CLASS='restclients.dao_implementation.sws.File', - RESTCLIENTS_PWS_DAO_CLASS='restclients.dao_implementation.pws.File'): + RESTCLIENTS_SWS_DAO_CLASS=SWSF, + RESTCLIENTS_PWS_DAO_CLASS=PWSF): term = Term(quarter="summer", year=2013) delegate = Person(uwregid="FBB38FE46A7C11D5A4AE0004AC494FFE") - sections = SectionSws.get_sections_by_delegate_and_term(delegate, term) + sections = get_sections_by_delegate_and_term(delegate, term) self.assertEquals(len(sections), 2) def test_sections_by_curriculum_and_term(self): with self.settings( - RESTCLIENTS_SWS_DAO_CLASS='restclients.dao_implementation.sws.File', - RESTCLIENTS_PWS_DAO_CLASS='restclients.dao_implementation.pws.File'): + RESTCLIENTS_SWS_DAO_CLASS=SWSF, + RESTCLIENTS_PWS_DAO_CLASS=PWSF): term = Term(quarter="winter", year=2013) curriculum = Curriculum(label="ENDO") - sections = SectionSws.get_sections_by_curriculum_and_term(curriculum, term) + sections = get_sections_by_curriculum_and_term(curriculum, term) self.assertEquals(len(sections), 2) # Valid curriculum, with no file self.assertRaises(DataFailureException, - SectionSws.get_sections_by_curriculum_and_term, + get_sections_by_curriculum_and_term, Curriculum(label="FINN"), term) def test_changed_sections_by_term(self): with self.settings( - RESTCLIENTS_SWS_DAO_CLASS='restclients.dao_implementation.sws.File', - RESTCLIENTS_PWS_DAO_CLASS='restclients.dao_implementation.pws.File'): + RESTCLIENTS_SWS_DAO_CLASS=SWSF, + RESTCLIENTS_PWS_DAO_CLASS=PWSF): changed_date = datetime(2013, 12, 12).date() term = Term(quarter="winter", year=2013) - sections = SectionSws.get_changed_sections_by_term(changed_date, term) + sections = get_changed_sections_by_term(changed_date, term) self.assertEquals(len(sections), 2) def test_instructor_published(self): with self.settings( - RESTCLIENTS_SWS_DAO_CLASS='restclients.dao_implementation.sws.File', - RESTCLIENTS_PWS_DAO_CLASS='restclients.dao_implementation.pws.File'): + RESTCLIENTS_SWS_DAO_CLASS=SWSF, + RESTCLIENTS_PWS_DAO_CLASS=PWSF): # Published Instructors - pi_section = SectionSws.get_section_by_label('2013,summer,B BIO,180/A') - self.assertEquals(pi_section.meetings[0].instructors[0].TSPrint, True) + pi_section = get_section_by_label('2013,summer,B BIO,180/A') + self.assertEquals( + pi_section.meetings[0].instructors[0].TSPrint, True) # Unpublished Instructors - upi_section = SectionSws.get_section_by_label('2013,summer,MATH,125/G') - self.assertEquals(upi_section.meetings[0].instructors[0].TSPrint, False) + upi_section = get_section_by_label('2013,summer,MATH,125/G') + self.assertEquals( + upi_section.meetings[0].instructors[0].TSPrint, False) def test_secondary_grading(self): with self.settings( - RESTCLIENTS_SWS_DAO_CLASS='restclients.dao_implementation.sws.File', - RESTCLIENTS_PWS_DAO_CLASS='restclients.dao_implementation.pws.File'): + RESTCLIENTS_SWS_DAO_CLASS=SWSF, + RESTCLIENTS_PWS_DAO_CLASS=PWSF): - section1 = SectionSws.get_section_by_label('2012,summer,PHYS,121/A') + section1 = get_section_by_label('2012,summer,PHYS,121/A') self.assertEquals(section1.allows_secondary_grading, True, "Allows secondary grading") - for linked in SectionSws.get_linked_sections(section1): + for linked in get_linked_sections(section1): self.assertEquals(linked.allows_secondary_grading, True, "Allows secondary grading") - section2 = SectionSws.get_section_by_label('2013,winter,EMBA,503/A') + section2 = get_section_by_label('2013,winter,EMBA,503/A') self.assertEquals(section2.allows_secondary_grading, False, "Does not allow secondary grading") def test_grading_period_open(self): with self.settings( - RESTCLIENTS_SWS_DAO_CLASS='restclients.dao_implementation.sws.File', - RESTCLIENTS_PWS_DAO_CLASS='restclients.dao_implementation.pws.File'): + RESTCLIENTS_SWS_DAO_CLASS=SWSF, + RESTCLIENTS_PWS_DAO_CLASS=PWSF): - section = SectionSws.get_section_by_label('2012,summer,PHYS,121/A') + section = get_section_by_label('2012,summer,PHYS,121/A') - self.assertEquals(section.is_grading_period_open(), False, "Grading window is not open") + self.assertEquals(section.is_grading_period_open(), False, + "Grading window is not open") # Spring 2013 is 'current' term - section = SectionSws.get_section_by_label('2013,spring,MATH,125/G') + section = get_section_by_label('2013,spring,MATH,125/G') - self.assertEquals(section.is_grading_period_open(), True, "Grading window is open") + self.assertEquals(section.is_grading_period_open(), True, + "Grading window is open") def test_canvas_sis_ids(self): with self.settings( - RESTCLIENTS_SWS_DAO_CLASS='restclients.dao_implementation.sws.File', - RESTCLIENTS_PWS_DAO_CLASS='restclients.dao_implementation.pws.File'): + RESTCLIENTS_SWS_DAO_CLASS=SWSF, + RESTCLIENTS_PWS_DAO_CLASS=PWSF): # Primary section containing linked secondary sections - section = SectionSws.get_section_by_label('2012,summer,PHYS,121/A') + section = get_section_by_label('2012,summer,PHYS,121/A') self.assertEquals(section.canvas_course_sis_id(), '2012-summer-PHYS-121-A', 'Canvas course SIS ID') self.assertRaises(InvalidCanvasSection, section.canvas_section_sis_id) # Primary section with no linked sections - section = SectionSws.get_section_by_label('2013,autumn,REHAB,585/A') + section = get_section_by_label('2013,autumn,REHAB,585/A') self.assertEquals(section.canvas_course_sis_id(), '2013-autumn-REHAB-585-A', 'Canvas course SIS ID') self.assertEquals(section.canvas_section_sis_id(), '2013-autumn-REHAB-585-A--', 'Canvas section SIS ID') # Secondary (linked) section - section = SectionSws.get_section_by_label('2013,autumn,PHYS,121/AB') + section = get_section_by_label('2013,autumn,PHYS,121/AB') self.assertEquals(section.canvas_course_sis_id(), '2013-autumn-PHYS-121-A', 'Canvas course SIS ID') self.assertEquals(section.canvas_section_sis_id(), '2013-autumn-PHYS-121-AB', 'Canvas section SIS ID') # Independent study section - section = SectionSws.get_section_by_label('2013,summer,PHIL,600/A') + section = get_section_by_label('2013,summer,PHIL,600/A') # ..missing instructor regid self.assertRaises(InvalidCanvasIndependentStudyCourse, section.canvas_course_sis_id) - section.independent_study_instructor_regid = 'A9D2DDFA6A7D11D5A4AE0004AC494FFE' + section.independent_study_instructor_regid =\ + 'A9D2DDFA6A7D11D5A4AE0004AC494FFE' self.assertEquals(section.canvas_course_sis_id(), '2013-summer-PHIL-600-A-A9D2DDFA6A7D11D5A4AE0004AC494FFE', 'Canvas course SIS ID') self.assertEquals(section.canvas_section_sis_id(), '2013-summer-PHIL-600-A-A9D2DDFA6A7D11D5A4AE0004AC494FFE--', 'Canvas section SIS ID') + + def test_summer_terms(self): + with self.settings( + RESTCLIENTS_SWS_DAO_CLASS=SWSF, + RESTCLIENTS_PWS_DAO_CLASS=PWSF): + + section = get_section_by_label('2013,summer,B BIO,180/A') + self.assertFalse(section.is_summer_a_term()) + self.assertFalse(section.is_summer_b_term()) + self.assertFalse(section.is_half_summer_term()) + self.assertTrue(section.is_full_summer_term()) + + self.assertTrue(section.is_same_summer_term("full-term")) + self.assertFalse(section.is_same_summer_term("a-term")) + self.assertFalse(section.is_same_summer_term("B-term")) + self.assertFalse(section.is_same_summer_term(None)) + + section = get_section_by_label('2013,summer,PHIL,600/A') + # section.summer_term is "" + self.assertFalse(section.is_summer_a_term()) + self.assertFalse(section.is_summer_b_term()) + self.assertFalse(section.is_full_summer_term()) + self.assertTrue(section.is_same_summer_term(None)) + self.assertTrue(section.is_same_summer_term("")) + + def test_summer_term_statics(self): + self.assertTrue(is_a_term("A-term")) + self.assertTrue(is_b_term("B-term")) + self.assertTrue(is_full_summer_term("Full-term")) + self.assertFalse(is_full_summer_term("A-term")) + self.assertFalse(is_full_summer_term("B-term")) diff --git a/restclients/test/sws/term.py b/restclients/test/sws/term.py index 399230f3..8dc5087b 100644 --- a/restclients/test/sws/term.py +++ b/restclients/test/sws/term.py @@ -2,17 +2,22 @@ from django.conf import settings from datetime import datetime, timedelta from restclients.exceptions import DataFailureException -from restclients.sws.term import get_term_by_year_and_quarter, get_term_before, get_term_after -from restclients.sws.term import get_current_term, get_next_term, get_previous_term -from restclients.sws.term import get_term_by_date +from restclients.sws.term import get_term_by_year_and_quarter,\ + get_term_before, get_term_after, get_current_term, get_next_term,\ + get_previous_term, get_term_by_date, get_specific_term,\ + get_next_autumn_term, get_next_non_summer_term + + +SWSF = 'restclients.dao_implementation.sws.File' +PWSF = 'restclients.dao_implementation.pws.File' class SWSTestTerm(TestCase): def test_mock_data_fake_grading_window(self): with self.settings( - RESTCLIENTS_SWS_DAO_CLASS='restclients.dao_implementation.sws.File', - RESTCLIENTS_PWS_DAO_CLASS='restclients.dao_implementation.pws.File'): + RESTCLIENTS_SWS_DAO_CLASS=SWSF, + RESTCLIENTS_PWS_DAO_CLASS=PWSF): # This rounds down to 0 days, so check by seconds :( hour1_delta = timedelta(hours=-1) @@ -20,48 +25,63 @@ def test_mock_data_fake_grading_window(self): now = datetime.now() term = get_current_term() - self.assertEquals(term.is_grading_period_open(), True, "Grading period is open") - self.assertEquals(term.is_grading_period_past(), False, "Grading period is not past") + self.assertEquals(term.is_grading_period_open(), + True, "Grading period is open") + self.assertEquals(term.is_grading_period_past(), + False, "Grading period is not past") deadline = term.grade_submission_deadline - self.assertEquals(deadline + hour1_delta > now, True, "Deadline is in the future") - self.assertEquals(deadline + hour48_delta < now, True, "But not too far in the future") + self.assertEquals(deadline + hour1_delta > now, + True, "Deadline is in the future") + self.assertEquals(deadline + hour48_delta < now, + True, "But not too far in the future") open_diff_all = now - term.grading_period_open # Timezone configuration can mess this up, so using seconds - self.assertEquals(open_diff_all.seconds > 0, True, "Open date is in the past") - self.assertEquals(open_diff_all.days < 2, True, "But not too far in the past") + self.assertEquals(open_diff_all.seconds > 0, + True, "Open date is in the past") + self.assertEquals(open_diff_all.days < 2, + True, "But not too far in the past") open_diff_summer_a = now - term.aterm_grading_period_open - self.assertEquals(open_diff_summer_a.seconds > 0, True, "Open date is in the past") - self.assertEquals(open_diff_summer_a.days < 2, True, "But not too far in the past") + self.assertEquals(open_diff_summer_a.seconds > 0, + True, "Open date is in the past") + self.assertEquals(open_diff_summer_a.days < 2, + True, "But not too far in the past") # Also test for Spring 2013, as that's the "current" quarter term = get_term_by_year_and_quarter(2013, 'spring') - self.assertEquals(term.is_grading_period_open(), True, "Grading period is open") - self.assertEquals(term.is_grading_period_past(), False, "Grading period is not past") + self.assertEquals(term.is_grading_period_open(), + True, "Grading period is open") + self.assertEquals(term.is_grading_period_past(), + False, "Grading period is not past") deadline = term.grade_submission_deadline - self.assertEquals(deadline + hour1_delta > now, True, "Deadline is in the future") - self.assertEquals(deadline + hour48_delta < now, True, "But not too far in the future") + self.assertEquals(deadline + hour1_delta > now, + True, "Deadline is in the future") + self.assertEquals(deadline + hour48_delta < now, + True, "But not too far in the future") open_diff_all = now - term.grading_period_open # Timezone configuration can mess this up, so using seconds - self.assertEquals(open_diff_all.seconds > 0, True, "Open date is in the past") - self.assertEquals(open_diff_all.days < 2, True, "But not too far in the past") + self.assertEquals(open_diff_all.seconds > 0, + True, "Open date is in the past") + self.assertEquals(open_diff_all.days < 2, True, + "But not too far in the past") open_diff_summer_a = now - term.aterm_grading_period_open - self.assertEquals(open_diff_summer_a.seconds > 0, True, "Open date is in the past") - self.assertEquals(open_diff_summer_a.days < 2, True, "But not too far in the past") - + self.assertEquals(open_diff_summer_a.seconds > 0, + True, "Open date is in the past") + self.assertEquals(open_diff_summer_a.days < 2, True, + "But not too far in the past") def test_current_quarter(self): with self.settings( - RESTCLIENTS_SWS_DAO_CLASS='restclients.dao_implementation.sws.File', - RESTCLIENTS_PWS_DAO_CLASS='restclients.dao_implementation.pws.File'): + RESTCLIENTS_SWS_DAO_CLASS=SWSF, + RESTCLIENTS_PWS_DAO_CLASS=PWSF): term = get_current_term() @@ -76,12 +96,53 @@ def test_current_quarter(self): "Return %s for the current quarter" % expected_quarter) + self.assertEquals(term.first_day_quarter.year, 2013) + self.assertEquals(term.first_day_quarter.month, 4) + self.assertEquals(term.first_day_quarter.day, 1) + self.assertEquals(term.get_bod_first_day(), + datetime(2013, 4, 1, 0, 0, 0)) + + self.assertEquals(term.census_day.year, 2013) + self.assertEquals(term.census_day.month, 4) + self.assertEquals(term.census_day.day, 12) + self.assertEquals(term.get_eod_census_day(), + datetime(2013, 4, 13, 0, 0, 0)) + + self.assertEquals(term.get_bod_reg_period1_start(), + datetime(2013, 2, 15, 0, 0, 0)) + + self.assertEquals(term.get_bod_reg_period2_start(), + datetime(2013, 3, 4, 0, 0, 0)) + + self.assertEquals(term.get_bod_reg_period3_start(), + datetime(2013, 4, 1, 0, 0, 0)) + + self.assertEquals(term.get_eod_last_final_exam(), + datetime(2013, 6, 15, 0, 0, 0)) + + self.assertEquals(term.last_day_instruction.year, 2013) + self.assertEquals(term.last_day_instruction.month, 6) + self.assertEquals(term.last_day_instruction.day, 7) + self.assertEquals(term.get_eod_last_instruction(), + datetime(2013, 6, 8, 0, 0, 0)) + + next_autumn_term = get_next_autumn_term(term) + self.assertEquals(next_autumn_term.year, 2013) + self.assertEquals(next_autumn_term.quarter, 'autumn') + + next_non_summer_term = get_next_non_summer_term(term) + self.assertEquals(next_non_summer_term.year, + next_autumn_term.year) + self.assertEquals(next_non_summer_term.quarter, + next_autumn_term.quarter) + + self.assertFalse(term.is_summer_quarter()) #Expected values will have to change when the json files are updated def test_previous_quarter(self): with self.settings( - RESTCLIENTS_SWS_DAO_CLASS='restclients.dao_implementation.sws.File', - RESTCLIENTS_PWS_DAO_CLASS='restclients.dao_implementation.pws.File'): + RESTCLIENTS_SWS_DAO_CLASS=SWSF, + RESTCLIENTS_PWS_DAO_CLASS=PWSF): term = get_previous_term() @@ -96,27 +157,45 @@ def test_previous_quarter(self): "Return %s for the previous quarter" % expected_quarter) - self.assertEquals(term.first_day_quarter.year, 2013) - self.assertEquals(term.first_day_quarter.month, 1) - self.assertEquals(term.first_day_quarter.day, 7) + self.assertEquals(term.get_bod_first_day(), + datetime(2013, 1, 7, 0, 0, 0)) - self.assertEquals(term.last_day_instruction.year, 2013) - self.assertEquals(term.last_day_instruction.month, 3) - self.assertEquals(term.last_day_instruction.day, 15) + self.assertEquals(term.grading_period_open.date().year, 2013) + self.assertEquals(term.grading_period_open.date().month, 2) + self.assertEquals(term.grading_period_open.date().day, 25) + self.assertEquals(term.grading_period_open.time().hour, 8) + self.assertEquals(term.grading_period_open.time().minute, 0) - self.assertEquals(term.aterm_last_date, None) - self.assertEquals(term.bterm_first_date, None) - self.assertEquals(term.aterm_grading_period_open, None) + self.assertEquals(term.get_bod_reg_period1_start(), + datetime(2012, 11, 2, 0, 0, 0)) - self.assertEquals(term.last_final_exam_date.year, 2013) - self.assertEquals(term.last_final_exam_date.month, 3) - self.assertEquals(term.last_final_exam_date.day, 22) + self.assertEquals(term.get_bod_reg_period2_start(), + datetime(2012, 11, 26, 0, 0, 0)) + + self.assertEquals(term.get_bod_reg_period3_start(), + datetime(2013, 1, 7, 0, 0, 0)) self.assertEquals(term.grade_submission_deadline.date().year, 2013) self.assertEquals(term.grade_submission_deadline.date().month, 3) self.assertEquals(term.grade_submission_deadline.date().day, 26) self.assertEquals(term.grade_submission_deadline.time().hour, 17) self.assertEquals(term.grade_submission_deadline.time().minute, 0) + self.assertEquals(term.get_eod_grade_submission(), + datetime(2013, 3, 27, 0, 0, 0)) + + self.assertEquals(term.last_final_exam_date.year, 2013) + self.assertEquals(term.last_final_exam_date.month, 3) + self.assertEquals(term.last_final_exam_date.day, 22) + self.assertEquals(term.get_eod_last_final_exam(), + datetime(2013, 3, 23, 0, 0, 0)) + + self.assertEquals(term.get_eod_last_instruction(), + datetime(2013, 3, 16, 0, 0, 0)) + + self.assertFalse(term.is_summer_quarter()) + self.assertEquals(term.aterm_last_date, None) + self.assertEquals(term.bterm_first_date, None) + self.assertEquals(term.aterm_grading_period_open, None) self.assertEquals(len(term.time_schedule_construction), 3) @@ -124,18 +203,20 @@ def test_previous_quarter(self): if tsc.campus == 'seattle': self.assertEquals(tsc.is_on, False) - self.assertEquals(term.is_grading_period_open(), False, "Grading period is not open") - self.assertEquals(term.is_grading_period_past(), True, "Grading period is past") + self.assertEquals(term.is_grading_period_open(), False, + "Grading period is not open") + self.assertEquals(term.is_grading_period_past(), True, + "Grading period is past") self.assertEquals(term.term_label(), "2013,winter", "Term label") #Expected values will have to change when the json files are updated def test_next_quarter(self): with self.settings( - RESTCLIENTS_SWS_DAO_CLASS='restclients.dao_implementation.sws.File', - RESTCLIENTS_PWS_DAO_CLASS='restclients.dao_implementation.pws.File'): + RESTCLIENTS_SWS_DAO_CLASS=SWSF, + RESTCLIENTS_PWS_DAO_CLASS=PWSF): term = get_next_term() - + self.assertTrue(term.is_summer_quarter()) expected_quarter = "summer" expected_year = 2013 @@ -147,21 +228,35 @@ def test_next_quarter(self): "Return %s for the next quarter" % expected_quarter) + self.assertEquals(term.census_day.year, 2013) + self.assertEquals(term.census_day.month, 7) + self.assertEquals(term.census_day.day, 5) + self.assertEquals(term.get_eod_census_day(), + datetime(2013, 7, 6, 0, 0, 0)) + self.assertEquals(term.last_day_add.year, 2013) self.assertEquals(term.last_day_add.month, 7) self.assertEquals(term.last_day_add.day, 14) + self.assertEquals(term.get_eod_last_day_add(), + datetime(2013, 7, 15, 0, 0, 0)) self.assertEquals(term.last_day_drop.year, 2013) self.assertEquals(term.last_day_drop.month, 8) self.assertEquals(term.last_day_drop.day, 11) + self.assertEquals(term.get_eod_last_day_drop(), + datetime(2013, 8, 12, 0, 0, 0)) self.assertEquals(term.first_day_quarter.year, 2013) self.assertEquals(term.first_day_quarter.month, 6) self.assertEquals(term.first_day_quarter.day, 24) + self.assertEquals(term.get_bod_first_day(), + datetime(2013, 6, 24, 0, 0, 0)) self.assertEquals(term.last_day_instruction.year, 2013) self.assertEquals(term.last_day_instruction.month, 8) self.assertEquals(term.last_day_instruction.day, 23) + self.assertEquals(term.get_eod_last_instruction(), + datetime(2013, 8, 24, 0, 0, 0)) self.assertEquals(term.aterm_last_date.year, 2013) self.assertEquals(term.aterm_last_date.month, 7) @@ -170,24 +265,34 @@ def test_next_quarter(self): self.assertEquals(term.bterm_first_date.year, 2013) self.assertEquals(term.bterm_first_date.month, 7) self.assertEquals(term.bterm_first_date.day, 25) + self.assertEquals(term.get_eod_summer_aterm(), + datetime(2013, 7, 25, 0, 0, 0)) self.assertEquals(term.aterm_last_day_add.year, 2013) self.assertEquals(term.aterm_last_day_add.month, 7) self.assertEquals(term.aterm_last_day_add.day, 14) + self.assertEquals(term.get_eod_aterm_last_day_add(), + datetime(2013, 7, 15, 0, 0, 0)) self.assertEquals(term.bterm_last_day_add.year, 2013) self.assertEquals(term.bterm_last_day_add.month, 7) self.assertEquals(term.bterm_last_day_add.day, 31) + self.assertEquals(term.get_eod_bterm_last_day_add(), + datetime(2013, 8, 1, 0, 0, 0)) self.assertEquals(term.last_final_exam_date.year, 2013) self.assertEquals(term.last_final_exam_date.month, 8) self.assertEquals(term.last_final_exam_date.day, 23) + self.assertEquals(term.get_eod_last_final_exam(), + datetime(2013, 8, 24, 0, 0, 0)) self.assertEquals(term.grade_submission_deadline.date().year, 2013) self.assertEquals(term.grade_submission_deadline.date().month, 8) self.assertEquals(term.grade_submission_deadline.date().day, 27) self.assertEquals(term.grade_submission_deadline.time().hour, 17) self.assertEquals(term.grade_submission_deadline.time().minute, 0) + self.assertEquals(term.get_eod_grade_submission(), + datetime(2013, 8, 28, 0, 0, 0)) self.assertEquals(term.aterm_grading_period_open.date().year, 2013) self.assertEquals(term.aterm_grading_period_open.date().month, 7) @@ -201,14 +306,16 @@ def test_next_quarter(self): if tsc.campus == 'bothell': self.assertEquals(tsc.is_on, True) - self.assertEquals(term.is_grading_period_open(), False, "Grading period is not open") - self.assertEquals(term.is_grading_period_past(), True, "Grading period is past") + self.assertEquals(term.is_grading_period_open(), False, + "Grading period is not open") + self.assertEquals(term.is_grading_period_past(), True, + "Grading period is past") self.assertEquals(term.term_label(), "2013,summer", "Term label") - def test_quarter_before(self): + def test_term_before(self): with self.settings( - RESTCLIENTS_SWS_DAO_CLASS='restclients.dao_implementation.sws.File', - RESTCLIENTS_PWS_DAO_CLASS='restclients.dao_implementation.pws.File'): + RESTCLIENTS_SWS_DAO_CLASS=SWSF, + RESTCLIENTS_PWS_DAO_CLASS=PWSF): starting = get_next_term() self.assertEquals(starting.year, 2013) @@ -226,19 +333,24 @@ def test_quarter_before(self): self.assertEquals(next3.year, 2012) self.assertEquals(next3.quarter, 'autumn') - def test_quarter_after(self): + def test_terms_after(self): with self.settings( - RESTCLIENTS_SWS_DAO_CLASS='restclients.dao_implementation.sws.File', - RESTCLIENTS_PWS_DAO_CLASS='restclients.dao_implementation.pws.File'): + RESTCLIENTS_SWS_DAO_CLASS=SWSF, + RESTCLIENTS_PWS_DAO_CLASS=PWSF): starting = get_next_term() self.assertEquals(starting.year, 2013) self.assertEquals(starting.quarter, 'summer') + next_autumn = get_next_autumn_term(starting) next1 = get_term_after(starting) self.assertEquals(next1.year, 2013) self.assertEquals(next1.quarter, 'autumn') + self.assertEquals(next_autumn, next1) + next_non_summer_term = get_next_non_summer_term(get_current_term()) + self.assertEquals(next_autumn, next_non_summer_term) + next2 = get_term_after(next1) self.assertEquals(next2.year, 2014) self.assertEquals(next2.quarter, 'winter') @@ -246,8 +358,8 @@ def test_quarter_after(self): def test_specific_quarters(self): #testing bad data - get_by_year_and_quarter with self.settings( - RESTCLIENTS_SWS_DAO_CLASS='restclients.dao_implementation.sws.File', - RESTCLIENTS_PWS_DAO_CLASS='restclients.dao_implementation.pws.File'): + RESTCLIENTS_SWS_DAO_CLASS=SWSF, + RESTCLIENTS_PWS_DAO_CLASS=PWSF): self.assertRaises(DataFailureException, get_term_by_year_and_quarter, @@ -277,13 +389,22 @@ def test_specific_quarters(self): self.assertEquals(get_term_by_year_and_quarter(2012, 'autumn'), get_term_by_year_and_quarter(2012, 'autumn')) - self.assertNotEquals(get_term_by_year_and_quarter(2012, 'autumn'), + self.assertEquals(get_specific_term(2012, 'autumn'), + get_term_by_year_and_quarter(2012, 'autumn')) + + self.assertEquals(get_specific_term(2013, 'spring'), + get_current_term()) + + self.assertEquals(get_term_by_year_and_quarter(2013, 'spring'), + get_current_term()) + + self.assertNotEquals(get_specific_term(2012, 'autumn'), get_term_by_year_and_quarter(2013, 'winter')) def test_week_of_term(self): with self.settings( - RESTCLIENTS_SWS_DAO_CLASS='restclients.dao_implementation.sws.File', - RESTCLIENTS_PWS_DAO_CLASS='restclients.dao_implementation.pws.File'): + RESTCLIENTS_SWS_DAO_CLASS=SWSF, + RESTCLIENTS_PWS_DAO_CLASS=PWSF): now = datetime.now() term = get_current_term() @@ -292,80 +413,95 @@ def test_week_of_term(self): term.first_day_quarter = now.date() # First day of class - self.assertEquals(term.get_week_of_term(), 1, "Term starting now in first week") - self.assertEquals(term.get_week_of_term_for_date(now), 1, "Term starting now in first week, by date") + self.assertEquals(term.get_week_of_term(), 1, + "Term starting now in first week") + self.assertEquals(term.get_week_of_term_for_date(now), 1, + "Term starting now in first week, by date") # Middle of the term start_date = now + timedelta(days=-6) term.first_day_quarter = start_date.date() self.assertEquals(term.get_week_of_term(), 1, "6 days in") - self.assertEquals(term.get_week_of_term_for_date(now), 1, "6 days in") + self.assertEquals(term.get_week_of_term_for_date(now), + 1, "6 days in") start_date = now + timedelta(days=-7) term.first_day_quarter = start_date.date() self.assertEquals(term.get_week_of_term(), 2, "7 days in") - self.assertEquals(term.get_week_of_term_for_date(now), 2, "7 days in") + self.assertEquals(term.get_week_of_term_for_date(now), + 2, "7 days in") start_date = now + timedelta(days=-8) term.first_day_quarter = start_date.date() self.assertEquals(term.get_week_of_term(), 2, "8 days in") - self.assertEquals(term.get_week_of_term_for_date(now), 2, "8 days in") + self.assertEquals(term.get_week_of_term_for_date(now), + 2, "8 days in") start_date = now + timedelta(days=-13) term.first_day_quarter = start_date.date() self.assertEquals(term.get_week_of_term(), 2, "13 days in") - self.assertEquals(term.get_week_of_term_for_date(now), 2, "13 days in") + self.assertEquals(term.get_week_of_term_for_date(now), 2, + "13 days in") start_date = now + timedelta(days=-14) term.first_day_quarter = start_date.date() self.assertEquals(term.get_week_of_term(), 3, "14 days in") - self.assertEquals(term.get_week_of_term_for_date(now), 3, "14 days in") + self.assertEquals(term.get_week_of_term_for_date(now), 3, + "14 days in") # Before the term start_date = now + timedelta(days=1) term.first_day_quarter = start_date.date() self.assertEquals(term.get_week_of_term(), -1, "-1 days") - self.assertEquals(term.get_week_of_term_for_date(now), -1, "-1 days") + self.assertEquals(term.get_week_of_term_for_date(now), -1, + "-1 days") start_date = now + timedelta(days=7) term.first_day_quarter = start_date.date() self.assertEquals(term.get_week_of_term(), -1, "-7 days") - self.assertEquals(term.get_week_of_term_for_date(now), -1, "-7 days") + self.assertEquals(term.get_week_of_term_for_date(now), -1, + "-7 days") start_date = now + timedelta(days=8) term.first_day_quarter = start_date.date() self.assertEquals(term.get_week_of_term(), -2, "-8 days") - self.assertEquals(term.get_week_of_term_for_date(now), -2, "-8 days") + self.assertEquals(term.get_week_of_term_for_date(now), -2, + "-8 days") start_date = now + timedelta(days=9) term.first_day_quarter = start_date.date() self.assertEquals(term.get_week_of_term(), -2, "-9 days") - self.assertEquals(term.get_week_of_term_for_date(now), -2, "-9 days") + self.assertEquals(term.get_week_of_term_for_date(now), -2, + "-9 days") start_date = now + timedelta(days=14) term.first_day_quarter = start_date.date() self.assertEquals(term.get_week_of_term(), -2, "-14 days") - self.assertEquals(term.get_week_of_term_for_date(now), -2, "-14 days") + self.assertEquals(term.get_week_of_term_for_date(now), -2, + "-14 days") start_date = now + timedelta(days=15) term.first_day_quarter = start_date.date() self.assertEquals(term.get_week_of_term(), -3, "-15 days") - self.assertEquals(term.get_week_of_term_for_date(now), -3, "-15 days") + self.assertEquals(term.get_week_of_term_for_date(now), -3, + "-15 days") def test_canvas_sis_id(self): with self.settings( - RESTCLIENTS_SWS_DAO_CLASS='restclients.dao_implementation.sws.File', - RESTCLIENTS_PWS_DAO_CLASS='restclients.dao_implementation.pws.File'): + RESTCLIENTS_SWS_DAO_CLASS=SWSF, + RESTCLIENTS_PWS_DAO_CLASS=PWSF): term = get_term_by_year_and_quarter(2013, 'spring') - self.assertEquals(term.canvas_sis_id(), '2013-spring', 'Canvas SIS ID') + self.assertEquals(term.canvas_sis_id(), '2013-spring', + 'Canvas SIS ID') term = get_previous_term() - self.assertEquals(term.canvas_sis_id(), '2013-winter', 'Canvas SIS ID') + self.assertEquals(term.canvas_sis_id(), '2013-winter', + 'Canvas SIS ID') def test_by_date(self): with self.settings( - RESTCLIENTS_SWS_DAO_CLASS='restclients.dao_implementation.sws.File'): + RESTCLIENTS_SWS_DAO_CLASS=SWSF): date = datetime.strptime("2013-01-10", "%Y-%m-%d").date() term = get_term_by_date(date) diff --git a/restclients/test/trumba/accounts.py b/restclients/test/trumba/accounts.py index 3234207c..7007928c 100644 --- a/restclients/test/trumba/accounts.py +++ b/restclients/test/trumba/accounts.py @@ -1,139 +1,218 @@ from django.test import TestCase from django.conf import settings from restclients.exceptions import DataFailureException -import restclients.trumba.account as Account -from restclients.trumba.exceptions import AccountNameEmpty, AccountNotExist, AccountUsedByDiffUser, CalendarNotExist, CalendarOwnByDiffAccount, InvalidEmail, InvalidPermissionLevel, FailedToClosePublisher, NoAllowedPermission, ErrorCreatingEditor, NoDataReturned, UnknownError, UnexpectedError +from restclients.trumba.account import _make_add_account_url,\ + add_editor, _make_del_account_url, delete_editor,\ + _make_set_permissions_url, set_bot_permissions, set_sea_permissions,\ + set_tac_permissions, set_sea_editor, set_sea_showon, set_sea_none,\ + set_bot_editor, set_bot_showon, set_bot_none,\ + set_tac_editor, set_tac_showon, set_tac_none,\ + _is_editor_added, _is_editor_deleted, _is_permission_set,\ + _check_err +from restclients.trumba.exceptions import AccountNameEmpty, AccountNotExist,\ + AccountUsedByDiffUser, CalendarNotExist, CalendarOwnByDiffAccount,\ + InvalidEmail, InvalidPermissionLevel, FailedToClosePublisher,\ + NoAllowedPermission, ErrorCreatingEditor, NoDataReturned, UnknownError,\ + UnexpectedError + + +ADD_ACC_URL = "/service/accounts.asmx/CreateEditor?" +DEL_ACC_URL = "/service/accounts.asmx/CloseEditor?" +SET_PERM_URL = "/service/calendars.asmx/SetPermissions?" -class TrumbaTestAccounts(TestCase): - def test_make_add_editor_url(self): - with self.settings( - RESTCLIENTS_TRUMBA_SEA_DAO_CLASS='restclients.dao_implementation.trumba.FileSea' - ): - self.assertEqual(Account._make_add_editor_url('Margaret Murray', 'murray4'), - "/service/accounts.asmx/CreateEditor?Name=Margaret%20Murray&Email=murray4@washington.edu&Password=") +class TrumbaTestAccounts(TestCase): + def test_make_add_account_url(self): + with self.settings(RESTCLIENTS_TRUMBA_SEA_DAO_CLASS=\ + 'restclients.dao_implementation.trumba.FileSea' + ): + self.assertEqual(_make_add_account_url('Margaret Murray', + 'murray4'), + "%sName=%s&Email=%s@washington.edu&Password=" % ( + ADD_ACC_URL, "Margaret%20Murray", 'murray4')) def test_add_editor_error_cases(self): - with self.settings( - RESTCLIENTS_TRUMBA_SEA_DAO_CLASS='restclients.dao_implementation.trumba.FileSea' - ): + with self.settings(RESTCLIENTS_TRUMBA_SEA_DAO_CLASS=\ + 'restclients.dao_implementation.trumba.FileSea' + ): self.assertRaises(AccountNameEmpty, - Account.add_editor,'','') + add_editor,'','') self.assertRaises(InvalidEmail, - Account.add_editor,'010','') + add_editor,'010','') self.assertRaises(AccountUsedByDiffUser, - Account.add_editor,'011','test10') + add_editor,'011','test10') def test_add_editor_normal_cases(self): - with self.settings( - RESTCLIENTS_TRUMBA_SEA_DAO_CLASS='restclients.dao_implementation.trumba.FileSea' - ): - self.assertTrue(Account.add_editor('008','test8')) + with self.settings(RESTCLIENTS_TRUMBA_SEA_DAO_CLASS=\ + 'restclients.dao_implementation.trumba.FileSea' + ): + self.assertTrue(add_editor('008','test8')) + + self.assertTrue(add_editor('010','test10')) + - self.assertTrue(Account.add_editor('010','test10')) + def test_make_del_account_url(self): + with self.settings(RESTCLIENTS_TRUMBA_SEA_DAO_CLASS=\ + 'restclients.dao_implementation.trumba.FileSea' + ): + self.assertEqual(_make_del_account_url('murray4'), + "%sEmail=%s@washington.edu" % (DEL_ACC_URL, + 'murray4')) + def test_delete_editor_normal_cases(self): + with self.settings(RESTCLIENTS_TRUMBA_SEA_DAO_CLASS=\ + 'restclients.dao_implementation.trumba.FileSea' + ): + self.assertTrue(delete_editor('test10')) def test_delete_editor_error_cases(self): - with self.settings( - RESTCLIENTS_TRUMBA_SEA_DAO_CLASS='restclients.dao_implementation.trumba.FileSea' - ): + with self.settings(RESTCLIENTS_TRUMBA_SEA_DAO_CLASS=\ + 'restclients.dao_implementation.trumba.FileSea' + ): self.assertRaises(AccountNotExist, - Account.delete_editor,'') + delete_editor,'') self.assertRaises(AccountNotExist, - Account.delete_editor,'test') + delete_editor,'test') - def test_delete_editor_normal_cases(self): - with self.settings( - RESTCLIENTS_TRUMBA_SEA_DAO_CLASS='restclients.dao_implementation.trumba.FileSea' - ): - self.assertTrue(Account.delete_editor('test10')) + def test_make_set_permissions_url(self): + with self.settings(RESTCLIENTS_TRUMBA_SEA_DAO_CLASS=\ + 'restclients.dao_implementation.trumba.FileSea' + ): + self.assertEqual( + _make_set_permissions_url(1, 'test10', 'EDIT'), + "%sCalendarID=%s&Email=%s@washington.edu&Level=%s" % ( + SET_PERM_URL, 1, 'test10', 'EDIT')) def test_set_sea_permissions_error_cases(self): - with self.settings( - RESTCLIENTS_TRUMBA_SEA_DAO_CLASS='restclients.dao_implementation.trumba.FileSea' - ): + with self.settings(RESTCLIENTS_TRUMBA_SEA_DAO_CLASS=\ + 'restclients.dao_implementation.trumba.FileSea' + ): self.assertRaises(AccountNotExist, - Account.set_sea_permissions, 1, '', 'EDIT') + set_sea_permissions, 1, '', 'EDIT') self.assertRaises(NoAllowedPermission, - Account.set_sea_permissions, 1, 'test10', 'PUBLISH') + set_sea_permissions, 1, 'test10', 'PUBLISH') def test_set_sea_permissions_normal_cases(self): - with self.settings( - RESTCLIENTS_TRUMBA_SEA_DAO_CLASS='restclients.dao_implementation.trumba.FileSea' - ): - self.assertTrue(Account.set_sea_permissions(1, 'test10', 'SHOWON')) - - self.assertTrue(Account.set_sea_permissions(1, 'test10', 'EDIT')) + with self.settings(RESTCLIENTS_TRUMBA_SEA_DAO_CLASS=\ + 'restclients.dao_implementation.trumba.FileSea' + ): + self.assertTrue(set_sea_permissions(1, 'test10', 'SHOWON')) + self.assertTrue(set_sea_permissions(1, 'test10', 'EDIT')) + self.assertTrue(set_sea_editor(1, 'test10')) + self.assertTrue(set_sea_showon(1, 'test10')) + self.assertTrue(set_sea_none(1, 'test10')) def test_is_permission_set(self): - self.assertTrue(Account._is_permission_set(1003)) - self.assertFalse(Account._is_permission_set(-1003)) + self.assertTrue(_is_permission_set(1003)) + self.assertFalse(_is_permission_set(-1003)) def test_is_editor_added(self): - self.assertTrue(Account._is_editor_added(1001)) - self.assertTrue(Account._is_editor_added(3012)) - self.assertFalse(Account._is_editor_added(-1001)) + self.assertTrue(_is_editor_added(1001)) + self.assertTrue(_is_editor_added(3012)) + self.assertFalse(_is_editor_added(-1001)) def test_is_editor_deleted(self): - self.assertTrue(Account._is_editor_deleted(1002)) - self.assertFalse(Account._is_editor_deleted(-1002)) + self.assertTrue(_is_editor_deleted(1002)) + self.assertFalse(_is_editor_deleted(-1002)) def test_check_err(self): self.assertRaises(CalendarNotExist, - Account._check_err, + _check_err, 3006, 'test if CalendarNotExist is thrown') self.assertRaises(CalendarOwnByDiffAccount, - Account._check_err, + _check_err, 3007, 'test if CalendarOwnByDiffAccount is thrown') self.assertRaises(AccountNotExist, - Account._check_err, + _check_err, 3008, 'test if AccountNotExist is thrown') self.assertRaises(AccountUsedByDiffUser, - Account._check_err, + _check_err, 3009, 'test if AccountUsedByDiffUser is thrown') self.assertRaises(AccountUsedByDiffUser, - Account._check_err, + _check_err, 3013, 'test if AccountUsedByDiffUser is thrown') self.assertRaises(InvalidPermissionLevel, - Account._check_err, + _check_err, 3010, 'test if InvalidPermissionLevel is thrown') self.assertRaises(FailedToClosePublisher, - Account._check_err, + _check_err, 3011, 'test if FailedToClosePublisher is thrown') self.assertRaises(InvalidEmail, - Account._check_err, + _check_err, 3014, 'test if InvalidEmail is thrown') self.assertRaises(NoAllowedPermission, - Account._check_err, + _check_err, 3015, 'test if NoAllowedPermission is thrown') self.assertRaises(AccountNameEmpty, - Account._check_err, + _check_err, 3016, 'test if AccountNameEmpty is thrown') self.assertRaises(ErrorCreatingEditor, - Account._check_err, + _check_err, 3017, 'test if ErrorCreatingEditor is thrown') self.assertRaises(ErrorCreatingEditor, - Account._check_err, + _check_err, 3018, 'test if ErrorCreatingEditor is thrown') self.assertRaises(UnexpectedError, - Account._check_err, + _check_err, 3020, 'test if UnexpectedError is thrown') + + def test_set_bot_permissions_error_cases(self): + with self.settings(RESTCLIENTS_TRUMBA_BOT_DAO_CLASS=\ + 'restclients.dao_implementation.trumba.FileBot' + ): + self.assertRaises(AccountNotExist, + set_bot_permissions, 2, '', 'EDIT') + + self.assertRaises(NoAllowedPermission, + set_bot_permissions, 2, 'test10', 'PUBLISH') + + def test_set_bot_permissions_normal_cases(self): + with self.settings(RESTCLIENTS_TRUMBA_BOT_DAO_CLASS=\ + 'restclients.dao_implementation.trumba.FileBot' + ): + self.assertTrue(set_bot_permissions(2, 'test10', 'SHOWON')) + self.assertTrue(set_bot_permissions(2, 'test10', 'EDIT')) + self.assertTrue(set_bot_editor(2, 'test10')) + self.assertTrue(set_bot_showon(2, 'test10')) + self.assertTrue(set_bot_none(2, 'test10')) + + + def test_set_tac_permissions_error_cases(self): + with self.settings(RESTCLIENTS_TRUMBA_TAC_DAO_CLASS=\ + 'restclients.dao_implementation.trumba.FileTac' + ): + self.assertRaises(AccountNotExist, + set_tac_permissions, 3, '', 'EDIT') + + self.assertRaises(NoAllowedPermission, + set_tac_permissions, 3, 'test10', 'PUBLISH') + + def test_set_tac_permissions_normal_cases(self): + with self.settings(RESTCLIENTS_TRUMBA_TAC_DAO_CLASS=\ + 'restclients.dao_implementation.trumba.FileTac' + ): + self.assertTrue(set_tac_permissions(3, 'test10', 'SHOWON')) + self.assertTrue(set_tac_permissions(3, 'test10', 'EDIT')) + self.assertTrue(set_tac_editor(3, 'test10')) + self.assertTrue(set_tac_showon(3, 'test10')) + self.assertTrue(set_tac_none(3, 'test10')) + diff --git a/restclients/test/trumba/calendar.py b/restclients/test/trumba/calendar.py index 90549fff..4d9c6d67 100644 --- a/restclients/test/trumba/calendar.py +++ b/restclients/test/trumba/calendar.py @@ -3,8 +3,11 @@ from restclients.trumba import get_calendar_by_name +CAL_DAO = 'restclients.dao_implementation.trumba.CalendarFile' + + class TestCalendarParse(TestCase): def test_ical_parsing(self): - with self.settings(RESTCLIENTS_CALENDAR_DAO_CLASS='restclients.dao_implementation.trumba.CalendarFile'): + with self.settings(RESTCLIENTS_CALENDAR_DAO_CLASS=CAL_DAO): calendar = get_calendar_by_name('sea_acad-comm') self.assertEquals(len(calendar.walk('vevent')), 4) diff --git a/restclients/test/trumba/calendars.py b/restclients/test/trumba/calendars.py index b3c5696d..f705bb9b 100644 --- a/restclients/test/trumba/calendars.py +++ b/restclients/test/trumba/calendars.py @@ -1,22 +1,26 @@ from django.test import TestCase from django.conf import settings from restclients.exceptions import DataFailureException +from restclients.models.trumba import is_view_permission,\ + is_showon_permission, is_edit_permission, is_publish_permission,\ + is_higher_permission import restclients.trumba.calendar as Calendar from restclients.trumba.exceptions import TrumbaException, CalendarNotExist, CalendarOwnByDiffAccount, NoDataReturned, UnknownError, UnexpectedError + class TrumbaTestCalendars(TestCase): def test_get_bot_calendars_normal_cases(self): - with self.settings( - RESTCLIENTS_TRUMBA_BOT_DAO_CLASS='restclients.dao_implementation.trumba.FileBot' - ): + with self.settings(RESTCLIENTS_TRUMBA_BOT_DAO_CLASS=\ + 'restclients.dao_implementation.trumba.FileBot' + ): result = Calendar.get_bot_calendars() self.assertTrue(result is not None and len(result) == 4) def test_get_sea_calendars_normal_cases(self): - with self.settings( - RESTCLIENTS_TRUMBA_SEA_DAO_CLASS='restclients.dao_implementation.trumba.FileSea' - ): + with self.settings(RESTCLIENTS_TRUMBA_SEA_DAO_CLASS=\ + 'restclients.dao_implementation.trumba.FileSea' + ): result = Calendar.get_sea_calendars() self.assertIsNotNone(result) self.assertTrue(len(result) == 10) @@ -33,67 +37,99 @@ def test_get_sea_calendars_normal_cases(self): self.assertEqual(trumba_cal.calendarid, 11321) self.assertEqual(trumba_cal.campus, 'sea') self.assertEqual(trumba_cal.name, - 'Seattle calendar >> Seattle child calendar3 >> Seattle child-sub-calendar32 >> Seattle child-sub-sub-calendar321') + "%s%s%s%s" % ('Seattle calendar >> Seattle', + ' child calendar3 >> Seattle', + ' child-sub-calendar32 >> Seattle', + ' child-sub-sub-calendar321')) + sorted_cals = sorted(result.values()) + self.assertEqual(sorted_cals[0].name, "Seattle calendar") + self.assertEqual(sorted_cals[1].name, + "Seattle calendar >> Seattle child calendar1") + self.assertEqual(sorted_cals[4].name, + "Seattle calendar >> Seattle child calendar2") + self.assertEqual(sorted_cals[5].name, + "Seattle calendar >> Seattle child calendar3") + self.assertEqual(sorted_cals[9].name, + "%s%s%s%s" % ("Seattle calendar >> Seattle", + " child calendar3 >> Seattle", + " child-sub-calendar32 >> Seattle", + " child-sub-sub-calendar322")) self.assertTrue(trumba_cal.is_sea()) self.assertFalse(trumba_cal.is_bot()) self.assertFalse(trumba_cal.is_tac()) def test_get_tac_calendars_normal_cases(self): - with self.settings( - RESTCLIENTS_TRUMBA_TAC_DAO_CLASS='restclients.dao_implementation.trumba.FileTac' - ): + with self.settings(RESTCLIENTS_TRUMBA_TAC_DAO_CLASS=\ + 'restclients.dao_implementation.trumba.FileTac' + ): self.assertIsNotNone(Calendar.get_tac_calendars()) self.assertTrue(len(Calendar.get_tac_calendars()) == 1) def test_get_sea_permissions_normal_cases(self): - with self.settings( - RESTCLIENTS_TRUMBA_SEA_DAO_CLASS='restclients.dao_implementation.trumba.FileSea' - ): + with self.settings(RESTCLIENTS_TRUMBA_SEA_DAO_CLASS=\ + 'restclients.dao_implementation.trumba.FileSea' + ): result = Calendar.get_sea_permissions(1) self.assertIsNotNone(result) self.assertTrue(len(result) == 3) + perm = result[0] self.assertEqual(perm.calendarid, 1) self.assertEqual(perm.campus, 'sea') self.assertEqual(perm.name, 'Dummy publisher') self.assertEqual(perm.uwnetid, 'dummyp') self.assertEqual(perm.level, 'PUBLISH') + self.assertTrue(perm.is_publish()) self.assertTrue(perm.is_edit()) self.assertFalse(perm.is_showon()) self.assertTrue(perm.is_sea()) self.assertFalse(perm.is_bot()) self.assertFalse(perm.is_tac()) - - perm = result[1] - self.assertEqual(perm.calendarid, 1) - self.assertEqual(perm.campus, 'sea') - self.assertEqual(perm.name, 'Dummy editor') - self.assertEqual(perm.uwnetid, 'dummye') - self.assertEqual(perm.level, 'EDIT') - self.assertTrue(perm.is_edit()) - self.assertFalse(perm.is_showon()) - self.assertTrue(perm.is_sea()) - self.assertFalse(perm.is_bot()) - self.assertFalse(perm.is_tac()) - - perm = result[2] - self.assertEqual(perm.calendarid, 1) - self.assertEqual(perm.campus, 'sea') - self.assertEqual(perm.name, 'Dummy showon') - self.assertEqual(perm.uwnetid, 'dummys') - self.assertEqual(perm.level, 'SHOWON') - self.assertFalse(perm.is_edit()) - self.assertTrue(perm.is_showon()) - self.assertTrue(perm.is_sea()) - self.assertFalse(perm.is_bot()) - self.assertFalse(perm.is_tac()) + self.assertTrue(is_higher_permission(perm.level, 'EDIT')) + self.assertTrue(perm.is_gt_level('EDIT')) + + perm2 = result[1] + self.assertEqual(perm2.calendarid, 1) + self.assertEqual(perm2.campus, 'sea') + self.assertEqual(perm2.name, 'Dummy editor') + self.assertEqual(perm2.uwnetid, 'dummye') + self.assertEqual(perm2.level, 'EDIT') + self.assertTrue(perm2.is_edit()) + self.assertFalse(perm2.is_showon()) + self.assertTrue(perm2.is_sea()) + self.assertFalse(perm2.is_bot()) + self.assertFalse(perm2.is_tac()) + self.assertTrue(is_higher_permission(perm2.level, 'SHOWON')) + self.assertFalse(perm2.is_gt_level('EDIT')) + + perm3 = result[2] + self.assertEqual(perm3.calendarid, 1) + self.assertEqual(perm3.campus, 'sea') + self.assertEqual(perm3.name, 'Dummy showon') + self.assertEqual(perm3.uwnetid, 'dummys') + self.assertEqual(perm3.level, 'SHOWON') + self.assertFalse(perm3.is_edit()) + self.assertTrue(perm3.is_showon()) + self.assertTrue(perm3.is_sea()) + self.assertFalse(perm3.is_bot()) + self.assertFalse(perm3.is_tac()) + self.assertFalse(perm3.is_gt_level('SHOWON')) + + unordered_list = [] + unordered_list.append(perm3) + unordered_list.append(perm2) + unordered_list.append(perm) + sorted_list = sorted(unordered_list) + self.assertEqual(sorted_list[0].uwnetid, 'dummyp') + self.assertEqual(sorted_list[1].uwnetid, 'dummye') + self.assertEqual(sorted_list[2].uwnetid, 'dummys') def test_get_sea_permissions_error_cases(self): - with self.settings( - RESTCLIENTS_TRUMBA_SEA_DAO_CLASS='restclients.dao_implementation.trumba.FileSea' - ): + with self.settings(RESTCLIENTS_TRUMBA_SEA_DAO_CLASS=\ + 'restclients.dao_implementation.trumba.FileSea' + ): self.assertRaises(CalendarNotExist, Calendar.get_sea_permissions, 0) @@ -101,7 +137,8 @@ def test_get_sea_permissions_error_cases(self): Calendar.get_sea_permissions, 2) def test_create_body(self): - self.assertEqual(Calendar._create_get_perm_body(1), '{"CalendarID": 1}') + self.assertEqual(Calendar._create_get_perm_body(1), + '{"CalendarID": 1}') def test_is_valid_calendarid(self): self.assertTrue(Calendar._is_valid_calendarid(1)) @@ -118,7 +155,8 @@ def test_is_valid_email(self): self.assertFalse(Calendar._is_valid_email('')) def test_extract_uwnetid(self): - self.assertEqual(Calendar._extract_uwnetid('test@washington.edu'), 'test') + self.assertEqual(Calendar._extract_uwnetid('test@washington.edu'), + 'test') self.assertEqual(Calendar._extract_uwnetid('test'), 'test') self.assertEqual(Calendar._extract_uwnetid('@washington.edu'), '') self.assertEqual(Calendar._extract_uwnetid('bad@uw.edu'), 'bad@uw.edu') diff --git a/restclients/test/util/date_formator.py b/restclients/test/util/date_formator.py index 8c763096..7ae52121 100644 --- a/restclients/test/util/date_formator.py +++ b/restclients/test/util/date_formator.py @@ -10,7 +10,7 @@ from restclients.util.date_formator import past_datetime_str -class formatorTest(TestCase): +class FormatorTest(TestCase): def test_full_month_date_str(self): diff --git a/restclients/test/util/datetime_convertor.py b/restclients/test/util/datetime_convertor.py new file mode 100644 index 00000000..222304a2 --- /dev/null +++ b/restclients/test/util/datetime_convertor.py @@ -0,0 +1,26 @@ +from django.test import TestCase +from datetime import date, datetime +from restclients.util.datetime_convertor import convert_to_begin_of_day,\ + convert_to_end_of_day + + +class DatetimeConvertorTest(TestCase): + + + def test_convert_to_begin_of_day(self): + + self.assertEquals(convert_to_begin_of_day(date(2013, 4, 9)), + datetime(2013, 4, 9, 0, 0, 0)) + + self.assertEquals( + convert_to_begin_of_day(datetime(2013, 4, 9, 10, 10, 10)), + datetime(2013, 4, 9, 0, 0, 0)) + + + def test_convert_to_end_of_day(self): + self.assertEquals(convert_to_end_of_day(date(2012, 2, 28)), + datetime(2012, 2, 29, 0, 0, 0)) + + self.assertEquals( + convert_to_end_of_day(datetime(2012, 2, 28, 10, 10, 10)), + datetime(2012, 2, 29, 0, 0, 0)) diff --git a/restclients/test/util/retry.py b/restclients/test/util/retry.py new file mode 100644 index 00000000..715681df --- /dev/null +++ b/restclients/test/util/retry.py @@ -0,0 +1,177 @@ +from django.test import TestCase +from restclients.exceptions import DataFailureException +from restclients.util.retry import retry + + +class RetryableError(Exception): + pass + + +class AnotherRetryableError(Exception): + pass + + +class UnexpectedError(Exception): + pass + + +class RetryTest(TestCase): + + def test_bad_args(self): + self.assertRaises(TypeError, retry, RetryableError, tries=None) + self.assertRaises(ValueError, retry, RetryableError, delay=None) + self.assertRaises(ValueError, retry, RetryableError, backoff=None) + self.assertRaises(ValueError, retry, RetryableError, tries=-1) + self.assertRaises(ValueError, retry, RetryableError, delay=0) + self.assertRaises(ValueError, retry, RetryableError, backoff=0) + + def test_no_retry(self): + self.counter = 0 + + @retry(RetryableError, tries=4, delay=0.1) + def succeeds(): + self.counter += 1 + return 'success' + + r = succeeds() + + self.assertEqual(r, 'success') + self.assertEqual(self.counter, 1) + + def test_retry_once(self): + self.counter = 0 + + @retry(RetryableError, tries=4, delay=0.1) + def fails_once(): + self.counter += 1 + if self.counter < 2: + raise RetryableError('failed') + else: + return 'success' + + r = fails_once() + self.assertEqual(r, 'success') + self.assertEqual(self.counter, 2) + + def test_limit_reached(self): + self.counter = 0 + + @retry(RetryableError, tries=4, delay=0.1) + def always_fails(): + self.counter += 1 + raise RetryableError('failed') + + self.assertRaises(RetryableError, always_fails) + self.assertEqual(self.counter, 4) + + def test_multiple_exception_types(self): + self.counter = 0 + + @retry((RetryableError, AnotherRetryableError), tries=4, delay=0.1) + def raise_multiple_exceptions(): + self.counter += 1 + if self.counter == 1: + raise RetryableError('a retryable error') + elif self.counter == 2: + raise AnotherRetryableError('another retryable error') + else: + return 'success' + + r = raise_multiple_exceptions() + self.assertEqual(r, 'success') + self.assertEqual(self.counter, 3) + + def test_unexpected_exception_does_not_retry(self): + self.counter = 0 + + @retry(RetryableError, tries=4, delay=0.1) + def raise_unexpected_error(): + self.counter += 1 + raise UnexpectedError('unexpected error') + + self.assertRaises(UnexpectedError, raise_unexpected_error) + self.assertEqual(self.counter, 1) + + def test_dfe_with_no_status(self): + self.counter = 0 + + @retry(DataFailureException, tries=4, delay=0.1) + def not_found(): + self.counter += 1 + raise DataFailureException('/', 404, '') + + self.assertRaises(DataFailureException, not_found) + self.assertEqual(self.counter, 4) + + def test_no_retry_with_status(self): + self.counter = 0 + + @retry(DataFailureException, status_codes=[500], tries=4, delay=0.1) + def not_found(): + self.counter += 1 + raise DataFailureException('/', 404, '') + + self.assertRaises(DataFailureException, not_found) + self.assertEqual(self.counter, 1) + + def test_retry_once_with_status(self): + self.counter = 0 + + @retry(DataFailureException, status_codes=[500], tries=4, delay=0.1) + def fails_once(): + self.counter += 1 + if self.counter < 2: + raise DataFailureException('/', 500, '') + else: + return 'success' + + r = fails_once() + self.assertEqual(r, 'success') + self.assertEqual(self.counter, 2) + + def test_limit_reached_with_status(self): + self.counter = 0 + + @retry(DataFailureException, status_codes=[500], tries=4, delay=0.1) + def always_fails(): + self.counter += 1 + raise DataFailureException('/', 500, '') + + self.assertRaises(DataFailureException, always_fails) + self.assertEqual(self.counter, 4) + + def test_multiple_status_codes(self): + self.counter = 0 + + @retry(DataFailureException, status_codes=[500, 503, 504], tries=4, delay=0.1) + def raise_multiple_exceptions(): + self.counter += 1 + if self.counter == 1: + raise DataFailureException('/', 500, '') + elif self.counter == 2: + raise DataFailureException('/', 503, '') + else: + return 'success' + + r = raise_multiple_exceptions() + self.assertEqual(r, 'success') + self.assertEqual(self.counter, 3) + + def test_multiple_exceptions_and_multiple_status_codes(self): + self.counter = 0 + + @retry((DataFailureException, RetryableError), status_codes=[500, 503, 504], tries=4, delay=0.1) + def raise_multiple_exceptions(): + self.counter += 1 + if self.counter == 1: + raise DataFailureException('/', 500, '') + elif self.counter == 2: + raise DataFailureException('/', 503, '') + elif self.counter == 3: + raise RetryableError() + else: + return 'success' + + r = raise_multiple_exceptions() + self.assertEqual(r, 'success') + self.assertEqual(self.counter, 4) diff --git a/restclients/test/view.py b/restclients/test/view.py index 5c115820..3a96ff9e 100644 --- a/restclients/test/view.py +++ b/restclients/test/view.py @@ -1,5 +1,13 @@ -from django.test import TestCase +# -*- coding: utf-8 -*- +from django.test import TestCase, RequestFactory from restclients.views import clean_self_closing_divs +from restclients.views import proxy +from django.contrib.auth.middleware import AuthenticationMiddleware +from django.contrib.sessions.middleware import SessionMiddleware +from userservice.user import UserServiceMiddleware +from django.contrib.auth.models import User +import os + class ViewTest(TestCase): def test_simple(self): @@ -21,3 +29,24 @@ def test_valid_div(self): def test_div_then_valid_self_closing(self): valid = "

" self.assertEquals(valid, clean_self_closing_divs(valid)) + + def test_bad_url(self): + # Something was sending urls that should have been + # ...=®_id=... into ...=®_id=A + # That should be fixed, but in the mean time we shouldn't crash + request = RequestFactory().get("/", { "i": "u\xae_id=A" }) + SessionMiddleware().process_request(request) + AuthenticationMiddleware().process_request(request) + UserServiceMiddleware().process_request(request) + + request.user = User.objects.create_user(username='tbu_user', + email='fake@fake', + password='top_secret') + + backend = "authz_group.authz_implementation.all_ok.AllOK" + with self.settings(RESTCLIENTS_ADMIN_GROUP="ok", + AUTHZ_GROUP_BACKEND=backend): + res = proxy(request, "sws", "/fake/") + self.assertEquals(res.content, + "Bad URL param given to the restclients browser") + self.assertEquals(res.status_code, 200) diff --git a/restclients/tests.py b/restclients/tests.py index acb49f7b..02e6400b 100644 --- a/restclients/tests.py +++ b/restclients/tests.py @@ -1,10 +1,15 @@ -from django.utils import unittest - from restclients.test.uwnetid.subscription import EmailForwardingTest -from restclients.test.util.date_formator import formatorTest +from restclients.test.util.date_formator import FormatorTest +from restclients.test.util.datetime_convertor import DatetimeConvertorTest +from restclients.test.util.retry import RetryTest from restclients.test.hfs.idcard import HfsTest from restclients.test.library.mylibinfo import MyLibInfoTest +from restclients.test.library.currics import CurricsTest from restclients.test.digitlib.curric import DigitLibTest +from restclients.test.grad.committee import CommitteeTest +from restclients.test.grad.degree import DegreeTest +from restclients.test.grad.leave import LeaveTest +from restclients.test.grad.petition import PetitionTest from restclients.test.sws.compatible import SWSTest from restclients.test.sws.financial import SWSFinance @@ -42,6 +47,8 @@ from restclients.test.pws.invalid_dao import PWSTestInvalidDAO from restclients.test.pws.file_implementation.dao import PWSTestFileDAO +from restclients.test.kws.key import KWSTestKeyData + from restclients.test.gws.group import GWSGroupBasics from restclients.test.gws.course_group import GWSCourseGroupBasics from restclients.test.gws.search import GWSGroupSearch @@ -49,6 +56,7 @@ from restclients.test.cache.none import NoCacheTest from restclients.test.cache.time import TimeCacheTest from restclients.test.cache.etag import ETagCacheTest +from restclients.test.cache.memcached import MemcachedCacheTest from restclients.test.book.by_schedule import BookstoreScheduleTest @@ -75,6 +83,7 @@ from restclients.test.canvas.submissions import CanvasTestSubmissions from restclients.test.canvas.assignments import CanvasTestAssignments from restclients.test.canvas.quizzes import CanvasTestQuizzes +from restclients.test.canvas.external_tools import CanvasTestExternalTools from restclients.test.catalyst.gradebook import CatalystTestGradebook diff --git a/restclients/thread.py b/restclients/thread.py index cd6ae245..2a9c0750 100644 --- a/restclients/thread.py +++ b/restclients/thread.py @@ -7,6 +7,7 @@ import threading from django.conf import settings + class Thread(threading.Thread): _use_thread = False @@ -14,7 +15,8 @@ def __init__(self, *args, **kwargs): # Threading has been tested w/ the mysql backend. # It should also work with the postgres/oracle/and so on backends, # but we don't use those. - if settings.DATABASES['default']['ENGINE'] == 'django.db.backends.mysql': + if settings.DATABASES['default']['ENGINE'] ==\ + 'django.db.backends.mysql': if hasattr(settings, "RESTCLIENTS_DISABLE_THREADING"): if not settings.RESTCLIENTS_DISABLE_THREADING: self._use_thread = True @@ -41,7 +43,6 @@ def start(self): else: self.run() - def join(self): if self._use_thread: return super(Thread, self).join() diff --git a/restclients/trumba/__init__.py b/restclients/trumba/__init__.py index 3fc80026..07dc37c4 100644 --- a/restclients/trumba/__init__.py +++ b/restclients/trumba/__init__.py @@ -43,20 +43,25 @@ def _log_xml_resp(campus, url, response, timer): logger.info("%s %s ==message==> %s" % (campus, url, resp_msg)) else: log_err(logger, - "%s %s ==error==> %s %s" % ( - campus, url, response.status, response.reason), + "%s %s ==error==> %s %s" % (campus, url, + response.status, + response.reason), timer) def _log_json_resp(campus, url, body, response, timer): if response.status == 200 and response.data is not None: log_info(logger, - "%s %s %s ==status==> %s" % (campus, url, body, response.status), + "%s %s %s ==status==> %s" % (campus, url, body, + response.status), timer) - logger.debug("%s %s %s ==data==> %s" % (campus, url, body, response.data)) + logger.debug("%s %s %s ==data==> %s" % (campus, url, body, + response.data)) else: log_err(logger, - "%s %s %s ==error==> %s %s" % (campus, url, body, response.status, response.reason), + "%s %s %s ==error==> %s %s" % (campus, url, body, + response.status, + response.reason), timer) @@ -129,9 +134,10 @@ def post_bot_resource(url, body): response = None while True: timer = Timer() - response = TrumbaBot_DAO().postURL(url, - {"Content-Type": "application/json"}, - body) + response = TrumbaBot_DAO().postURL( + url, + {"Content-Type": "application/json"}, + body) _log_json_resp("Bothell", url, body, response, timer) if response.status != 500 or retry == 1: break @@ -150,9 +156,10 @@ def post_sea_resource(url, body): response = None while True: timer = Timer() - response = TrumbaSea_DAO().postURL(url, - {"Content-Type": "application/json"}, - body) + response = TrumbaSea_DAO().postURL( + url, + {"Content-Type": "application/json"}, + body) _log_json_resp("Seattle", url, body, response, timer) if response.status != 500 or retry == 1: break @@ -171,9 +178,10 @@ def post_tac_resource(url, body): response = None while True: timer = Timer() - response = TrumbaTac_DAO().postURL(url, - {"Content-Type": "application/json"}, - body) + response = TrumbaTac_DAO().postURL( + url, + {"Content-Type": "application/json"}, + body) _log_json_resp("Tacoma", url, body, response, timer) if response.status != 500 or retry == 1: break diff --git a/restclients/trumba/account.py b/restclients/trumba/account.py index 7c944f13..604dfc70 100644 --- a/restclients/trumba/account.py +++ b/restclients/trumba/account.py @@ -6,33 +6,35 @@ Be sure to set the logging configuration if you use the LiveDao! """ + from functools import partial import logging import re from lxml import etree, objectify from urllib import quote, unquote from restclients.exceptions import DataFailureException -import restclients.trumba as Trumba -from restclients.trumba.exceptions import AccountNameEmpty, AccountNotExist -from restclients.trumba.exceptions import AccountUsedByDiffUser, CalendarNotExist -from restclients.trumba.exceptions import CalendarOwnByDiffAccount, InvalidEmail -from restclients.trumba.exceptions import InvalidPermissionLevel, FailedToClosePublisher -from restclients.trumba.exceptions import NoAllowedPermission, ErrorCreatingEditor -from restclients.trumba.exceptions import NoDataReturned, UnexpectedError, UnknownError +from restclients.models.trumba import Permission +from restclients.trumba import get_bot_resource, get_sea_resource,\ + get_tac_resource +from restclients.trumba.exceptions import AccountNameEmpty, AccountNotExist,\ + AccountUsedByDiffUser, CalendarNotExist, CalendarOwnByDiffAccount,\ + InvalidEmail, InvalidPermissionLevel, FailedToClosePublisher,\ + NoAllowedPermission, ErrorCreatingEditor, NoDataReturned,\ + UnexpectedError, UnknownError -add_editor_url_prefix = "/service/accounts.asmx/CreateEditor" -del_editor_url_prefix = "/service/accounts.asmx/CloseEditor" +add_account_url_prefix = "/service/accounts.asmx/CreateEditor" +del_account_url_prefix = "/service/accounts.asmx/CloseEditor" set_permission_url_prefix = "/service/calendars.asmx/SetPermissions" -def _make_add_editor_url(name, userid): +def _make_add_account_url(name, userid): """ :return: the URL string for the GET request call to Trumba CreateEditor method """ return "%s?Name=%s&Email=%s@washington.edu&Password=" % ( - add_editor_url_prefix, re.sub(' ', '%20', name), userid) + add_account_url_prefix, re.sub(' ', '%20', name), userid) def add_editor(name, userid): @@ -43,19 +45,19 @@ def add_editor(name, userid): raise DataFailureException or a corresponding TrumbaException if the request failed or an error code has been returned. """ - url = _make_add_editor_url(name, userid) + url = _make_add_account_url(name, userid) return _process_resp(url, - Trumba.get_sea_resource(url), + get_sea_resource(url), _is_editor_added ) -def _make_del_editor_url(userid): +def _make_del_account_url(userid): """ :return: the URL string for GET request call to Trumba CloseEditor method """ - return "%s?Email=%s@washington.edu" % (del_editor_url_prefix, userid) + return "%s?Email=%s@washington.edu" % (del_account_url_prefix, userid) def delete_editor(userid): @@ -65,9 +67,9 @@ def delete_editor(userid): raise DataFailureException or a corresponding TrumbaException if the request failed or an error code has been returned. """ - url = _make_del_editor_url(userid) + url = _make_del_account_url(userid) return _process_resp(url, - Trumba.get_sea_resource(url), + get_sea_resource(url), _is_editor_deleted ) @@ -81,6 +83,18 @@ def _make_set_permissions_url(calendar_id, userid, level): set_permission_url_prefix, calendar_id, userid, level) +def set_bot_editor(calendar_id, userid): + return set_bot_permissions(calendar_id, userid, Permission.EDIT) + + +def set_bot_showon(calendar_id, userid): + return set_bot_permissions(calendar_id, userid, Permission.SHOWON) + + +def set_bot_none(calendar_id, userid): + return set_bot_permissions(calendar_id, userid, Permission.NONE) + + def set_bot_permissions(calendar_id, userid, level): """ :param calendar_id: an integer representing calendar ID @@ -93,11 +107,23 @@ def set_bot_permissions(calendar_id, userid, level): url = _make_set_permissions_url( calendar_id, userid, level) return _process_resp(url, - Trumba.get_bot_resource(url), + get_bot_resource(url), _is_permission_set ) +def set_sea_editor(calendar_id, userid): + return set_sea_permissions(calendar_id, userid, Permission.EDIT) + + +def set_sea_showon(calendar_id, userid): + return set_sea_permissions(calendar_id, userid, Permission.SHOWON) + + +def set_sea_none(calendar_id, userid): + return set_sea_permissions(calendar_id, userid, Permission.NONE) + + def set_sea_permissions(calendar_id, userid, level): """ :param calendar_id: an integer representing calendar ID @@ -110,11 +136,23 @@ def set_sea_permissions(calendar_id, userid, level): url = _make_set_permissions_url( calendar_id, userid, level) return _process_resp(url, - Trumba.get_sea_resource(url), + get_sea_resource(url), _is_permission_set ) +def set_tac_editor(calendar_id, userid): + return set_tac_permissions(calendar_id, userid, Permission.EDIT) + + +def set_tac_showon(calendar_id, userid): + return set_tac_permissions(calendar_id, userid, Permission.SHOWON) + + +def set_tac_none(calendar_id, userid): + return set_tac_permissions(calendar_id, userid, Permission.NONE) + + def set_tac_permissions(calendar_id, userid, level): """ :param calendar_id: an integer representing calendar ID @@ -127,7 +165,7 @@ def set_tac_permissions(calendar_id, userid, level): url = _make_set_permissions_url( calendar_id, userid, level) return _process_resp(url, - Trumba.get_tac_resource(url), + get_tac_resource(url), _is_permission_set ) @@ -136,7 +174,8 @@ def _process_resp(request_id, response, is_success_func): """ :param request_id: campus url identifying the request :param response: the GET method response object - :param is_success_func: the name of the function for verifying a success code + :param is_success_func: the name of the function for + verifying a success code :return: True if successful, False otherwise. raise DataFailureException or a corresponding TrumbaException if the request failed or an error code has been returned. @@ -149,7 +188,8 @@ def _process_resp(request_id, response, is_success_func): if response.data is None: raise NoDataReturned() root = objectify.fromstring(response.data) - if root.ResponseMessage is None or root.ResponseMessage.attrib['Code'] is None: + if root.ResponseMessage is None or\ + root.ResponseMessage.attrib['Code'] is None: raise UnknownError() resp_code = int(root.ResponseMessage.attrib['Code']) func = partial(is_success_func) diff --git a/restclients/trumba/calendar.py b/restclients/trumba/calendar.py index d7de122e..696b2174 100644 --- a/restclients/trumba/calendar.py +++ b/restclients/trumba/calendar.py @@ -7,17 +7,20 @@ Be sure to set the logging configuration if you use the LiveDao! """ + import logging import json import re -from restclients.models.trumba import TrumbaCalendar, Permission, is_bot, is_sea, is_tac +from restclients.models.trumba import TrumbaCalendar, Permission,\ + is_bot, is_sea, is_tac from restclients.exceptions import DataFailureException -import restclients.trumba as Trumba -from restclients.trumba.exceptions import CalendarOwnByDiffAccount, CalendarNotExist -from restclients.trumba.exceptions import NoDataReturned, UnknownError, UnexpectedError +from restclients.trumba import post_bot_resource, post_sea_resource,\ + post_tac_resource +from restclients.trumba.exceptions import CalendarOwnByDiffAccount,\ + CalendarNotExist, NoDataReturned, UnknownError, UnexpectedError -logger = logging.getLogger(__name__) +logger = logging.getLogger(__name__) get_calendarlist_url = "/service/calendars.asmx/GetCalendarList" get_permissions_url = "/service/calendars.asmx/GetPermissions" @@ -51,7 +54,7 @@ def get_bot_calendars(): """ return _process_get_cal_resp( get_calendarlist_url, - Trumba.post_bot_resource(get_calendarlist_url, "{}"), + post_bot_resource(get_calendarlist_url, "{}"), TrumbaCalendar.BOT_CAMPUS_CODE) @@ -64,7 +67,7 @@ def get_sea_calendars(): """ return _process_get_cal_resp( get_calendarlist_url, - Trumba.post_sea_resource(get_calendarlist_url, "{}"), + post_sea_resource(get_calendarlist_url, "{}"), TrumbaCalendar.SEA_CAMPUS_CODE) @@ -77,13 +80,13 @@ def get_tac_calendars(): """ return _process_get_cal_resp( get_calendarlist_url, - Trumba.post_tac_resource(get_calendarlist_url, "{}"), + post_tac_resource(get_calendarlist_url, "{}"), TrumbaCalendar.TAC_CAMPUS_CODE) def get_campus_permissions(calendar_id, campus_code): """ - :return: a list of trumba.Permission objects + :return: a list of sorted trumba.Permission objects corresponding to the given campus calendar. None if error, [] if not exists raise DataFailureException if the request failed. @@ -108,7 +111,7 @@ def _create_get_perm_body(calendar_id): def get_bot_permissions(calendar_id): """ :param calendar_id: an integer representing calendar ID - :return: a list of trumba.Permission objects + :return: a list of sorted trumba.Permission objects corresponding to the given campus calendar. None if error, [] if not exists Return a list of Permission objects representing @@ -118,8 +121,8 @@ def get_bot_permissions(calendar_id): """ return _process_get_perm_resp( get_permissions_url, - Trumba.post_bot_resource(get_permissions_url, - _create_get_perm_body(calendar_id)), + post_bot_resource(get_permissions_url, + _create_get_perm_body(calendar_id)), TrumbaCalendar.BOT_CAMPUS_CODE, calendar_id) @@ -128,7 +131,7 @@ def get_sea_permissions(calendar_id): """ Return a list of Permission objects representing the user permissions of a given Seattle calendar. - :return: a list of trumba.Permission objects + :return: a list sorted of trumba.Permission objects corresponding to the given campus calendar. None if error, [] if not exists raise DataFailureException or a corresponding TrumbaException @@ -136,15 +139,15 @@ def get_sea_permissions(calendar_id): """ return _process_get_perm_resp( get_permissions_url, - Trumba.post_sea_resource(get_permissions_url, - _create_get_perm_body(calendar_id)), + post_sea_resource(get_permissions_url, + _create_get_perm_body(calendar_id)), TrumbaCalendar.SEA_CAMPUS_CODE, calendar_id) def get_tac_permissions(calendar_id): """ - Return a list of Permission objects representing + Return a list of sorted Permission objects representing the user permissions of a given Tacoma calendar. :return: a list of trumba.Permission objects corresponding to the given campus calendar. @@ -154,8 +157,8 @@ def get_tac_permissions(calendar_id): """ return _process_get_perm_resp( get_permissions_url, - Trumba.post_tac_resource(get_permissions_url, - _create_get_perm_body(calendar_id)), + post_tac_resource(get_permissions_url, + _create_get_perm_body(calendar_id)), TrumbaCalendar.TAC_CAMPUS_CODE, calendar_id) @@ -173,7 +176,8 @@ def _load_calendar(campus, resp_fragment, calendar_dict, parent): None if error, {} if not exists """ for record in resp_fragment: - if re.match('Internal Event Actions', record['Name']) or re.match('Migrated .*', record['Name']): + if re.match('Internal Event Actions', record['Name']) or\ + re.match('Migrated .*', record['Name']): continue trumba_cal = TrumbaCalendar() trumba_cal.calendarid = record['ID'] @@ -189,7 +193,8 @@ def _load_calendar(campus, resp_fragment, calendar_dict, parent): continue calendar_dict[trumba_cal.calendarid] = trumba_cal - if record['ChildCalendars'] is not None and len(record['ChildCalendars']) > 0: + if record['ChildCalendars'] is not None and\ + len(record['ChildCalendars']) > 0: _load_calendar(campus, record['ChildCalendars'], calendar_dict, @@ -225,7 +230,7 @@ def _extract_uwnetid(email): def _load_permissions(campus, calendarid, resp_fragment, permission_list): """ - :return: a list of trumba.Permission objects + :return: a list of sorted trumba.Permission objects None if error, [] if not exists """ for record in resp_fragment: @@ -244,6 +249,7 @@ def _load_permissions(campus, calendarid, resp_fragment, permission_list): def _process_get_perm_resp(url, post_response, campus, calendarid): """ :return: a list of trumba.Permission objects + sorted by descending level and ascending uwnetid None if error, [] if not exists If the response is successful, process the response data and load into the return objects @@ -256,7 +262,7 @@ def _process_get_perm_resp(url, post_response, campus, calendarid): _load_permissions(campus, calendarid, data['d']['Users'], permission_list) - return permission_list + return sorted(permission_list) def _check_err(data): diff --git a/restclients/urls.py b/restclients/urls.py index a6246617..b8f03974 100644 --- a/restclients/urls.py +++ b/restclients/urls.py @@ -1,5 +1,6 @@ from django.conf.urls import patterns, include, url -urlpatterns = patterns('', +urlpatterns = patterns( + '', url(r'view/(\w+)/(.*)$', 'restclients.views.proxy'), -) + ) diff --git a/restclients/util/date_formator.py b/restclients/util/date_formator.py index f30716fd..db73e218 100644 --- a/restclients/util/date_formator.py +++ b/restclients/util/date_formator.py @@ -44,7 +44,8 @@ def abbr_week_month_day_str(adatetime): def past_datetime_str(adatetime): """ For adatetime is since 12:00AM, return: "Today at H:MM [A]PM" - For adatetime is between 12:00AM and 11:59PM on yesterday: "Yesterday at 6:23 PM" + For adatetime is between 12:00AM and 11:59PM on yesterday: + "Yesterday at 6:23 PM" For adatetime is between 2 and 6 days ago: "[2-6] days ago" For adatetime is 7 days ago: "1 week ago" For adatetime is 8-14 days ago: "Over 1 week ago" @@ -150,4 +151,5 @@ def get_total_seconds(time_delta): """ Returns the total number of seconds in a passed timedelta """ - return (time_delta.microseconds + (time_delta.seconds + time_delta.days * 24 * 3600) * 10**6) / 10**6 + return (time_delta.microseconds + + (time_delta.seconds + time_delta.days * 24 * 3600) * 10**6) / 10**6 diff --git a/restclients/util/datetime_convertor.py b/restclients/util/datetime_convertor.py new file mode 100644 index 00000000..f65615d5 --- /dev/null +++ b/restclients/util/datetime_convertor.py @@ -0,0 +1,21 @@ +from datetime import date, datetime, timedelta + + +def convert_to_begin_of_day(a_date): + """ + @return the naive datetime object of the beginning of day + for the give date or datetime object + """ + if a_date is None: + return None + return datetime(a_date.year, a_date.month, a_date.day, 0, 0, 0) + + +def convert_to_end_of_day(a_date): + """ + @return the naive datetime object of the end of day + for the give date or datetime object + """ + if a_date is None: + return None + return convert_to_begin_of_day(a_date) + timedelta(days=1) diff --git a/restclients/util/retry.py b/restclients/util/retry.py new file mode 100644 index 00000000..b0cab02f --- /dev/null +++ b/restclients/util/retry.py @@ -0,0 +1,58 @@ +from restclients.exceptions import DataFailureException +import math +import time + + +def retry(ExceptionToCheck, tries=4, delay=3, backoff=2, status_codes=[], + logger=None): + """ + Decorator function for retrying the decorated function, + using an exponential or fixed backoff. + + Original: https://wiki.python.org/moin/PythonDecoratorLibrary#Retry + + ExceptionToCheck: the exception to check. Can be a tuple of + exceptions to check + tries: number of times to try (not retry) before giving up + delay: initial delay between tries in seconds + backoff: backoff multiplier + status_codes: list of http status codes to check for retrying, only applies + when ExceptionToCheck is a DataFailureException + logger: logging.Logger instance + """ + if backoff is None or backoff <= 0: + raise ValueError("backoff must be a number greater than 0") + + tries = math.floor(tries) + if tries < 0: + raise ValueError("tries must be a number 0 or greater") + + if delay is None or delay <= 0: + raise ValueError("delay must be a number greater than 0") + + def deco_retry(f): + def f_retry(*args, **kwargs): + mtries, mdelay = tries, delay + while mtries > 1: + try: + return f(*args, **kwargs) + + except ExceptionToCheck as err: + if (type(err) is DataFailureException and + len(status_codes) and + err.status not in status_codes): + raise + + if logger: + logger.warning('%s: %s, Retrying in %s seconds.' % ( + f.__name__, err, mdelay)) + + time.sleep(mdelay) + mtries -= 1 + mdelay *= backoff + + return f(*args, **kwargs) + + return f_retry + + return deco_retry diff --git a/restclients/util/timer.py b/restclients/util/timer.py index a498a9fb..490db45d 100644 --- a/restclients/util/timer.py +++ b/restclients/util/timer.py @@ -6,13 +6,13 @@ def uctnow(): class Timer: + def __init__(self): """ Start the timer """ self.start = uctnow() - def get_elapsed(self): """ Return the time spent in milliseconds diff --git a/restclients/views.py b/restclients/views.py index a1557822..a6d1be41 100644 --- a/restclients/views.py +++ b/restclients/views.py @@ -1,12 +1,18 @@ +try: + from importlib import import_module +except: + # python 2.6 + from django.utils.importlib import import_module from django.conf import settings from django.contrib.auth.decorators import login_required from django.views.decorators.csrf import csrf_protect from django.http import HttpResponseNotFound, HttpResponseRedirect +from django.http import HttpResponse from django.template import loader, RequestContext, TemplateDoesNotExist from django.shortcuts import render_to_response -from restclients.dao import SWS_DAO, PWS_DAO, GWS_DAO, NWS_DAO, Hfs_DAO -from restclients.dao import Book_DAO, Canvas_DAO, Uwnetid_DAO, Libraries_DAO -from restclients.dao import TrumbaCalendar_DAO, MyPlan_DAO +from restclients.dao import SWS_DAO, PWS_DAO, GWS_DAO, NWS_DAO, Hfs_DAO,\ + Book_DAO, Canvas_DAO, Uwnetid_DAO, MyLibInfo_DAO, LibCurrics_DAO,\ + TrumbaCalendar_DAO, MyPlan_DAO, IASYSTEM_DAO, Grad_DAO from restclients.mock_http import MockHTTP from authz_group import Group from userservice.user import UserService @@ -29,7 +35,8 @@ def proxy(request, service, url): user_service = UserService() actual_user = user_service.get_original_user() g = Group() - is_admin = g.is_member_of_group(actual_user, settings.RESTCLIENTS_ADMIN_GROUP) + is_admin = g.is_member_of_group(actual_user, + settings.RESTCLIENTS_ADMIN_GROUP) if not is_admin: return HttpResponseRedirect("/") @@ -51,12 +58,27 @@ def proxy(request, service, url): dao = Book_DAO() elif service == "canvas": dao = Canvas_DAO() + elif service == "grad": + dao = Grad_DAO() elif service == "uwnetid": dao = Uwnetid_DAO() elif service == "libraries": - dao = Libraries_DAO() + dao = MyLibInfo_DAO() + elif service == "libcurrics": + dao = LibCurrics_DAO() elif service == "myplan": dao = MyPlan_DAO() + elif service == "iasystem": + dao = IASYSTEM_DAO() + headers = {"Accept": "application/vnd.collection+json"} + subdomain = None + if url.endswith('/evaluation'): + if url.startswith('uwb/') or url.startswith('uwt/'): + subdomain = url[:3] + url = url[4:] + else: + subdomain = url[:2] + url = url[3:] elif service == "calendar": dao = TrumbaCalendar_DAO() use_pre = True @@ -66,11 +88,18 @@ def proxy(request, service, url): url = "/%s" % quote(url) if request.GET: - url = "%s?%s" % (url, urlencode(request.GET)) + try: + url = "%s?%s" % (url, urlencode(request.GET)) + except UnicodeEncodeError: + err = "Bad URL param given to the restclients browser" + return HttpResponse(err) start = time() try: - response = dao.getURL(url, headers) + if service == "iasystem" and subdomain is not None: + response = dao.getURL(url, headers, subdomain) + else: + response = dao.getURL(url, headers) except Exception as ex: response = MockHTTP() response.status = 500 @@ -82,13 +111,13 @@ def proxy(request, service, url): try: if not use_pre: content = format_json(service, response.data) - json_data = response.data; + json_data = response.data else: content = response.data json_data = None except Exception as e: content = format_html(service, response.data) - json_data = None; + json_data = None context = { "url": unquote(url), @@ -145,13 +174,16 @@ def format_json(service, content): formatted = formatted.replace(" ", " ") formatted = formatted.replace("\n", "
\n") - formatted = re.sub(r"\"/(.*?)\"", r'"/\1"' % service, formatted) + formatted = re.sub(r"\"/(.*?)\"", + r'"/\1"' % + service, formatted) return formatted def format_html(service, content): - formatted = re.sub(r"href\s*=\s*\"/(.*?)\"", r"href='/restclients/view/%s/\1'" % service, content) + formatted = re.sub(r"href\s*=\s*\"/(.*?)\"", + r"href='/restclients/view/%s/\1'" % service, content) formatted = re.sub(re.compile(r"", re.S), "", formatted) formatted = clean_self_closing_divs(formatted) return formatted diff --git a/setup.py b/setup.py index edf71593..cd0e254f 100644 --- a/setup.py +++ b/setup.py @@ -16,5 +16,5 @@ include_package_data=True, # use MANIFEST.in during install url='https://github.com/uw-it-aca/uw-restclients', description='Clients for a variety of RESTful web services at the University of Washington', - install_requires=['Django<1.8', 'lxml==2.3.5', 'urllib3==1.10.2', 'twilio==3.4.1', 'boto', 'simplejson>=2.1', 'djangorestframework>=2.0', 'jsonpickle>=0.4.0', 'ordereddict>=1.1', 'python-dateutil>=2.1', 'unittest2>=0.5.1', 'pytz', 'icalendar', 'AuthZ-Group>=1.1.4', 'Django-UserService'], + install_requires=['Django', 'lxml==2.3.5', 'urllib3==1.10.2', 'twilio==3.4.1', 'boto', 'simplejson>=2.1', 'djangorestframework>=2.0', 'jsonpickle>=0.4.0', 'ordereddict>=1.1', 'python-dateutil>=2.1', 'unittest2>=0.5.1', 'pytz', 'icalendar', 'AuthZ-Group>=1.1.4', 'Django-UserService', 'python-binary-memcached',], ) diff --git a/travis-ci/settings.py b/travis-ci/settings.py index 749c8956..6268cae8 100644 --- a/travis-ci/settings.py +++ b/travis-ci/settings.py @@ -87,3 +87,8 @@ # https://docs.djangoproject.com/en/1.7/howto/static-files/ STATIC_URL = '/static/' + + +# Test the memcached cache code +RESTCLIENTS_TEST_MEMCACHED = True +RESTCLIENTS_MEMCACHED_SERVERS = ('localhost:11211', ) diff --git a/travis-ci/urls.py b/travis-ci/urls.py index a7861240..756e2b23 100644 --- a/travis-ci/urls.py +++ b/travis-ci/urls.py @@ -4,8 +4,5 @@ urlpatterns = patterns('', # Examples: # url(r'^$', 'project.views.home', name='home'), - # url(r'^blog/', include('blog.urls')), - url(r'^oauth/', include('oauth_provider.urls')), url(r'^admin/', include(admin.site.urls)), - url(r'^', include('nagios_registration.urls')), ) diff --git a/vm/tests.py b/vm/tests.py index 6c8aca40..7409e3cc 100644 --- a/vm/tests.py +++ b/vm/tests.py @@ -1,5 +1,3 @@ -from django.utils import unittest - from vm.v1.test.channel import ChannelDetailViewModelTest from vm.v1.test.endpoint import EndpointDetailViewModelTest from vm.v1.test.message import MessageTest diff --git a/vm/v1/test/channel.py b/vm/v1/test/channel.py index 45911e13..5a916649 100644 --- a/vm/v1/test/channel.py +++ b/vm/v1/test/channel.py @@ -21,6 +21,9 @@ class ChannelModel(models.Model): created = models.DateTimeField(auto_now_add=True, blank=True) last_modified = models.DateTimeField(auto_now=True, blank=True) modified_by = models.CharField(max_length=80, null=True) + + class Meta: + app_label = 'vm' class ExampleChannel(object): @@ -270,6 +273,4 @@ def test_to_model(self): #self.assertEqual(channel_model.template_surrogate_id, view_model.template_surrogate_id) self.assertEqual(channel_model.description, view_model.description) self.assertEqual(channel_model.expires, view_model.expires) - - \ No newline at end of file