Пример #1
0
    def _validateVerbResult(self, result, verb, obj_data):
        completedVerbs = ['completed', 'mastered', 'passed', 'failed']

        #If completion is false then verb cannot be completed, mastered,
        if 'completion' in result:
            if result['completion'] == False:
                if verb in completedVerbs:
                    #Throw exceptions b/c those verbs must have true completion
                    raise exceptions.ParamError(
                        'Completion must be True if using the verb ' + verb)

        if verb == 'mastered' and result['success'] == False:
            #Throw exception b/c mastered and success contradict each other or completion is false
            raise exceptions.ParamError(
                'Result success must be True if verb is ' + verb)

        if verb == 'passed' and result['success'] == False:
            #Throw exception b/c passed and success contradict each other or completion is false
            raise exceptions.ParamError(
                'Result success must be True if verb is ' + verb)

        if verb == 'failed' and result['success'] == True:
            #Throw exception b/c failed and success contradict each other or completion is false
            raise exceptions.ParamError(
                'Result success must be False if verb is ' + verb)
Пример #2
0
    def __init__(self, data, auth):
        # pdb.set_trace()
        unallowed_fields = ['id', 'stored', 'authority']

        for field in unallowed_fields:
            if field in data:
                raise exceptions.ParamError(
                    "%s is not allowed in a SubStatement.")

        if 'objectType' in data['object']:
            if data['object']['objectType'].lower() == 'substatement':
                raise exceptions.ParamError(
                    "SubStatements cannot be nested inside of other SubStatements"
                )

        self._populate(data, auth, sub=True)
Пример #3
0
 def parse(self, data):
     try:
         params = json.loads(data)
     except Exception, e:
         err_msg = "Error parsing the Activity object. Expecting json. Received: %s which is %s" % (
             data, type(data))
         raise exceptions.ParamError(err_msg)
Пример #4
0
 def _parse(self,initial):
     if initial:
         try:
             return json.loads(initial)
         except Exception as e:
             raise exceptions.ParamError("Error parsing the Activity object. Expecting json. Received: %s which is %s" % (initial, type(initial))) 
     return {}
Пример #5
0
    def _parse(self, initial):
        if initial:
            if type(initial) is dict:
                initial = json.dumps(initial)

            #Don't put in try..catching exception to raise exception removes stack trace-will have better stack trace if this fails
            try:
                return json.loads(initial)
            except:
                raise exceptions.ParamError("Invalid JSON")
        return {}
Пример #6
0
    def _build_verb_object(self, incoming_verb):
        verb = {}

        if 'id' not in incoming_verb:
            raise exceptions.ParamError(
                "ID field is not included in statement verb")

        # verb_object, created = models.Verb.objects.get_or_create(verb_id=incoming_verb['id'], statement=self.statement)
        verb_object, created = models.Verb.objects.get_or_create(
            verb_id=incoming_verb['id'])

        if not created:
            existing_lang_map_keys = verb_object.display.all().values_list(
                'key', flat=True)
        else:
            existing_lang_map_keys = []

        # Save verb displays
        if 'display' in incoming_verb:
            # Iterate incoming lang maps
            for verb_lang_map in incoming_verb['display'].items():
                # Make sure it's a dict
                if isinstance(verb_lang_map, tuple):
                    # If incoming key doesn't already exist in verb's lang maps - add it
                    if not verb_lang_map[0] in existing_lang_map_keys:
                        lang_map = self._save_lang_map(verb_lang_map)
                        verb_object.display.add(lang_map)
                    else:
                        existing_verb_lang_map = verb_object.display.get(
                            key=verb_lang_map[0])
                        models.LanguageMap.objects.filter(
                            id=existing_verb_lang_map.id).update(
                                value=verb_lang_map[1])
                        # existing_verb_lang_map.update(value=verb_lang_map[1])
                else:
                    raise exceptions.ParamError(
                        "Verb display for verb %s is not a correct language map"
                        % incoming_verb['id'])
            verb_object.save()
        return verb_object
Пример #7
0
    def _populate(self, stmt_data, auth, sub=False):
        # pdb.set_trace()
        args = {}
        #Must include verb - set statement verb
        try:
            raw_verb = stmt_data['verb']
        except KeyError:
            raise exceptions.ParamError(
                "No verb provided, must provide 'verb' field")

        #Must include object - set statement object
        try:
            statementObjectData = stmt_data['object']
        except KeyError:
            raise exceptions.ParamError(
                "No object provided, must provide 'object' field")

        try:
            raw_actor = stmt_data['actor']
        except KeyError:
            raise exceptions.ParamError(
                "No actor provided, must provide 'actor' field")

        # Throw error since you can't set voided to True
        if 'voided' in stmt_data:
            if stmt_data['voided']:
                raise exceptions.Forbidden(
                    'Cannot have voided statement unless it is being voided by another statement'
                )

        # If not specified, the object is assumed to be an activity
        if not 'objectType' in statementObjectData:
            statementObjectData['objectType'] = 'Activity'

        args['verb'] = self._build_verb_object(raw_verb)

        valid_agent_objects = ['agent', 'group']
        # Check to see if voiding statement
        # if raw_verb['id'] == 'http://adlnet.gov/expapi/verbs/voided':
        if args['verb'].verb_id == 'http://adlnet.gov/expapi/verbs/voided':
            # objectType must be statementRef if want to void another statement
            if statementObjectData['objectType'].lower(
            ) == 'statementref' and 'id' in statementObjectData.keys():
                stmt_ref = self._voidStatement(statementObjectData['id'])
                args['stmt_object'] = stmt_ref
            else:
                raise exceptions.ParamError(
                    "There was a problem voiding the Statement")
        else:
            # Check objectType, get object based on type
            if statementObjectData['objectType'].lower() == 'activity':
                if auth is not None:
                    args['stmt_object'] = Activity(
                        json.dumps(statementObjectData),
                        auth=auth.username).activity
                else:
                    args['stmt_object'] = Activity(
                        json.dumps(statementObjectData)).activity
            elif statementObjectData['objectType'].lower(
            ) in valid_agent_objects:
                args['stmt_object'] = Agent(initial=statementObjectData,
                                            create=True).agent
            elif statementObjectData['objectType'].lower() == 'substatement':
                sub_statement = SubStatement(statementObjectData, auth)
                args['stmt_object'] = sub_statement.statement
            elif statementObjectData['objectType'].lower() == 'statementref':
                try:
                    existing_stmt = models.statement.objects.get(
                        statement_id=statementObjectData['id'])
                except models.statement.DoesNotExist:
                    raise exceptions.IDNotFoundError(
                        "No statement with ID %s was found" %
                        statementObjectData['id'])
                else:
                    stmt_ref = models.StatementRef(
                        ref_id=statementObjectData['id'])
                    stmt_ref.save()
                    args['stmt_object'] = stmt_ref

        #Retrieve actor
        args['actor'] = Agent(initial=stmt_data['actor'], create=True).agent

        #Set voided to default false
        args['voided'] = False

        #Set result when present - result object can be string or JSON object
        if 'result' in stmt_data:
            # args['result'] = self._populateResult(stmt_data, raw_verb)
            args['result'] = self._populateResult(stmt_data, args['verb'])

# Set context when present
        if 'context' in stmt_data:
            args['context'] = self._populateContext(stmt_data)

    # Set timestamp when present
        if 'timestamp' in stmt_data:
            args['timestamp'] = stmt_data['timestamp']

        if 'authority' in stmt_data:
            args['authority'] = Agent(initial=stmt_data['authority'],
                                      create=True).agent
        else:
            if auth:
                authArgs = {}
                authArgs['name'] = auth.username
                authArgs['mbox'] = auth.email
                args['authority'] = Agent(initial=authArgs, create=True).agent

        #See if statement_id already exists, throw exception if it does
        if 'statement_id' in stmt_data:
            try:
                existingSTMT = models.statement.objects.get(
                    statement_id=stmt_data['statement_id'])
            except models.statement.DoesNotExist:
                args['statement_id'] = stmt_data['statement_id']
            else:
                raise exceptions.ParamConflict(
                    "The Statement ID %s already exists in the system" %
                    stmt_data['statement_id'])
        else:
            #Create uuid for ID
            args['statement_id'] = uuid.uuid4()

        # args['stored'] = datetime.datetime.utcnow().replace(tzinfo=utc).isoformat()
        #Save statement
        self.statement = self._saveStatementToDB(args, sub)
Пример #8
0
    def _parseXML(self, xmldoc):
        #Create namespace and get the root
        ns = {'tc':'http://projecttincan.com/tincan.xsd'}
        root = xmldoc.getroot()
        act_def = {}

        #Parse the name (required)
        if len(root.xpath('//tc:activities/tc:activity/tc:name', namespaces=ns)) > 0:
            act_def['name'] = {}

            for element in root.xpath('//tc:activities/tc:activity/tc:name', namespaces=ns):
                lang = element.get('lang')
                act_def['name'][lang] = element.text
        else:
            raise exceptions.ParamError("XML is missing name")
            
        #Parse the description (required)    
        if len(root.xpath('//tc:activities/tc:activity/tc:description', namespaces=ns)) > 0:
            act_def['description'] = {}
            
            for element in root.xpath('//tc:activities/tc:activity/tc:description', namespaces=ns):
                lang = element.get('lang')
                act_def['description'][lang] = element.text

            # lang = root.xpath('//tc:activities/tc:activity/tc:description/@lang', namespaces=ns)[0]
            # act_def['description'][lang] = root.xpath('//tc:activities/tc:activity/tc:description/text()', namespaces=ns)[0]
        else:
            raise exceptions.ParamError("XML is missing description")
            
        #Parse the interactionType (required)
        if root.xpath('//tc:activities/tc:activity/tc:interactionType/text()', namespaces=ns)[0]:
            act_def['interactionType'] = root.xpath('//tc:activities/tc:activity/tc:interactionType/text()', namespaces=ns)[0]
        else:
            raise exceptions.ParamError("XML is missing interactionType")

        #Parse the type (required)
        if root.xpath('//tc:activities/tc:activity/@type', namespaces=ns)[0]:
            act_def['type'] = root.xpath('//tc:activities/tc:activity/@type', namespaces=ns)[0]
        else:
            raise exceptions.ParamError("XML is missing type")

        #Parse extensions if any
        if root.xpath('//tc:activities/tc:activity/tc:extensions', namespaces=ns) is not None:
            extensions = {}
            extensionTags = root.xpath('//tc:activities/tc:activity/tc:extensions/tc:extension', namespaces=ns)
            
            for tag in extensionTags:
                extensions[tag.get('key')] = tag.text

            act_def['extensions'] = extensions

        #Parse correctResponsesPattern if any
        if root.xpath('//tc:activities/tc:activity/tc:correctResponsesPattern', namespaces=ns) is not None:
            crList = []
            correctResponseTags = root.xpath('//tc:activities/tc:activity/tc:correctResponsesPattern/tc:correctResponsePattern', namespaces=ns)
                
            for cr in correctResponseTags:    
                crList.append(cr.text)

            act_def['correctResponsesPattern'] = crList

        return act_def
Пример #9
0
    def _populate_correctResponsesPattern(self, act_def, interactionFlag):
        # crp = models.activity_def_correctresponsespattern(activity_definition=self.activity_definition)
        crp = models.activity_def_correctresponsespattern()
        crp.save()
        self.activity_definition.correctresponsespattern = crp
        self.activity_definition.save()
        self.correctResponsesPattern = crp
        
        #For each answer in the pattern save it
        self.answers = []
        for i in act_def['correctResponsesPattern']:
            answer = models.correctresponsespattern_answer(answer=i, correctresponsespattern=self.correctResponsesPattern)
            answer.save()
            self.answers.append(answer)

        #Depending on which type of interaction, save the unique fields accordingly
        if interactionFlag == 'choices' or interactionFlag == 'sequencing':
            self.choices = []
            for c in act_def['choices']:
                choice = models.activity_definition_choice(choice_id=c['id'], activity_definition=self.activity_definition)
                choice.save()
                #Save description as string, not a dictionary
                for desc_lang_map in c['description'].items():
                    if isinstance(desc_lang_map, tuple):
                        lang_map = self._save_lang_map(desc_lang_map)
                        choice.description.add(lang_map)
                        choice.save()
                    else:
                        raise exceptions.ParamError("Choice description must be a language map")

                self.choices.append(choice)
        
        elif interactionFlag == 'scale':
            self.scale_choices = []
            for s in act_def['scale']:
                scale = models.activity_definition_scale(scale_id=s['id'], activity_definition=self.activity_definition)        
                scale.save()
                # Save description as string, not a dictionary
                for desc_lang_map in s['description'].items():
                    if isinstance(desc_lang_map, tuple):
                        lang_map = self._save_lang_map(desc_lang_map)
                        scale.description.add(lang_map)
                        scale.save()
                    else:
                        raise exceptions.ParamError("Scale description must be a language map")                        

                self.scale_choices.append(scale)

        elif interactionFlag == 'steps':
            self.steps = []
            for s in act_def['steps']:
                step = models.activity_definition_step(step_id=s['id'], activity_definition=self.activity_definition)
                step.save()
                #Save description as string, not a dictionary
                for desc_lang_map in s['description'].items():
                    if isinstance(desc_lang_map, tuple):
                        lang_map = self._save_lang_map(desc_lang_map)
                        step.description.add(lang_map)
                        step.save()
                    else:
                        raise exceptions.ParamError("Step description must be a language map")                        

                self.steps.append(step)

        elif interactionFlag == 'source':
            self.source_choices = []
            self.target_choices = []
            for s in act_def['source']:
                source = models.activity_definition_source(source_id=s['id'], activity_definition=self.activity_definition)
                source.save()                        
                #Save description as string, not a dictionary
                for desc_lang_map in s['description'].items():
                    if isinstance(desc_lang_map, tuple):
                        lang_map = self._save_lang_map(desc_lang_map)
                        source.description.add(lang_map)
                        source.save()
                    else:
                        raise exceptions.ParamError("Source description must be a language map")                        
                self.source_choices.append(source)
            
            for t in act_def['target']:
                target = models.activity_definition_target(target_id=t['id'], activity_definition=self.activity_definition)
                target.save()
                #Save description as string, not a dictionary
                for desc_lang_map in t['description'].items():
                    if isinstance(desc_lang_map, tuple):
                        lang_map = self._save_lang_map(desc_lang_map)
                        target.description.add(lang_map)
                        target.save()
                    else:
                        raise exceptions.ParamError("Target description must be a language map")                        

                self.target_choices.append(target)        
Пример #10
0
    def _populate_definition(self, act_def, act_id, objType):
        #Needed for cmi.interaction args
        interactionFlag = ""

        #Check if all activity definition required fields are present - deletes existing activity model
        #if error with required activity definition fields
        for k in Activity.ADRFs:
            if k not in act_def.keys() and k != 'extensions':
                raise exceptions.ParamError("Activity definition error with key: %s" % k)

        #Check definition type
        # if act_def['type'] not in Activity.ADTs:
        #     raise Exception("Activity definition type not valid")


        #If the type is cmi.interaction, have to check interactionType
        if act_def['type'] == 'cmi.interaction':

            scormInteractionTypes = ['true-false', 'choice', 'fill-in', 'long-fill-in',
                                     'matching', 'performance', 'sequencing', 'likert', 'numeric',
                                     'other']
        
            #Check if valid SCORM interactionType
            if act_def['interactionType'] not in scormInteractionTypes:
                raise exceptions.ParamError("Activity definition interactionType not valid")

            #Must have correctResponsesPattern if they have a valid interactionType
            try:
                act_def['correctResponsesPattern']  
            except KeyError:    
                raise exceptions.ParamError("Activity definition missing correctResponsesPattern")    

            #Multiple choice and sequencing must have choices
            if act_def['interactionType'] == 'choice' or \
                act_def['interactionType'] == 'sequencing':
                    try:
                        act_def['choices']
                    except KeyError:
                        raise exceptions.ParamError("Activity definition missing choices")
                    interactionFlag = 'choices' 

            #Matching must have both source and target
            if act_def['interactionType'] == 'matching':
                try:
                    act_def['source']
                    act_def['target']
                except KeyError:
                    raise exceptions.ParamError("Activity definition missing source/target for matching")
                interactionFlag = 'source'

            #Performance must have steps
            if act_def['interactionType'] == 'performance':
                try:
                    act_def['steps']
                except KeyError:
                    raise exceptions.ParamError("Activity definition missing steps for performance")    
                interactionFlag = 'steps'

            #Likert must have scale
            if act_def['interactionType'] == 'likert':
                try:
                    act_def['scale']
                except KeyError:
                    raise exceptions.ParamError("Activity definition missing scale for likert")
                interactionFlag = 'scale'

        self.activity_definition = self._save_activity_definition_to_db(act_def['type'], act_def.get('interactionType', None))

        # Save activity definition name and description
        for name_lang_map in act_def['name'].items():
            if isinstance(name_lang_map, tuple):
                lang_map = self._save_lang_map(name_lang_map)
                self.activity_definition.name.add(lang_map)
                self.activity_definition.save()
            else:
                raise exceptions.ParamError("Activity with id %s has a name that is not a language map" % act_id)

        for desc_lang_map in act_def['description'].items():
            if isinstance(desc_lang_map, tuple):
                lang_map = self._save_lang_map(desc_lang_map)
                self.activity_definition.description.add(lang_map)
                self.activity_definition.save()
            else:
                raise exceptions.ParamError("Activity with id %s has a description that is not a language map" % act_id)

        self.activity = self._save_actvity_to_db(act_id, objType, self.activity_definition)

        #If there is a correctResponsesPattern then save the pattern
        if 'correctResponsesPattern' in act_def.keys():
            self._populate_correctResponsesPattern(act_def, interactionFlag)

        #See if activity definition has extensions
        if 'extensions' in act_def.keys():
            self._populate_extensions(act_def) 
Пример #11
0
    def _populate(self, the_object):
        valid_schema = False
        xml_data = {}
        
        #Must include activity_id - set object's activity_id
        try:
            activity_id = the_object['id']
        except KeyError:
            raise exceptions.ParamError("No id provided, must provide 'id' field")
        # Check if activity ID already exists
        id_list = models.activity.objects.values_list('activity_id', flat=True)
        if activity_id in id_list:
            # Grab pre-existing activity
            existing_activity = models.activity.objects.get(activity_id=activity_id)

            # If authority is required to update the activity
            if existing_activity.authoritative is not None:
                # Request has correct authority
                if existing_activity.authoritative == self.auth:
                    # Update name and desc if needed
                    self._update_activity_name_and_description(the_object, existing_activity)
            
                    # Set activity to existing one
                    self.activity = existing_activity
                # Someone with wrong auth trying to update activity
                else:
                    raise exceptions.Forbidden("This ActivityID already exists, and you do not have" + 
                        " the correct authority to create or update it.") 
            # No auth required to update activity
            else:
                # Update name and desc if needed
                self._update_activity_name_and_description(the_object, existing_activity)
        
                # Set activity to existing one
                self.activity = existing_activity                

        # Activity ID doesn't exist, create a new one
        else:
            #Set objectType to nothing
            objectType = None

            #ObjectType should always be Activity when present
            if 'objectType' in the_object.keys():
                objectType = 'Activity'

            #Try to grab XML from ID if no other JSON is provided - since it won't have a definition it's not a link
            #therefore it can be allowed to not resolve and will just return an empty dictionary
            if not 'definition' in the_object.keys():
                xml_data = self._validateID(activity_id)

                #If the ID validated against the XML schema then proceed with populating the definition with the info
                #from the XML - else just save the activity (someone sent in an ID that doesn't resolve and an objectType
                #with no other data)                
                if xml_data:
                    self._populate_definition(xml_data, activity_id, objectType)
                else:    
                    self.activity = self._save_actvity_to_db(activity_id, objectType)
            #Definition is provided
            else:
                activity_definition = the_object['definition']
             
                #Verify the given activity_id resolves if it is a link (has to resolve if link) 
                try:
                    if activity_definition['type'] == 'link':
                        try:
                            Activity.validator(activity_id)
                        except ValidationError, e:
                            raise exceptions.ParamError(str(e))
                    else:
                        #Type is not a link - it can be allowed to not resolve and will just return an empty dictionary    
                        #If activity is not a link, the ID either must not resolve or validate against metadata schema
                        xml_data = self._validateID(activity_id)
Пример #12
0
    def _update_activity_name_and_description(self, new_activity, existing_activity):
        # Try grabbing the activity definition (these aren't required)
        existing_act_def = None        
        try:
            existing_act_def = models.activity_definition.objects.get(activity=existing_activity)
        except models.activity_definition.DoesNotExist:
            pass

        # If there is an existing activity definition and the names or descriptions are different,
        # update it with new name and/or description info
        if existing_act_def:
            # Get list of existing name lang maps
            existing_name_lang_map_set = existing_act_def.name.all()

            # Make lists of keys and values from existing name lang maps
            existing_name_key_set = existing_name_lang_map_set.values_list('key', flat=True)

            # Get list of existing desc lang maps
            existing_desc_lang_map_set = existing_act_def.description.all()

            # Make lists of keys and values from existing desc lang maps
            existing_desc_key_set = existing_desc_lang_map_set.values_list('key', flat=True)

            # Loop through all language maps in name
            try:
                the_definition = new_activity['definition']
            except KeyError:
                raise exceptions.ParamError("Activity missing definition")
            try:
                the_names = the_definition['name']
            except KeyError:
                raise exceptions.ParamError("Activity definition has no name attribute")
            for new_name_lang_map in the_names.items():
                # If there is already an entry in the same language
                if new_name_lang_map[0] in existing_name_key_set:
                    name_same = True                    
                    # Retrieve existing language map with same key (all in the existing act_def)
                    existing_lang_map = existing_act_def.name.get(key=new_name_lang_map[0])
                    name_same = self._check_activity_definition_value(new_name_lang_map[1], existing_lang_map.value)
                    # If names are different, update the language map with the new name
                    if not name_same:
                        models.LanguageMap.objects.filter(id=existing_lang_map.id).update(value=new_name_lang_map[1])
                # Else it's a new lang map and needs added
                else:
                    lang_map = self._save_lang_map(new_name_lang_map)
                    existing_act_def.name.add(lang_map)
                    existing_act_def.save()                    

            # Loop through all language maps in description
            try:
                the_descriptions = the_definition['description']
            except KeyError:
                raise exceptions.ParamError("Activity definition has no description attribute")
            for new_desc_lang_map in the_descriptions.items():
                # If there is already an entry in the same language
                if new_desc_lang_map[0] in existing_desc_key_set:
                    desc_same = False
                    # Retrieve existing language map with same key (all in the existing act_def)
                    existing_lang_map = existing_act_def.description.get(key=new_desc_lang_map[0])
                    desc_same = self._check_activity_definition_value(new_desc_lang_map[1], existing_lang_map.value)
                    # If desc are different, update the langage map with the new desc
                    if not desc_same:
                        models.LanguageMap.objects.filter(id=existing_lang_map.id).update(value=new_desc_lang_map[1])
                else:
                    lang_map = self._save_lang_map(new_desc_lang_map)
                    existing_act_def.description.add(lang_map)
                    existing_act_def.save()                    
Пример #13
0
        #only conform to the TC schema - if it fails that means the URL didn't resolve at all
        try:    
            act_resp = urllib2.urlopen(act_id, timeout=10)
        except Exception, e:
            resolves = False
        else:
            act_XML = act_resp.read()

        #Validate that it is good XML with the schema - if it fails it means the URL resolved but didn't conform to the schema
        if resolves:
            try:
                act_xmlschema_doc = etree.parse(StringIO(act_XML))    
                validXML = Activity.XMLschema.validate(act_xmlschema_doc)
            except Exception, e:
                #TODO: should put any warning here? validXML will still be false if there is an exception
                raise exceptions.ParamError("The activity id resolved to invalid activity description")
                #pass        

        #Parse XML, create dictionary with the values from the XML doc
        if validXML:
            return self._parseXML(act_xmlschema_doc)
        else:
            return {}

    # TODO: Thought xml was taken out? Need to update parsing of it then for name and desc?
    def _parseXML(self, xmldoc):
        #Create namespace and get the root
        ns = {'tc':'http://projecttincan.com/tincan.xsd'}
        root = xmldoc.getroot()
        act_def = {}