blob: 94157ababaf77ddd119a8c7e065e6d1877c2c7a5 [file] [log] [blame]
# Copyright 2013 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.
"""Module for slot allocation."""
import json
import logging
from google.appengine.ext import db
from google.appengine.ext import ndb
from django import http
from melange.models import organization as org_model
from melange.request import access
from melange.request import exception
from soc.views.helper import lists
from soc.views.helper import url_patterns
from soc.modules.gsoc.templates import org_list
from soc.modules.gsoc.views import base
from soc.modules.gsoc.views.helper import url_patterns as gsoc_url_patterns
from summerofcode.logic import proposal as proposal_logic
from summerofcode.models import organization as soc_org_model
class SlotsList(org_list.OrgList):
"""Template for list of accepted organizations to allocate slots."""
class ListPrefetcher(lists.Prefetcher):
"""Prefetcher used by SlotsList.
See lists.Prefetcher for specification.
"""
def prefetch(self, entities):
"""Prefetches the number of unused slots for each item in the
specified list of organization entities.
See lists.Prefetcher.prefetch for specification.
Args:
entities: List of organization entities.
Returns:
Prefetched numbers in a structure whose format is described
in lists.ListModelPrefetcher.prefetch.
"""
org_slots_unused = {}
if entities:
timeline = db.get(entities[0].program.to_old_key()).timeline
for entity in entities:
used_slots = proposal_logic.getUsedSlots(entity.key, timeline)
org_slots_unused[entity.key] = max(
entity.slot_allocation - used_slots, 0)
return ([org_slots_unused], {})
def _getDescription(self):
"""See org_list.OrgList._getDescription for specification."""
return org_list.ACCEPTED_ORG_LIST_DESCRIPTION % self.data.program.name
def _getListConfig(self):
"""See org_list.OrgList._getListConfig for specification."""
list_config = lists.ListConfiguration()
list_config.addPlainTextColumn('name', 'Name',
lambda e, *args: e.name.strip())
list_config.addSimpleColumn('org_id', 'Organization ID', hidden=True)
options = [('', 'All'), ('true', 'New'), ('false', 'Veteran')]
list_config.addPlainTextColumn('is_veteran', 'New/Veteran',
lambda e, *args: 'Veteran' if e.is_veteran else 'New', width=60,
options=options)
list_config.setColumnEditable('is_veteran', True, 'select')
list_config.addSimpleColumn('slot_request_min', 'Min',
width=25, column_type=lists.NUMERICAL)
list_config.addSimpleColumn('slot_request_max', 'Max',
width=25, column_type=lists.NUMERICAL)
list_config.addSimpleColumn('slot_allocation', 'Slots',
width=50, column_type=lists.NUMERICAL)
list_config.setColumnEditable('slot_allocation', True)
list_config.setColumnSummary('slot_allocation', 'sum', '<b>Total: {0}</b>')
list_config.addHtmlColumn(
'slots_unused', 'Unused slots',
lambda ent, s, *args: ('<strong><font color="red">%s</font></strong>'
% (s[ent.key])))
# TODO(daniel): add note to organization model?
#list_config.addSimpleColumn('note', 'Note')
#list_config.setColumnEditable('note', True)
list_config.setDefaultPagination(False)
list_config.setDefaultSort('name')
list_config.addPostEditButton('save', 'Save', "", [], refresh='none')
return list_config
def _getQuery(self):
"""See org_list.OrgList._getQuery for specification."""
query = soc_org_model.SOCOrganization.query(
soc_org_model.SOCOrganization.program ==
ndb.Key.from_old_key(self.data.program.key()),
soc_org_model.SOCOrganization.status == org_model.Status.ACCEPTED)
return query
def post(self):
"""POST handler for the list actions.
Returns:
True if the data is successfully modified; False otherwise.
"""
idx = lists.getListIndex(self.data.request)
if idx != 0:
return False
data = self.data.POST.get('data')
if not data:
raise exception.BadRequest(message="Missing data")
parsed = json.loads(data)
for key_id, properties in parsed.iteritems():
note = properties.get('note')
slot_allocation = properties.get('slot_allocation')
is_veteran = properties.get('is_veteran')
if ('note' not in properties and 'slot_allocation' not in properties and
'is_veteran' not in properties):
logging.warning(
'Neither note nor slots nor is_veteran present in "%s"', properties)
continue
if 'slot_allocation' in properties:
if not slot_allocation.isdigit():
logging.warning('Non-int value for slots: "%s', slot_allocation)
properties.pop('slot_allocation')
else:
slot_allocation = int(slot_allocation)
if is_veteran:
if not is_veteran in ['New', 'Veteran']:
logging.warning('Invalid value for new_org: "%s"', is_veteran)
properties.pop('is_veteran')
else:
is_veteran = True if is_veteran == 'Veteran' else False
def update_org_txn():
org = soc_org_model.SOCOrganization.get_by_id(key_id)
if not org:
logging.warning('Invalid org_key "%s"', key_id)
elif 'note' in properties:
pass
# TODO(daniel): add note to organization model
#org.note = note
elif 'slot_allocation' in properties:
org.slot_allocation = slot_allocation
if 'is_veteran' in properties:
org.is_veteran = is_veteran
org.put()
db.run_in_transaction(update_org_txn)
return True
def _getPrefetcher(self):
"""See org_list.OrgList._getPrefetcher for specification."""
return SlotsList.ListPrefetcher()
class SlotsPage(base.GSoCRequestHandler):
"""View for the participant profile."""
access_checker = access.PROGRAM_ADMINISTRATOR_ACCESS_CHECKER
def djangoURLPatterns(self):
return [
gsoc_url_patterns.url(r'admin/slots/%s$' % url_patterns.PROGRAM, self,
name='gsoc_slots'),
]
def templatePath(self):
return 'modules/gsoc/admin/list.html'
def jsonContext(self, data, check, mutator):
list_content = SlotsList(data).getListData()
if list_content:
return list_content.content()
else:
raise exception.BadRequest(
message='Missing idx parameter for component.')
def post(self, data, check, mutator):
slots_list = SlotsList(data)
if slots_list.post():
return http.HttpResponse()
else:
raise exception.Forbidden(message='You cannot change this data')
def context(self, data, check, mutator):
return {
'page_name': 'Slots page',
'list': SlotsList(data),
}