Example #1
0
    def clean(self):

        config_schema = schema.Schema({
            'name': str,
            'app_sequence': list,
            'participation_fee': object,
            'real_world_currency_per_point': object,
            'num_demo_participants': int,
            'doc': str,
            str: object,
        })

        try:
            config_schema.validate(self)
        except schema.SchemaError as e:
            raise ValueError(
                'settings.SESSION_CONFIGS: {}'.format(e)) from None

        # Allow non-ASCII chars in session config keys, because they are
        # configurable in the admin, so need to be readable by non-English
        # speakers. However, don't allow punctuation, spaces, etc.
        # They make it harder to reason about and could cause problems
        # later on. also could uglify the user's code.

        INVALID_IDENTIFIER_MSG = (
            'Key "{}" in settings.SESSION_CONFIGS '
            'must not contain spaces, punctuation, '
            'or other special characters. '
            'It can contain non-English characters, '
            'but it must be a valid Python variable name '
            'according to string.isidentifier().')

        for key in self:
            if not key.isidentifier():
                raise ValueError(INVALID_IDENTIFIER_MSG.format(key))

        validate_alphanumeric(
            self['name'],
            identifier_description='settings.SESSION_CONFIGS name')

        app_sequence = self['app_sequence']
        if len(app_sequence) != len(set(app_sequence)):
            msg = ('settings.SESSION_CONFIGS: '
                   'app_sequence of "{}" '
                   'must not contain duplicate elements. '
                   'If you want multiple rounds, '
                   'you should set Constants.num_rounds.')
            raise ValueError(msg.format(self['name']))

        if len(app_sequence) == 0:
            raise ValueError(
                'settings.SESSION_CONFIGS: Need at least one subsession.')

        self.setdefault('display_name', self['name'])
        self.setdefault('doc', '')

        self['participation_fee'] = RealWorldCurrency(
            self['participation_fee'])
Example #2
0
    def get_participant_labels(self):
        '''
        Decided to just re-read the file on every request,
        rather than loading in the DB. Reasons:

        (1) Simplifies the code; we don't need an ExpectedRoomParticipant model,
            and don't need to load data into there (which involves considerations
            of race conditions)
        (2) Don't need any complicated rule deciding when to reload the file,
            whether it's upon starting the process or resetting the database,
            or both. Should the status be stored in the DB or in the process?
        (3) Checking if a given participant label is in the file is actually faster
            than looking it up in the DB table, even with .filter() and and index!
            (tested on Postgres with 10000 iterations: 17s vs 18s)
        '''

        # if i refactor this, i should use chardet instead
        encodings = ['ascii', 'utf-8', 'utf-16']
        for e in encodings:
            try:
                plabel_path = self.participant_label_file
                with codecs.open(plabel_path, "r", encoding=e) as f:
                    seen = set()
                    labels = []
                    for line in f:
                        label = line.strip()
                        if not label:
                            continue
                        validate_alphanumeric(
                            label, identifier_description='participant label')
                        if label not in seen:
                            labels.append(label)
                            seen.add(label)
            except UnicodeDecodeError:
                continue
            except FileNotFoundError:
                msg = ('settings.ROOMS: The room "{}" references '
                       ' nonexistent participant_label_file "{}".')
                raise FileNotFoundError(
                    msg.format(self.name,
                               self.participant_label_file)) from None
            else:
                return labels
        raise Exception('settings.ROOMS: participant_label_file "{}" '
                        'not encoded correctly.'.format(
                            self.participant_label_file))
Example #3
0
 def load_participant_labels_to_db(self):
     if self.has_participant_labels():
         encodings = ['ascii', 'utf-8', 'utf-16']
         for e in encodings:
             try:
                 plabel_path = self.participant_label_file
                 with codecs.open(plabel_path, "r", encoding=e) as f:
                     seen = set()
                     labels = []
                     for line in f:
                         label = line.strip()
                         if not label:
                             continue
                         label = validate_alphanumeric(
                             line.strip(),
                             identifier_description='participant label'
                         )
                         if label not in seen:
                             labels.append(label)
                             seen.add(label)
             except UnicodeDecodeError:
                 continue
             except OSError as err:
                 # this code is equivalent to "except FileNotFoundError:"
                 # but works in py2 and py3
                 if err.errno == errno.ENOENT:
                     msg = (
                         'settings.ROOMS: The room "{}" references '
                         ' nonexistent participant_label_file "{}".'
                     )
                     raise IOError(
                         msg.format(self.name, self.participant_label_file)
                     ) from None
                 raise err
             else:
                 with global_lock():
                     # before I used select_for_update to prevent race
                     # conditions. But the queryset was not evaluated
                     # so it did not hit the DB. maybe simpler to use an
                     # explicit lock
                     ExpectedRoomParticipant.objects.select_for_update()
                     ExpectedRoomParticipant.objects.filter(
                         room_name=self.name).delete()
                     ExpectedRoomParticipant.objects.bulk_create(
                         ExpectedRoomParticipant(
                             room_name=self.name,
                             participant_label=participant_label
                         ) for participant_label in labels
                     )
                 self._participant_labels_loaded = True
                 return
         raise Exception(
             'settings.ROOMS: participant_label_file "{}" '
             'not encoded correctly.'.format(self.participant_label_file)
         )
     raise Exception('no guestlist')
Example #4
0
    def __init__(self, config_dict):
        self.participant_label_file = config_dict.get('participant_label_file')

        self.name = validate_alphanumeric(
            config_dict['name'],
            identifier_description='settings.ROOMS room name')
        self.display_name = config_dict['display_name']
        # secure URLs are complicated, don't use them by default
        self.use_secure_urls = config_dict['use_secure_urls']
        if self.use_secure_urls and not self.participant_label_file:
            raise ValueError(
                'Room "{}": you must either set "participant_label_file", '
                'or set "use_secure_urls": False'.format(self.name))
Example #5
0
 def __init__(self,
              name,
              display_name,
              use_secure_urls,
              participant_label_file=None):
     self.name = validate_alphanumeric(
         name, identifier_description='settings.ROOMS room name')
     if use_secure_urls and not participant_label_file:
         raise ValueError(
             'Room "{}": you must either set "participant_label_file", '
             'or set "use_secure_urls": False'.format(name))
     self.participant_label_file = participant_label_file
     self.display_name = display_name
     # secure URLs are complicated, don't use them by default
     self.use_secure_urls = use_secure_urls
Example #6
0
    def __init__(self, config_dict):
        self.participant_label_file = config_dict.get('participant_label_file')

        self.name = validate_alphanumeric(
            config_dict['name'],
            identifier_description='settings.ROOMS room name')
        self.display_name = config_dict['display_name']
        # secure URLs are complicated, don't use them by default
        self.use_secure_urls = config_dict['use_secure_urls']
        self.pin_code = config_dict.get('pin_code')
        self._participant_labels_loaded = False
        if self.use_secure_urls and not self.participant_label_file:
            raise ValueError(
                'Room "{}": you must either set "participant_label_file", '
                'or set "use_secure_urls": False'.format(self.name)
            )
Example #7
0
    def clean(self):

        required_keys = [
            'name',
            'app_sequence',
            'num_demo_participants',
            'participation_fee',
            'real_world_currency_per_point',
        ]

        for key in required_keys:
            if key not in self:
                raise SessionConfigError(
                    'settings.SESSION_CONFIGS: all configs must have a '
                    '"{}"'.format(key)
                )

        datatypes = {
            'app_sequence': list,
            'num_demo_participants': int,
            'name': str,
        }

        for key, datatype in datatypes.items():
            if not isinstance(self[key], datatype):
                msg = (
                    'SESSION_CONFIGS "{}": '
                    'the entry "{}" must be of type {}'
                ).format(self['name'], key, datatype.__name__)
                raise SessionConfigError(msg)

        # Allow non-ASCII chars in session config keys, because they are
        # configurable in the admin, so need to be readable by non-English
        # speakers. However, don't allow punctuation, spaces, etc.
        # They make it harder to reason about and could cause problems
        # later on. also could uglify the user's code.

        INVALID_IDENTIFIER_MSG = (
            'Key "{}" in settings.SESSION_CONFIGS '
            'must not contain spaces, punctuation, '
            'or other special characters. '
            'It can contain non-English characters, '
            'but it must be a valid Python variable name '
            'according to string.isidentifier().'
        )

        for key in self:
            if not key.isidentifier():
                raise SessionConfigError(INVALID_IDENTIFIER_MSG.format(key))

        validate_alphanumeric(
            self['name'],
            identifier_description='settings.SESSION_CONFIGS name'
        )

        app_sequence = self['app_sequence']
        if len(app_sequence) != len(set(app_sequence)):
            msg = (
                'settings.SESSION_CONFIGS: '
                'app_sequence of "{}" '
                'must not contain duplicate elements. '
                'If you want multiple rounds, '
                'you should set Constants.num_rounds.')
            raise SessionConfigError(msg.format(self['name']))

        if len(app_sequence) == 0:
            raise SessionConfigError(
                'settings.SESSION_CONFIGS: app_sequence cannot be empty.')

        self.setdefault('display_name', self['name'])
        self.setdefault('doc', '')

        self['participation_fee'] = RealWorldCurrency(
            self['participation_fee'])
Example #8
0
    def clean(self):

        config_schema = schema.Schema({
            'name': str,
            'app_sequence': list,
            'participation_fee': object,
            'real_world_currency_per_point': object,
            'num_demo_participants': int,
            'doc': str,
            str: object,
        })

        try:
            config_schema.validate(self)
        except schema.SchemaError as e:
            raise ValueError('settings.SESSION_CONFIGS: {}'.format(e)) from None

        # Allow non-ASCII chars in session config keys, because they are
        # configurable in the admin, so need to be readable by non-English
        # speakers. However, don't allow punctuation, spaces, etc.
        # They make it harder to reason about and could cause problems
        # later on. also could uglify the user's code.

        INVALID_IDENTIFIER_MSG = (
            'Key "{}" in settings.SESSION_CONFIGS '
            'must not contain spaces, punctuation, '
            'or other special characters. '
            'It can contain non-English characters, '
            'but it must be a valid Python variable name '
            'according to string.isidentifier().'
        )

        for key in self:
            if not key.isidentifier():
                raise ValueError(INVALID_IDENTIFIER_MSG.format(key))

        validate_alphanumeric(
            self['name'],
            identifier_description='settings.SESSION_CONFIGS name'
        )

        app_sequence = self['app_sequence']
        if len(app_sequence) != len(set(app_sequence)):
            msg = (
                'settings.SESSION_CONFIGS: '
                'app_sequence of "{}" '
                'must not contain duplicate elements. '
                'If you want multiple rounds, '
                'you should set Constants.num_rounds.')
            raise ValueError(msg.format(self['name']))

        if len(app_sequence) == 0:
            raise ValueError(
                'settings.SESSION_CONFIGS: Need at least one subsession.')

        self.setdefault('display_name', self['name'])
        self.setdefault('doc', '')

        # TODO: fixed_pay is deprecated as of 2015-05-07,
        # in favor of participation_fee. make this required at some point.
        if (('participation_fee' not in self) and
                ('fixed_pay' in self)):
            warn_msg = (
                "'fixed_pay' is deprecated; "
                "you should rename it to 'participation_fee'.")
            raise ValueError(warn_msg)

            self['participation_fee'] = self['fixed_pay']

        self['participation_fee'] = RealWorldCurrency(
            self['participation_fee'])
Example #9
0
    def clean(self):

        config_schema = schema.Schema({
            'name': str,
            'app_sequence': list,
            'participation_fee': object,
            'real_world_currency_per_point': object,
            'num_demo_participants': int,
            'doc': str,
            str: object,
        })

        try:
            config_schema.validate(self)
        except schema.SchemaError as e:
            raise ValueError('settings.SESSION_CONFIGS: {}'.format(e)) from None

        # Allow non-ASCII chars in session config keys, because they are
        # configurable in the admin, so need to be readable by non-English
        # speakers. However, don't allow punctuation, spaces, etc.
        # They make it harder to reason about and could cause problems
        # later on. also could uglify the user's code.

        INVALID_IDENTIFIER_MSG = (
            'Key "{}" in settings.SESSION_CONFIGS '
            'must not contain spaces, punctuation, '
            'or other special characters. '
            'It can contain non-English characters, '
            'but it must be a valid Python variable name '
            'according to string.isidentifier().'
        )

        for key in self:
            if not key.isidentifier():
                raise ValueError(INVALID_IDENTIFIER_MSG.format(key))

        validate_alphanumeric(
            self['name'],
            identifier_description='settings.SESSION_CONFIGS name'
        )

        app_sequence = self['app_sequence']
        if len(app_sequence) != len(set(app_sequence)):
            msg = (
                'settings.SESSION_CONFIGS: '
                'app_sequence of "{}" '
                'must not contain duplicate elements. '
                'If you want multiple rounds, '
                'you should set Constants.num_rounds.')
            raise ValueError(msg.format(self['name']))

        if len(app_sequence) == 0:
            raise ValueError(
                'settings.SESSION_CONFIGS: Need at least one subsession.')

        self.setdefault('display_name', self['name'])
        self.setdefault('doc', '')

        # TODO: fixed_pay is deprecated as of 2015-05-07,
        # in favor of participation_fee. make this required at some point.
        if (('participation_fee' not in self) and
                ('fixed_pay' in self)):
            warn_msg = (
                "'fixed_pay' is deprecated; "
                "you should rename it to 'participation_fee'.")
            warnings.warn(warn_msg, OtreeDeprecationWarning)

            self['participation_fee'] = self['fixed_pay']

        self['participation_fee'] = RealWorldCurrency(
            self['participation_fee'])

        # normalize to decimal so we can do multiplications, etc
        # quantize because the original value may be a float,
        # which when converted to Decimal may have some 'decimal junk'
        # like 0.010000000000000000208166817...
        self['real_world_currency_per_point'] = Decimal(
            self['real_world_currency_per_point']
        ).quantize(Decimal('0.00001'))