Пример #1
0
    def do(self):
        if self.access_restricted(['student', 'public']):
            return

        program_info = {}

        programs = self.api.see('program', {'see': 'abbreviation'})
        for p in programs:
            s_config = Program.get_app_configuration(p.abbreviation, 'student')
            t_config = Program.get_app_configuration(p.abbreviation, 'teacher')
            program_config = Program.get_app_configuration(p.abbreviation)
            program_info[p.id] = {
                'student_outline':
                s_config['outline'] if s_config else None,
                'teacher_outline':
                t_config['outline'] if t_config else None,
                'normal_school_instructions':
                markdown.markdown(
                    getattr(program_config, 'normal_school_instructions', '')),
                'alias_school_instructions':
                markdown.markdown(
                    getattr(program_config, 'alias_school_instructions', '')),
            }

        return 'dashboard.html', {
            'program_info': program_info,
            'cohort_aggregation_data_template':
            Cohort.aggregation_data_template,
        }
Пример #2
0
 def program_outline(self, program_id):
     program = self.get('program', {'id': program_id})[0]
     t_conf = Program.get_app_configuration(program.abbreviation, 'teacher')
     s_conf = Program.get_app_configuration(program.abbreviation, 'student')
     return {
         'teacher': t_conf['outline'] if t_conf else None,
         'student': s_conf['outline'] if s_conf else None
     }
Пример #3
0
    def test_unique_retrieval(self):
        """Test that QualtricsLink.get_link catches inconsistent results
        proving that get_link() handles inconsistency and doesn't return
        deleted (already used and thus non-unique) links"""

        p = Program.create(name='Test Program', abbreviation='TP1')

        # Create two links and put them to the db
        a = QualtricsLink.create(key_name='test_a',
                                 link='test_a',
                                 session_ordinal=1,
                                 program=p.id)
        b = QualtricsLink.create(key_name='test_b',
                                 link='test_b',
                                 session_ordinal=1,
                                 program=p.id)
        a.put()
        b.put()

        # db.get by key forces consistency
        a, b = db.get([a.key(), b.key()])

        # delete link a to put db into inconsistent state.
        db.delete(a)

        # get_link pulls in alphabetical order by link, so
        # a is retrieved first, then b.
        b_link = QualtricsLink.get_link(p, 1, default_link='qualtrics.com')

        # If we have consistency issues, get_link() may not see that a has been
        # deleted, and thus return it rather than b. We want to assert that we
        # don't have those issues.
        self.assertEquals(b_link, 'test_b')
Пример #4
0
    def check_params(self, params):
        """Raises exceptions if there are problems with request data."""

        # Make sure all parameters are present.
        required = ['program', 'user', 'activity_ordinal']
        missing = set(required) - set(params.keys())
        if missing:
            raise Exception("Missing required parameters: {}.".format(missing))

        program_list = self.internal_api.get(
            'program', {'abbreviation': params['program']})
        if len(program_list) is not 1:
            raise Exception("Invalid program: {}.".format(params['program']))
        else:
            program = program_list[0]

        # User must exist and be associated with provided program.
        user = self.internal_api.get_from_path('user', params['user'])
        if user.assc_program_list[0] != program.id:
            raise Exception("Invalid program for user: {} {}.".format(
                params['program'], params['user']))

        # Activity ordinal must be among the available program sessions.
        o = int(params['activity_ordinal'])
        student_program_outline = Program.get_app_configuration(
            program.abbreviation, 'student')['outline']
        modules_match = [
            module.get('activity_ordinal', None) is o
            for module in student_program_outline
        ]
        if not any(modules_match):
            raise Exception("Invalid activity_ordinal: {}.".format(o))

        return program
Пример #5
0
    def do(self, program_abbreviation):
        # No access is restricted.

        abbr = program_abbreviation.upper()
        program_list = self.internal_api.get('program', {'abbreviation': abbr})
        if len(program_list) is not 1:
            logging.error("Bad public registration link: {}.".format(
                self.request.path))
            return self.http_not_found()

        program = program_list[0]

        program_config = Program.get_app_configuration(program.abbreviation)

        # Not all programs are public access. Check before continuing.
        if not getattr(program_config, 'public_registration', False):
            return self.http_not_found()

        # Registration requires the user to select a school. Provide them a
        # list of existing schools to pick from.
        cohorts = self.internal_api.get('cohort',
                                        {'assc_program_list': program.id})
        school_ids = [c.assc_school_list[0] for c in cohorts]
        schools = self.internal_api.get_by_ids(school_ids)

        template_path = '/programs/{}/public_registration.html'.format(abbr)
        params = {
            'program': program.to_dict(),
            'program_config': program_config,
            'school_list': [s.to_dict() for s in schools],
        }
        template_directory = ''  # i.e. from the root of the app
        return template_path, params, template_directory
Пример #6
0
    def do(self):
        new_programs = []
        deleted_programs = []
        current_programs = []
        current_abbreviations = []
        user = self.get_current_user()

        if user.user_type != 'god':
            raise core.PermissionDenied()

        # Check what program directories exist
        path = os.path.join(os.getcwd(), 'programs')
        new_abbreviations = util.get_immediate_subdirectories(path)

        # Check what program entities exist
        current_programs = Program.all().filter('deleted =', False).fetch(11)
        if len(current_programs) > 10:
            raise Exception("Too many programs. Limit is 10.")

        # Check them against each other.
        for p in current_programs:
            if p.abbreviation in new_abbreviations:
                current_abbreviations.append(p.abbreviation)
                new_abbreviations.remove(p.abbreviation)
            else:
                p.deleted = True
                deleted_programs.append(p)
        db.put(deleted_programs)

        # Anything remaining in the list is new.
        for a in new_abbreviations:
            program_config = Program.get_app_configuration(a)
            p = Program.create(abbreviation=a, name=program_config.name)
            current_programs.append(p)
            new_programs.append(p)
        db.put(new_programs)

        return {
            'success': True,
            'data': {
                'deleted_programs': [p.to_dict() for p in deleted_programs],
                'new_programs': [p.to_dict() for p in new_programs],
                'current_programs': [p.to_dict() for p in current_programs],
            }
        }
Пример #7
0
    def do(self):
        if self.access_restricted(
            ['researcher', 'school_admin', 'teacher', 'student', 'public']):
            return

        # We want to avoid populating the same datastore twice. So attempt to
        # detect if the populate script has already been done.
        programs_exist = Program.all().count() > 0

        # Load the available short links.
        short_links = []
        for sl in ShortLink.all().filter('deleted =', False).run():
            short_links.append(sl.to_dict())
        return 'god.html', {
            'programs_exist': programs_exist,
            'short_links': short_links,
        }
Пример #8
0
    def do(self, program_id, user_type, activity_ordinal):
        # URL args come in as srings, we need this to be an int.
        activity_ordinal = int(activity_ordinal)

        # We need to find the default link for this program.
        # First look up the program.
        program = self.internal_api.get_from_path('program', program_id)
        # Then the module for the relevant session.
        module = Program.get_activity_configuration(program.abbreviation,
                                                    user_type,
                                                    activity_ordinal)

        default_link = module.get('qualtrics_default_link', None)
        if default_link is None:
            raise Exception('No default link set in config.')

        link = QualtricsLink.get_link(program, int(activity_ordinal),
                                      default_link)
        return {'success': True, 'data': link}
Пример #9
0
    def get_reminders_by_date(self, date_string=None):
        emails_by_user = collections.defaultdict(list)

        # render each unsent reminder
        for program in Program.list_all():
            for user_type in ['student', 'teacher']:
                for reminder in program.get_reminders(user_type):
                    new_emails = self._get_emails_from_reminder(
                        date_string, program, user_type, reminder)
                    for user_id, emails in new_emails.items():
                        emails_by_user[user_id] += emails

        # consolidate
        # If a user has more than one message then they should be merged into a
        # single message.
        merged = {}
        for user_id, emails in emails_by_user.items():
            merged[user_id] = Reminder.merge_multiple_emails(emails)

        return merged.values()
Пример #10
0
    def do(self, program_abbreviation, template_name):

        abbr = program_abbreviation.upper()
        program_list = self.internal_api.get('program', {'abbreviation': abbr})
        if len(program_list) is not 1:
            logging.error("Bad program link: {}.".format(self.request.path))
            return self.http_not_found()

        program = program_list[0]
        program_config = Program.get_app_configuration(abbr)

        if getattr(program_config, 'public_registration', False):
            # Allow public users to look up cohorts, because this program is
            # public-access.
            temp_api = self.internal_api
        elif self.access_restricted(['public']):
            # Programs which use public registration don't require sign-in to
            # view documents. If this is not a public registration program and
            # the user is not signed in, redirect them to login.
            return
        else:
            temp_api = self.api

        # Some documents require the cohort id to display the cohort code.
        cohort_id = self.request.get('cohort')
        # Validate the id. It should exist and match the program.
        try:
            cohort = temp_api.get_from_path('cohort', cohort_id)
        except:
            cohort = None

        template_path = 'programs/{}/{}.html'.format(program_abbreviation,
                                                     template_name)
        params = {
            'cohort': cohort,
            'template_name': template_name,
            'program': program,
        }
        template_directory = ''  # i.e. from the root of the app

        return template_path, params, template_directory
Пример #11
0
 def test_default(self):
     """If no link entities exist, the default link is returned."""
     p = Program.create(name='Test Program', abbreviation='TP1')
     default_link = 'qualtrics.com'
     link = QualtricsLink.get_link(p, 1, default_link=default_link)
     self.assertEquals(link, default_link)
Пример #12
0
    def do(self, program_abbreviation, template_name):
        if self.access_restricted(['public']):
            return

        abbr = program_abbreviation.upper()
        program_list = self.internal_api.get('program', {'abbreviation': abbr})
        if len(program_list) is not 1:
            logging.error("Bad program link: {}.".format(self.request.path))
            return self.http_not_found()
        program = program_list[0]

        # We'll display a warning to the user if this is not true.
        valid_url = True

        # The cohort is also a critical part of the app, otherwise we can't
        # record data.
        cohort = None
        cohort_id = self.request.get('cohort')
        # Validate the id. It should exist and match the program.
        if cohort_id == '':
            valid_url = False
        else:
            cohort = self.api.get_from_path('cohort', cohort_id)
            if cohort is None:
                valid_url = False
            else:
                if program.id not in cohort.assc_program_list:
                    valid_url = False

        # The classroom is critical IF the user is a student.
        classroom = None
        classroom_id = self.request.get('classroom')
        if template_name == 'student' and cohort is not None:
            # Validate.
            if classroom_id == '':
                valid_url = False
            else:
                classroom = self.api.get_from_path('classroom', classroom_id)
                if classroom is None:
                    valid_url = False
                else:
                    if cohort.id not in classroom.assc_cohort_list:
                        valid_url = False

        if not valid_url:
            return self.http_not_found()

        # get program app config
        app_config = Program.get_app_configuration(abbr, template_name)

        template_path = 'programs/{}/{}.html'.format(program_abbreviation,
                                                     template_name)
        params = {
            'show_main_nav': False,
            'show_footer': False,
            'template_name': template_name,
            'program': program,
            'cohort': cohort,
            'classroom': classroom,
            'config_json': json.dumps(app_config),
            'server_date_time': str(datetime.datetime.now()),
        }
        template_directory = ''  # i.e. from the root of the app

        return template_path, params, template_directory
Пример #13
0
    def do(self, program_abbreviation):
        user = self.get_current_user()

        # initialize variables
        registration_requested = self.request.get('registration_requested')
        redirect = str(self.request.get('redirect'))
        login_url = '/yellowstone_login'
        success_message = None
        error_message = None
        program_id = None
        program_name = None
        show_registration = False
        show_google_login = True

        # if a program was specified, look it up and adjust future redirects
        if program_abbreviation:
            program_abbreviation = program_abbreviation.upper()
            login_url += '/' + program_abbreviation
            # User has arrived via a program-specific link. Look up the program
            # to get its id (for association) and name (for a welcome message).
            results = self.internal_api.see(
                'program', {'abbreviation': program_abbreviation})
            if len(results) is 1:
                # If this is a public-registration program, then deny access.
                program_conf = Program.get_app_configuration(
                    program_abbreviation)
                if getattr(program_conf, 'public_registration', False):
                    return self.http_not_found()
                # Otherwise we can continue...
                program = results[0]
                program_id = program.id
                program_name = program.name
                show_registration = True
            elif len(results) is 0:
                # There's no program with this abbreviation.
                return self.http_not_found()

        # Set up how the user will COME BACK from google login, which will be
        # used if they click the google login button.
        google_redirect = '{}?registration_requested=google'.format(login_url)
        if redirect:
            google_redirect += '&redirect={}'.format(redirect)
        if program_id:
            google_redirect += '&program={}'.format(program_id)
        google_login_url = app_engine_users.create_login_url(google_redirect)

        if registration_requested:
            # They're signed in with google or facebok, allow registration IF
            # they've arrive with a program id. If they're a new user AND
            # there's no program id, display a failure message.
            auth_response = self.authenticate(auth_type=registration_requested)
            if auth_response is None:
                error_message = "Error during sign in. Please try again."
                redirect = None
            elif auth_response is False and program_id is None:
                error_message = ("Unable to register.<br>Please use the "
                                 "registration link provided by your school.")
                redirect = None
            else:
                # OK to register and/or sign in; register() does both
                user, user_is_new = self.register(program_id,
                                                  registration_requested)
                if user is False:
                    error_message = ("You already have a PERTS account with "
                                     "that email address.")
                    redirect = None
                else:
                    if (user.user_type in ['teacher', 'school_admin']
                            and not user.registration_complete):
                        # Special case: force certain users to take additional
                        # registration steps. The registration_email_sent flag
                        # displays a confirmation that a registration email was
                        # sent.
                        redirect = '/registration_survey?'
                        if user_is_new:
                            redirect += 'registration_email_sent=true&'
                        if program_id:
                            redirect += 'program={}&'.format(program_id)

        # Any users who have successfully registered or logged in via the code
        # above and are still here should be sent on (this is normal for google
        # users, who are returning from a google login page).
        # Also, any signed in user with a home page arriving at login should be
        # redirected to their home page.
        if (user is not False and user.user_type != 'public'
                and user.user_type in config.user_home_page):
            if not redirect:
                redirect = config.user_home_page[user.user_type]
            # else leave redirect set per the URL
            self.redirect(redirect)
            return

        # If user has just finished a password reset, show them a message.
        if self.request.get('password_reset_successful') == 'true':
            success_message = "Your password has been reset."
            show_google_login = False

        return 'login.html', {
            'show_registration': show_registration,
            'program_id': program_id,
            'program_name': program_name,
            'google_login_url': google_login_url,
            'error_message': error_message,
            'success_message': success_message,
            'show_google_login': show_google_login,
        }
Пример #14
0
    def register(self, program_id, auth_type, username=None, password=None, cohort_id=None):
        """Logs in users and registers them if they're new.

        Raises exceptions when the system appears to be broken (e.g. redundant
        users).

        Returns tuple(
            user - mixed, False when given non-sensical data by users (e.g.
                registering an existing email under a new auth type) or
                matching user entity,
            user_is_new - bool, True if user was newly registered
        )

        """
        user_is_new = False

        if auth_type not in config.allowed_auth_types:
            raise Exception("Bad auth_type: {}.".format(auth_type))
        if auth_type == 'direct':
            if None in [username, password]:
                raise Exception("Credentials incomplete.")
            creation_kwargs = {
                'login_email': username,
                'auth_id': 'direct_' + username,
                'plaintext_password': password,  # it WILL be hashed later
            }

        # These are the third party identity providers we currently know
        # how to handle. See util_handlers.BaseHandler.get_third_party_auth().
        elif auth_type in ['google']:
            # may raise CredentialsMissing
            creation_kwargs = self.get_third_party_auth(auth_type)

        # Check that the user hasn't already registered in two ways.

        # 1) If the email matches but the auth type is different, we reject
        #    the request to register so the UI can warn the user.
        email_match_params = {'login_email': creation_kwargs['login_email']}
        email_matches = self.internal_api.get('user', email_match_params)
        for user in email_matches:
            if user.auth_type() != auth_type:
                user = False
                return (user, user_is_new)

        # 2) If the auth_id matches, they tried to register when they should
        #    have logged in. Just log them in.
        auth_match_params = {'auth_id': creation_kwargs['auth_id']}
        auth_matches = self.internal_api.get('user', auth_match_params)
        if len(auth_matches) == 1:
            # they already have an account
            user = auth_matches[0]

        # This user hasn't already registered. Register them.
        elif len(auth_matches) == 0:
            user_is_new = True

            # Having collected the user's information, build a user object.
            creation_kwargs['user_type'] = 'teacher'
            # Include the program id as an initial relationship (see
            # config.optional_associations).
            creation_kwargs['program'] = program_id
            user = self.api.create('user', creation_kwargs)

            # Refresh the api with the user's new privileges.
            self.api = Api(user)
            # Send them an email to confirm that they have registered.
            # The email text is hard coded in the program app.
            program = self.api.get_from_path('program', program_id)
            program_conf = Program.get_app_configuration(program.abbreviation)
            mandrill.send(
                to_address=creation_kwargs['login_email'],
                subject=program_conf.registration_email_subject,
                body=mandrill.render_markdown(
                    program_conf.registration_email_body),
                template_data={'email': creation_kwargs['login_email'],
                               'cohort_id': cohort_id}
            )

            logging.info('url_handlers.BaseHandler.register()')
            logging.info('sending an email to: {}'
                         .format(creation_kwargs['login_email']))

        # There's a big problem.
        else:
            logging.error("Two matching users! {}".format(auth_match_params))
            # Sort the users by modified time, so we take the most recently
            # modified one.
            auth_matches = sorted(
                auth_matches, key=lambda u: u.modified, reverse=True)
            user = auth_matches[0]

        # Sign in the user.
        self.session['user'] = user.id

        return (user, user_is_new)
Пример #15
0
    def do(self):
        params = util.get_request_dictionary(self.request)

        # Check that the requested program allows public registration.
        program = self.internal_api.get_from_path('program', params['program'])
        program_config = Program.get_app_configuration(program.abbreviation)
        if not getattr(program_config, 'public_registration', False):
            user = self.get_current_user()
            logging.error("User {} attempted public registration on program "
                          "{}, but it isn't allowed.".format(
                              user, program.abbreviation))

        # Create a school admin based on the user's information.
        # They get the special auth_type 'public', preventing them from
        # appearing in sign-in queries or reset-password queries.
        # However, the user entity will still hold data and be associated with
        # the created schools.
        params['user']['user_type'] = 'school_admin'
        params['user']['auth_id'] = 'public_' + params['user']['login_email']
        school_admin = self.internal_api.create('user', params['user'])

        # If the school already exists, use the id to find the right cohort.
        if 'existing_school_id' in params:
            s_id = params['existing_school_id']
            cohort_list = self.internal_api.get('cohort',
                                                {'assc_school_list': s_id})
            if len(cohort_list) is not 1:
                raise Exception("Problem with public registration: found {} "
                                "cohorts for school {}".format(
                                    len(cohort_list), s_id))
            cohort = cohort_list[0]
            school = None
            classroom = None
            activities = None

        # Otherwise, create a school, cohort, and classroom based on the
        # provided data.
        else:
            school = self.internal_api.create('school', params['new_school'])
            cohort = self.internal_api.create(
                'cohort', {
                    'name': params['new_school']['name'],
                    'school': school.id,
                    'program': program.id,
                    'promised_students': params['promised_students'],
                })
            classroom = self.internal_api.create(
                'classroom', {
                    'name': 'All Students',
                    'program': program.id,
                    'cohort': cohort.id,
                    'user': school_admin.id,
                })
            activities = self.internal_api.init_activities(
                'student',
                school_admin.id,
                program.id,
                cohort_id=cohort.id,
                classroom_id=classroom.id)

        # Whether the cohort is new or exisiting, make the new user owner of it
        self.internal_api.associate('set_owner', school_admin, cohort)

        # Send an email to the user with all the information they need to
        # participate.
        mandrill.send(to_address=school_admin.login_email,
                      subject=program_config.registration_email_subject,
                      body=mandrill.render_markdown(
                          program_config.registration_email_body),
                      template_data={
                          'email': school_admin.login_email,
                          'cohort_id': cohort.id
                      })

        logging.info('api_handlers.CreatePublicSchoolHandler')
        logging.info('sending an email to: {}'.format(
            school_admin.login_email))

        return {
            'success': True,
            'data': {
                'user':
                school_admin.to_dict(),
                'program':
                program.to_dict(),
                'school':
                school.to_dict() if school else None,
                'cohort':
                cohort.to_dict(),
                'classroom':
                classroom.to_dict() if classroom else None,
                'activities':
                ([a.to_dict() for a in activities] if activities else None),
            }
        }