| # 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 containing the views for Summer Of Code organization application.""" |
| |
| |
| from django import forms as django_forms |
| from django.utils import translation |
| |
| from melange.request import links |
| from melange.templates import readonly |
| from melange.views import organization as organization_view |
| |
| from soc.views import forms |
| from soc.views import survey as survey_view |
| from soc.views import template |
| from soc.views.helper import url as url_helper |
| from soc.views.helper import lists |
| |
| from soc.modules.gsoc.views import base |
| from soc.modules.gsoc.views import forms as gsoc_forms |
| from soc.modules.gsoc.views.helper import url_patterns as soc_url_patterns |
| |
| from summerofcode.request import error |
| from summerofcode.request import render |
| from summerofcode.views.helper import urls |
| |
| |
| # TODO(daniel): fields should be explicitly included rather than excluded |
| # List of form fields which are not used by Summer Of Code programs |
| _UNUSED_FIELD_IDS = [ |
| 'notifications_email', organization_view.LOGO_IMAGE_FIELD_ID] |
| |
| # Field IDs to create organization profiles used by Summer Of Code programs |
| _CREATE_ORG_PROFILE_FIELD_IDS = set([ |
| 'org_id', 'name', 'description', 'tags', 'license', |
| 'ideas_page', 'mailing_list', 'web_page', 'irc_channel', |
| 'feed_url', 'google_plus', 'twitter', 'blog', 'facebook', 'is_veteran', |
| 'backup_admin', 'eligible_country' |
| ]) |
| |
| # Field IDs to edit organization profiles used by Code In programs |
| _EDIT_ORG_PROFILE_FIELD_IDS = ( |
| _CREATE_ORG_PROFILE_FIELD_IDS - |
| organization_view.ONLY_CREATE_ORG_PROFILE_FIELD_IDS) |
| |
| SLOTS_REQUEST_MIN_HELP_TEXT = translation.ugettext( |
| 'Number of amazing proposals submitted to this organization that ' |
| 'have a mentor assigned and the organization would <strong>really</strong> ' |
| 'like to have a slot for.') |
| |
| SLOTS_REQUEST_MAX_HELP_TEXT = translation.ugettext( |
| 'Number of slots that this organization would like to be assigned if ' |
| 'there was an unlimited amount of slots available.') |
| |
| MAX_SCORE_HELP_TEXT = translation.ugettext( |
| 'Mentors review proposals when student registration is open. This value ' |
| 'specifies the maximum number of points that can be given to a proposal by ' |
| 'one mentor. Please keep in mind changing this value does not have impact ' |
| 'on the existing scores. In particular, scores, which have been higher ' |
| 'than the updated maximum value, are still considered valid.') |
| |
| CONTRIB_TEMPLATE_HELP_TEXT = translation.ugettext( |
| 'This template can be used by contributors, such as students ' |
| 'and other non-member participants, when they apply to contribute ' |
| 'to the organization.') |
| |
| SCORING_ENABLED_HELP_TEXT = translation.ugettext( |
| 'When scoring is not enabled, mentors for the organization are not ' |
| 'allowed to score on proposals.') |
| |
| ACCEPTED_STUDENTS_MESSAGE_HELP_TEXT = translation.ugettext( |
| 'Custom message that is sent to all accepted students for ' |
| 'for this organization.') |
| |
| REJECTED_STUDENTS_MESSAGE_HELP_TEXT = translation.ugettext( |
| 'Custom message that is sent to all rejected students who submitted a ' |
| 'proposal to this organization.') |
| |
| EXTRA_FIELDS_HELP_TEXT = translation.ugettext( |
| 'Additional fields that will be added to proposals for this ' |
| 'organization. These fields are displayed as columns in the list of ' |
| 'proposals and may be edited by organization members. Field names should ' |
| 'appear on lines by themselves. Additionally, each name must be shorter ' |
| 'than %s characters') |
| |
| SLOTS_REQUEST_MIN_LABEL = translation.ugettext('Min slots requested') |
| |
| SLOTS_REQUEST_MAX_LABEL = translation.ugettext('Max slots requested') |
| |
| MAX_SCORE_LABEL = translation.ugettext('Max score') |
| |
| CONTRIB_TEMPLATE_LABEL = translation.ugettext('Application template') |
| |
| SCORING_ENABLED_LABEL = translation.ugettext('Scoring enabled') |
| |
| ACCEPTED_STUDENTS_MESSAGE_LABEL = translation.ugettext( |
| 'Message to accepted students') |
| |
| REJECTED_STUDENTS_MESSAGE_LABEL = translation.ugettext( |
| 'Message to rejected students') |
| |
| EXTRA_FIELDS_LABEL = translation.ugettext( |
| 'Proposal extra fields') |
| |
| EXTRA_FIELD_TOO_LONG = translation.ugettext( |
| 'Extra field %s is too long: %s. Please make sure that all names are ' |
| 'shorter than %s characters.') |
| |
| ORGANIZATION_LIST_DESCRIPTION = 'List of organizations' |
| |
| _ORG_PREFERENCES_PROPERTIES_FIELD_KEYS = [ |
| 'max_score', 'slot_request_max', 'slot_request_min', 'contrib_template', |
| 'scoring_enabled', 'extra_fields'] |
| |
| _ORG_MESSAGES_PROPERTIES_FIELD_KEYS = [ |
| 'accepted_students_message', 'rejected_students_message'] |
| |
| EXTRA_FIELD_MAX_LENGTH = 25 |
| MAX_SCORE_MIN_VALUE = 1 |
| MAX_SCORE_MAX_VALUE = 12 |
| |
| |
| def cleanExtraFields(text): |
| """Cleans extra_fields field. |
| |
| Args: |
| text: The submitted value which is an arbitrary string. |
| |
| Returns: |
| A list of extra fields. |
| |
| Raises: |
| django_forms.ValidationError if at least one of the fields is not valid. |
| """ |
| extra_fields = [] |
| for field in [field for field in text.split('\n') if field]: |
| field = field.strip() |
| if len(field) > EXTRA_FIELD_MAX_LENGTH: |
| raise django_forms.ValidationError( |
| EXTRA_FIELD_TOO_LONG % (field, len(field), EXTRA_FIELD_MAX_LENGTH)) |
| else: |
| extra_fields.append(field) |
| return extra_fields |
| |
| |
| class SOCOrgPreferencesForm(organization_view.OrgPreferencesForm): |
| """Form to set preferences of organization by organization administrators.""" |
| |
| slot_request_min = django_forms.IntegerField( |
| label=SLOTS_REQUEST_MIN_LABEL, help_text=SLOTS_REQUEST_MIN_HELP_TEXT, |
| required=True) |
| |
| slot_request_max = django_forms.IntegerField( |
| label=SLOTS_REQUEST_MAX_LABEL, help_text=SLOTS_REQUEST_MAX_HELP_TEXT, |
| required=True) |
| |
| max_score = django_forms.IntegerField( |
| label=MAX_SCORE_LABEL, help_text=MAX_SCORE_HELP_TEXT, |
| min_value=MAX_SCORE_MIN_VALUE, max_value=MAX_SCORE_MAX_VALUE) |
| |
| contrib_template = django_forms.CharField( |
| widget=django_forms.Textarea, label=CONTRIB_TEMPLATE_LABEL, |
| help_text=CONTRIB_TEMPLATE_HELP_TEXT, required=False) |
| |
| scoring_enabled = django_forms.BooleanField( |
| label=SCORING_ENABLED_LABEL, help_text=SCORING_ENABLED_HELP_TEXT, |
| required=False) |
| |
| accepted_students_message = django_forms.CharField( |
| widget=django_forms.Textarea, label=ACCEPTED_STUDENTS_MESSAGE_LABEL, |
| help_text=ACCEPTED_STUDENTS_MESSAGE_HELP_TEXT, required=False) |
| |
| rejected_students_message = django_forms.CharField( |
| widget=django_forms.Textarea, label=REJECTED_STUDENTS_MESSAGE_LABEL, |
| help_text=REJECTED_STUDENTS_MESSAGE_HELP_TEXT, required=False) |
| |
| extra_fields = django_forms.CharField( |
| widget=django_forms.Textarea, label=EXTRA_FIELDS_LABEL, |
| help_text=EXTRA_FIELDS_HELP_TEXT % EXTRA_FIELD_MAX_LENGTH, required=False) |
| |
| def __init__(self, **kwargs): |
| """Initializes a new instance of the form for the specified parameters.""" |
| super(SOCOrgPreferencesForm, self).__init__( |
| gsoc_forms.GSoCBoundField, template_path=gsoc_forms.TEMPLATE_PATH, |
| org_properties_field_keys=_ORG_PREFERENCES_PROPERTIES_FIELD_KEYS, |
| org_messages_properties_field_keys=_ORG_MESSAGES_PROPERTIES_FIELD_KEYS, |
| **kwargs) |
| |
| def clean_extra_fields(self): |
| """Cleans extra_fields field. |
| |
| Returns: |
| A list of submitted extra fields. |
| |
| Raises: |
| django_forms.ValidationError if at least one of the fields is not valid. |
| """ |
| return cleanExtraFields(self.cleaned_data['extra_fields']) |
| |
| |
| class SOCOrgLogoFormFactory(organization_view.OrgLogoFormFactory): |
| """Implementation of organization_view.OrgLogoFormFactory for Summer |
| Of Code.""" |
| |
| def create(self, **kwargs): |
| return organization_view.OrgLogoForm( |
| gsoc_forms.GSoCBoundField, template_path=gsoc_forms.TEMPLATE_PATH, |
| **kwargs) |
| |
| SOC_ORG_LOGO_FORM_FACTORY = SOCOrgLogoFormFactory() |
| |
| |
| class SOCOrgPreferencesFormFactory(organization_view.OrgPreferencesFormFactory): |
| """Implementation of organization_view.OrgPreferencesFormFactory to be used |
| to create forms for organization preferences for Summer Of Code programs. |
| """ |
| |
| def create(self, **kwargs): |
| """See organization_view.OrgPreferencesFormFactory.create for |
| specification. |
| """ |
| return SOCOrgPreferencesForm(**kwargs) |
| |
| SOC_ORG_PREFERENCES_FORM_FACTORY = SOCOrgPreferencesFormFactory() |
| |
| |
| class SOCCreateOrgProfileFormFactory(organization_view.OrgProfileFormFactory): |
| """Implementation of organization_view.OrgProfileFormFactory to be used |
| to create forms to create organization profiles for Summer Of Code programs. |
| """ |
| |
| def create(self, request_data, **kwargs): |
| """See organization_view.OrgProfileFormFactory.create for specification.""" |
| return organization_view._OrgProfileForm( |
| gsoc_forms.GSoCBoundField, request_data, |
| template_path=gsoc_forms.TEMPLATE_PATH, |
| include_fields=_CREATE_ORG_PROFILE_FIELD_IDS, |
| **kwargs) |
| |
| SOC_CREATE_ORG_PROFILE_FORM_FACTORY = SOCCreateOrgProfileFormFactory() |
| |
| |
| class SOCEditOrgProfileFormFactory(organization_view.OrgProfileFormFactory): |
| """Implementation of organization_view.OrgProfileFormFactory to be used |
| to create forms to edit organization profiles for Summer Of Code programs. |
| """ |
| |
| def create(self, request_data, **kwargs): |
| """See organization_view.OrgProfileFormFactory.create for specification.""" |
| return organization_view._OrgProfileForm( |
| gsoc_forms.GSoCBoundField, request_data, |
| template_path=gsoc_forms.TEMPLATE_PATH, |
| include_fields=_EDIT_ORG_PROFILE_FIELD_IDS, |
| **kwargs) |
| |
| SOC_EDIT_ORG_PROFILE_FORM_FACTORY = SOCEditOrgProfileFormFactory() |
| |
| |
| class SOCOrgApplicationFormFactory(survey_view.SurveyTakeFormFactory): |
| """Implementation of survey_view.SurveyTakeFormFactory to be used to create |
| forms for organization applications for Summer Of Code programs. |
| """ |
| |
| def create(self, survey, **kwargs): |
| """See survey_view.SurveyTakeFormFactory.create for specification.""" |
| return forms.SurveyTakeForm( |
| gsoc_forms.GSoCBoundField, survey=survey, |
| template_path=gsoc_forms.TEMPLATE_PATH, |
| **kwargs) |
| |
| SOC_ORG_APPLICATION_FORM_FACTORY = SOCOrgApplicationFormFactory() |
| |
| |
| class SOCOrgApplicationReadonlyFactory( |
| organization_view.OrgApplicationReadonlyFactory): |
| |
| def create(self, data, app_response): |
| """See organization_view.OrgApplicationReadonlyFactory.create for |
| specification. |
| """ |
| builder = readonly.ReadonlyBuilder() |
| builder.setTemplatePath('summerofcode/_readonly_template.html') |
| builder.setGroupTemplatePath('summerofcode/readonly/_group.html') |
| |
| builder.addGroupDescriptor(organization_view.GENERAL_INFO_GROUP_DESCRIPTOR) |
| builder.addGroupDescriptor(organization_view.CONTACT_GROUP_DESCRIPTOR) |
| |
| builder.setValues( |
| organization_view.adaptOrgProfilePropertiesForForm(data.url_ndb_org)) |
| builder.setValue( |
| organization_view.ADMINS_FIELD_ID, |
| organization_view.getAdminsValue(data.url_ndb_org)) |
| |
| # descriptor for survey response |
| if app_response: |
| builder.addGroupDescriptor( |
| readonly.SurveyGroupDescriptor( |
| organization_view.APP_RESPONSE_GROUP_TITLE, data.org_app)) |
| builder.setValues( |
| survey_view.surveyResponseAsDict(data.org_app, app_response)) |
| |
| # remove unused fields |
| for field_id in _UNUSED_FIELD_IDS: |
| builder.removeField(field_id) |
| |
| return builder.build(data) |
| |
| SOC_ORG_APPLICATION_READONLY_FACTORY = SOCOrgApplicationReadonlyFactory() |
| |
| |
| class SOCAppResponseReadonlyFactory( |
| organization_view.AppResponseReadonlyFactory): |
| """Implementation of organization_view.AppResponseReadonlyFactory to be used |
| for organization application responses for Summer Of Code programs. |
| """ |
| |
| def create(self, data, app_response): |
| """See organization_view.AppResponseReadonlyFactory.create |
| for specification. |
| """ |
| builder = readonly.ReadonlyBuilder() |
| builder.setTemplatePath('summerofcode/_readonly_template.html') |
| builder.setGroupTemplatePath('summerofcode/readonly/_group.html') |
| |
| builder.addGroupDescriptor( |
| readonly.SurveyGroupDescriptor( |
| organization_view.APP_RESPONSE_GROUP_TITLE, data.org_app)) |
| builder.setValues( |
| survey_view.surveyResponseAsDict(data.org_app, app_response)) |
| |
| return builder.build(data) |
| |
| SOC_APP_RESPONSE_READONLY_FACTORY = SOCAppResponseReadonlyFactory() |
| |
| |
| ORG_PROFILE_CREATE_PAGE = organization_view.OrgProfileCreatePage( |
| base._GSOC_INITIALIZER, links.LINKER, render.SOC_RENDERER, |
| error.SOC_ERROR_HANDLER, soc_url_patterns.SOC_URL_PATTERN_CONSTRUCTOR, |
| urls.UrlNames, 'summerofcode/organization/org_profile_edit.html', |
| SOC_CREATE_ORG_PROFILE_FORM_FACTORY) |
| |
| |
| ORG_PROFILE_EDIT_PAGE = organization_view.OrgProfileEditPage( |
| base._GSOC_INITIALIZER, links.LINKER, render.SOC_RENDERER, |
| error.SOC_ERROR_HANDLER, soc_url_patterns.SOC_URL_PATTERN_CONSTRUCTOR, |
| urls.UrlNames, 'summerofcode/organization/org_profile_edit.html', |
| SOC_EDIT_ORG_PROFILE_FORM_FACTORY) |
| |
| ORG_APPLICATION_LIST_PAGE = organization_view.OrgApplicationListPage( |
| base._GSOC_INITIALIZER, links.LINKER, render.SOC_RENDERER, |
| error.SOC_ERROR_HANDLER, soc_url_patterns.SOC_URL_PATTERN_CONSTRUCTOR, |
| urls.UrlNames, 'soc/org_app/records.html', |
| 'summerofcode/organization/_org_application_list.html') |
| |
| ORG_APPLICATION_SUBMIT_PAGE = organization_view.OrgApplicationSubmitPage( |
| base._GSOC_INITIALIZER, links.LINKER, render.SOC_RENDERER, |
| error.SOC_ERROR_HANDLER, soc_url_patterns.SOC_URL_PATTERN_CONSTRUCTOR, |
| urls.UrlNames, 'modules/gsoc/org_app/take.html', |
| SOC_ORG_APPLICATION_FORM_FACTORY) |
| |
| |
| ORG_APPLICATION_SHOW_PAGE = organization_view.OrgApplicationShowPage( |
| base._GSOC_INITIALIZER, links.LINKER, render.SOC_RENDERER, |
| error.SOC_ERROR_HANDLER, soc_url_patterns.SOC_URL_PATTERN_CONSTRUCTOR, |
| urls.UrlNames, 'modules/gsoc/org_app/show.html', |
| SOC_ORG_APPLICATION_READONLY_FACTORY) |
| |
| |
| ORG_APPLICATION_RESPONSE_SHOW_PAGE = ( |
| organization_view.OrgApplicationResponseShowPage( |
| base._GSOC_INITIALIZER, links.LINKER, render.SOC_RENDERER, |
| error.SOC_ERROR_HANDLER, soc_url_patterns.SOC_URL_PATTERN_CONSTRUCTOR, |
| urls.UrlNames, 'modules/gsoc/org_app/show.html', |
| SOC_APP_RESPONSE_READONLY_FACTORY)) |
| |
| |
| ORG_APPLICATION_RESPONSE_SHOW_PAGE = ( |
| organization_view.OrgApplicationResponseShowPage( |
| base._GSOC_INITIALIZER, links.LINKER, render.SOC_RENDERER, |
| error.SOC_ERROR_HANDLER, soc_url_patterns.SOC_URL_PATTERN_CONSTRUCTOR, |
| urls.UrlNames, 'modules/gsoc/org_app/show.html', |
| SOC_APP_RESPONSE_READONLY_FACTORY)) |
| |
| |
| ORG_LOGO_EDIT_PAGE = organization_view.OrgLogoEditPage( |
| base._GSOC_INITIALIZER, links.LINKER, render.SOC_RENDERER, |
| error.SOC_ERROR_HANDLER, soc_url_patterns.SOC_URL_PATTERN_CONSTRUCTOR, |
| urls.UrlNames, 'modules/gsoc/org_profile/base.html', |
| SOC_ORG_LOGO_FORM_FACTORY) |
| |
| |
| ORG_PREFERENCES_EDIT_PAGE = organization_view.OrgPreferencesEditPage( |
| base._GSOC_INITIALIZER, links.LINKER, render.SOC_RENDERER, |
| error.SOC_ERROR_HANDLER, soc_url_patterns.SOC_URL_PATTERN_CONSTRUCTOR, |
| urls.UrlNames, 'modules/gsoc/org_profile/base.html', |
| SOC_ORG_PREFERENCES_FORM_FACTORY) |
| |
| |
| # TODO(daniel): replace this class with new style list |
| class PublicOrganizationList(template.Template): |
| """Public list of organizations participating in a specified program.""" |
| |
| def __init__(self, data): |
| """See template.Template.__init__ for specification.""" |
| super(PublicOrganizationList, self).__init__(data) |
| self._list_config = lists.ListConfiguration() |
| self._list_config.addPlainTextColumn( |
| 'name', 'Name', lambda e, *args: e.name.strip()) |
| self._list_config.addPlainTextColumn( |
| 'tags', 'Tags', lambda e, *args: ', '.join(e.tags)) |
| self._list_config.addPlainTextColumn('ideas', 'Ideas', |
| lambda e, *args: url_helper.urlize(e.ideas, name='[ideas page]'), |
| hidden=True) |
| self._list_config.setDefaultSort('name', 'asc') |
| |
| def templatePath(self): |
| """See template.Template.templatePath for specification.""" |
| return 'modules/gsoc/admin/_accepted_orgs_list.html' |
| |
| def context(self): |
| """See template.Template.context for specification.""" |
| description = ORGANIZATION_LIST_DESCRIPTION |
| |
| list_configuration_response = lists.ListConfigurationResponse( |
| self.data, self._list_config, 0, description) |
| |
| return { |
| 'lists': [list_configuration_response], |
| } |
| |
| |
| class SOCPublicOrganizationListFactory( |
| organization_view.PublicOrganizationListFactory): |
| """Implementation of organization_view.PublicOrganizationListFactory to be |
| used to create public list of organizations for Summer Of Code programs. |
| """ |
| |
| def create(self, data): |
| """See organization_view.PublicOrganizationListFactory for specification.""" |
| return PublicOrganizationList(data) |
| |
| SOC_PUBLIC_ORGANIZATION_LIST_FACTORY = SOCPublicOrganizationListFactory() |
| |
| PUBLIC_ORGANIZATION_LIST_PAGE = organization_view.PublicOrganizationListPage( |
| base._GSOC_INITIALIZER, links.LINKER, render.SOC_RENDERER, |
| error.SOC_ERROR_HANDLER, soc_url_patterns.SOC_URL_PATTERN_CONSTRUCTOR, |
| urls.UrlNames, 'modules/gsoc/accepted_orgs/base.html', |
| SOC_PUBLIC_ORGANIZATION_LIST_FACTORY) |