32 from django import http |
32 from django import http |
33 from django.utils.translation import ugettext |
33 from django.utils.translation import ugettext |
34 |
34 |
35 from soc.logic import cleaning |
35 from soc.logic import cleaning |
36 from soc.logic import dicts |
36 from soc.logic import dicts |
37 from soc.logic.models import host as host_logic |
37 from soc.logic.models import host as host_logic |
38 from soc.logic.models import student as student_logic |
38 from soc.logic.models import student as student_logic |
39 from soc.logic.models import user as user_logic |
39 from soc.logic.models import user as user_logic |
40 from soc.views import helper |
40 from soc.views import helper |
41 from soc.views import out_of_band |
41 from soc.views import out_of_band |
42 from soc.views.helper import decorators |
42 from soc.views.helper import decorators |
88 |
88 |
89 DEF_STUDENT_SIGNUP_MSG = ugettext( |
89 DEF_STUDENT_SIGNUP_MSG = ugettext( |
90 'You have successfully completed this task. Sign up as a student ' |
90 'You have successfully completed this task. Sign up as a student ' |
91 'before you proceed further.') |
91 'before you proceed further.') |
92 |
92 |
93 DEF_TASK_CLAIMED_BY_YOU_MSG = ugettext( |
93 DEF_TASK_CLAIMED_BY_YOU_MSG = ugettext( |
94 'You have claimed this task!') |
94 'You have claimed this task!') |
95 |
95 |
96 DEF_TASK_CLAIMED_BY_STUDENT_MSG = ugettext( |
96 DEF_TASK_CLAIMED_BY_STUDENT_MSG = ugettext( |
97 'This task has been claimed by a student!') |
97 'This task has been claimed by a student!') |
98 |
98 |
148 # TODO: create and suggest_task don't need access checks which use state |
148 # TODO: create and suggest_task don't need access checks which use state |
149 # also feels like roles are being checked twice? |
149 # also feels like roles are being checked twice? |
150 rights['create'] = [ |
150 rights['create'] = [ |
151 ('checkCanOrgAdminOrMentorEdit', ['scope_path', True]), |
151 ('checkCanOrgAdminOrMentorEdit', ['scope_path', True]), |
152 ('checkRoleAndStatusForTask', |
152 ('checkRoleAndStatusForTask', |
153 [['ghop/org_admin'], ['active'], |
153 [['ghop/org_admin'], ['active'], |
154 []])] |
154 []])] |
155 rights['edit'] = [ |
155 rights['edit'] = [ |
156 ('checkCanOrgAdminOrMentorEdit', ['scope_path', False]), |
156 ('checkCanOrgAdminOrMentorEdit', ['scope_path', False]), |
157 ('checkRoleAndStatusForTask', |
157 ('checkRoleAndStatusForTask', |
158 [['ghop/org_admin'], ['active'], |
158 [['ghop/org_admin'], ['active'], |
159 ['Unapproved', 'Unpublished', 'Open']])] |
159 ['Unapproved', 'Unpublished', 'Open']])] |
160 rights['delete'] = ['checkIsDeveloper'] |
160 rights['delete'] = ['checkIsDeveloper'] |
161 rights['show'] = ['checkStatusForTask'] |
161 rights['show'] = ['checkStatusForTask'] |
162 rights['list_org_tasks'] = [ |
162 rights['list_org_tasks'] = [ |
163 ('checkCanOrgAdminOrMentorEdit', ['scope_path', False])] |
163 ('checkCanOrgAdminOrMentorEdit', ['scope_path', False])] |
164 rights['suggest_task'] = [ |
164 rights['suggest_task'] = [ |
165 ('checkCanOrgAdminOrMentorEdit', ['scope_path', True]), |
165 ('checkCanOrgAdminOrMentorEdit', ['scope_path', True]), |
166 ('checkRoleAndStatusForTask', |
166 ('checkRoleAndStatusForTask', |
167 [['ghop/org_admin', 'ghop/mentor'], ['active'], |
167 [['ghop/org_admin', 'ghop/mentor'], ['active'], |
168 ['Unapproved']])] |
168 ['Unapproved']])] |
169 rights['search'] = ['allow'] |
169 rights['search'] = ['allow'] |
170 |
170 |
171 new_params = {} |
171 new_params = {} |
172 new_params['logic'] = soc.modules.ghop.logic.models.task.logic |
172 new_params['logic'] = soc.modules.ghop.logic.models.task.logic |
180 new_params['url_name'] = 'ghop/task' |
180 new_params['url_name'] = 'ghop/task' |
181 |
181 |
182 new_params['scope_view'] = ghop_org_view |
182 new_params['scope_view'] = ghop_org_view |
183 new_params['scope_redirect'] = redirects.getCreateRedirect |
183 new_params['scope_redirect'] = redirects.getCreateRedirect |
184 |
184 |
185 new_params['list_heading'] = 'modules/ghop/task/list/heading.html' |
185 new_params['list_heading'] = 'modules/ghop/task/list/heading.html' |
186 new_params['list_row'] = 'modules/ghop/task/list/row.html' |
186 new_params['list_row'] = 'modules/ghop/task/list/row.html' |
187 |
187 |
188 new_params['extra_dynaexclude'] = ['task_type', 'mentors', 'user', |
188 new_params['extra_dynaexclude'] = ['task_type', 'mentors', 'user', |
189 'student', 'program', 'status', |
189 'student', 'program', 'status', |
190 'deadline', 'created_by', |
190 'deadline', 'created_by', |
191 'created_on', 'modified_by', |
191 'created_on', 'modified_by', |
192 'modified_on', 'history', |
192 'modified_on', 'history', |
193 'link_id', 'difficulty'] |
193 'link_id', 'difficulty'] |
194 |
194 |
246 |
246 |
247 super(View, self).__init__(params=params) |
247 super(View, self).__init__(params=params) |
248 |
248 |
249 # holds the base form for the task creation and editing |
249 # holds the base form for the task creation and editing |
250 self._params['base_create_form'] = self._params['create_form'] |
250 self._params['base_create_form'] = self._params['create_form'] |
251 self._params['base_edit_form'] = self._params['edit_form'] |
251 self._params['base_edit_form'] = self._params['edit_form'] |
252 |
252 |
253 # extend create and edit form for org admins |
253 # extend create and edit form for org admins |
254 dynafields = [ |
254 dynafields = [ |
255 {'name': 'mentors_list', |
255 {'name': 'mentors_list', |
256 'required': True, |
256 'required': True, |
303 dynaproperties = params_helper.getDynaFields(dynafields) |
303 dynaproperties = params_helper.getDynaFields(dynafields) |
304 dynaproperties['clean_comment'] = cleaning.clean_html_content('comment') |
304 dynaproperties['clean_comment'] = cleaning.clean_html_content('comment') |
305 dynaproperties['clean'] = ghop_cleaning.cleanTaskComment( |
305 dynaproperties['clean'] = ghop_cleaning.cleanTaskComment( |
306 'comment', 'action', 'work_submission') |
306 'comment', 'action', 'work_submission') |
307 |
307 |
308 comment_form = dynaform.newDynaForm(dynamodel=None, |
308 comment_form = dynaform.newDynaForm(dynamodel=None, |
309 dynabase=helper.forms.BaseForm, dynainclude=None, |
309 dynabase=helper.forms.BaseForm, dynainclude=None, |
310 dynaexclude=None, dynaproperties=dynaproperties) |
310 dynaexclude=None, dynaproperties=dynaproperties) |
311 self._params['comment_form'] = comment_form |
311 self._params['comment_form'] = comment_form |
312 |
312 |
313 def _getTagsForProgram(self, form_name, params, **kwargs): |
313 def _getTagsForProgram(self, form_name, params, **kwargs): |
314 """Extends form dynamically from difficulty levels in program entity. |
314 """Extends form dynamically from difficulty levels in program entity. |
316 Args: |
316 Args: |
317 form_name: the Form entry in params to extend |
317 form_name: the Form entry in params to extend |
318 params: the params for the view |
318 params: the params for the view |
319 """ |
319 """ |
320 |
320 |
321 # obtain program_entity using scope_path which holds |
321 # obtain program_entity using scope_path which holds |
322 # the org_entity key_name |
322 # the org_entity key_name |
323 org_entity = ghop_org_logic.logic.getFromKeyName(kwargs['scope_path']) |
323 org_entity = ghop_org_logic.logic.getFromKeyName(kwargs['scope_path']) |
324 program_entity = ghop_program_logic.logic.getFromKeyName( |
324 program_entity = ghop_program_logic.logic.getFromKeyName( |
325 org_entity.scope_path) |
325 org_entity.scope_path) |
326 |
326 |
327 # get a list difficulty levels stored for the program entity |
327 # get a list difficulty levels stored for the program entity |
328 tds = ghop_task_model.TaskDifficultyTag.get_by_scope( |
328 tds = ghop_task_model.TaskDifficultyTag.get_by_scope( |
329 program_entity) |
329 program_entity) |
330 |
330 |
331 difficulties = [] |
331 difficulties = [] |
332 for td in tds: |
332 for td in tds: |
333 difficulties.append((td.tag, td.tag)) |
333 difficulties.append((td.tag, td.tag)) |
334 |
334 |
335 # get a list of task type tags stored for the program entity |
335 # get a list of task type tags stored for the program entity |
336 tts = ghop_task_model.TaskTypeTag.get_by_scope(program_entity) |
336 tts = ghop_task_model.TaskTypeTag.get_by_scope(program_entity) |
337 |
337 |
338 type_tags = [] |
338 type_tags = [] |
339 for tt in tts: |
339 for tt in tts: |
340 type_tags.append((tt.tag, tt.tag)) |
340 type_tags.append((tt.tag, tt.tag)) |
341 |
341 |
342 # create the difficultly level field containing the choices |
342 # create the difficultly level field containing the choices |
343 # defined in the program entity |
343 # defined in the program entity |
344 dynafields = [ |
344 dynafields = [ |
345 {'name': 'difficulty', |
345 {'name': 'difficulty', |
346 'base': forms.ChoiceField, |
346 'base': forms.ChoiceField, |
347 'label': 'Difficulty level', |
347 'label': 'Difficulty level', |
473 if 'approved' in form.fields: |
473 if 'approved' in form.fields: |
474 if entity.status == 'Unapproved': |
474 if entity.status == 'Unapproved': |
475 form.fields['approved'].initial = False |
475 form.fields['approved'].initial = False |
476 else: |
476 else: |
477 form.fields['approved'].initial = True |
477 form.fields['approved'].initial = True |
478 |
478 |
479 # checks if the task is already published or not and sets |
479 # checks if the task is already published or not and sets |
480 # the form published field |
480 # the form published field |
481 if 'published' in form.fields: |
481 if 'published' in form.fields: |
482 if entity.status == 'Unapproved' or entity.status == 'Unpublished': |
482 if entity.status == 'Unapproved' or entity.status == 'Unpublished': |
483 form.fields['published'].initial = False |
483 form.fields['published'].initial = False |
484 else: |
484 else: |
485 form.fields['published'].initial = True |
485 form.fields['published'].initial = True |
545 fields['modified_on'] = datetime.datetime.now() |
545 fields['modified_on'] = datetime.datetime.now() |
546 |
546 |
547 if not entity: |
547 if not entity: |
548 fields['link_id'] = 't%i' % (int(time.time()*100)) |
548 fields['link_id'] = 't%i' % (int(time.time()*100)) |
549 fields['modified_by'] = role_entity |
549 fields['modified_by'] = role_entity |
550 fields['created_by'] = role_entity |
550 fields['created_by'] = role_entity |
551 fields['created_on'] = datetime.datetime.now() |
551 fields['created_on'] = datetime.datetime.now() |
552 else: |
552 else: |
553 fields['link_id'] = entity.link_id |
553 fields['link_id'] = entity.link_id |
554 fields['modified_by'] = role_entity |
554 fields['modified_by'] = role_entity |
555 if 'status' not in fields: |
555 if 'status' not in fields: |
572 |
572 |
573 return |
573 return |
574 |
574 |
575 @decorators.merge_params |
575 @decorators.merge_params |
576 @decorators.check_access |
576 @decorators.check_access |
577 def suggestTask(self, request, access_type, page_name=None, |
577 def suggestTask(self, request, access_type, page_name=None, |
578 params=None, **kwargs): |
578 params=None, **kwargs): |
579 """View used to allow mentors to create or edit a task. |
579 """View used to allow mentors to create or edit a task. |
580 |
580 |
581 Tasks created by mentors must be approved by org admins |
581 Tasks created by mentors must be approved by org admins |
582 before they are published. |
582 before they are published. |
583 """ |
583 """ |
584 |
584 |
695 |
695 |
696 for key_name in task_keys: |
696 for key_name in task_keys: |
697 |
697 |
698 task_entity = ghop_task_logic.logic.getFromKeyName(key_name) |
698 task_entity = ghop_task_logic.logic.getFromKeyName(key_name) |
699 |
699 |
700 # Of course only the tasks from this organization and those with a valid |
700 # Of course only the tasks from this organization and those with a valid |
701 # state can be updated. |
701 # state can be updated. |
702 if task_entity and task_entity.scope.key() == org_entity.key() and \ |
702 if task_entity and task_entity.scope.key() == org_entity.key() and \ |
703 task_entity.status in ['Unapproved', 'Unpublished']: |
703 task_entity.status in ['Unapproved', 'Unpublished']: |
704 task_entity.status = changed_status |
704 task_entity.status = changed_status |
705 |
705 |
802 'List of Published tasks.') |
802 'List of Published tasks.') |
803 |
803 |
804 filter = { |
804 filter = { |
805 'scope': org_entity, |
805 'scope': org_entity, |
806 'status': ['Open', 'Reopened', 'ClaimRequested', 'Claimed', |
806 'status': ['Open', 'Reopened', 'ClaimRequested', 'Claimed', |
807 'ActionNeeded', 'Closed', 'AwaitingRegistration', |
807 'ActionNeeded', 'Closed', 'AwaitingRegistration', |
808 'NeedsWork', 'NeedsReview'], |
808 'NeedsWork', 'NeedsReview'], |
809 } |
809 } |
810 |
810 |
811 ap_list = lists.getListContent(request, ap_params, filter, idx=2, |
811 ap_list = lists.getListContent(request, ap_params, filter, idx=2, |
812 need_content=True) |
812 need_content=True) |
848 entity, comment_entity = ghop_task_logic.logic.updateTaskStatus(entity) |
848 entity, comment_entity = ghop_task_logic.logic.updateTaskStatus(entity) |
849 if comment_entity: |
849 if comment_entity: |
850 comment_entities.append(comment_entity) |
850 comment_entities.append(comment_entity) |
851 |
851 |
852 context['entity'] = entity |
852 context['entity'] = entity |
853 context['entity_key_name'] = entity.key().id_or_name() |
853 context['entity_key_name'] = entity.key().id_or_name() |
854 context['entity_type'] = params['name'] |
854 context['entity_type'] = params['name'] |
855 context['entity_type_url'] = params['url_name'] |
855 context['entity_type_url'] = params['url_name'] |
856 |
856 |
857 user_account = user_logic.logic.getForCurrentAccount() |
857 user_account = user_logic.logic.getForCurrentAccount() |
858 |
858 |
859 # get some entity specific context |
859 # get some entity specific context |
860 self.updatePublicContext(context, entity, comment_entities, |
860 self.updatePublicContext(context, entity, comment_entities, |
861 ws_entities, user_account, params) |
861 ws_entities, user_account, params) |
862 |
862 |
863 validation = self._constructActionsList(context, entity, |
863 validation = self._constructActionsList(context, entity, |
864 user_account, params) |
864 user_account, params) |
865 |
865 |
866 context = dicts.merge(params['context'], context) |
866 context = dicts.merge(params['context'], context) |
867 |
867 |
868 if request.method == 'POST': |
868 if request.method == 'POST': |
869 return self.publicPost(request, context, params, entity, |
869 return self.publicPost(request, context, params, entity, |
870 user_account, validation, **kwargs) |
870 user_account, validation, **kwargs) |
871 else: # request.method == 'GET' |
871 else: # request.method == 'GET' |
872 return self.publicGet(request, context, params, entity, |
872 return self.publicGet(request, context, params, entity, |
873 user_account, **kwargs) |
873 user_account, **kwargs) |
874 |
874 |
916 st_filter, unique=True) |
916 st_filter, unique=True) |
917 |
917 |
918 if student_entity: |
918 if student_entity: |
919 properties['student'] = student_entity |
919 properties['student'] = student_entity |
920 |
920 |
921 changes.extend([ugettext('User-Student'), |
921 changes.extend([ugettext('User-Student'), |
922 ugettext('Action-Claim Requested'), |
922 ugettext('Action-Claim Requested'), |
923 ugettext('Status-%s' % (properties['status'])) |
923 ugettext('Status-%s' % (properties['status'])) |
924 ]) |
924 ]) |
925 elif (validation == 'claim_withdraw' or |
925 elif (validation == 'claim_withdraw' or |
926 validation == 'needs_review') and action == 'withdraw': |
926 validation == 'needs_review') and action == 'withdraw': |
927 properties = { |
927 properties = { |
928 'user': None, |
928 'user': None, |
929 'student': None, |
929 'student': None, |
930 'status': 'Reopened', |
930 'status': 'Reopened', |
1123 def _constructActionsList(self, context, entity, |
1123 def _constructActionsList(self, context, entity, |
1124 user_account, params): |
1124 user_account, params): |
1125 """Constructs a list of actions for the task page and extends |
1125 """Constructs a list of actions for the task page and extends |
1126 the comment form with this list. |
1126 the comment form with this list. |
1127 |
1127 |
1128 This method also returns the validation used by POST method to |
1128 This method also returns the validation used by POST method to |
1129 validate the user input data. |
1129 validate the user input data. |
1130 |
1130 |
1131 Args: |
1131 Args: |
1132 context: the context that should be updated |
1132 context: the context that should be updated |
1133 entity: a task used to set context |
1133 entity: a task used to set context |
1180 else: |
1180 else: |
1181 validation, student_actions = self._constructStudentActions( |
1181 validation, student_actions = self._constructStudentActions( |
1182 context, entity, user_account) |
1182 context, entity, user_account) |
1183 actions += student_actions |
1183 actions += student_actions |
1184 |
1184 |
1185 # create the difficultly level field containing the choices |
1185 # create the difficultly level field containing the choices |
1186 # defined in the program entity |
1186 # defined in the program entity |
1187 dynafields = [ |
1187 dynafields = [ |
1188 {'name': 'action', |
1188 {'name': 'action', |
1189 'base': forms.ChoiceField, |
1189 'base': forms.ChoiceField, |
1190 'label': 'Action', |
1190 'label': 'Action', |
1290 actions = [] |
1290 actions = [] |
1291 |
1291 |
1292 if entity.status in ['Open', 'Reopened']: |
1292 if entity.status in ['Open', 'Reopened']: |
1293 task_filter = { |
1293 task_filter = { |
1294 'user': user_account, |
1294 'user': user_account, |
1295 'status': ['ClaimRequested', 'Claimed', 'ActionNeeded', |
1295 'status': ['ClaimRequested', 'Claimed', 'ActionNeeded', |
1296 'NeedsWork', 'NeedsReview'] |
1296 'NeedsWork', 'NeedsReview'] |
1297 } |
1297 } |
1298 task_entities = ghop_task_logic.logic.getForFields(task_filter) |
1298 task_entities = ghop_task_logic.logic.getForFields(task_filter) |
1299 |
1299 |
1300 if len(task_entities) >= entity.program.nr_simultaneous_tasks: |
1300 if len(task_entities) >= entity.program.nr_simultaneous_tasks: |
1317 if entity.user and user_account.key() == entity.user.key(): |
1317 if entity.user and user_account.key() == entity.user.key(): |
1318 if entity.status == 'ClaimRequested': |
1318 if entity.status == 'ClaimRequested': |
1319 context['header_msg'] = self.DEF_TASK_REQ_CLAIMED_BY_YOU_MSG |
1319 context['header_msg'] = self.DEF_TASK_REQ_CLAIMED_BY_YOU_MSG |
1320 actions.append(('withdraw', 'Withdraw from the task')) |
1320 actions.append(('withdraw', 'Withdraw from the task')) |
1321 validation = 'claim_withdraw' |
1321 validation = 'claim_withdraw' |
1322 elif entity.status in ['Claimed', 'NeedsWork', |
1322 elif entity.status in ['Claimed', 'NeedsWork', |
1323 'NeedsReview', 'ActionNeeded']: |
1323 'NeedsReview', 'ActionNeeded']: |
1324 context['header_msg'] = self.DEF_TASK_CLAIMED_BY_YOU_MSG |
1324 context['header_msg'] = self.DEF_TASK_CLAIMED_BY_YOU_MSG |
1325 actions.extend([ |
1325 actions.extend([ |
1326 ('withdraw', 'Withdraw from the task'), |
1326 ('withdraw', 'Withdraw from the task'), |
1327 ('needs_review', 'Submit work and Request for review')]) |
1327 ('needs_review', 'Submit work and Request for review')]) |
1336 elif entity.status == 'AwaitingRegistration': |
1336 elif entity.status == 'AwaitingRegistration': |
1337 context['header_msg'] = self.DEF_STUDENT_SIGNUP_MSG |
1337 context['header_msg'] = self.DEF_STUDENT_SIGNUP_MSG |
1338 elif entity.status == 'Closed': |
1338 elif entity.status == 'Closed': |
1339 context['header_msg'] = self.DEF_TASK_CMPLTD_BY_YOU_MSG |
1339 context['header_msg'] = self.DEF_TASK_CMPLTD_BY_YOU_MSG |
1340 else: |
1340 else: |
1341 if entity.status in ['ClaimRequested', 'Claimed', |
1341 if entity.status in ['ClaimRequested', 'Claimed', |
1342 'ActionNeeded', 'NeedsWork', |
1342 'ActionNeeded', 'NeedsWork', |
1343 'NeedsReview']: |
1343 'NeedsReview']: |
1344 context['header_msg'] = self.DEF_TASK_CLAIMED_MSG |
1344 context['header_msg'] = self.DEF_TASK_CLAIMED_MSG |
1345 if entity.status in ['AwaitingRegistration', 'Closed']: |
1345 if entity.status in ['AwaitingRegistration', 'Closed']: |
1346 context['header_msg'] = self.DEF_TASK_CLOSED_MSG |
1346 context['header_msg'] = self.DEF_TASK_CLOSED_MSG |