def next_task(guts): """Get the next task for a user, and redirect. It is possible this belongs with the task views. # TODO: Testing The input request should have a logged in user. The result should be: * If the user has nothing to do, redirect to the home page. * If the user has a pending review, redirect to that review's page. * If the user has a task in an auto-review project to look at, redirect to that page. * If the user either has a WorkInProgress or there is a task available for them to work on, a redirect to that task's page. * If a WorkInProgress exists, the .start_time property of the WIP should be updated to the current time. * If no WIP exists, one should be created with the next available task and the current logged in user. """ review = Review.objects.filter(response__user=guts.user, complete=False) if review.count(): return ViewResponse('main:next-review') auto_review_pending = AutoReview.objects.filter(user=guts.user, start_time__isnull=False, end_time__isnull=True) if auto_review_pending.exists(): return ViewResponse('main:view-task', auto_review_pending[0].task.id) new_auto_reviews = AutoReview.objects.filter( user=guts.user, task__project__priority__gte=0, start_time__isnull=True, end_time__isnull=True, ).order_by("-task__project__priority") if new_auto_reviews.exists(): auto_review = new_auto_reviews[0] auto_review.start_time = timezone.now() auto_review.full_clean() auto_review.save() return ViewResponse('main:view-task', auto_review.task.id) wip = None wips = WorkInProgress.objects.filter(user=guts.user) if wips.count(): wip = wips[0] wip.start_time = timezone.now() wip.full_clean() wip.save() else: task = Task.objects.next_for(guts.user) if task: wip = WorkInProgress(user=guts.user, task=task) wip.full_clean() wip.save() if wip: return ViewResponse('main:view-task', wip.task.id) else: return ViewResponse('main:home')
def next_review(guts): """If a user has an outstanding task to review, redirect them to it. Otherwise, redirect them to the homepage.""" import main.views.base review = Review.objects.filter(response__user=guts.user, complete=False) if review.count(): review = review[0] return ViewResponse('main:view-task', review.id) return ViewResponse('main:home')
def wip_review(get, guts): if guts.user.is_superuser: wips = WorkInProgress.objects.all() elif Project.objects.filter(admin=guts.user).count(): wips = WorkInProgress.objects.filter(task__project__admin=guts.user) else: return ForbiddenResponse( "Only project administrators and superusers may see this page.") if get: wip_list = [{ "id": wip.id, "user": wip.user.username, "task_id": wip.task.id, "task_url": wip.task.get_absolute_url(), "project_id": wip.task.project.id, "project_name": wip.task.project.title, "project_url": wip.task.project.get_absolute_url(), "start_time": wip.start_time, } for wip in wips.order_by("-start_time")] template_data = {"wips": wip_list} template = get_template("wip-review.html") return TemplateResponse(template, template_data) else: try: wips_to_delete = [ wips.get(pk=pk) for pk in guts.parameters.getlist("wips_to_delete") ] for wip in wips_to_delete: wip.delete() return ViewResponse('main:wip-list') except WorkInProgress.DoesNotExist: return ForbiddenResponse("You can only delete a project " "that you are an admin for.")
def task_review(get, guts, review_id): """Present an interface to request the user to review a given task.""" review = get_object_or_404(Review, pk=review_id) response = review.response ## NOTE: an earlier version of this code would throw a Review.DoesNotExist exception ## if the Review object with the right id and user had complete=True. I don't see ## the harm in re-reviewing so I am not checking for that here. if (response.user != guts.user) and not (guts.user.is_superuser or get): return ForbiddenResponse("You are not authorized to see this review.") if get: task = response.task project_type = get_project_type(task.project) task = project_type.cast(task) try: template = get_template(review.review_template) except NotImplementedError: template = task.template() template_data = task.template_data(review=review) return TemplateResponse(template, template_data) else: review.complete = True review.full_clean() review.save() return ViewResponse('main:next-review')
def task_adhoc_review(get, guts): if get: response_id = guts.parameters['response'] response = Response.objects.get(pk=response_id) reviews = Review.objects.filter(response=response) if reviews.count() == 1: review = reviews[0] else: review = Review(response=response, comment="") task = response.task result = task.result # Tagger or merger can both view, as well as super-user if ((response.user != guts.user) and not (guts.user.is_superuser) and (result.user != guts.user)): return ForbiddenResponse( "You are not authorized to see this review.") project_type = get_project_type(task.project) task = project_type.cast(task) try: template = get_template(review.review_template) except NotImplementedError: template = task.template() template_data = task.template_data(review=review) return TemplateResponse(template, template_data) else: return ViewResponse('main:next-review')
def unmerge(get, guts, task_id): """Given a task that has already been merged, undo the merge, give the user a WIP for re-merging it, and redirect to the page for handling that WIP. This operation may not be supported for all project types, and may only be executed by a superuser.""" if guts.user.is_superuser: if not get: task = get_object_or_404(Task, pk=task_id) project_type = get_project_type(task.project) task = project_type.cast(task) task.handle_unmerge() wip = WorkInProgress(user=guts.user, task=task) wip.full_clean() wip.save() return ViewResponse('main:view-task', task_id) else: return ForbiddenResponse("Only superusers may perform this operation.")
def project_upload(get, guts, project): """Take an upload, and queue it for running through the task processor. # TODO: Testing # TODO: Return something better than the current Upload complete response. (template) # TODO: fix allowing user to select project form a dropdown (project is in the URL). # TODO: only allow certain users to upload projects """ ptype = get_project_type(project) project = ptype.cast(project) if get: template = get_template("project/upload_form.html") return TemplateResponse(template, {"form": UploadForm()}) else: action = guts.parameters["action"] if action == "Upload": pu = ProjectUpload(project=project) item = UploadForm(guts.parameters, guts.files, instance=pu) if item.is_valid(): item.save() if not hasattr(settings, "PROCESS_INLINE") or settings.PROCESS_INLINE: project.handle_input(pu) if project.auto_review: project.add_auto_reviews() pu.complete = True pu.save() message = ( "Upload complete to project %s, tasks processed" % project.id) else: message = "Upload complete, queued as %s" % pu.id guts.log_info(message) return DefaultResponse(message) else: ## re-present the page with input errors marked template = get_template("project/upload_form.html") return TemplateResponse(template, {"form": item}) elif action == "Empty": ## NOTE: if subclasses have customized the delete() method, ## this bulk-delete will not call it. Task.objects.filter(project=project).delete() return ViewResponse('main:view-project', project.id) else: return ErrorResponse("Bad form", "Action parameter %s not understood" % action)
def abandon_wip(get, guts): """A view to abandon a WIP. When GETting this page, the user sees the \"are you sure?\" page. When POSTing, the first WIP that the user has is deleted. """ if get: wips = WorkInProgress.objects.filter(user=guts.user) template = get_template("abandon_wip.html") return TemplateResponse(template, {'wips': wips}) else: wips = WorkInProgress.objects.filter(user=guts.user) if wips.count(): wip = wips[0] wip.delete() return ViewResponse('main:home') else: template = get_template("abandon_wip.html") return TemplateResponse(template, {"wips": wips})
def change_password(get, guts): message = "" if get: f = ChangePassForm() else: f = ChangePassForm(guts.parameters) if f.is_valid(): cleaned = f.cleaned_data if (guts.user.check_password(cleaned['old_pass']) and cleaned['pass1'] == cleaned['pass2']): guts.user.set_password(cleaned['pass1']) guts.user.save() return ViewResponse('main:home') elif not guts.user.check_password(cleaned['old_pass']): message = "You did not enter your current password correctly" else: message = "Passwords didn't match." template = get_template("change_password.html") return TemplateResponse(template, { 'form': f, 'user': guts.user, 'message': message })
def task_view(get, guts, task_id): """View a given task ID or submit a response to it; in either case, this should dispatch appropriately based on the task's type.""" import main.views.base task = Task.objects.get(pk=task_id) project_type = type_list[task.project.type] task = project_type.cast(task) try: wip = WorkInProgress.objects.get(task=task, user=guts.user) except WorkInProgress.DoesNotExist: if task.project.auto_review: try: ## Tasks in auto-review projects have a kludgey workflow: ## the task is responsible for noticing that it is in an ## auto-review project and adjusting the output of template() ## and template_data accordingly. ## When GETting the page, if a Response object already exists, then ## the output should be appropriate for displaying a review. auto_review = AutoReview.objects.get(task=task, user=guts.user) if get: ## If the Task subclass has not been retrofitted to handle ## auto-review projects, then this line will throw an exception ## "TypeError: ... unexpected keyword argument 'auto_review_user'" return TemplateResponse( task.template(), task.template_data(auto_review_user=guts.user)) else: ## NOTE: does the downcasting of task screw this query up? response_query = Response.objects.filter(user=guts.user, task=task) if not response_query.exists(): ## No response? Ask the task to make one! kwargs = { "user": guts.user, "task": task, "start_time": auto_review.start_time, } task.handle_response(guts, **kwargs) ## (Note that this code path does NOT update the task's ## completed_assignments field, because auto-review projects ## do not have a limit on how many responses they can have.) ## ## And then GET this page again, so the user can see the review. return ViewResponse('main:view-task', task_id) else: ## The user must have clicked the "I read this review" button. auto_review.end_time = timezone.now() auto_review.full_clean() auto_review.save() return ViewResponse('main:next-task') except AutoReview.DoesNotExist: ## fall through to the case below, since an admin is allowed to ## look at someone else's auto-review pass ## an admin can look at any task, but not even an admin ## can submit a response or result to a task without getting a WIP first if not (task.viewable_by(guts.user) and get): return ForbiddenResponse("You are not allowed to view %s." % six.text_type(task)) if get: return TemplateResponse(task.template(), task.template_data()) else: ## TODO: if we successfully make handle_response a method of the task, ## then we don't have to pass the task in kwargs kwargs = { "user": guts.user, "task": task, "start_time": wip.start_time } try: task.handle_response(guts, **kwargs) if task.completed: if 'review_user' in guts.parameters: users = guts.parameters.getlist('review_user') for user in users: user_obj = User.objects.get(pk=user) comment = guts.parameters.get("comment_%s" % user, "") rev = Review( response=task.response_set.get(user=user_obj), comment=comment, ) rev.full_clean() rev.save() else: task.completed_assignments = task.completed_assignments + 1 task.full_clean() task.save() wip.delete() if 'stop_working' in guts.parameters: return ViewResponse('main:home') else: return ViewResponse('main:next-task') except MultiValueDictKeyError: ## translate the MultiValueDict into a list of (key, list) pairs params = guts.parameters.lists() exc_type, exc_value, exc_traceback = sys.exc_info() tb_info = traceback.extract_tb(exc_traceback) template = get_template("parameter-error-in-task.html") context = { "task": str(task), "params": params, "exc_value": exc_value, "traceback": tb_info, } guts.log_error("Bad form? " + repr(context)) return TemplateResponse(template, context, status=500)