def answer_randomly(task, overwrite=False, halt_impute=True, skip_impute=False, quiet=False): def log(item): if not quiet: print(item) current_answers = [x for x in task.get_current_answer_records()] # sooo... sometimes we might have accidentally created a project with no admin. Use the org admin instead. dummy_user = task.project.organization.get_organization_project( ).get_admins()[0] # we want to communicate back to the caller whether this was fully skipped or not did_anything = False for question in task.module.questions.order_by('definition_order'): type = question.spec['type'] if type == 'raw': print("'raw' question type is out-of-scope, skipping") continue if (halt_impute or skip_impute) and 'impute' in question.spec: log("'impute' handling not yet implemented, skipping " + question.key) if halt_impute: break continue if not overwrite: has_answer = len([ x for x in current_answers if x[1] and x[0].key == question.key ]) > 0 if has_answer: log("Already answered " + question.key) continue answer = None if type == 'yesno': answer = sample(['yes', 'no'], 1)[0] elif type == 'text': answer = get_random_sentence() elif type == 'longtext': answer = get_random_paragraph() elif type == 'choice': answer = sample(question.spec['choices'], 1)[0]['key'] elif type == 'multiple-choice': choices = question.spec['choices'] amount = randint(question.spec['min'], len(choices)) answer = [x['key'] for x in sample(choices, amount)] elif type == 'datagrid': choices = question.spec['fields'] amount = randint(question.spec['min'], len(fields)) answer = [x['key'] for x in sample(fields, amount)] elif type == 'module' and 'module-id' in question.spec: subtask = task.get_or_create_subtask(dummy_user, question, create=True) log("doing subtask") did_anything = True continue if not answer and not (type == 'interstitial' or type == 'raw'): print("Cannot answer question of type '" + type + "'") continue did_anything = True log(str((question.key, type, answer))) taskans, isnew = TaskAnswer.objects.get_or_create(task=task, question=question) from guidedmodules.answer_validation import validator try: value = validator.validate(question, answer) except ValueError as e: print("Answering {}: {}...".format(question.key, e)) #return False break except Error as e: print("------\nUnknown error occurred, debug info follows.\n") print( "Next line is: 'str((question.key, type, answer, question.spec))'" ) print(str((question.key, type, answer, question.spec))) print("------") raise e # Save the value. if taskans.save_answer(value, [], None, dummy_user, "api"): log("Answered {} with {}...".format(question.key, answer)) else: log("No change?") break return did_anything
def project_api(request, org_slug, project_id): from collections import OrderedDict # Get user from API key. api_key = request.META.get("HTTP_AUTHORIZATION", "").strip() if len( api_key ) < 32: # prevent null values from matching against users without api keys return JsonResponse(OrderedDict([ ("status", "error"), ("error", "An API key was not present in the Authorization header.") ]), json_dumps_params={"indent": 2}, status=403) from django.db.models import Q from .models import User, Project try: user = User.objects.get( Q(api_key_rw=api_key) | Q(api_key_ro=api_key) | Q(api_key_wo=api_key)) except User.DoesNotExist: return JsonResponse(OrderedDict([ ("status", "error"), ("error", "A valid API key was not present in the Authorization header.") ]), json_dumps_params={"indent": 2}, status=403) # Get project and check authorization. organization = get_object_or_404(Organization, subdomain=org_slug) project = get_object_or_404(Project, id=project_id, organization=organization) if not project.has_read_priv(user): return JsonResponse(OrderedDict([ ("status", "error"), ("error", "The user associated with the API key does not have read perission on the project." ) ]), json_dumps_params={"indent": 2}, status=403) if request.method == "GET": # Export data. value = project.export_json(include_file_content=False, include_metadata=False) # Return the value as JSON. return JsonResponse(value, json_dumps_params={"indent": 2}) elif request.method == "POST": # Update the value. # Check that the API key has write access. if not project.root_task.has_write_priv(user): return JsonResponse(OrderedDict([ ("status", "error"), ("error", "The user associated with the API key does not have write perission on the project." ) ]), json_dumps_params={"indent": 2}, status=403) if api_key not in (user.api_key_rw, user.api_key_wo): return JsonResponse(OrderedDict([ ("status", "error"), ("error", "The API key does not have write perission on the project.") ]), json_dumps_params={"indent": 2}, status=403) # Parse the new project body. if request.META.get("CONTENT_TYPE") == "application/json": # A JSON object is given in the import/export format. Parse it. import json try: value = json.loads(request.body.decode("utf-8")) except json.decoder.JSONDecodeError as e: return JsonResponse(OrderedDict([ ("status", "error"), ("error", "Invalid JSON in request body. " + str(e)) ]), json_dumps_params={"indent": 2}, status=400) # Update. log = [] ok = project.import_json(value, user, "api", lambda msg: log.append(msg)) else: # Form fields are given in POST and FILES. Process each item... from guidedmodules.models import TaskAnswer from guidedmodules.answer_validation import question_input_parser, validator import django.core.files.uploadedfile # For each item... log = [] ok = True for key, v in list(request.POST.lists()) + list( request.FILES.items()): try: # The item is a dotted path of project + question IDs to the # question to update. Follow the path to find the Task and ModuleQuestion # to update. Start with the project root task, then for each item in the path... task = project.root_task question = None if not key.startswith("project."): raise ValueError("Invalid question ID: " + key) for i, pathitem in enumerate(key.split(".")[1:]): # If this is not the first item, then in the last iteration we # were left with a Task and a ModuleQuestion in that task. Since # we've continued with another dot and path part, the last ModuleQuestion # must have been a module-type question. Move to its *answer*, # which is another Task. if question is not None: if question.spec["type"] != "module": raise ValueError("Invalid question ID: " + key) try: task = task.get_or_create_subtask( user, question) except ValueError: # Raised if the question is not answered and the question uses # a protocol for selecting compliance apps rather than specifying # a concrete Module to use for answers. In this case, we can't # start a Task implicitly. raise ValueError( "Invalid question ID '{}': {} has not been answered yet by a compliance app so its data fields cannot be set." .format(key, ".".join(key.split(".")[:i + 1]))) # Get the question this corresponds to within the task question = task.module.questions.filter( key=pathitem).first() if not question: raise ValueError("Invalid question ID: " + key) # Parse and validate the answer. v = question_input_parser.parse(question, v) v = validator.validate(question, v) # Save. v_file = None if question.spec["type"] == "file": v_file = v v = None ta, _ = TaskAnswer.objects.get_or_create(task=task, question=question) saved = ta.save_answer(v, [], v_file, user, "api") if saved: log.append(key + " updated") else: log.append(key + " unchanged") except ValueError as e: log.append(key + ": " + str(e)) ok = False return JsonResponse(OrderedDict([ ("status", "ok" if ok else "error"), ("details", log), ]), json_dumps_params={"indent": 2})
def set_answer(self, task, question, answer, update_answer_func, basedir, options): # Set the answer to the question for the given task. if question.spec[ "type"] == "module" and question.answer_type_module is not None: # If there is no answer provided, normally we leave # the answer blank. However, for module-type questions # with a module type (not a protocol), we should at least # *start* the sub-task with the module answer type, even if we # don't answer any of its questions, because it may be # a question-less module that only provides output documents. # And because we use get_or_create_subtask, it doesn't matter # if this question was imputed. sub_task = task.get_or_create_subtask(self.dummy_user, question) # self.log("OK", "Answered {} with {}.".format(question.key, sub_task)) if answer is None and options["add_blank_answers"]: answer = update_answer_func({"questions": []}) if answer is not None: self.set_answers(sub_task, answer.setdefault("questions", []), basedir, options) return # Check if the question is answerable by computing the imputed # answers to this task so far. If the question is not answerable, # issue a warning if the user tried to answer it in the input # file, and then move on. if question not in task.get_answers().with_extended_info().can_answer: if answer is not None: self.log( "WARN", "The answer for {} in {} will be ignored because this question's answer is imputed from other values." .format(question.key, self.str_task(task))) return # If the question isn't answered... if answer is None: # If --add-blank-answers is specified on the command line, # add a blank answer record to the YAML for any unanswered # question so the user has an easy spot to provide an answer. # Does not apply to interstitial questions which have no # meaningful answers and module/module-set questions which # we either handled above or can't handle this way. if options["add_blank_answers"] \ and question.spec["type"] not in ("module", "module-set", "raw"): update_answer_func({"answer": None}) return # If --startapps is given on the command-line, then choose an # app automatically for module-type questions with protocols # and keep going to save the choice below. elif options["startapps"] and question.spec["type"] == "module" \ and question.answer_type_module is None and question.spec.get("protocol"): # Look for an app that can be used to answer this question. from siteapp.views import app_satifies_interface for app in options["startapps"]: if app_satifies_interface(app["catalog_info"], question.spec.get("protocol")): # We found one. Set the answer to be the path to the app, # adjusted to be relative to the location of the assemble # YAML file. self.log( "INFO", "Choosing app {} for {} (from --startapps).". format(app["app"], question.key)) answer = update_answer_func( {"app": os.path.relpath(app["app"], basedir)}) break if answer is None: # If no app was available, abort. self.log( "WARN", "No app was available to answer {} in {}.".format( question.key, self.str_task(task))) return else: # If the question isn't answered, and we aren't initializing it using # one of the above options, warn and leave it alone. self.log( "WARN", "No answer was provided for {} in {}.".format( question.key, self.str_task(task))) return # Set an answer to the question. # Get the TaskAnswer record, which has the save_answer function. taskans, isnew = TaskAnswer.objects.get_or_create(task=task, question=question) if question.spec["type"] in ("module", "module-set"): # A module-type question with a protocol (if there was no protocol, it # was handled above) or a module-set question (with or without a protocol). if question.spec["type"] == "module": answers = [answer] else: answers = answer.setdefault("answers", []) # Start the app(s). subtasks = [] for i, answer in enumerate(answers): if question.answer_type_module is not None: # Start the sub-task. subtasks.append( task.get_or_create_subtask(self.dummy_user, question)) else: # Start the app. The app to start is specified in the 'app' key. if not isinstance(answer, dict): self.log( "ERROR", "Invalid data type of answer for {} element {} in {}, got {}." .format(question.key, i, self.str_task(task), repr(answer))) return project = self.start_app(answer.get("app"), basedir) if project is None: return # error # Validate that the protocols match. unimplemented_protocols = set( question.spec.get("protocol", [])) - set( project.root_task.module.spec.get("protocol", [])) if unimplemented_protocols: # There are unimplemented protocols. self.log( "ERROR", "{} doesn't implement the protocol {} required to answer {}." .format(project.root_task.module.app, ", ".join(sorted(unimplemented_protocols)), question.key)) return # Keep the root Task to be an answer to the question. subtasks.append(project.root_task) # Save the answer. if taskans.save_answer(None, subtasks, None, self.dummy_user, "api"): self.log( "OK", "Answered {} with {}.".format( question.key, ", ".join([self.str_task(t) for t in subtasks]))) # Set answers of sub-task. for subtask, subtask_answers in zip(subtasks, answers): self.set_answers( subtask, subtask_answers.setdefault("questions", []), basedir, options) return else: # This is a regular question type with YAML data holding the answer value. # Validate the value, unless the value is null. value = answer["answer"] if value is None: # Null answers are always acceptable. pass else: from guidedmodules.answer_validation import validator try: value = validator.validate(question, value) except ValueError as e: self.log("ERROR", "Answering {}: {}".format(question.key, e)) return # Save the value. if taskans.save_answer(value, [], None, self.dummy_user, "api"): self.log( "OK", "Answered {} with {}.".format(question.key, repr(value))) return # No change, somehow. return
def project_api(request, org_slug, project_id): from collections import OrderedDict # Get user from API key. api_key = request.META.get("HTTP_AUTHORIZATION", "").strip() if len( api_key ) < 32: # prevent null values from matching against users without api keys return JsonResponse(OrderedDict([ ("status", "error"), ("error", "An API key was not present in the Authorization header.") ]), json_dumps_params={"indent": 2}, status=403) from django.db.models import Q from .models import User, Project try: user = User.objects.get( Q(api_key_rw=api_key) | Q(api_key_ro=api_key) | Q(api_key_wo=api_key)) except User.DoesNotExist: return JsonResponse(OrderedDict([ ("status", "error"), ("error", "A valid API key was not present in the Authorization header.") ]), json_dumps_params={"indent": 2}, status=403) # Get project and check authorization. organization = get_object_or_404(Organization, subdomain=org_slug) project = get_object_or_404(Project, id=project_id, organization=organization) if not project.has_read_priv(user): return JsonResponse(OrderedDict([ ("status", "error"), ("error", "The user associated with the API key does not have read perission on the project." ) ]), json_dumps_params={"indent": 2}, status=403) if request.method == "GET": # Export data. value = project.export_json(include_file_content=False, include_metadata=False) # Return the value as JSON. return JsonResponse(value, json_dumps_params={"indent": 2}) elif request.method == "POST": # Update the value. # Check that the API key has write access. if not project.root_task.has_write_priv(user): return JsonResponse(OrderedDict([ ("status", "error"), ("error", "The user associated with the API key does not have write perission on the project." ) ]), json_dumps_params={"indent": 2}, status=403) if api_key not in (user.api_key_rw, user.api_key_wo): return JsonResponse(OrderedDict([ ("status", "error"), ("error", "The API key does not have write perission on the project.") ]), json_dumps_params={"indent": 2}, status=403) # Parse the new project body. if request.META.get("CONTENT_TYPE") == "application/json": # A JSON object is given in the import/export format. Parse it. import json try: value = json.loads(request.body.decode("utf-8")) except json.decoder.JSONDecodeError as e: return JsonResponse(OrderedDict([ ("status", "error"), ("error", "Invalid JSON in request body. " + str(e)) ]), json_dumps_params={"indent": 2}, status=400) # Update. log = [] ok = project.import_json(value, user, "api", lambda msg: log.append(msg)) else: # Form fields are given in POST and FILES. Process each item... from guidedmodules.models import TaskAnswer from guidedmodules.answer_validation import question_input_parser, validator import django.core.files.uploadedfile log = [] ok = True for key, v in list(request.POST.lists()) + list( request.FILES.items()): try: # Find the Task and ModuleQuestion that corresponds to # this key-value pair. task = project.root_task question = None if not key.startswith("project."): raise ValueError("Invalid question ID: " + key) for pathitem in key.split(".")[1:]: # Advance to the task pointed to by the last question. if question is not None: if question.spec["type"] != "module": raise ValueError("Invalid question ID: " + key) ta = task.answers.filter(question=question).first() if ta is None: raise ValueError( "Invalid question ID (question on path is not answered yet): " + key) a = ta.get_current_answer() if not a or a.cleared: raise ValueError( "Invalid question ID (question on path is not answered yet): " + key) task = a.answered_by_task.first() # Get the question this corresponds to within the task question = task.module.questions.filter( key=pathitem).first() if not question: raise ValueError("Invalid question ID: " + key) # Parse and validate the answer. v = question_input_parser.parse(question, v) v = validator.validate(question, v) # Save. v_file = None if question.spec["type"] == "file": v_file = v v = None ta, _ = TaskAnswer.objects.get_or_create(task=task, question=question) saved = ta.save_answer(v, [], v_file, user, "api") if saved: log.append(key + " updated") else: log.append(key + " unchanged") except ValueError as e: log.append(key + ": " + str(e)) ok = False return JsonResponse(OrderedDict([ ("status", "ok" if ok else "error"), ("details", log), ]), json_dumps_params={"indent": 2})
def set_answer(task, question, answer): # Set the answer to the question for the given task. if question.spec[ "type"] == "module" and question.answer_type_module is not None: # If there is no answer provided, normally we leave # the answer blank. However, for module-type questions # with a module type (not a protocol), we should at least # *start* the sub-task with the module answer type, even if we # don't answer any of its questions, because it may be # a question-less module that only provides output documents. log("Starting {}...".format(question.key)) sub_task = task.get_or_create_subtask(Command.dummy_user, question) if answer is not None: Command.set_answers(sub_task, answer.get("questions", [])) return True return False # If the question isn't answered, leave it alone. if answer is None: return False # Set an answer to the question. # Get the TaskAnswer record, which has the save_answer function. taskans, isnew = TaskAnswer.objects.get_or_create(task=task, question=question) if question.spec["type"] in ("module", "module-set"): # A module-type question with a protocol (if there was no protocol, it # was handled above) or a module-set question (with or without a protocol). if question.spec["type"] == "module": answers = [answer] else: answers = answer.get("answers", []) # Start the app(s). for i, answer in enumerate(answers): if question.answer_type_module is not None: # Start the sub-task. answers[i] = task.get_or_create_subtask( Command.dummy_user, question) else: # Start the app. The app to start is specified in the 'app' key. if not isinstance(answer, dict): raise ValueError("invalid data type") project = Command.start_app(answer.get("app")) # Validate that the protocols match. unimplemented_protocols = set( question.spec.get("protocol", [])) - set( project.root_task.module.spec.get("protocol", [])) if unimplemented_protocols: # There are unimplemented protocols. log("{} doesn't implement the protocol {} required to answer {}." .format(project.root_task.module.app, ", ".join(sorted(unimplemented_protocols)), question.key)) return False # Keep the root Task to be an answer to the question. answers[i] = project.root_task # Set answers of sub-task. Command.set_answers(answers[i], answer.get("questions", [])) # Save the answer. if taskans.save_answer(None, answers, None, Command.dummy_user, "api"): log("Answered {} with {}...".format(question.key, answers)) return True else: # This is a regular question type with YAML data holding the answer value. # Validate the value. from guidedmodules.answer_validation import validator try: value = validator.validate(question, answer["answer"]) except ValueError as e: log("Answering {}: {}...".format(question.key, e)) return False # Save the value. if taskans.save_answer(value, [], None, Command.dummy_user, "api"): log("Answered {} with {}...".format(question.key, answer)) return True # No change, somehow. return False
def set_answer(self, task, question, answer, update_answer_func, basedir, options): # Set the answer to the question for the given task. if question.spec[ "type"] == "module" and question.answer_type_module is not None: # If there is no answer provided, normally we leave # the answer blank. However, for module-type questions # with a module type (not a protocol), we should at least # *start* the sub-task with the module answer type, even if we # don't answer any of its questions, because it may be # a question-less module that only provides output documents. sub_task = task.get_or_create_subtask(self.dummy_user, question) # self.log("OK", "Answered {} with {}.".format(question.key, sub_task)) if answer is None and options["add_blank_answers"]: answer = update_answer_func({"questions": []}) if answer is not None: self.set_answers(sub_task, answer.setdefault("questions", []), basedir, options) return True return False # If the question isn't answered and --startapps is given on the # command-line, then choose an app automatically for module-type # questions with protocols. if answer is None and options["startapps"] and question.spec["type"] == "module" \ and question.answer_type_module is None and question.spec.get("protocol"): # Look for an app that can be used to answer this question. from siteapp.views import app_satifies_interface for app in options["startapps"]: if app_satifies_interface(app["catalog_info"], question.spec.get("protocol")): # We found one. Set the answer to be the path to the app, # adjusted to be relative to the location of the assemble # YAML file. self.log( "INFO", "Choosing app {} for {} (from --startapps).".format( app["app"], question.key)) answer = update_answer_func( {"app": os.path.relpath(app["app"], basedir)}) break # If the question isn't answered, leave it alone. if answer is None: return False # Set an answer to the question. # Get the TaskAnswer record, which has the save_answer function. taskans, isnew = TaskAnswer.objects.get_or_create(task=task, question=question) if question.spec["type"] in ("module", "module-set"): # A module-type question with a protocol (if there was no protocol, it # was handled above) or a module-set question (with or without a protocol). if question.spec["type"] == "module": answers = [answer] else: answers = answer.setdefault("answers", []) # Start the app(s). subtasks = [] for answer in answers: if question.answer_type_module is not None: # Start the sub-task. subtasks.append( task.get_or_create_subtask(self.dummy_user, question)) else: # Start the app. The app to start is specified in the 'app' key. if not isinstance(answer, dict): raise ValueError("invalid data type") project = self.start_app(answer.get("app"), basedir) if project is None: return False # error # Validate that the protocols match. unimplemented_protocols = set( question.spec.get("protocol", [])) - set( project.root_task.module.spec.get("protocol", [])) if unimplemented_protocols: # There are unimplemented protocols. self.log( "ERROR", "{} doesn't implement the protocol {} required to answer {}." .format(project.root_task.module.app, ", ".join(sorted(unimplemented_protocols)), question.key)) return False # Keep the root Task to be an answer to the question. subtasks.append(project.root_task) # Save the answer. if taskans.save_answer(None, subtasks, None, self.dummy_user, "api"): self.log( "OK", "Answered {} with {}.".format( question.key, ", ".join([self.str_task(t) for t in subtasks]))) # Set answers of sub-task. for subtask, subtask_answers in zip(subtasks, answers): self.set_answers( subtask, subtask_answers.setdefault("questions", []), basedir, options) return True else: # This is a regular question type with YAML data holding the answer value. # Validate the value, unless the value is null. value = answer["answer"] if value is None: # Null answers are always acceptable. pass else: from guidedmodules.answer_validation import validator try: value = validator.validate(question, value) except ValueError as e: self.log("ERROR", "Answering {}: {}".format(question.key, e)) return False # Save the value. if taskans.save_answer(value, [], None, self.dummy_user, "api"): self.log( "OK", "Answered {} with {}.".format(question.key, repr(value))) return True # No change, somehow. return False