| # 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 django import http |
| |
| from melange.request import exception |
| from soc.views.helper import lists |
| from soc.views.helper import url_patterns |
| |
| from soc.modules.gsoc.models import organization as org_model |
| from soc.modules.gsoc.models import proposal as proposal_model |
| 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 |
| |
| |
| 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 GSoCOrganization entities. |
| |
| See lists.Prefetcher.prefetch for specification. |
| |
| Args: |
| entities: the specified list of GSoCOrganization instances |
| |
| Returns: |
| prefetched numbers in a structure whose format is described |
| in lists.ListModelPrefetcher.prefetch |
| """ |
| org_slots_unused = {} |
| |
| for entity in entities: |
| query = proposal_model.GSoCProposal.all(keys_only=True) |
| query.filter('org', entity) |
| query.filter('has_mentor', True) |
| query.filter('accept_as_project', True) |
| slots_used = query.count() |
| |
| org_slots_unused[entity.key()] = entity.slots - slots_used if \ |
| entity.slots > slots_used else 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('link_id', 'Organization ID', hidden=True) |
| |
| options = [('', 'All'), ('true', 'New'), ('false', 'Veteran')] |
| list_config.addPlainTextColumn('new_org', 'New/Veteran', |
| lambda e, *args: 'New' if e.new_org else 'Veteran', width=60, |
| options=options) |
| list_config.setColumnEditable('new_org', True, 'select') |
| |
| list_config.addSimpleColumn('slots_desired', 'Min', |
| width=25, column_type=lists.ColumnType.NUMERICAL) |
| list_config.addSimpleColumn('max_slots_desired', 'Max', |
| width=25, column_type=lists.ColumnType.NUMERICAL) |
| |
| list_config.addSimpleColumn('slots', 'Slots', |
| width=50, column_type=lists.ColumnType.NUMERICAL) |
| list_config.setColumnEditable('slots', True) |
| list_config.setColumnSummary('slots', '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()]))) |
| |
| 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 = org_model.GSoCOrganization.all() |
| query.filter('scope', self.data.program) |
| 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_name, properties in parsed.iteritems(): |
| note = properties.get('note') |
| slots = properties.get('slots') |
| new_org = properties.get('new_org') |
| |
| if ('note' not in properties and 'slots' not in properties and |
| 'new_org' not in properties): |
| logging.warning("Neither note or slots present in '%s'" % properties) |
| continue |
| |
| if 'slots' in properties: |
| if not slots.isdigit(): |
| logging.warning("Non-int value for slots: '%s'" % slots) |
| properties.pop('slots') |
| else: |
| slots = int(slots) |
| |
| if new_org: |
| if not new_org in ['New', 'Veteran']: |
| logging.warning("Invalid value for new_org: '%s'" % new_org) |
| properties.pop('new_org') |
| else: |
| new_org = True if new_org == 'New' else False |
| |
| def update_org_txn(): |
| org = org_model.GSoCOrganization.get_by_key_name(key_name) |
| if not org: |
| logging.warning("Invalid org_key '%s'" % key_name) |
| return |
| if 'note' in properties: |
| org.note = note |
| if 'slots' in properties: |
| org.slots = slots |
| if 'new_org' in properties: |
| org.new_org = new_org |
| |
| 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.""" |
| |
| def djangoURLPatterns(self): |
| return [ |
| gsoc_url_patterns.url(r'admin/slots/%s$' % url_patterns.PROGRAM, self, |
| name='gsoc_slots'), |
| ] |
| |
| def checkAccess(self, data, check, mutator): |
| check.isHost() |
| |
| 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.Forbidden(message='You do not have access to this data') |
| |
| 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), |
| } |