diff --git a/pybossa/forms/forms.py b/pybossa/forms/forms.py index 43a724ecc1..babc469449 100644 --- a/pybossa/forms/forms.py +++ b/pybossa/forms/forms.py @@ -136,7 +136,9 @@ class ProjectUpdateForm(ProjectForm): short_name = TextField(label=None, widget=HiddenInput()) long_description = TextAreaField(lazy_gettext('Long Description')) allow_anonymous_contributors = BooleanField(lazy_gettext('Allow Anonymous Contributors')) - allow_taskrun_edit = BooleanField(lazy_gettext('Allow Editing of Task Submissions')) + private_instance = data_access.data_access_levels.get("valid_access_levels", []) == ["L1", "L2", "L3", "L4"] + if not private_instance: + allow_taskrun_edit = BooleanField(lazy_gettext('Allow Editing of Task Submissions')) zip_download = BooleanField(lazy_gettext('Allow ZIP data download')) category_id = SelectField(lazy_gettext('Category'), coerce=int) hidden = BooleanField(lazy_gettext('Hide?')) diff --git a/pybossa/themes/default b/pybossa/themes/default index ed8192fbe6..8b77ed2e6f 160000 --- a/pybossa/themes/default +++ b/pybossa/themes/default @@ -1 +1 @@ -Subproject commit ed8192fbe60fd2f339a0c9f08957842446465255 +Subproject commit 8b77ed2e6f7eebad0e762d94e11273aedf782158 diff --git a/pybossa/view/projects.py b/pybossa/view/projects.py index d1a582a67c..72e880e532 100644 --- a/pybossa/view/projects.py +++ b/pybossa/view/projects.py @@ -823,7 +823,8 @@ def handle_valid_form(form): 'input_data': form.input_data_class.data, 'output_data': form.output_data_class.data } - new_project.info["allow_taskrun_edit"] = form.allow_taskrun_edit.data + if "allow_taskrun_edit" in form: + new_project.info["allow_taskrun_edit"] = form.allow_taskrun_edit.data project_repo.update(new_project) auditlogger.add_log_entry(old_project, new_project, current_user) @@ -854,7 +855,7 @@ def handle_valid_form(form): project.kpi = project.info.get('kpi') project.input_data_class = project.info.get('data_classification', {}).get('input_data') project.output_data_class = project.info.get('data_classification', {}).get('output_data') - project.allow_taskrun_edit = project.info.get("allow_taskrun_edit") + project.allow_taskrun_edit = project.info.get("allow_taskrun_edit", False) ensure_amp_config_applied_to_project(project, project.info.get('annotation_config', {})) form = dynamic_project_form(ProjectUpdateForm, None, data_access_levels, obj=project, products=prodsubprods, data_classes=data_classes) @@ -1349,6 +1350,11 @@ def task_presenter(short_name, task_id, task_submitter_id=None): tp_code = process_table_component(tp_code, user_response, task) template_args["project"]["info"]["task_presenter"] = tp_code + # with edit submission, pass task run id so that taskrun can be updated + if request.args.get("mode") == "edit_submission": + template_args["taskrun_id"] = taskruns[0].id + template_args["taskrun_user_id"] = taskruns[0].user_id + def respond(tmpl): response = dict(template=tmpl, **template_args) return handle_content_type(response) @@ -1356,33 +1362,39 @@ def respond(tmpl): if not (task.project_id == project.id): return respond('/projects/task/wrong.html') - guard = ContributionsGuard(sentinel.master, - timeout=project.info.get('timeout')) - guard.stamp(task, get_user_id_or_ip()) - - # Verify the worker has an unexpired lock on the task. Otherwise, task will fail to submit. - timeout, ttl = fetch_lock_for_user(task.project_id, task_id, user_id) - remaining_time = float(ttl) - time.time() if ttl else None - if (not remaining_time or remaining_time <= 0) and mode != 'read_only': - current_app.logger.info("unable to lock task or task expired. \ - project %s, task %s, user %s, remaining time %s, mode %s", - task.project_id, task_id, user_id, remaining_time, mode) - flash(gettext("Unable to lock task or task expired. Please cancel and begin a new task."), "error") - else: - if mode != 'read_only': - # Set the original timeout seconds to display in the message. - template_args['project']['original_timeout'] = timeout - # Set the seconds remaining to display in the message. - template_args['project']['timeout'] = remaining_time - current_app.logger.info("User %s present task %s, remaining time %s, original timeout %s", - user_id, task_id, remaining_time, timeout) - - if not guard.check_task_presented_timestamp(task, get_user_id_or_ip()): - guard.stamp_presented_time(task, get_user_id_or_ip()) - - if has_no_presenter(project): - flash(gettext("Sorry, but this project is still a draft and does " - "not have a task presenter."), "error") + # bypass lock check for the submitter when submitter can edit their response + bypass_lock_check = request.args.get("mode") == "edit_submission" and \ + project.info.get("allow_taskrun_edit") and \ + current_user.id == task_submitter_id + + if not bypass_lock_check: + guard = ContributionsGuard(sentinel.master, + timeout=project.info.get('timeout')) + guard.stamp(task, get_user_id_or_ip()) + + # Verify the worker has an unexpired lock on the task. Otherwise, task will fail to submit. + timeout, ttl = fetch_lock_for_user(task.project_id, task_id, user_id) + remaining_time = float(ttl) - time.time() if ttl else None + if (not remaining_time or remaining_time <= 0) and mode != 'read_only': + current_app.logger.info("unable to lock task or task expired. \ + project %s, task %s, user %s, remaining time %s, mode %s", + task.project_id, task_id, user_id, remaining_time, mode) + flash(gettext("Unable to lock task or task expired. Please cancel and begin a new task."), "error") + else: + if mode != 'read_only': + # Set the original timeout seconds to display in the message. + template_args['project']['original_timeout'] = timeout + # Set the seconds remaining to display in the message. + template_args['project']['timeout'] = remaining_time + current_app.logger.info("User %s present task %s, remaining time %s, original timeout %s", + user_id, task_id, remaining_time, timeout) + + if not guard.check_task_presented_timestamp(task, get_user_id_or_ip()): + guard.stamp_presented_time(task, get_user_id_or_ip()) + + if has_no_presenter(project): + flash(gettext("Sorry, but this project is still a draft and does " + "not have a task presenter."), "error") return respond('/projects/presenter.html') diff --git a/test/test_web.py b/test/test_web.py index 6453d7208e..bc7493ae83 100644 --- a/test/test_web.py +++ b/test/test_web.py @@ -3461,6 +3461,36 @@ def test_task_presenter_with_allow_taskrun_edit_raises_forbidden(self): res = self.app.get('/project/%s/task/%s' % (project_short_name, task.id)) assert res.status_code == 403, res.status_code + @with_context + def test_task_presenter_with_allow_taskrun_edit_allows_submission(self): + """Test WEB with taskrun edit is permitted with task_submitter_id passed""" + self.register() + self.signin() + self.create() + project = db.session.query(Project).get(1) + project.info = dict(allow_taskrun_edit=True) + db.session.commit() + self.new_task(project.id) + project_short_name = project.short_name + + task = db.session.query(Task).filter(Task.project_id == 1).first() + # user = db.session.query(User).first() + regular_user = UserFactory.create(id=999, subadmin=False, admin=False) + regular_user.set_password('1234') + user_repo.save(regular_user) + self.signin(email=regular_user.email_addr, password='1234') + task_run = TaskRun(project_id=project.id, task_id=task.id, + info={'answer': 1, + 'odfoa': {'version': 1, 'source-uri': 'http://fake.com', 'odf': {}, 'oa': {}}, + 'fake': {'b': 27}}, + user_id=regular_user.id) + db.session.add(task_run) + db.session.commit() + + # task_submitter_id is passed to fetch task response recorded by the user + res = self.app.get('/project/%s/task/%s/%s?mode=edit_submission' % (project_short_name, task.id, regular_user.id)) + assert res.status_code == 200, res.status_code + @with_context @patch('pybossa.view.projects.uploader.upload_file', return_value=True) def test_25_get_wrong_task_app(self, mock):