app/soc/modules/ghop/views/models/program.py
changeset 2891 aba681d72b0a
child 2935 0b8b82b6764e
equal deleted inserted replaced
2890:515ea474e0a2 2891:aba681d72b0a
       
     1 #!/usr/bin/python2.5
       
     2 #
       
     3 # Copyright 2009 the Melange authors.
       
     4 #
       
     5 # Licensed under the Apache License, Version 2.0 (the "License");
       
     6 # you may not use this file except in compliance with the License.
       
     7 # You may obtain a copy of the License at
       
     8 #
       
     9 #   http://www.apache.org/licenses/LICENSE-2.0
       
    10 #
       
    11 # Unless required by applicable law or agreed to in writing, software
       
    12 # distributed under the License is distributed on an "AS IS" BASIS,
       
    13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
       
    14 # See the License for the specific language governing permissions and
       
    15 # limitations under the License.
       
    16 
       
    17 """GHOP specific views for Programs.
       
    18 """
       
    19 
       
    20 __authors__ = [
       
    21     '"Madhusudan.C.S" <madhusudancs@gmail.com>',
       
    22     '"Lennard de Rijk" <ljvderijk@gmail.com>',
       
    23   ]
       
    24 
       
    25 
       
    26 import datetime
       
    27 import os
       
    28 
       
    29 from django import forms
       
    30 from django import http
       
    31 from django.utils.translation import ugettext
       
    32 
       
    33 from soc.logic import cleaning
       
    34 from soc.logic import dicts
       
    35 from soc.logic.helper import timeline as timeline_helper
       
    36 from soc.logic.models import host as host_logic
       
    37 from soc.views import out_of_band
       
    38 from soc.views import helper
       
    39 from soc.views.helper import decorators
       
    40 from soc.views.helper import dynaform
       
    41 from soc.views.helper import lists
       
    42 from soc.views.helper import params as params_helper
       
    43 from soc.views.helper import redirects
       
    44 from soc.views.helper import widgets
       
    45 from soc.views.models import document as document_view
       
    46 from soc.views.models import program 
       
    47 from soc.views.sitemap import sidebar
       
    48 
       
    49 import soc.cache.logic
       
    50 
       
    51 from soc.modules.ghop.logic.models import mentor as ghop_mentor_logic
       
    52 from soc.modules.ghop.logic.models import org_admin as ghop_org_admin_logic
       
    53 from soc.modules.ghop.logic.models import program as ghop_program_logic
       
    54 from soc.modules.ghop.logic.models import student as ghop_student_logic
       
    55 from soc.modules.ghop.logic.models import task as ghop_task_logic
       
    56 from soc.modules.ghop.models import task as ghop_task_model
       
    57 from soc.modules.ghop.views.helper import access as ghop_access
       
    58 from soc.modules.ghop.views.helper import redirects as ghop_redirects
       
    59 
       
    60 import soc.modules.ghop.logic.models.program
       
    61 
       
    62 
       
    63 class View(program.View):
       
    64   """View methods for the GHOP Program model.
       
    65   """
       
    66 
       
    67   DEF_PARTICIPATING_ORGS_MSG_FMT = ugettext(
       
    68       'The following is a list of all the participating organizations under '
       
    69       'the programme %(name)s. To know more about each organization and see '
       
    70       'the tasks published by them please visit the corresponding links.')
       
    71 
       
    72   DEF_TASK_QUOTA_ALLOCATION_MSG = ugettext(
       
    73       "Use this view to assign task quotas.")
       
    74 
       
    75   DEF_TASK_QUOTA_ERROR_MSG_FMT = ugettext(
       
    76       "Task Quota limit for the organizations %s do not contain"
       
    77       " a valid number(>0) and has not been updated.")
       
    78 
       
    79   def __init__(self, params=None):
       
    80     """Defines the fields and methods required for the program View class
       
    81     to provide the user with list, public, create, edit and delete views.
       
    82 
       
    83     Params:
       
    84       params: a dict with params for this View
       
    85     """
       
    86 
       
    87     rights = ghop_access.GHOPChecker(params)
       
    88     rights['show'] = ['allow']
       
    89     rights['create'] = [('checkSeeded', ['checkHasActiveRoleForScope',
       
    90                                          host_logic.logic])]
       
    91     rights['edit'] = [('checkIsHostForProgram',
       
    92                        [ghop_program_logic.logic])]
       
    93     rights['delete'] = ['checkIsDeveloper']
       
    94     rights['task_difficulty'] = [('checkIsHostForProgram',
       
    95                                   [ghop_program_logic.logic])]
       
    96     rights['task_type'] = [('checkIsHostForProgram',
       
    97                             [ghop_program_logic.logic])]
       
    98     rights['difficulty_tag_edit'] = [('checkIsHostForProgram',
       
    99                                       [ghop_program_logic.logic])]
       
   100     rights['type_tag_edit'] = [('checkIsHostForProgram',
       
   101                                 [ghop_program_logic.logic])]
       
   102 
       
   103     new_params = {}
       
   104     new_params['logic'] = soc.modules.ghop.logic.models.program.logic
       
   105     new_params['rights'] = rights
       
   106 
       
   107     new_params['name'] = "GHOP Program"
       
   108     new_params['module_name'] = "program"
       
   109     new_params['sidebar_grouping'] = 'Programs'
       
   110 
       
   111     new_params['module_package'] = 'soc.modules.ghop.views.models'
       
   112     new_params['url_name'] = 'ghop/program'
       
   113 
       
   114     # TODO: this list can be reduced after GSoC has been moved
       
   115     new_params['extra_dynaexclude'] = ['apps_tasks_limit',
       
   116                                        'min_slots', 'max_slots',
       
   117                                        'slots', 'slot_allocation',
       
   118                                        'allocations_visible',
       
   119                                        'task_difficulties', 'task_types',
       
   120                                        ]
       
   121 
       
   122     patterns = []
       
   123     patterns += [
       
   124         (r'^%(url_name)s/(?P<access_type>assign_task_quotas)/%(key_fields)s$',
       
   125           '%(module_package)s.%(module_name)s.assign_task_quotas',
       
   126           'Assign task quota limits'),
       
   127         (r'^%(url_name)s/(?P<access_type>task_difficulty)/%(key_fields)s$',
       
   128          '%(module_package)s.%(module_name)s.task_difficulty_edit',
       
   129          'Edit Task Difficulty Tags'),
       
   130         (r'^%(url_name)s/(?P<access_type>task_type)/%(key_fields)s$',
       
   131          '%(module_package)s.%(module_name)s.task_type_edit',
       
   132          'Edit Task Type Tags'),
       
   133         (r'^%(url_name)s/(?P<access_type>difficulty_tag_edit)$',
       
   134          '%(module_package)s.%(module_name)s.difficulty_tag_edit',
       
   135          'Edit a Difficulty Tag'),
       
   136         (r'^%(url_name)s/(?P<access_type>type_tag_edit)$',
       
   137          '%(module_package)s.%(module_name)s.task_type_tag_edit',
       
   138          'Edit a Task Type Tag'),
       
   139         ]
       
   140 
       
   141     new_params['extra_django_patterns'] = patterns
       
   142 
       
   143     params = dicts.merge(params, new_params, sub_merge=True)
       
   144 
       
   145     super(View, self).__init__(params=params)
       
   146 
       
   147     dynafields = [
       
   148         {'name': 'task_difficulties',
       
   149          'base': forms.CharField,
       
   150          'label': 'Task Difficulty Levels',
       
   151          'widget': widgets.ReadOnlyInput(),
       
   152          'required': False,
       
   153          'help_text': ugettext('Lists all the difficulty levels that '
       
   154                                'can be assigned to a task. Edit them '
       
   155                                'from the Program menu on sidebar.'),
       
   156          },
       
   157          {'name': 'task_types',
       
   158          'base': forms.CharField,
       
   159          'label': 'Task Type Tags',
       
   160          'widget': widgets.ReadOnlyInput(),
       
   161          'required': False,
       
   162          'help_text': ugettext('Lists all the types a task can be in. '
       
   163                                'Edit them from the Program menu on sidebar.'),
       
   164          },
       
   165         ]
       
   166 
       
   167     dynaproperties = params_helper.getDynaFields(dynafields)
       
   168 
       
   169     edit_form = dynaform.extendDynaForm(
       
   170         dynaform=self._params['edit_form'],
       
   171         dynaproperties=dynaproperties)
       
   172 
       
   173     self._params['edit_form'] = edit_form
       
   174 
       
   175   def _editGet(self, request, entity, form):
       
   176     """See base.View._editGet().
       
   177     """
       
   178 
       
   179     # TODO: can't a simple join operation do this?
       
   180     tds = ghop_task_model.TaskDifficultyTag.get_by_scope(entity)
       
   181     if tds:
       
   182       td_str = ''
       
   183       for td in tds[:-1]:
       
   184         td_str += str(td) + ', '
       
   185 
       
   186       td_str += str(tds[-1])
       
   187 
       
   188       form.fields['task_difficulties'].initial = td_str
       
   189 
       
   190     tts = ghop_task_model.TaskTypeTag.get_by_scope(entity)
       
   191     if tts:
       
   192       tt_str = ''
       
   193       for tt in tts[:-1]:
       
   194         tt_str += str(tt) + ', '
       
   195 
       
   196       tt_str += str(tts[-1])
       
   197 
       
   198       form.fields['task_types'].initial = tt_str
       
   199 
       
   200     return super(View, self)._editGet(request, entity, form)
       
   201 
       
   202   @decorators.merge_params
       
   203   @decorators.check_access
       
   204   def assignTaskQuotas(self, request, access_type, page_name=None,
       
   205                        params=None, filter=None, **kwargs):
       
   206     """View that allows to assign task quotas for accepted GHOP organization.
       
   207 
       
   208     This view allows the program admin to set the task quota limits
       
   209     and change them at any time when the program is active. 
       
   210     """
       
   211 
       
   212     # TODO: Once GAE Task APIs arrive, this view will be managed by them
       
   213     program = ghop_program_logic.logic.getFromKeyFieldsOr404(kwargs)
       
   214 
       
   215     from soc.modules.ghop.views.models import \
       
   216         organization as ghop_organization_view
       
   217 
       
   218     org_params = ghop_organization_view.view.getParams().copy()
       
   219 
       
   220     context = {}
       
   221 
       
   222     if request.method == 'POST':
       
   223       return self.assignTaskQuotasPost(request, context, org_params, 
       
   224                                        page_name, params, program,
       
   225                                        **kwargs)
       
   226     else: # request.method == 'GET'
       
   227       return self.assignTaskQuotasGet(request, context, org_params,
       
   228                                       page_name, params, program, 
       
   229                                       **kwargs)
       
   230 
       
   231   def assignTaskQuotasPost(self, request, context, org_params, 
       
   232                            page_name, params, entity, **kwargs):
       
   233     """Handles the POST request for the task quota allocation page.
       
   234 
       
   235     Args:
       
   236         entity: the program entity
       
   237         rest: see base.View.public()
       
   238     """
       
   239 
       
   240     ghop_org_logic = org_params['logic']
       
   241 
       
   242     error_orgs = ''
       
   243     for link_id, task_count in request.POST.items():
       
   244       fields = {
       
   245           'link_id': link_id,
       
   246           'scope': entity,
       
   247           'scope_path': entity.key().id_or_name(),
       
   248           }
       
   249       key_name = ghop_org_logic.getKeyNameFromFields(fields)
       
   250 
       
   251       try:
       
   252         task_count = int(task_count)
       
   253         if task_count >= 0:
       
   254           fields['task_quota_limit'] = task_count
       
   255           ghop_org_logic.updateOrCreateFromKeyName(fields, key_name)
       
   256         else:
       
   257           raise ValueError
       
   258       except ValueError:
       
   259         org_entity = ghop_org_logic.getFromKeyName(key_name)
       
   260         error_orgs += org_entity.name + ', ' 
       
   261 
       
   262     if error_orgs:
       
   263       context['error_message'] = self.DEF_TASK_QUOTA_ERROR_MSG_FMT % (
       
   264           error_orgs[:-2])    
       
   265 
       
   266       return self.assignTaskQuotasGet(request, context, org_params,
       
   267                                       page_name, params, entity,
       
   268                                       **kwargs) 
       
   269 
       
   270     # redirect to the same page
       
   271     return http.HttpResponseRedirect('')
       
   272 
       
   273   def assignTaskQuotasGet(self, request, context, org_params, 
       
   274                           page_name, params, entity, **kwargs):
       
   275     """Handles the GET request for the task quota allocation page.
       
   276 
       
   277     Args:
       
   278         entity: the program entity
       
   279         rest see base.View.public()
       
   280     """
       
   281 
       
   282     org_params['list_template'] = ('modules/ghop/program/'
       
   283         'allocation/allocation.html')
       
   284     org_params['list_heading'] = ('modules/ghop/program/'
       
   285         'allocation/heading.html')
       
   286     org_params['list_row'] = 'modules/ghop/program/allocation/row.html'
       
   287     org_params['list_pagination'] = 'soc/list/no_pagination.html'
       
   288 
       
   289     description = self.DEF_TASK_QUOTA_ALLOCATION_MSG
       
   290 
       
   291     filter = {
       
   292         'scope': entity,
       
   293         'status': 'active',
       
   294         }
       
   295 
       
   296     content = self._getAcceptedOrgsList(description, org_params,
       
   297                                         filter, False)
       
   298 
       
   299     contents = [content]
       
   300 
       
   301     return self._list(request, org_params, contents, page_name, context)
       
   302 
       
   303   @decorators.merge_params
       
   304   def getExtraMenus(self, id, user, params=None):
       
   305     """See soc.views.models.program.View.getExtraMenus().
       
   306     """
       
   307 
       
   308     logic = params['logic']
       
   309     rights = params['rights']
       
   310 
       
   311     # only get all invisible and visible programs
       
   312     fields = {'status': ['invisible', 'visible']}
       
   313     entities = logic.getForFields(fields)
       
   314 
       
   315     menus = []
       
   316 
       
   317     rights.setCurrentUser(id, user)
       
   318 
       
   319     for entity in entities:
       
   320       items = []
       
   321 
       
   322       if entity.status == 'visible':
       
   323         # show the documents for this program, even for not logged in users
       
   324         items += document_view.view.getMenusForScope(entity, params)
       
   325         items += self._getTimeDependentEntries(entity, params, id, user)
       
   326 
       
   327       try:
       
   328         # check if the current user is a host for this program
       
   329         rights.doCachedCheck('checkIsHostForProgram',
       
   330                              {'scope_path': entity.scope_path,
       
   331                               'link_id': entity.link_id}, [])
       
   332 
       
   333         if entity.status == 'invisible':
       
   334           # still add the document links so hosts can see how it looks like
       
   335           items += document_view.view.getMenusForScope(entity, params)
       
   336           items += self._getTimeDependentEntries(entity, params, id, user)
       
   337 
       
   338         items += [(redirects.getReviewOverviewRedirect(
       
   339             entity, {'url_name': 'ghop/org_app', 'scope_view': self}),
       
   340             "Review Organization Applications", 'any_access')]
       
   341         # add link to edit Program Profile
       
   342         items += [(redirects.getEditRedirect(entity, params),
       
   343             'Edit Program Profile', 'any_access')]
       
   344         # add link to Assign Task Quota limits
       
   345         items += [(ghop_redirects.getAssignTaskQuotasRedirect(entity, params),
       
   346             'Assign Task Quota limits', 'any_access')]
       
   347         # add link to edit Program Timeline
       
   348         items += [(redirects.getEditRedirect(
       
   349             entity, {'url_name': 'ghop/timeline'}),
       
   350             "Edit Program Timeline", 'any_access')]
       
   351         # add link to create a new Program Document
       
   352         items += [(redirects.getCreateDocumentRedirect(entity, 'ghop/program'),
       
   353             "Create a New Document", 'any_access')]
       
   354         # add link to list all Program Document
       
   355         items += [(redirects.getListDocumentsRedirect(entity, 'ghop/program'),
       
   356             "List Documents", 'any_access')]
       
   357         # add link to edit Task Difficulty Levels
       
   358         items += [(ghop_redirects.getDifficultyEditRedirect(
       
   359             entity, {'url_name': 'ghop/program'}),
       
   360             "Edit Task Difficulty Levels", 'any_access')]
       
   361         # add link to edit Task Type Tags
       
   362         items += [(ghop_redirects.getTaskTypeEditRedirect(
       
   363             entity, {'url_name': 'ghop/program'}),
       
   364             "Edit Task Type Tags", 'any_access')]
       
   365 
       
   366       except out_of_band.Error:
       
   367         pass
       
   368 
       
   369       items = sidebar.getSidebarMenu(id, user, items, params=params)
       
   370       if not items:
       
   371         continue
       
   372 
       
   373       menu = {}
       
   374       menu['heading'] = entity.short_name
       
   375       menu['items'] = items
       
   376       menu['group'] = 'Programs'
       
   377       menus.append(menu)
       
   378     
       
   379     return menus
       
   380 
       
   381   def _getTimeDependentEntries(self, ghop_program_entity, params, id, user):
       
   382     """Returns a list with time dependent menu items.
       
   383     """
       
   384 
       
   385     items = []
       
   386 
       
   387     timeline_entity = ghop_program_entity.timeline
       
   388 
       
   389     # get the student entity for this user and program
       
   390     filter = {'user': user,
       
   391               'scope': ghop_program_entity,
       
   392               'status': 'active'}
       
   393     student_entity = ghop_student_logic.logic.getForFields(filter, unique=True)
       
   394 
       
   395     if student_entity:
       
   396       items += self._getStudentEntries(ghop_program_entity, student_entity,
       
   397                                        params, id, user)
       
   398 
       
   399     # get mentor and org_admin entity for this user and program
       
   400     filter = {'user': user,
       
   401               'program': ghop_program_entity,
       
   402               'status': 'active'}
       
   403     mentor_entity = ghop_mentor_logic.logic.getForFields(filter, unique=True)
       
   404     org_admin_entity = ghop_org_admin_logic.logic.getForFields(
       
   405         filter, unique=True)
       
   406 
       
   407     if mentor_entity or org_admin_entity:
       
   408       items += self._getOrganizationEntries(
       
   409           ghop_program_entity, org_admin_entity,
       
   410           mentor_entity, params, id, user)
       
   411 
       
   412     if user and not (student_entity or mentor_entity or org_admin_entity):
       
   413       if timeline_helper.isActivePeriod(timeline_entity, 'student_signup'):
       
   414         # this user does not have a role yet for this program
       
   415         items += [('/ghop/student/apply/%s' % (
       
   416             ghop_program_entity.key().id_or_name()),
       
   417             "Register as a Student", 'any_access')]
       
   418 
       
   419     if timeline_helper.isAfterEvent(timeline_entity, 'org_signup_start'):
       
   420       url = ghop_redirects.getParticipatingOrgsRedirect(
       
   421           ghop_program_entity, params)
       
   422       # add a link to list all the organizations
       
   423       items += [(url, "List participating Organizations", 'any_access')]
       
   424 
       
   425     return items
       
   426 
       
   427   def _getStudentEntries(self, ghop_program_entity, student_entity, 
       
   428                          params, id, user):
       
   429     """Returns a list with menu items for students in a specific program.
       
   430     """
       
   431 
       
   432     items = []
       
   433 
       
   434     timeline_entity = ghop_program_entity.timeline
       
   435 
       
   436     if timeline_helper.isAfterEvent(timeline_entity,
       
   437                                    'student_signup_start'):
       
   438       # add a link to show all projects
       
   439       items += [(redirects.getListProjectsRedirect(ghop_program_entity,
       
   440           {'url_name':'ghop/task'}),
       
   441           "List my Tasks", 'any_access')]
       
   442 
       
   443     items += [(redirects.getEditRedirect(student_entity, 
       
   444         {'url_name': 'ghop/student'}),
       
   445         "Edit my Student Profile", 'any_access')]
       
   446 
       
   447     items += [(redirects.getManageRedirect(student_entity,
       
   448         {'url_name':'ghop/student'}),
       
   449         "Resign as a Student", 'any_access')]
       
   450 
       
   451     return items
       
   452 
       
   453   @decorators.merge_params
       
   454   @decorators.check_access
       
   455   def taskDifficultyEdit(self, request, access_type, page_name=None,
       
   456                          params=None, filter=None, **kwargs):
       
   457     """View method used to edit Difficulty Level tags.
       
   458     """
       
   459 
       
   460     params = dicts.merge(params, self._params)
       
   461 
       
   462     try:
       
   463       entity = self._logic.getFromKeyFieldsOr404(kwargs)
       
   464     except out_of_band.Error, error:
       
   465       return helper.responses.errorResponse(
       
   466           error, request, template=params['error_public'])
       
   467 
       
   468     context = helper.responses.getUniversalContext(request)
       
   469     helper.responses.useJavaScript(context, params['js_uses_all'])
       
   470     context['page_name'] = page_name
       
   471 
       
   472     context['program_key_name'] = entity.key().name()
       
   473 
       
   474     context['difficulties'] = ghop_task_model.TaskDifficultyTag.get_by_scope(
       
   475         entity)
       
   476 
       
   477     params['edit_template'] = 'modules/ghop/program/tag/difficulty.html'
       
   478 
       
   479     return self._constructResponse(request, entity, context, None, params)
       
   480 
       
   481   @decorators.merge_params
       
   482   @decorators.check_access
       
   483   def difficultyTagEdit(self, request, access_type, page_name=None,
       
   484                         params=None, filter=None, **kwargs):
       
   485     """View method used to edit a supplied Difficulty level tag.
       
   486     """
       
   487 
       
   488     get_params = request.GET
       
   489 
       
   490     order = get_params.getlist('order')
       
   491     program_key_name = get_params.get('program_key_name')
       
   492 
       
   493     program_entity = ghop_program_logic.logic.getFromKeyName(
       
   494         program_key_name)
       
   495 
       
   496     if order:
       
   497       for index, elem in enumerate(order):
       
   498         ghop_task_model.TaskDifficultyTag.update_order(
       
   499               program_entity, elem, index)
       
   500       return http.HttpResponse()
       
   501     else:
       
   502       tag_data = get_params.getlist('tag_data')
       
   503 
       
   504       tag_name = tag_data[0].strip()
       
   505       tag_value = tag_data[1].strip()
       
   506 
       
   507       if tag_name:
       
   508         if not tag_value:
       
   509           ghop_task_model.TaskDifficultyTag.delete_tag(
       
   510               program_entity, tag_name)
       
   511         elif tag_name != tag_value:
       
   512           ghop_task_model.TaskDifficultyTag.copy_tag(
       
   513               program_entity, tag_name, tag_value)
       
   514       else:
       
   515         ghop_task_model.TaskDifficultyTag.get_or_create(
       
   516             program_entity, tag_value)
       
   517 
       
   518       return http.HttpResponse(tag_value)
       
   519 
       
   520   @decorators.merge_params
       
   521   @decorators.check_access
       
   522   def taskTypeEdit(self, request, access_type, page_name=None,
       
   523                    params=None, filter=None, **kwargs):
       
   524     """View method used to edit Task Type tags.
       
   525     """
       
   526 
       
   527     params = dicts.merge(params, self._params)
       
   528 
       
   529     try:
       
   530       entity = self._logic.getFromKeyFieldsOr404(kwargs)
       
   531     except out_of_band.Error, error:
       
   532       return helper.responses.errorResponse(
       
   533           error, request, template=params['error_public'])
       
   534 
       
   535     context = helper.responses.getUniversalContext(request)
       
   536     helper.responses.useJavaScript(context, params['js_uses_all'])
       
   537     context['page_name'] = page_name
       
   538 
       
   539     context['program_key_name'] = entity.key().name()
       
   540 
       
   541     context['task_types'] = ghop_task_model.TaskTypeTag.get_by_scope(
       
   542         entity)
       
   543 
       
   544     params['edit_template'] = 'modules/ghop/program/tag/task_type.html'
       
   545 
       
   546     return self._constructResponse(request, entity, context, None, params)
       
   547 
       
   548   @decorators.merge_params
       
   549   @decorators.check_access
       
   550   def taskTypeTagEdit(self, request, access_type, page_name=None,
       
   551                       params=None, filter=None, **kwargs):
       
   552     """View method used to edit a supplied Task Type tag.
       
   553     """
       
   554 
       
   555     get_params = request.GET
       
   556 
       
   557     order = get_params.getlist('order')
       
   558     program_key_name = get_params.get('program_key_name')
       
   559 
       
   560     program_entity = ghop_program_logic.logic.getFromKeyName(
       
   561         program_key_name)
       
   562 
       
   563     if order:
       
   564       for index, elem in enumerate(order):
       
   565         ghop_task_model.TaskTypeTag.update_order(
       
   566               program_entity, elem, index)
       
   567       return http.HttpResponse()
       
   568     else:
       
   569       tag_data = get_params.getlist('tag_data')
       
   570       program_key_name = get_params.get('program_key_name')
       
   571 
       
   572       tag_name = tag_data[0].strip()
       
   573       tag_value = tag_data[1].strip()
       
   574 
       
   575       program_entity = ghop_program_logic.logic.getFromKeyName(
       
   576           program_key_name)
       
   577 
       
   578       if tag_name:
       
   579         if not tag_value:
       
   580           ghop_task_model.TaskTypeTag.delete_tag(
       
   581               program_entity, tag_name)
       
   582         elif tag_name != tag_value:
       
   583           ghop_task_model.TaskTypeTag.copy_tag(
       
   584               program_entity, tag_name, tag_value)
       
   585       else:
       
   586         ghop_task_model.TaskTypeTag.get_or_create(program_entity, tag_value)
       
   587 
       
   588       return http.HttpResponse(tag_value)
       
   589 
       
   590 
       
   591 view = View()
       
   592 
       
   593 admin = decorators.view(view.admin)
       
   594 assign_task_quotas = decorators.view(view.assignTaskQuotas)
       
   595 create = decorators.view(view.create)
       
   596 delete = decorators.view(view.delete)
       
   597 edit = decorators.view(view.edit)
       
   598 list = decorators.view(view.list)
       
   599 public = decorators.view(view.public)
       
   600 export = decorators.view(view.export)
       
   601 home = decorators.view(view.home)
       
   602 difficulty_tag_edit = decorators.view(view.difficultyTagEdit)
       
   603 task_type_tag_edit = decorators.view(view.taskTypeTagEdit)
       
   604 task_difficulty_edit = decorators.view(view.taskDifficultyEdit)
       
   605 task_type_edit = decorators.view(view.taskTypeEdit)