| # Copyright 2014 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 with slot allocation related views.""" |
| |
| import collections |
| |
| from django import forms as django_forms |
| from django import http |
| from django.utils import html |
| from django.utils import translation |
| |
| from melange.request import access |
| from melange.request import exception |
| |
| from soc.modules.gsoc.views import base as gsoc_base |
| from soc.modules.gsoc.views import forms |
| from soc.modules.gsoc.views.helper import url_patterns as soc_url_patterns |
| from soc.views import base |
| from soc.views import template |
| from soc.views.helper import url_patterns |
| |
| from summerofcode.logic import proposal as proposal_logic |
| from summerofcode.logic import slot_transfer as slot_transfer_logic |
| from summerofcode.models import slot_transfer as slot_transfer_model |
| from summerofcode.request import error |
| from summerofcode.request import links |
| from summerofcode.request import render |
| from summerofcode.templates import tabs |
| from summerofcode.views.helper import urls as soc_urls |
| |
| |
| SLOT_ALLOCATION_DETAILS_PAGE_NAME = translation.ugettext( |
| 'Slot Allocation Details') |
| |
| SLOT_ALLOCATION_LABEL = translation.ugettext('Slot allocation') |
| SLOT_USAGE_LABEL = translation.ugettext('Used slots') |
| SLOT_DIFFERENCE_LABEL = translation.ugettext('Difference') |
| |
| SLOT_TRANSFER_FORM_NAME = 'slot-transfer' |
| |
| QUANTITY_LABEL = translation.ugettext('Number of slots') |
| QUANTITY_HELP_TEXT = translation.ugettext( |
| 'Number of slots your organization is willing to give back to the pool.') |
| |
| ORG_MESSAGE_LABEL = translation.ugettext('Message') |
| ORG_MESSAGE_HELP_TEXT = translation.ugettext( |
| 'A brief explanation mentioning the reason for transferring the ' |
| 'slots back to the pool. It may also contain recommendation to which ' |
| 'organization the slots should be given.') |
| |
| _SLOT_TRANSFER_SUMMARY_TEMPLATE_PATH = ( |
| 'summerofcode/slot_allocation/_slot_transfer_summary.html') |
| |
| _MESSAGE_SLOT_ALLOCATION_NOT_VISIBLE = translation.ugettext( |
| 'Slot allocation is not visible at this time.') |
| |
| class SlotTransferForm(forms.GSoCModelForm): |
| """Django form to request slot transfer.""" |
| |
| quantity = django_forms.IntegerField( |
| label=QUANTITY_LABEL, help_text=QUANTITY_HELP_TEXT) |
| |
| org_message = django_forms.CharField( |
| widget=django_forms.Textarea(), required=False, |
| label=ORG_MESSAGE_LABEL, help_text=ORG_MESSAGE_HELP_TEXT) |
| |
| Meta = object |
| |
| |
| def _slotTransferForm(data): |
| """Returns a Django form to request a slot transfer to the pool. |
| |
| Returns: |
| SlotTransferForm adjusted to request a slot transfer to the pool. |
| """ |
| form = SlotTransferForm(data=data.POST, name=SLOT_TRANSFER_FORM_NAME) |
| form.fields['quantity'].widget = django_forms.Select( |
| choices=[(i, i) for i in range(data.url_ndb_org.slot_allocation + 1)]) |
| return form |
| |
| |
| _SLOT_TRANSFER_STATUS_ENUM_TO_VERBOSE_MAP = { |
| slot_transfer_model.Status.ACCEPTED: translation.ugettext('Accepted'), |
| slot_transfer_model.Status.REJECTED: translation.ugettext('Rejected'), |
| slot_transfer_model.Status.PENDING: translation.ugettext('Pending'), |
| } |
| |
| class SlotTransferSummary(template.Template): |
| """Template to render summary for a single slot transfer request.""" |
| |
| def __init__(self, data, template_path, slot_transfer): |
| """Initializes a new instance of the template. |
| |
| Args: |
| data: request_data.RequestData for the current request. |
| template_path: The path of the template to be used. |
| slot_transfer: slot_transfer.SlotTransfer entity to include summary for. |
| """ |
| super(SlotTransferSummary, self).__init__(data) |
| self.template_path = template_path |
| self.slot_transfer = slot_transfer |
| |
| def templatePath(self): |
| """See template.Template.templatePath for specification.""" |
| return self._template_path |
| |
| def context(self): |
| """See template.Template.context for specification.""" |
| return { |
| 'status': _SLOT_TRANSFER_STATUS_ENUM_TO_VERBOSE_MAP[ |
| self.slot_transfer.status], |
| 'created_on': self.slot_transfer.created_on, |
| 'quantity': self.slot_transfer.quantity, |
| 'org_message': self.slot_transfer.org_message, |
| } |
| |
| |
| class SlotAllocationVisibleAccessChecker(access.AccessChecker): |
| """AccessChecker that ensures that the access is granted only after the |
| slot allocation is visible for the program defined in the URL. |
| """ |
| |
| def checkAccess(self, data, check): |
| """See access.AccessChecker.checkAccess for specification.""" |
| if not data.program.allocations_visible: |
| raise exception.Forbidden(message=_MESSAGE_SLOT_ALLOCATION_NOT_VISIBLE) |
| |
| |
| class SlotAllocationDetailsPage(base.RequestHandler): |
| """Page for organization administrators to see slot allocation details.""" |
| |
| access_checker = access.ConjuctionAccessChecker([ |
| access.IS_USER_ORG_ADMIN_FOR_NDB_ORG, |
| SlotAllocationVisibleAccessChecker()]) |
| |
| def __init__(self, initializer, linker, renderer, error_handler, |
| url_pattern_constructor, url_names, template_path): |
| """Initializes a new instance of the request handler for the specified |
| parameters. |
| |
| Args: |
| initializer: Implementation of initialize.Initializer interface. |
| linker: Instance of links.Linker class. |
| renderer: Implementation of render.Renderer interface. |
| error_handler: Implementation of error.ErrorHandler interface. |
| url_pattern_constructor: |
| Implementation of url_patterns.UrlPatternConstructor. |
| url_names: Instance of url_names.UrlNames. |
| template_path: The path of the template to be used. |
| """ |
| super(SlotAllocationDetailsPage, self).__init__( |
| initializer, linker, renderer, error_handler) |
| self.url_pattern_constructor = url_pattern_constructor |
| self.url_names = url_names |
| self.template_path = template_path |
| |
| def djangoURLPatterns(self): |
| """See base.RequestHandler.djangoURLPatterns for specification.""" |
| return [ |
| self.url_pattern_constructor.construct( |
| r'slot_allocation/details/%s$' % url_patterns.ORG, |
| self, name=self.url_names.SLOT_ALLOCATION_DETAILS), |
| ] |
| |
| def templatePath(self): |
| """See base.GCIRequestHandler.templatePath for specification.""" |
| return self.template_path |
| |
| def context(self, data, check, mutator): |
| """See base.RequestHandler.context for specification.""" |
| slot_transfers = [ |
| SlotTransferSummary( |
| data, _SLOT_TRANSFER_SUMMARY_TEMPLATE_PATH, slot_transfer) |
| for slot_transfer in slot_transfer_logic.getSlotTransfersForOrg( |
| data.url_ndb_org.key)] |
| |
| slot_transfer_form = ( |
| _slotTransferForm(data) |
| if not slot_transfer_logic.hasPendingSlotTransfer(data.url_ndb_org.key) |
| else None) |
| |
| used_slots = proposal_logic.getUsedSlots( |
| data.url_ndb_org.key, data.program_timeline) |
| difference = data.url_ndb_org.slot_allocation - used_slots |
| summary_items = collections.OrderedDict() |
| summary_items[SLOT_ALLOCATION_LABEL] = data.url_ndb_org.slot_allocation |
| summary_items[SLOT_USAGE_LABEL] = used_slots |
| |
| if difference == 0: |
| summary_items[SLOT_DIFFERENCE_LABEL] = html.mark_safe( |
| '<font color="green"><strong>%s</strong></font>' % difference) |
| elif difference > 0: |
| summary_items[SLOT_DIFFERENCE_LABEL] = html.mark_safe( |
| '<font color="red"><strong>%s remaining slots</strong></font>' % |
| difference) |
| else: |
| summary_items[SLOT_DIFFERENCE_LABEL] = html.mark_safe( |
| '<font color="red"><strong>%s too many slots used</strong></font>' % |
| difference) |
| |
| return { |
| 'page_name': SLOT_ALLOCATION_DETAILS_PAGE_NAME, |
| 'items': summary_items, |
| 'slot_transfer_form': slot_transfer_form, |
| 'slot_transfers': slot_transfers, |
| 'tabs': tabs.orgTabs(data, selected_tab_id=tabs.ORG_SLOTS_TAB_ID), |
| } |
| |
| def post(self, data, check, mutator): |
| """See base.RequestHandler.post for specification.""" |
| slot_transfer_form = _slotTransferForm(data) |
| if slot_transfer_form.is_valid(): |
| result = slot_transfer_logic.createSlotTransfer( |
| data.url_ndb_org.key, slot_transfer_form.cleaned_data['quantity'], |
| slot_transfer_form.cleaned_data['org_message']) |
| if not result: |
| raise exception.BadRequest(message=result.extra) |
| else: |
| return http.HttpResponseRedirect('') |
| else: |
| # TODO(nathaniel): problematic self-use. |
| return self.get(data, check, mutator) |
| |
| SLOT_ALLOCATION_DETAILS_PAGE = SlotAllocationDetailsPage( |
| gsoc_base._GSOC_INITIALIZER, links.SOC_LINKER, render.SOC_RENDERER, |
| error.SOC_ERROR_HANDLER, soc_url_patterns.SOC_URL_PATTERN_CONSTRUCTOR, |
| soc_urls.UrlNames, |
| 'summerofcode/slot_allocation/slot_allocation_details.html') |