--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/app/soc/modules/ghop/views/models/task.py Tue Sep 08 20:54:19 2009 +0200
@@ -0,0 +1,1421 @@
+#!/usr/bin/python2.5
+#
+# Copyright 2009 the Melange authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Views for Tasks.
+"""
+
+__authors__ = [
+ '"Madhusudan.C.S" <madhusudancs@gmail.com>'
+ ]
+
+
+import datetime
+import time
+
+from google.appengine.ext import db
+
+from django import forms
+from django import http
+from django.utils.translation import ugettext
+
+from soc.logic import cleaning
+from soc.logic import dicts
+from soc.logic.models import host as host_logic
+from soc.logic.models import student as student_logic
+from soc.logic.models import user as user_logic
+from soc.views import helper
+from soc.views import out_of_band
+from soc.views.helper import decorators
+from soc.views.helper import dynaform
+from soc.views.helper import lists
+from soc.views.helper import params as params_helper
+from soc.views.helper import requests
+from soc.views.helper import redirects
+from soc.views.helper import responses
+from soc.views.helper import widgets
+from soc.views.models import base
+
+from soc.modules.ghop.logic import cleaning as ghop_cleaning
+from soc.modules.ghop.logic.models import comment as ghop_comment_logic
+from soc.modules.ghop.logic.models import mentor as ghop_mentor_logic
+from soc.modules.ghop.logic.models import organization as ghop_org_logic
+from soc.modules.ghop.logic.models import org_admin as ghop_org_admin_logic
+from soc.modules.ghop.logic.models import program as ghop_program_logic
+from soc.modules.ghop.logic.models import task as ghop_task_logic
+from soc.modules.ghop.models import task as ghop_task_model
+from soc.modules.ghop.views.helper import access
+from soc.modules.ghop.views.helper import redirects as ghop_redirects
+from soc.modules.ghop.views.models import organization as ghop_org_view
+from soc.modules.ghop.tasks import task_update
+
+import soc.modules.ghop.logic.models.task
+
+
+class View(base.View):
+ """View methods for the Tasks.
+ """
+
+ DEF_AWAITING_REG_MSG = ugettext(
+ 'The task is open but you cannot claim this task since you '
+ 'have not completed the student signup after finishing your '
+ 'first task.')
+
+ DEF_CAN_EDIT_TASK_MSG = ugettext(
+ 'The task can be edited by clicking on the edit link '
+ 'next to the title above.')
+
+ DEF_MAX_TASK_LIMIT_MSG_FMT = ugettext(
+ 'The task is open but you cannot claim this task since you '
+ 'either have already claimed %d task.')
+
+ DEF_SIGNIN_TO_COMMENT_MSG = ugettext(
+ '<a href=%s>Sign in</a> to perform any action or comment on '
+ 'this task.')
+
+ DEF_STUDENT_SIGNUP_MSG = ugettext(
+ 'You have successfully completed this task. Sign up as a student '
+ 'before you proceed further.')
+
+ DEF_TASK_CLAIMED_BY_YOU_MSG = ugettext(
+ 'This task has been claimed by you!')
+
+ DEF_TASK_CLAIMED_BY_STUDENT_MSG = ugettext(
+ 'This task has been claimed by a student!')
+
+ DEF_TASK_CLAIMED_MSG = ugettext(
+ 'The task is already claimed and the work is in progress.')
+
+ DEF_TASK_CLAIM_REQUESTED_MSG = ugettext(
+ 'A student has requested to claim this task. Accept or '
+ 'Reject the request.')
+
+ DEF_TASK_CLOSED_MSG = ugettext('The task is closed.')
+
+ DEF_TASK_CMPLTD_BY_YOU_MSG = ugettext(
+ 'You have successfully completed this task!')
+
+ DEF_TASK_NO_MORE_SUBMIT_MSG = ugettext(
+ 'You have submitted the work to this task, but deadline has passed '
+ 'You cannot submit any more work until your mentor extends the '
+ 'deadline.')
+
+ DEF_TASK_MENTOR_REOPENED_MSG = ugettext(
+ 'The task has been reopened.')
+
+ DEF_TASK_NEEDS_REVIEW_MSG = ugettext(
+ 'Student has submitted his work for this task! It needs review.')
+
+ DEF_TASK_OPEN_MSG = ugettext(
+ 'This task is open. If you are GHOP student, you can claim it!')
+
+ DEF_TASK_REOPENED_MSG = ugettext(
+ 'This task has been reopened. If you are a GHOP student, '
+ 'you can clam it!')
+
+ DEF_TASK_REQ_CLAIMED_BY_YOU_MSG = ugettext(
+ 'You have requested to claim this task!')
+
+ DEF_TASK_UNPUBLISHED_MSG = ugettext(
+ 'The task is not yet published. It can be edited by clicking on '
+ 'the edit button below.')
+
+ DEF_WS_MSG_FMT = ugettext(
+ '(To see the work submitted <a href=#ws%d>click here</a>.)')
+
+ def __init__(self, params=None):
+ """Defines the fields and methods required for the task View class
+ to provide the user with the necessary views.
+
+ Params:
+ params: a dict with params for this View
+ """
+
+ rights = access.GHOPChecker(params)
+ # TODO: create and suggest_task don't need access checks which use state
+ # also feels like roles are being checked twice?
+ rights['create'] = [
+ ('checkCanOrgAdminOrMentorEdit', ['scope_path', True]),
+ ('checkRoleAndStatusForTask',
+ [['ghop/org_admin'], ['active'],
+ []])]
+ rights['edit'] = [
+ ('checkCanOrgAdminOrMentorEdit', ['scope_path', False]),
+ ('checkRoleAndStatusForTask',
+ [['ghop/org_admin'], ['active'],
+ ['Unapproved', 'Unpublished', 'Open']])]
+ rights['delete'] = ['checkIsDeveloper']
+ rights['show'] = ['checkStatusForTask']
+ rights['list_org_tasks'] = [
+ ('checkCanOrgAdminOrMentorEdit', ['scope_path', False])]
+ rights['suggest_task'] = [
+ ('checkCanOrgAdminOrMentorEdit', ['scope_path', True]),
+ ('checkRoleAndStatusForTask',
+ [['ghop/org_admin', 'ghop/mentor'], ['active'],
+ []])]
+ rights['search'] = ['allow']
+
+ new_params = {}
+ new_params['logic'] = soc.modules.ghop.logic.models.task.logic
+ new_params['rights'] = rights
+
+ new_params['name'] = "Task"
+ new_params['module_name'] = "task"
+ new_params['sidebar_grouping'] = 'Tasks'
+
+ new_params['module_package'] = 'soc.modules.ghop.views.models'
+ new_params['url_name'] = 'ghop/task'
+
+ new_params['scope_view'] = ghop_org_view
+ new_params['scope_redirect'] = redirects.getCreateRedirect
+
+ new_params['list_heading'] = 'modules/ghop/task/list/heading.html'
+ new_params['list_row'] = 'modules/ghop/task/list/row.html'
+
+ new_params['extra_dynaexclude'] = ['task_type', 'mentors', 'user',
+ 'student', 'program', 'status',
+ 'deadline', 'created_by',
+ 'created_on', 'modified_by',
+ 'modified_on', 'history',
+ 'link_id', 'difficulty']
+
+ patterns = []
+ patterns += [
+ (r'^%(url_name)s/(?P<access_type>suggest_task)/%(scope)s$',
+ '%(module_package)s.%(module_name)s.suggest_task',
+ 'Mentors suggest %(name)s'),
+ (r'^%(url_name)s/(?P<access_type>suggest_task)/%(key_fields)s$',
+ '%(module_package)s.%(module_name)s.suggest_task',
+ 'Mentors edit a %(name)s'),
+ (r'^%(url_name)s/(?P<access_type>list_org_tasks)/%(scope)s$',
+ '%(module_package)s.%(module_name)s.list_org_tasks',
+ 'List Organization %(name)s'),
+ (r'^%(url_name)s/(?P<access_type>search)/'
+ '(?P<scope_path>%(ulnp)s)/%(lnp)s$',
+ '%(module_package)s.%(module_name)s.search',
+ 'Search for %(name)ss'),
+ ]
+
+ new_params['extra_django_patterns'] = patterns
+
+ new_params['create_dynafields'] = [
+ {'name': 'arbit_tags',
+ 'base': forms.fields.CharField,
+ 'label': 'Arbitrary Tags',
+ 'required': False,
+ 'group': ugettext('Tags'),
+ 'help_text': ugettext(
+ 'Enter arbitrary Tags for this task separated by comma.\n'
+ '<b>Note:</b> Tag names are case sensitive. If the tag is same '
+ 'as the program mandated tag, it will be considered as a '
+ 'mandatory tag.')
+ },
+ ]
+
+ new_params['create_extra_dynaproperties'] = {
+ 'description': forms.fields.CharField(required=True,
+ widget=widgets.FullTinyMCE(attrs={'rows': 25, 'cols': 100})),
+ 'scope_path': forms.CharField(widget=forms.HiddenInput,
+ required=True),
+ 'time_to_complete': forms.IntegerField(min_value=1,
+ required=True),
+ 'clean_description': cleaning.clean_html_content('description'),
+ 'clean_arbit_tags': cleaning.str2set('arbit_tags'),
+ }
+
+ new_params['edit_extra_dynaproperties'] = {
+ 'link_id': forms.CharField(widget=forms.HiddenInput)
+ }
+
+ new_params['public_template'] = 'modules/ghop/task/public.html'
+
+ params = dicts.merge(params, new_params, sub_merge=True)
+
+ super(View, self).__init__(params=params)
+
+ # holds the base form for the task creation and editing
+ self._params['base_create_form'] = self._params['create_form']
+ self._params['base_edit_form'] = self._params['edit_form']
+
+ # extend create and edit form for org admins
+ dynafields = [
+ {'name': 'mentors_list',
+ 'required': True,
+ 'base': forms.fields.CharField,
+ 'label': 'Assign mentors',
+ 'help_text': 'Assign mentors to this task by '
+ 'giving their link_ids separated by comma.',
+ },
+ {'name': 'published',
+ 'required': False,
+ 'initial': False,
+ 'base': forms.fields.BooleanField,
+ 'label': 'Publish the task',
+ 'help_text': ugettext('By ticking this box, the task will be'
+ 'made public and can be claimed by students.'),
+ }
+ ]
+
+ dynaproperties = params_helper.getDynaFields(dynafields)
+
+ dynaproperties['clean_mentors_list'] = ghop_cleaning.cleanMentorsList(
+ 'mentors_list')
+
+ create_form = dynaform.extendDynaForm(
+ dynaform=self._params['create_form'],
+ dynaproperties=dynaproperties)
+
+ self._params['create_form'] = create_form
+
+ edit_form = dynaform.extendDynaForm(
+ dynaform=self._params['edit_form'],
+ dynaproperties=dynaproperties)
+
+ self._params['edit_form'] = edit_form
+
+ # create the comment form
+ dynafields = [
+ {'name': 'comment',
+ 'base': forms.CharField,
+ 'widget': widgets.FullTinyMCE(attrs={'rows': 10, 'cols': 40}),
+ 'label': 'Comment',
+ 'required': False,
+ 'example_text': 'Comment is optional.<br/>'
+ 'Choose an appropriate Action below and Save your '
+ 'changes.<br /><br />Caution, you will not be able '
+ 'to edit your comment!',
+ },
+ ]
+
+ dynaproperties = params_helper.getDynaFields(dynafields)
+ dynaproperties['clean_comment'] = cleaning.clean_html_content('comment')
+ dynaproperties['clean'] = ghop_cleaning.clean_comment(
+ 'comment', 'action', 'work_submission')
+
+ comment_form = dynaform.newDynaForm(dynamodel=None,
+ dynabase=helper.forms.BaseForm, dynainclude=None,
+ dynaexclude=None, dynaproperties=dynaproperties)
+ self._params['comment_form'] = comment_form
+
+ def _getTagsForProgram(self, form_name, params, **kwargs):
+ """Extends form dynamically from difficulty levels in program entity.
+
+ Args:
+ form_name: the Form entry in params to extend
+ params: the params for the view
+ """
+
+ # obtain program_entity using scope_path which holds
+ # the org_entity key_name
+ org_entity = ghop_org_logic.logic.getFromKeyName(kwargs['scope_path'])
+ program_entity = ghop_program_logic.logic.getFromKeyName(
+ org_entity.scope_path)
+
+ # get a list difficulty levels stored for the program entity
+ tds = ghop_task_model.TaskDifficultyTag.get_by_scope(
+ program_entity)
+
+ difficulties = []
+ for td in tds:
+ difficulties.append((td.tag, td.tag))
+
+ # get a list of task type tags stored for the program entity
+ tts = ghop_task_model.TaskTypeTag.get_by_scope(program_entity)
+
+ type_tags = []
+ for tt in tts:
+ type_tags.append((tt.tag, tt.tag))
+
+ # create the difficultly level field containing the choices
+ # defined in the program entity
+ dynafields = [
+ {'name': 'difficulty',
+ 'base': forms.ChoiceField,
+ 'label': 'Difficulty level',
+ 'required': True,
+ 'passthrough': ['initial', 'required', 'choices',
+ 'label', 'help_text'],
+ 'choices': difficulties,
+ 'group': ugettext('Tags'),
+ 'help_text': ugettext('Difficulty Level of this task.'),
+ },
+ {'name': 'type_tags',
+ 'base': forms.MultipleChoiceField,
+ 'label': 'Task Types',
+ 'required': True,
+ 'passthrough': ['initial', 'required', 'choices',
+ 'label', 'help_text'],
+ 'choices': type_tags,
+ 'group': ugettext('Tags'),
+ 'help_text': ugettext('Task Type tags mandated by the program. You '
+ 'must select one or more of them.'),
+ },
+ ]
+
+ dynaproperties = params_helper.getDynaFields(dynafields)
+
+ extended_form = dynaform.extendDynaForm(
+ dynaform=params[form_name],
+ dynaproperties=dynaproperties)
+
+ return extended_form
+
+ @decorators.check_access
+ def create(self, request, access_type,
+ page_name=None, params=None, **kwargs):
+ """Replaces the create Form with the dynamic one.
+
+ For args see base.View.create().
+ """
+
+ params = dicts.merge(params, self._params)
+
+ # extend create_form to include difficulty levels
+ params['create_form'] = self._getTagsForProgram(
+ 'create_form', params, **kwargs)
+
+ return super(View, self).create(request, 'allow', page_name=page_name,
+ params=params, **kwargs)
+
+ @decorators.merge_params
+ @decorators.check_access
+ def edit(self, request, access_type,
+ page_name=None, params=None, seed=None, **kwargs):
+ """See base.View.edit().
+ """
+
+ logic = params['logic']
+
+ context = helper.responses.getUniversalContext(request)
+ helper.responses.useJavaScript(context, params['js_uses_all'])
+ context['page_name'] = page_name
+
+ try:
+ entity = logic.getFromKeyFieldsOr404(kwargs)
+ except out_of_band.Error, error:
+ msg = self.DEF_CREATE_NEW_ENTITY_MSG_FMT % {
+ 'entity_type_lower' : params['name'].lower(),
+ 'entity_type' : params['name'],
+ 'create' : params['missing_redirect']
+ }
+ error.message_fmt = error.message_fmt + msg
+ return helper.responses.errorResponse(
+ error, request, context=context)
+
+ user_account = user_logic.logic.getForCurrentAccount()
+
+ filter = {
+ 'user': user_account,
+ }
+
+ # extend edit_form to include difficulty levels
+ params['edit_form'] = self._getTagsForProgram(
+ 'edit_form', params, **kwargs)
+
+ org_admin_entities = ghop_org_admin_logic.logic.getForFields(filter)
+
+ if entity and entity.status == 'Unapproved':
+ approval_req = True
+ for org_admin_entity in org_admin_entities:
+ if org_admin_entity.key().name() == entity.created_by.key().name():
+ approval_req = False
+ break
+
+ if approval_req:
+ dynafields = [
+ {'name': 'approved',
+ 'required': False,
+ 'initial': False,
+ 'base': forms.fields.BooleanField,
+ 'label': 'Approve the task',
+ 'help_text': 'By ticking this box, the task will be'
+ 'will be approved for publishing.',
+ }
+ ]
+
+ dynaproperties = params_helper.getDynaFields(dynafields)
+
+ edit_form = dynaform.extendDynaForm(
+ dynaform=params['edit_form'],
+ dynaproperties=dynaproperties)
+
+ params['edit_form'] = edit_form
+
+ if request.method == 'POST':
+ return self.editPost(request, entity, context, params=params)
+ else:
+ return self.editGet(request, entity, context, params=params)
+
+ def _editGet(self, request, entity, form):
+ """See base.View._editGet().
+ """
+
+ if entity.task_type:
+ form.fields['type_tags'].initial = entity.tags_string(
+ entity.task_type, ret_list=True)
+ if entity.arbit_tag:
+ form.fields['arbit_tags'].initial = entity.tags_string(
+ entity.arbit_tag)
+
+ if entity.difficulty:
+ form.fields['difficulty'].initial = entity.tags_string(
+ entity.difficulty)
+
+ if entity.mentors and 'mentors_list' in form.fields:
+ mentor_entities = db.get(entity.mentors)
+ mentors_list = []
+ for mentor in mentor_entities:
+ mentors_list.append(mentor.link_id)
+ form.fields['mentors_list'].initial = ', '.join(mentors_list)
+
+ form.fields['link_id'].initial = entity.link_id
+
+ # checks if the task is already approved or not and sets
+ # the form approved field
+ if 'approved' in form.fields:
+ if entity.status == 'Unapproved':
+ form.fields['approved'].initial = False
+ else:
+ form.fields['approved'].initial = True
+
+ # checks if the task is already published or not and sets
+ # the form published field
+ if 'published' in form.fields:
+ if entity.status == 'Unapproved' or entity.status == 'Unpublished':
+ form.fields['published'].initial = False
+ else:
+ form.fields['published'].initial = True
+
+ return super(View, self)._editGet(request, entity, form)
+
+ def _editPost(self, request, entity, fields):
+ """See base._editPost().
+ """
+
+ super(View, self)._editPost(request, entity, fields)
+
+ # TODO: this method can be made more clear, it seems a bit of a mess
+
+ if not entity:
+ program_entity = fields['scope'].scope
+ fields['program'] = program_entity
+ else:
+ program_entity = entity.program
+
+ user_account = user_logic.logic.getForCurrentAccount()
+
+ filter = {
+ 'user': user_account,
+ 'scope': fields['scope'],
+ 'status': 'active'
+ }
+
+ role_entity = ghop_org_admin_logic.logic.getForFields(
+ filter, unique=True)
+
+ if role_entity:
+ # this user can publish/approve the task
+ if fields.get('approved') and fields.get('published'):
+ fields['status'] = 'Open'
+ elif not fields.get('approved'):
+ fields['status'] = 'Unapproved'
+ else:
+ fields['status'] = 'Unpublished'
+
+ fields['mentors'] = []
+ if fields.get('mentors_list'):
+
+ for mentor_link_id in fields['mentors_list']:
+ properties = {
+ 'scope_path': fields['scope_path'],
+ 'link_id': mentor_link_id,
+ }
+
+ mentor_entity = ghop_mentor_logic.logic.getFromKeyFields(properties)
+ fields['mentors'].append(mentor_entity.key())
+ else:
+ role_entity = ghop_mentor_logic.logic.getForFields(
+ filter, unique=True)
+ if not entity:
+ # creating a new task
+ fields['status'] = 'Unapproved'
+
+ # explicitly change the last_modified_on since the content has been edited
+ fields['modified_on'] = datetime.datetime.now()
+
+ if not entity:
+ fields['link_id'] = 't%i' % (int(time.time()*100))
+ fields['modified_by'] = role_entity
+ fields['created_by'] = role_entity
+ fields['created_on'] = datetime.datetime.now()
+ else:
+ fields['link_id'] = entity.link_id
+ fields['modified_by'] = role_entity
+ if 'status' not in fields:
+ fields['status'] = entity.status
+
+ fields['difficulty'] = {
+ 'tags': fields['difficulty'],
+ 'scope': program_entity
+ }
+
+ fields['task_type'] = {
+ 'tags': fields['type_tags'],
+ 'scope': program_entity
+ }
+
+ fields['arbit_tag'] = {
+ 'tags': fields['arbit_tags'],
+ 'scope': program_entity
+ }
+
+ return
+
+ @decorators.merge_params
+ @decorators.check_access
+ def suggestTask(self, request, access_type, page_name=None,
+ params=None, **kwargs):
+ """View used to allow mentors to create or edit a task.
+
+ Tasks created by mentors must be approved by org admins
+ before they are published.
+ """
+
+ params = dicts.merge(params, self._params)
+
+ if 'link_id' in kwargs:
+ # extend create_form to include difficulty levels
+ params['mentor_form'] = self._getTagsForProgram(
+ 'base_edit_form', params, **kwargs)
+ try:
+ entity = self._logic.getFromKeyFieldsOr404(kwargs)
+ except out_of_band.Error, error:
+ return helper.responses.errorResponse(
+ error, request, template=params['error_public'])
+ else:
+ # extend create_form to include difficulty levels
+ params['mentor_form'] = self._getTagsForProgram(
+ 'base_create_form', params, **kwargs)
+ entity = None
+
+ context = helper.responses.getUniversalContext(request)
+ helper.responses.useJavaScript(context, params['js_uses_all'])
+ context['page_name'] = page_name
+
+ if request.method == 'POST':
+ return self.suggestTaskPost(request, context,
+ params, entity)
+ else:
+ return self.suggestTaskGet(request, context,
+ params, entity, **kwargs)
+
+ def suggestTaskPost(self, request, context, params, entity):
+ """Handles the POST request for the suggest task view.
+ """
+
+ form = params['mentor_form'](request.POST)
+
+ if not form.is_valid():
+ return self._constructResponse(request, None, context, form, params)
+
+ _, fields = helper.forms.collectCleanedFields(form)
+ # TODO: this non-standard view shouldn't call _editPost but its own method
+ self._editPost(request, entity, fields)
+
+ logic = params['logic']
+ if entity:
+ entity = logic.updateEntityProperties(entity, fields)
+ else:
+ entity = logic.updateOrCreateFromFields(fields)
+
+ page_params = params['edit_params']
+
+ redirect = ghop_redirects.getSuggestTaskRedirect(
+ entity, params)
+
+ return http.HttpResponseRedirect(redirect)
+
+ def suggestTaskGet(self, request, context, params, entity, **kwargs):
+ """Handles the GET request for the suggest task view.
+ """
+
+ if entity:
+ # populate form with the existing entity
+ form = params['mentor_form'](instance=entity)
+
+ self._editGet(request, entity, form)
+ else:
+ form = params['mentor_form']()
+
+ return self._constructResponse(request, entity, context, form, params)
+
+ @decorators.merge_params
+ @decorators.check_access
+ def listOrgTasks(self, request, access_type,
+ page_name=None, params=None, **kwargs):
+ """See base.View.list()
+ """
+ if request.method == 'POST':
+ return self.listOrgTasksPost(request, params, **kwargs)
+ else: # request.method == 'GET'
+ return self.listOrgTasksGet(request, page_name, params, **kwargs)
+
+ def listOrgTasksPost(self, request, params, **kwargs):
+ """Handles the POST request for the list tasks view.
+ """
+
+ # update the status of task entities that have been approved
+ # and published
+ task_entities = []
+ for key_name, published in request.POST.items():
+ task_entity = ghop_task_logic.logic.getFromKeyName(key_name)
+ if task_entity:
+ task_entity.status = 'Open'
+
+ task_entities.append(task_entity)
+
+ # bulk update the task_entities
+ # TODO: Have to be replaced by Task Queue APIs later
+ db.put(task_entities)
+
+ # redirect to the same page
+ return http.HttpResponseRedirect('')
+
+ def listOrgTasksGet(self, request, page_name, params, **kwargs):
+ """Handles the GET request for the list tasks view.
+ """
+
+ from soc.modules.ghop.views.helper import list_info as list_info_helper
+
+ org_entity = ghop_org_logic.logic.getFromKeyNameOr404(
+ kwargs['scope_path'])
+
+ contents = []
+ context = {}
+
+ user_account = user_logic.logic.getForCurrentAccount()
+
+ fields = {
+ 'user': user_account,
+ 'scope': org_entity,
+ 'status': 'active'
+ }
+
+ up_params = params.copy()
+ # give the capability to approve tasks for the org_admins
+ if ghop_org_admin_logic.logic.getForFields(fields, unique=True):
+ up_params['list_template'] = 'modules/ghop/task/approve/approve.html'
+ up_params['list_heading'] = 'modules/ghop/task/approve/heading.html'
+ up_params['list_row'] = 'modules/ghop/task/approve/row.html'
+
+ up_params['list_action'] = (redirects.getPublicRedirect,
+ up_params)
+
+ up_params['list_description'] = ugettext(
+ 'List of Unapproved or Unpublished tasks')
+
+ filter = {
+ 'scope': org_entity,
+ 'status': ['Unapproved', 'Unpublished'],
+ }
+
+ up_list = lists.getListContent(request, up_params, filter,
+ order=order, idx=0,
+ need_content=True)
+
+ if up_list:
+ up_mentors_list = {}
+ for task_entity in up_list['data']:
+ up_mentors_list[task_entity.key()] = db.get(task_entity.mentors)
+
+ up_list['info'] = (list_info_helper.getTasksInfo(up_mentors_list), None)
+
+ contents.append(up_list)
+ context['up_list'] = True
+
+ ap_params = up_params.copy()
+ ap_params['list_template'] = 'soc/models/list.html'
+ ap_params['list_heading'] = 'modules/ghop/task/list/heading.html'
+ ap_params['list_row'] = 'modules/ghop/task/list/row.html'
+
+ ap_params['list_action'] = (redirects.getPublicRedirect,
+ ap_params)
+
+ ap_params['list_description'] = ugettext(
+ 'List of published tasks')
+
+ filter = {
+ 'scope': org_entity,
+ 'status': ['Open', 'Reopened', 'ClaimRequested', 'Claimed',
+ 'ActionNeeded', 'Closed', 'AwaitingRegistration',
+ 'NeedsWork', 'NeedsReview'],
+ }
+
+ ap_list = lists.getListContent(request, ap_params, filter,
+ order=order, idx=1,
+ need_content=True)
+
+ if ap_list:
+ ap_mentors_list = {}
+ for task_entity in ap_list['data']:
+ ap_mentors_list[task_entity.key()] = db.get(task_entity.mentors)
+
+ ap_list['info'] = (list_info_helper.getTasksInfo(ap_mentors_list), None)
+
+ contents.append(ap_list)
+
+ # call the _list method from base to display the list
+ return self._list(request, up_params, contents, page_name, context)
+
+ @decorators.merge_params
+ @decorators.check_access
+ def public(self, request, access_type,
+ page_name=None, params=None, **kwargs):
+ """See base.View.public().
+ """
+
+ # create default template context for use with any templates
+ context = helper.responses.getUniversalContext(request)
+ helper.responses.useJavaScript(context, params['js_uses_all'])
+ context['page_name'] = page_name
+ entity = None
+ logic = params['logic']
+
+ try:
+ entity, comment_entities, ws_entities = (
+ logic.getFromKeyFieldsWithCWSOr404(kwargs))
+ except out_of_band.Error, error:
+ return helper.responses.errorResponse(
+ error, request, template=params['error_public'], context=context)
+
+ if entity.status in ['Claimed', 'NeedsReview',
+ 'ActionNeeded', 'NeedsWork']:
+ entity, comment_entity, ws_entity = (
+ ghop_task_logic.logic.updateTaskStatus(entity))
+ if comment_entity:
+ comment_entities.append(comment_entity)
+ if ws_entity:
+ ws_entities.append(ws_entity)
+
+ context['entity'] = entity
+ context['entity_key_name'] = entity.key().id_or_name()
+ context['entity_type'] = params['name']
+ context['entity_type_url'] = params['url_name']
+
+ user_account = user_logic.logic.getForCurrentAccount()
+
+ # get some entity specific context
+ self.updatePublicContext(context, entity, comment_entities,
+ ws_entities, user_account, params)
+
+ validation = self._constructActionsList(context, entity,
+ user_account, params)
+
+ context = dicts.merge(params['context'], context)
+
+ if request.method == 'POST':
+ return self.publicPost(request, context, params, entity,
+ user_account, validation, **kwargs)
+ else: # request.method == 'GET'
+ return self.publicGet(request, context, params, entity,
+ user_account, **kwargs)
+
+ def publicPost(self, request, context, params, entity,
+ user_account=None, validation=None, **kwargs):
+ """Handles the POST request for the entity's public page.
+
+ Args:
+ entity: the task entity
+ rest: see base.View.public()
+ """
+
+ from soc.modules.ghop.logic.models import student as ghop_student_logic
+
+ form = params['comment_form'](request.POST)
+
+ if not form.is_valid():
+ template = params['public_template']
+ context['comment_form'] = form
+ return self._constructResponse(request, entity, context,
+ form, params, template=template)
+
+ _, fields = helper.forms.collectCleanedFields(form)
+
+ changes = []
+
+ action = fields['action']
+
+ properties = None
+ ws_properties = None
+
+ # TODO: this can be separated into several methods that handle the changes
+ if validation == 'claim_request' and action == 'request':
+ deadline = datetime.datetime.now() + datetime.timedelta(
+ hours=entity.time_to_complete)
+
+ properties = {
+ 'status': 'ClaimRequested',
+ 'user': user_account,
+ 'deadline': deadline,
+ }
+
+ st_filter = {
+ 'user': user_account,
+ 'scope': entity.program,
+ 'status': 'active'
+ }
+ student_entity = ghop_student_logic.logic.getForFields(
+ st_filter, unique=True)
+
+ if student_entity:
+ properties['student'] = student_entity
+
+ changes.extend([ugettext('User-Student'),
+ ugettext('Action-Claim Requested'),
+ ugettext('Status-%s' % (properties['status']))
+ ])
+ elif (validation == 'claim_withdraw' or
+ validation == 'needs_review') and action == 'withdraw':
+ properties = {
+ 'user': None,
+ 'student': None,
+ 'status': 'Reopened',
+ 'deadline': None,
+ }
+
+ changes.extend([ugettext('User-Student'),
+ ugettext('Action-Withdrawn'),
+ ugettext('Status-%s' % (properties['status']))
+ ])
+ elif validation == 'needs_review' and action == 'needs_review':
+ properties = {
+ 'status': 'NeedsReview',
+ }
+
+ changes.extend([
+ ugettext('User-Student'),
+ ugettext('Action-Submitted work and Requested for review'),
+ ugettext('Status-%s' % (properties['status']))])
+
+ ws_properties = {
+ 'parent': entity,
+ 'link_id': 't%i' % (int(time.time()*100)),
+ 'scope_path': entity.key().name(),
+ 'scope': entity.scope,
+ 'user': user_account,
+ 'information': fields['comment'],
+ 'url_to_work': fields['work_submission'],
+ 'submitted_on': datetime.datetime.now(),
+ }
+ elif validation == 'accept_claim':
+ if action == 'accept':
+ deadline = datetime.datetime.now() + datetime.timedelta(
+ hours=entity.time_to_complete)
+
+ properties = {
+ 'status': 'Claimed',
+ 'deadline': deadline,
+ }
+
+ changes.extend([ugettext('User-Mentor'),
+ ugettext('Action-Claim Accepted'),
+ ugettext('Status-%s' % (properties['status']))
+ ])
+
+ task_update.spawnUpdateTask(entity)
+ if action == 'reject':
+ properties = {
+ 'user': None,
+ 'student': None,
+ 'status': 'Reopened',
+ 'deadline': None,
+ }
+
+ changes.extend([ugettext('User-Mentor'),
+ ugettext('Action-Claim Rejected'),
+ ugettext('Status-%s' % (properties['status']))
+ ])
+ elif validation == 'close':
+ if action == 'needs_work':
+ properties = {
+ 'status': 'NeedsWork',
+ }
+
+ changes.extend([ugettext('User-Mentor'),
+ ugettext('Action-Requested more work'),
+ ugettext('Status-%s' % (properties['status']))
+ ])
+
+ if fields['extended_deadline'] > 0:
+ deadline = entity.deadline + datetime.timedelta(
+ hours=fields['extended_deadline'])
+
+ properties['deadline'] = deadline
+
+ changes.append(ugettext('DeadlineExtendedBy-%d hrs to %s' % (
+ fields['extended_deadline'], deadline.strftime(
+ '%d %B %Y, %H :%M'))))
+
+ task_update.spawnUpdateTask(entity)
+ else:
+ changes.append(ugettext('NoDeadlineExtensionGiven'))
+ elif action == 'reopened':
+ properties = {
+ 'user': None,
+ 'student': None,
+ 'status': 'Reopened',
+ 'deadline': None,
+ }
+
+ changes.extend([ugettext('User-Mentor'),
+ ugettext('Action-Reopened'),
+ ugettext('Status-%s' % (properties['status']))
+ ])
+ elif action == 'closed':
+ properties = {
+ 'deadline': None,
+ }
+
+ if entity.student:
+ properties['status'] = 'Closed'
+ else:
+ properties['status'] = 'AwaitingRegistration'
+
+ changes.extend([ugettext('User-Mentor'),
+ ugettext('Action-Closed the task'),
+ ugettext('Status-%s' % (properties['status']))
+ ])
+
+ comment_properties = {
+ 'parent': entity,
+ 'scope_path': entity.key().name(),
+ 'created_by': user_account,
+ 'changes': changes,
+ }
+
+ if ws_properties:
+ comment_properties['content'] = self.DEF_WS_MSG_FMT
+ else:
+ comment_properties['content'] = fields['comment']
+
+ ghop_task_logic.logic.updateEntityPropertiesWithCWS(
+ entity, properties, comment_properties, ws_properties)
+
+ # redirect to the same page
+ return http.HttpResponseRedirect('')
+
+ def publicGet(self, request, context, params, entity,
+ user_account, **kwargs):
+ """Handles the GET request for the entity's public page.
+
+ Args:
+ entity: the task entity
+ rest see base.View.public()
+ """
+
+ context['comment_form'] = params['comment_form']()
+
+ template = params['public_template']
+
+ return responses.respond(request, template, context=context)
+
+ def updatePublicContext(self, context, entity, comment_entities,
+ ws_entities, user_account, params):
+ """Updates the context for the public page with information.
+
+ Args:
+ context: the context that should be updated
+ entity: a task used to set context
+ user_account: user entity of the logged in user
+ params: dict with params for the view using this context
+ """
+
+ mentor_entities = db.get(entity.mentors)
+ mentors_str = ""
+ for mentor in mentor_entities:
+ mentors_str += mentor.name() + ", "
+
+ if mentors_str:
+ context['mentors_str'] = mentors_str[:-2]
+ else:
+ context['mentors_str'] = "Not Assigned"
+
+ context['difficulty_str'] = entity.tags_string(entity.difficulty)
+
+ context['task_type_str'] = entity.tags_string(entity.task_type)
+
+ if entity.deadline:
+ # TODO: it should be able to abuse Django functionality for this
+ ttc = entity.deadline - datetime.datetime.now()
+ (ttc_min, ttc_hour) = ((ttc.seconds / 60), (ttc.seconds / 3600))
+ if ttc_min >= 60:
+ ttc_min = ttc_min % 60
+ if ttc_hour > 1:
+ if ttc_min == 0:
+ ttc_str = '%d hours' % (ttc_hour)
+ else:
+ ttc_str = '%d:%02d hours' % (ttc_hour, ttc_min)
+ if ttc.days == 1:
+ ttc_str = '%d day, %s' % (ttc.days, ttc_str)
+ elif ttc.days > 1:
+ ttc_str = '%d days, %s' % (ttc.days, ttc_str)
+ else:
+ ttc_str = '%d mins' % (ttc_min)
+ context['time_to_complete'] = ttc_str
+ else:
+ if entity.status == 'NeedsReview':
+ context['time_to_complete'] = 'No Time Left'
+ else:
+ context['time_to_complete'] = '%d hours' % (entity.time_to_complete)
+
+ context['comments'] = comment_entities
+
+ context['work_submissions'] = ws_entities
+
+ def _constructActionsList(self, context, entity,
+ user_account, params):
+ """Constructs a list of actions for the task page and extends
+ the comment form with this list.
+
+ This method also returns the validation used by POST method to
+ validate the user input data.
+
+ Args:
+ context: the context that should be updated
+ entity: a task used to set context
+ user_account: user entity of the logged in user
+ params: dict with params for the view using this context
+ """
+
+ # variable that holds what kind of validation this user
+ # and task combination pass.
+ validation = None
+
+ # The following header messages are shown for non-logged in
+ # general public, logged in public and the student.
+ if entity.status is 'Closed':
+ context['header_msg'] = self.DEF_TASK_CLOSED_MSG
+ validation = 'closed'
+
+ if entity.status == 'Open':
+ context['header_msg'] = self.DEF_TASK_OPEN_MSG
+ elif entity.status == 'Reopened':
+ context['header_msg'] = self.DEF_TASK_REOPENED_MSG
+
+ if user_account:
+ actions = [('noaction', 'Comment without action')]
+
+ # if the user is logged give him the permission to claim
+ # the task only if he none of program host, org admin or mentor
+ filter = {
+ 'user': user_account,
+ }
+
+ host_entity = host_logic.logic.getForFields(filter, unique=True)
+
+ filter['scope_path'] = entity.scope_path
+ org_admin_entity = ghop_org_admin_logic.logic.getForFields(
+ filter, unique=True)
+ mentor_entity = ghop_mentor_logic.logic.getForFields(
+ filter, unique=True)
+
+ if host_entity or org_admin_entity or mentor_entity:
+ validation, mentor_actions = self._constructMentorActions(
+ context, entity)
+ actions += mentor_actions
+ if entity.status in ['Unapproved', 'Unpublished', 'Open']:
+ if host_entity or org_admin_entity:
+ context['edit_link'] = redirects.getEditRedirect(entity, params)
+ elif mentor_entity:
+ context['suggest_link'] = ghop_redirects.getSuggestTaskRedirect(
+ entity, params)
+ else:
+ validation, student_actions = self._constructStudentActions(
+ context, entity, user_account)
+ actions += student_actions
+
+ # create the difficultly level field containing the choices
+ # defined in the program entity
+ dynafields = [
+ {'name': 'action',
+ 'base': forms.ChoiceField,
+ 'label': 'Action',
+ 'required': False,
+ 'passthrough': ['initial', 'required', 'choices'],
+ 'choices': actions,
+ },
+ ]
+
+ if validation == 'needs_review':
+ dynafields.append(
+ {'name': 'work_submission',
+ 'base': forms.URLField,
+ 'label': 'Submit Work',
+ 'required': False,
+ 'help_text': 'Provide a link to your work in this box. '
+ 'Please use the comment box if you need to explain '
+ 'of your work.',
+ })
+
+ if validation == 'close':
+ dynafields.append(
+ {'name': 'extended_deadline',
+ 'base': forms.IntegerField,
+ 'min_value': 1,
+ 'label': 'Extend deadline by',
+ 'required': False,
+ 'passthrough': ['min_value', 'required', 'help_text'],
+ 'help_text': 'Optional: Specify the number of hours by '
+ 'which you want to extend the deadline for the task '
+ 'for this student. ',
+ })
+
+ dynaproperties = params_helper.getDynaFields(dynafields)
+ if validation == 'needs_review':
+ dynaproperties['clean_work_submission'] = cleaning.clean_url(
+ 'work_submission')
+
+ extended_comment_form = dynaform.extendDynaForm(
+ dynaform=params['comment_form'],
+ dynaproperties=dynaproperties)
+
+ params['comment_form'] = extended_comment_form
+ else:
+ # list of statuses a task can be in after it is requested to be
+ # claimed before closing or re-opening
+ claim_status = ['ClaimRequested', 'Claimed', 'ActionNeeded',
+ 'NeedsWork', 'NeedsReview']
+ if entity.status in claim_status:
+ context['header_msg'] = self.DEF_TASK_CLAIMED_MSG
+ elif entity.status in ['AwaitingRegistration', 'Closed']:
+ context['header_msg'] = self.DEF_TASK_CLOSED_MSG
+
+ context['signin_comment_msg'] = self.DEF_SIGNIN_TO_COMMENT_MSG % (
+ context['sign_in'])
+
+ return validation
+
+ def _constructMentorActions(self, context, entity):
+ """Constructs the list of actions for mentors, org admins and
+ hosts.
+ """
+
+ # variable that holds what kind of validation this user
+ # and task combination pass.
+ validation = None
+
+ actions = []
+
+ if entity.status in ['Unapproved', 'Unpublished']:
+ context['header_msg'] = self.DEF_TASK_UNPUBLISHED_MSG
+ context['comment_disabled'] = True
+ elif entity.status == 'Open':
+ context['header_msg'] = self.DEF_CAN_EDIT_TASK_MSG
+ elif entity.status == 'Reopened':
+ context['header_msg'] = self.DEF_TASK_MENTOR_REOPENED_MSG
+ elif entity.status == 'ClaimRequested':
+ actions.extend([('accept', 'Accept claim request'),
+ ('reject', 'Reject claim request')])
+ context['header_msg'] = self.DEF_TASK_CLAIM_REQUESTED_MSG
+ validation = 'accept_claim'
+ elif entity.status == 'Claimed':
+ context['header_msg'] = self.DEF_TASK_CLAIMED_BY_STUDENT_MSG
+ elif entity.status == 'NeedsReview':
+ context['header_msg'] = self.DEF_TASK_NEEDS_REVIEW_MSG
+ actions.extend([('needs_work', 'Needs More Work'),
+ ('reopened', 'Reopen the task'),
+ ('closed', 'Close the task')])
+ validation = 'close'
+ elif entity.status in ['AwaitingRegistration', 'Closed']:
+ context['header_msg'] = self.DEF_TASK_CLOSED_MSG
+
+ return validation, actions
+
+ def _constructStudentActions(self, context, entity, user_account):
+ """Constructs the list of actions for students.
+ """
+
+ # variable that holds what kind of validation this user
+ # and task combination pass.
+ validation = None
+
+ actions = []
+
+ if entity.status in ['Open', 'Reopened']:
+ task_filter = {
+ 'user': user_account,
+ 'status': ['ClaimRequested', 'Claimed', 'ActionNeeded',
+ 'NeedsWork', 'NeedsReview']
+ }
+ task_entities = ghop_task_logic.logic.getForFields(task_filter)
+
+ if len(task_entities) >= entity.program.nr_simultaneous_tasks:
+ context['header_msg'] = self.DEF_MAX_TASK_LIMIT_MSG_FMT % (
+ entity.program.nr_simultaneous_tasks)
+ validation = 'claim_ineligible'
+ return validation, actions
+
+ task_filter['status'] = 'AwaitingRegistration'
+ task_entities = ghop_task_logic.logic.getForFields(task_filter)
+
+ if task_entities:
+ context['header_msg'] = self.DEF_AWAITING_REG_MSG
+ validation = 'claim_ineligible'
+ else:
+ actions.append(('request', 'Request to claim the task'))
+ validation = 'claim_request'
+
+ # TODO: lot of double information here that can be simplified
+ if entity.user and user_account.key() == entity.user.key():
+ if entity.status == 'ClaimRequested':
+ context['header_msg'] = self.DEF_TASK_REQ_CLAIMED_BY_YOU_MSG
+ actions.append(('withdraw', 'Withdraw from the task'))
+ validation = 'claim_withdraw'
+ elif entity.status in ['Claimed', 'NeedsWork',
+ 'NeedsReview', 'ActionNeeded']:
+ context['header_msg'] = self.DEF_TASK_CLAIMED_BY_YOU_MSG
+ actions.extend([
+ ('withdraw', 'Withdraw from the task'),
+ ('needs_review', 'Submit work and Request for review')])
+ validation = 'needs_review'
+ elif entity.status == 'NeedsReview':
+ context['header_msg'] = self.DEF_TASK_NO_MORE_SUBMIT_MSG
+ actions.append(('withdraw', 'Withdraw from the task'))
+ if datetime.datetime.now < entity.deadline:
+ actions.append(
+ ('needs_review', 'Submit work and Request for review'))
+ validation = 'needs_review'
+ elif entity.status == 'AwaitingRegistration':
+ context['header_msg'] = self.DEF_STUDENT_SIGNUP_MSG
+ elif entity.status == 'Closed':
+ context['header_msg'] = self.DEF_TASK_CMPLTD_BY_YOU_MSG
+ else:
+ if entity.status in ['ClaimRequested', 'Claimed',
+ 'ActionNeeded', 'NeedsWork',
+ 'NeedsReview']:
+ context['header_msg'] = self.DEF_TASK_CLAIMED_MSG
+ if entity.status in ['AwaitingRegistration', 'Closed']:
+ context['header_msg'] = self.DEF_TASK_CLOSED_MSG
+
+ return validation, actions
+
+ @decorators.merge_params
+ @decorators.check_access
+ def search(self, request, access_type, page_name=None,
+ params=None, filter=None, order=None,**kwargs):
+ """View method to search for GHOP Tasks.
+
+ Args:
+ request: the standard Django HTTP request object
+ access_type : the name of the access type which should be checked
+ page_name: the page name displayed in templates as page and header title
+ params: a dict with params for this View
+ kwargs: the Key Fields for the specified entity
+ """
+
+ from soc.modules.ghop.views.helper import list_info as list_info_helper
+
+ get_params = request.GET
+
+ contents = []
+ context = {}
+ if not filter:
+ filter = {}
+
+ public_status = ['Open', 'Reopened', 'ClaimRequested', 'Claimed',
+ 'ActionNeeded', 'Closed', 'AwaitingRegistration',
+ 'NeedsWork', 'NeedsReview']
+
+ task_params = params.copy()
+ task_params['list_template'] = 'modules/ghop/task/search/search.html'
+ task_params['list_heading'] = 'modules/ghop/task/search/heading.html'
+ task_params['list_row'] = 'modules/ghop/task/search/row.html'
+
+ task_params['list_action'] = (redirects.getPublicRedirect,
+ task_params)
+
+ task_params['list_description'] = ugettext(
+ 'Search results: ')
+
+ program_entity = ghop_program_logic.logic.getFromKeyFields(kwargs)
+
+ org_fields = {
+ 'scope': program_entity,
+ }
+
+ org_entities = ghop_org_logic.logic.getForFields(org_fields)
+ org_names = []
+ for org in org_entities:
+ org_names.append(org.name)
+
+ df_entities = ghop_task_model.TaskDifficultyTag.get_by_scope(
+ program_entity)
+ difficulties = []
+ for df_entity in df_entities:
+ difficulties.append(df_entity.tag)
+
+ tt_entities = ghop_task_model.TaskTypeTag.get_by_scope(program_entity)
+ task_types = []
+ for tt_entity in tt_entities:
+ task_types.append(tt_entity.tag)
+
+ context['org_entities'] = org_names
+ context['public_status'] = public_status
+ context['difficulties'] = difficulties
+ context['tags'] = task_types
+
+ org_filter = get_params.getlist('Organization')
+ status_filter = get_params.getlist('Status')
+ df_filter = get_params.getlist('Difficulty')
+ tag_filter = get_params.getlist('Tags')
+
+ if org_filter:
+ org_fields = {
+ 'scope': program_entity,
+ 'name': org_filter,
+ }
+ org_entities = ghop_org_logic.logic.getForFields(org_fields)
+ filter['scope'] = org_entities
+ if status_filter:
+ filter['status']= status_filter
+ else:
+ filter['status'] = public_status
+ if df_filter:
+ filter['difficulty'] = df_filter
+ if tag_filter:
+ filter['task_type'] = tag_filter
+
+ filter['program'] = program_entity
+
+ task_list = lists.getListContent(request, task_params, filter,
+ order=order, idx=0, need_content=True)
+
+ if task_list:
+ contents.append(task_list)
+
+ # call the _list method from base to display the list
+ return self._list(request, task_params, contents, page_name, context)
+
+
+view = View()
+
+create = decorators.view(view.create)
+delete = decorators.view(view.delete)
+edit = decorators.view(view.edit)
+list = decorators.view(view.list)
+list_org_tasks = decorators.view(view.listOrgTasks)
+suggest_task = decorators.view(view.suggestTask)
+public = decorators.view(view.public)
+search = decorators.view(view.search)