コード例 #1
0
class Command(SISProvisionerCommand):
    help = "Sync LTI Manager app with actual external tools in Canvas"

    def handle(self, *args, **options):
        self._accounts = Accounts()
        self._tools = ExternalTools()

        account = self._accounts.get_account(
            getattr(settings, 'RESTCLIENTS_CANVAS_ACCOUNT_ID'))

        self.update_tools_in_account(account)
        self.update_job()

    def update_tools_in_account(self, account):
        for tool in self._tools.get_external_tools_in_account(
                account.account_id):
            self.update_model(account, tool)

        for subaccount in self._accounts.get_sub_accounts(account.account_id):
            self.update_tools_in_account(subaccount)

    def update_model(self, account, config):
        canvas_id = config['id']
        try:
            external_tool = ExternalTool.objects.get(canvas_id=canvas_id)
        except ExternalTool.DoesNotExist:
            external_tool = ExternalTool(canvas_id=canvas_id)

        try:
            et_account = ExternalToolAccount.objects.get(
                account_id=account.account_id)
        except ExternalToolAccount.DoesNotExist:
            et_account = ExternalToolAccount(account_id=account.account_id)
            et_account.sis_account_id = account.sis_account_id
            et_account.name = account.name
            et_account.save()

        external_tool.account = et_account
        external_tool.config = json.dumps(config)
        external_tool.changed_by = 'auto'
        external_tool.changed_date = datetime.utcnow().replace(tzinfo=utc)
        external_tool.save()
コード例 #2
0
class Command(SISProvisionerCommand):
    help = "Reconcile ASTRA / Canvas Administrators"

    option_list = BaseCommand.option_list + (
        make_option('-r', '--root-account', action='store', dest='root_account', type="string",
                    default=settings.RESTCLIENTS_CANVAS_ACCOUNT_ID,
                    help='reconcile sections at and below root account (default: %s)'
                    % settings.RESTCLIENTS_CANVAS_ACCOUNT_ID),
        make_option('-c', '--commit', action='store_true', dest='commit',
                    default=False, help='update Canvas with ASTRA admins and roles'),
        make_option('-a', '--astra-is-authoritative', action='store_true', dest='remove_non_astra',
                    default=False, help='Remove Canvas admins not found in ASTRA'),
        make_option('-o', '--override', action='store', dest='override_id',
                    default=0, help='Override blocked Canvas admins import of given process id'),
        )

    max_retry = 5
    sleep_interval = 5
    retry_status_codes = [408, 500, 502, 503, 504]

    def handle(self, *args, **options):
        self._options = options
        self._verbosity = int(options.get('verbosity'))
        self._canvas_admins = CanvasAdmins()
        self._canvas_accounts = CanvasAccounts()
        self._pws = PWS()
        self._accounts = Accounts()

        self._canvas_role_mapping = {}
        for role in settings.ASTRA_ROLE_MAPPING:
            self._canvas_role_mapping[settings.ASTRA_ROLE_MAPPING[role]] = role
        if not self._options.get('commit'):
            logger.info('NOT commiting ASTRA admins.  Only logging what would change.')

        # Compare table to Canvas reality
        try:
            if self._verbosity > 0:
                logger.info('building admin table from ASTRA...')

            ASTRA({ 'verbosity': self._verbosity }).load_all_admins({ 'override': self._options.get('override_id') })

            if self._verbosity > 0:
                logger.info('building sub account list...')

            accounts = []
            root = options.get('root_account')
            root_account_id = root if self._canvas_accounts.valid_canvas_id(root) else self._canvas_accounts.sis_account_id(root)

            if self._verbosity > 1:
                logger.info('get account for id: %s...' % root_account_id)

            root_canvas_admins = []

            account = self.get_account(root_account_id)
            sub_accounts = self.get_all_sub_accounts(root_account_id)

            accounts.append(account)
            accounts.extend(sub_accounts)
            for account in accounts:
                canvas_id = account.account_id
                self._shown_canvas_id = canvas_id
                account_model = self._accounts.load_account(account)

                # reconcile admins against Admin table
                if account_model.is_sdb():
                    self._shown_id = account.sis_account_id
                else:
                    self._shown_id = 'canvas_%s' % canvas_id

                astra_admins = Admin.objects.filter(canvas_id=canvas_id)
                canvas_admins = self.get_admins(canvas_id)

                if account_model.is_root():
                    root_canvas_admins = canvas_admins

                if self._verbosity > 0 and (len(astra_admins) or len(canvas_admins)):
                    logger.info('%d ASTRA and %s Canvas admins in account %s (%s)' %
                                   (len(astra_admins), len(canvas_admins),
                                    account.name, account.account_id))

                for astra_admin in astra_admins:
                    user_role = {
                        'canvas_account_id': account.account_id,
                        'role': settings.ASTRA_ROLE_MAPPING[astra_admin.role],
                        'net_id': astra_admin.net_id
                    }

                    canvas_admin = None

                    for admin in canvas_admins:
                        if (user_role['net_id'] == admin.user.login_id and
                            user_role['role'] == admin.role):
                            canvas_admin = admin
                            canvas_admin.in_astra = True
                            break

                    if astra_admin.is_deleted:
                        if canvas_admin:
                            user_role['user_id'] = canvas_admin.user.user_id
                            self._remove_admin(**user_role)
                            try:
                                ancillary = settings.ANCILLARY_CANVAS_ROLES[astra_admin.role]
                                user_role['role'] = ancillary['canvas_role']
                                if ancillary['account'] == 'common':
                                    self._remove_admin(**user_role)
                                elif len(Admin.objects.filter(net_id=canvas_admin.user.login_id,
                                                              role=astra_admin.role,
                                                              is_deleted__isnull=True)) == 0:
                                    user_role['canvas_account_id'] = settings.RESTCLIENTS_CANVAS_ACCOUNT_ID
                                    self._remove_admin(**user_role)
                            except Admin.DoesNotExist:
                                pass
                            except KeyError:
                                pass

                            astra_admin.deleted_date = datetime.datetime.utcnow().replace(tzinfo=utc)
                            astra_admin.save()
                    elif not canvas_admin:
                        user_role['user_id'] = self._canvas_admins.sis_user_id(astra_admin.reg_id)
                        self._add_admin(**user_role)

                        if astra_admin.role in settings.ANCILLARY_CANVAS_ROLES:
                            ancillary = settings.ANCILLARY_CANVAS_ROLES[astra_admin.role]
                            user_role['role'] = ancillary['canvas_role']
                            if ancillary['account'] == 'root':
                                user_role['canvas_account_id'] = settings.RESTCLIENTS_CANVAS_ACCOUNT_ID

                            self._add_admin(**user_role)

                        astra_admin.provisioned_date = datetime.datetime.utcnow().replace(tzinfo=utc)
                        astra_admin.save()
                    else:
                        if self._verbosity > 0:
                            logger.info('  %s already in Canvas as %s' % (astra_admin.net_id, astra_admin.role))

                        if astra_admin.role in settings.ANCILLARY_CANVAS_ROLES:
                            ancillary = settings.ANCILLARY_CANVAS_ROLES[astra_admin.role]
                            user_role['role'] = ancillary['canvas_role']
                            add_ancillary = True
                            if ancillary['account'] == 'root':
                                user_role['canvas_account_id'] = settings.RESTCLIENTS_CANVAS_ACCOUNT_ID
                                for root_canvas_admin in root_canvas_admins:
                                    if (astra_admin.net_id == root_canvas_admin.user.login_id
                                        and root_canvas_admin.role == user_role['role']):
                                        add_ancillary = False
                                        break
                            elif  ancillary['account'] == 'common':
                                for canvas_admin in canvas_admins:
                                    if (astra_admin.net_id == canvas_admin.user.login_id
                                        and canvas_admin.role == user_role['role']):
                                        add_ancillary = False
                                        break

                            if add_ancillary:
                                user_role['user_id'] = self._canvas_admins.sis_user_id(astra_admin.reg_id)
                                self._add_admin(**user_role)
                            elif self._verbosity > 0:
                                logger.info('  %s ancillary role %s already in %s'
                                               % (user_role['net_id'],
                                                  user_role['role'],
                                                  user_role['canvas_account_id']))

                # remove unrecognized admins
                for canvas_admin in canvas_admins:
                    if (self._options['remove_non_astra']
                        and not (hasattr(canvas_admin, 'in_astra')
                                 and canvas_admin.in_astra)):
                        if self._is_ancillary(account, canvas_admin.role,
                                              canvas_admin.user.login_id, account_model.is_root()):
                            logger.info('preserving ancillary role: %s as %s'
                                           % (canvas_admin.user.login_id,
                                              canvas_admin.role))

                            continue

                        try:
                            self._remove_admin(canvas_account_id=canvas_id,
                                               net_id=canvas_admin.user.login_id,
                                               user_id=canvas_admin.user.user_id,
                                               role=canvas_admin.role)
                        except DataFailureException as err:
                            if err.args[1] == 404:
                                logger.info('Ancillary role NOT in Canvas: %s as %s'
                                               % (canvas_admin.user.login_id,
                                                  canvas_admin.role))
                            else:
                                raise

            if self._verbosity > 0:
                logger.info('Done.')

        except ASTRAException as err:
            logger.error('ASTRA ERROR: %s\nAborting.' % err)

        except DataFailureException as err:
            if err.status in self.retry_status_codes:
                logger.error('RETRIES EXCEEDED: %s\nAborting.' % err)
                Admin.objects.dequeue()
            else:
                logger.error('REST ERROR: %s\nAborting.' % err)

        self.update_job()

    @retry(DataFailureException, status_codes=retry_status_codes,
           tries=max_retry, delay=sleep_interval, logger=logger)
    def get_admins(self, canvas_id):
        return self._canvas_admins.get_admins(canvas_id)

    @retry(DataFailureException, status_codes=retry_status_codes,
           tries=max_retry, delay=sleep_interval, logger=logger)
    def get_account(self, root_account_id):
        return self._canvas_accounts.get_account(root_account_id)

    @retry(DataFailureException, status_codes=retry_status_codes,
           tries=max_retry, delay=sleep_interval, logger=logger)
    def get_all_sub_accounts(self, root_account_id):
        return self._canvas_accounts.get_all_sub_accounts(root_account_id)

    def _is_ancillary(self, account, canvas_role, canvas_login_id, is_root):
        ancillary = settings.ANCILLARY_CANVAS_ROLES
        for astra_role in ancillary.keys():
            if ancillary[astra_role]['canvas_role'] == canvas_role:
                if ancillary[astra_role]['account'] == 'root' and is_root:
                    if len(Admin.objects.filter(net_id=canvas_login_id,
                                                role=astra_role,
                                                is_deleted__isnull=True)) > 0:
                        return True
                elif ancillary[astra_role]['account'] == 'common':
                    try:
                        Admin.objects.get(account_id=account.account_id,
                                          net_id=canvas_login_id,
                                          role=astra_role,
                                          is_deleted__isnull=True)
                        return True
                    except Admin.DoesNotExist:
                        pass

        return False

    def _add_admin(self, **kwargs):
        prefix = 'WOULD ADD'

        if self._options.get('commit'):
            prefix = 'ADDING'
            try:
                self._canvas_admins.create_admin(
                    kwargs['canvas_account_id'],
                    kwargs['user_id'],
                    kwargs['role'])
            except DataFailureException as err:
                if err.status == 404:  # Non-personal regid?
                    prefix = "ADD FAIL: MISSING USER %s" % (kwargs['user_id'])
                else:
                    raise

        self._record('  %s: %s as %s' % (
            prefix, kwargs['net_id'], kwargs['role']))

    def _remove_admin(self, **kwargs):
        action = 'WOULD DELETE'

        if kwargs['net_id'] in getattr(settings, 'CANVAS_SERVICE_USER_ACCOUNTS', []):
            action = 'SERVICE USER'
        elif self._options.get('commit'):
            action = 'DELETING'
            try:
                self._canvas_admins.delete_admin(kwargs['canvas_account_id'],
                                                 kwargs['user_id'],
                                                 kwargs['role'])
            except DataFailureException as err:
                if err.status == 404:  # Non-personal regid?
                    action = "ALREADY DELETED"
                else:
                    raise

        self._record('  %s: %s (%s) as %s' % (action, kwargs['net_id'],
                                              kwargs['user_id'], kwargs['role']))

    def _record(self, msg):
        if self._shown_id:
            logger.info('reconciling %s (%s)' % (self._shown_id, self._shown_canvas_id))
            self._shown_id = None

        logger.info(msg)
コード例 #3
0
    def handle(self, *args, **options):
        if len(args) == 2:
            subaccount_id = args[0]
            sis_term_id = args[1]
        else:
            raise CommandError("find_active_instructors <subaccount_id> <term_id>")

        accounts = Accounts()
        reports = Reports()
        pws = PWS()

        account = accounts.get_account(subaccount_id)
        term = reports.get_term_by_sis_id(sis_term_id)

        enrollment_report = reports.create_enrollments_provisioning_report(account.account_id, term.term_id)
        enrollment_data = reports.get_report_data(enrollment_report)

        all_instructors = {}

        enrollment_csv_data = csv.reader(enrollment_data)
        header = enrollment_csv_data.next()
        course_id_idx = header.index("course_id")
        sis_user_id_idx = header.index("user_id")
        role_idx = header.index("role")
        status_idx = header.index("status")

        for row in enrollment_csv_data:
            if not len(row):
                continue

            course_id = row[course_id_idx]
            sis_user_id = row[sis_user_id_idx]
            role = row[role_idx]
            status = row[status_idx]

            if sis_user_id != "" and role.lower() == "teacher" and status.lower() == "active":
                if course_id not in all_instructors:
                    all_instructors[course_id] = []

                all_instructors[course_id].append(sis_user_id)

        course_report = reports.create_course_provisioning_report(account.account_id, term.term_id)
        course_data = reports.get_report_data(course_report)

        course_csv_data = csv.reader(course_data)
        header = course_csv_data.next()
        course_id_idx = header.index("course_id")
        sis_account_id_idx = header.index("account_id")
        status_idx = header.index("status")

        active_instructors = {}
        for row in course_csv_data:
            if not len(row):
                continue

            course_id = row[course_id_idx]
            sis_account_id = row[sis_account_id_idx]
            status = row[status_idx]

            if sis_account_id != "" and status.lower() == "active" and course_id in all_instructors:
                for sis_user_id in all_instructors[course_id]:
                    if sis_user_id not in active_instructors:
                        try:
                            person = pws.get_person_by_regid(sis_user_id)
                            email = person.uwnetid + "@uw.edu"
                            active_instructors[sis_user_id] = email
                        except InvalidRegID:
                            continue
                        except DataFailureException as err:
                            if err.status == 404:
                                continue
                            else:
                                raise

        filename = "-".join(["active-instructors", subaccount_id, sis_term_id])
        outpath = dirname(__file__) + "/" + filename + ".txt"

        f = open(outpath, "w")
        data = active_instructors.values()
        data.sort()
        f.write("\n".join(data))
        f.close()

        reports.delete_report(enrollment_report)
        reports.delete_report(course_report)

        print outpath