| # 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. |
| |
| """The views and generalized organization related views are forms. |
| |
| The classes defined here are supposed to be instantiated with dependencies that |
| are specific to actual programs. |
| """ |
| |
| import json |
| |
| from google.appengine.ext import ndb |
| |
| from django import forms as django_forms |
| from django import http |
| from django.utils import html |
| from django.utils import translation |
| |
| from melange.logic import contact as contact_logic |
| from melange.logic import organization as org_logic |
| from melange.logic import profile as profile_logic |
| from melange.logic import signature as signature_logic |
| from melange.models import connection as connection_model |
| from melange.models import contact as contact_model |
| from melange.models import organization as org_model |
| from melange.request import access |
| from melange.request import exception |
| from melange.request import links |
| from melange.templates import readonly |
| from melange.templates import profile_form_below_header |
| from melange.utils import lists as melange_lists |
| from melange.utils import time as time_utils |
| from melange.templates import survey_response_list |
| from melange.views import connection as connection_view |
| from melange.views.helper import form_handler |
| |
| from soc.logic import cleaning |
| from soc.logic import conversation_updater |
| from soc.mapreduce.helper import control as mapreduce_control |
| from soc.models import licenses |
| from soc.models import program as program_model |
| from soc.views import base |
| from soc.views import forms |
| from soc.views import template |
| from soc.views.helper import lists |
| from soc.views.helper import url_patterns |
| |
| # TODO(daniel): tabs make sense only for Summer Of Code |
| from summerofcode.templates import tabs |
| # TODO(daniel): top-message makes sense only for Summer Of Code |
| from summerofcode.templates import top_message |
| |
| |
| ORG_ID_LABEL = translation.ugettext('Organization ID') |
| |
| ORG_ID_HELP_TEXT = translation.ugettext( |
| 'Organization ID is used as part of various URL links throughout ' |
| ' the site. You may reuse the same id for different years of the program. ' |
| '<a href="http://en.wikipedia.org/wiki/ASCII">ASCII</a> alphanumeric ' |
| 'characters, digits, and underscores only.') |
| |
| ORG_NAME_LABEL = translation.ugettext('Organization name') |
| |
| ORG_NAME_HELP_TEXT = translation.ugettext( |
| 'Complete, formal name of the organization.') |
| |
| DESCRIPTION_LABEL = translation.ugettext('Description') |
| |
| DESCRIPTION_HELP_TEXT = translation.ugettext( |
| 'Description of the organization to be displayed on a public profile page.') |
| |
| TAGS_LABEL = translation.ugettext('Tags') |
| |
| TAGS_HELP_TEXT = translation.ugettext( |
| 'Comma separated list of organization tags. Each tag must be shorter ' |
| 'than %s characters.') |
| |
| TAG_MAX_LENGTH = 30 |
| |
| LICENSE_LABEL = translation.ugettext('Main license') |
| |
| LICENSE_HELP_TEXT = translation.ugettext( |
| 'The main license which is used by this organization.') |
| |
| _LICENSE_CHOICES = ((_license, _license) for _license in licenses.LICENSES) |
| |
| LOGO_IMAGE_LABEL = translation.ugettext('Logo image') |
| |
| LOGO_IMAGE_HELP_TEXT = translation.ugettext( |
| 'Image file with logo of the organization. Please provide a 256px256px ' |
| 'image or ensure that the image will scale appropriately to this size. ' |
| 'Supported formats are JPG, PNG, WEBP.') |
| |
| LOGO_URL_LABEL = translation.ugettext('Logo URL') |
| |
| LOGO_URL_HELP_TEXT = translation.ugettext( |
| 'URL to the logo of the organization. Please provide a 64px64px image or ' |
| 'ensure that the image will scale appropriately to this size.') |
| |
| IDEAS_PAGE_LABEL = translation.ugettext('Ideas list') |
| |
| IDEAS_PAGE_HELP_TEXT = translation.ugettext( |
| 'The URL to a page with list of ideas for projects for this organization.') |
| |
| IDEAS_PAGE_HELP_TEXT_WITH_FAQ = translation.ugettext( |
| 'The URL to a page with list of ideas for projects for this organization.' |
| 'This is the most important part of your application. You can read about ' |
| 'ideas lists on the <a href="%s">FAQs</a>') |
| |
| MAILING_LIST_LABEL = translation.ugettext('Mailing list') |
| |
| MAILING_LIST_HELP_TEXT = translation.ugettext( |
| 'Mailing list email address, URL to the sign-up page, etc.') |
| |
| NOTIFICATIONS_EMAIL_LABEL = translation.ugettext('Notifications email') |
| |
| NOTIFICATIONS_EMAIL_HELP_TEXT = translation.ugettext( |
| 'If entered, all notifications for this organization will be sent ' |
| 'to this email address.') |
| |
| WEB_PAGE_LABEL = translation.ugettext('Organization website') |
| |
| WEB_PAGE_HELP_TEXT = translation.ugettext( |
| 'Main website of the organization.') |
| |
| IRC_CHANNEL_LABEL = translation.ugettext('IRC Channel') |
| |
| IRC_CHANNEL_HELP_TEXT = translation.ugettext( |
| 'Public IRC channel which may be used to get in touch with developers.') |
| |
| FEED_URL_LABEL = translation.ugettext('Feed URL') |
| |
| FEED_URL_HELP_TEXT = translation.ugettext( |
| 'The URL should be a valid ATOM or RSS feed. Feed entries are shown on ' |
| 'the organization home page in Melange.') |
| |
| GOOGLE_PLUS_LABEL = translation.ugettext('Google+ URL') |
| |
| GOOGLE_PLUS_HELP_TEXT = translation.ugettext( |
| 'URL to the Google+ page of the organization.') |
| |
| TWITTER_LABEL = translation.ugettext('Twitter URL') |
| |
| TWITTER_HELP_TEXT = translation.ugettext( |
| 'URL of the Twitter page of the organization.') |
| |
| BLOG_LABEL = translation.ugettext('Blog page') |
| |
| BLOG_HELP_TEXT = translation.ugettext( |
| 'URL to the blog page of the organization.') |
| |
| FACEBOOK_LABEL = translation.ugettext('Facebook URL') |
| |
| FACEBOOK_HELP_TEXT = translation.ugettext( |
| 'URL to the Facebook page of the organization.') |
| |
| IS_VETERAN_LABEL = translation.ugettext('Veteran organization') |
| |
| IS_VETERAN_HELP_TEXT = translation.ugettext( |
| 'Check this field if the organization has participated in a previous ' |
| 'instance of the program.') |
| |
| BACKUP_ADMIN_LABEL = translation.ugettext('Backup administrator') |
| |
| BACKUP_ADMIN_HELP_TEXT = translation.ugettext( |
| 'Username of the user who will also serve as administrator for this ' |
| 'organization. Please note that the user must have created ' |
| 'a profile for the current program in order to be eligible. ' |
| 'The organization will be allowed to assign more administrators upon ' |
| 'acceptance into the program. Organizations without a backup ' |
| 'admin will not be accepted.') |
| |
| # TODO(daniel): list of countries should be program-specific |
| ELIGIBLE_COUNTRY_LABEL = translation.ugettext( |
| 'I hereby declare that the applying organization is not located in ' |
| 'any of the countries which are not eligible to participate in the ' |
| 'program: Iran, Syria, Cuba, Sudan, North Korea.') |
| |
| TERMS_OF_SERVICE_LABEL = translation.ugettext( |
| 'Read the terms and conditions and acknowledge by selecting the checkbox ' |
| 'at the end.') |
| |
| TERMS_OF_SERVICE_AGREE_TEXT = translation.ugettext( |
| 'I have read and agree to the terms and conditions.') |
| |
| _CONTACT_PROPERTIES_FORM_KEYS = [ |
| 'blog', 'facebook', 'feed_url', 'google_plus', 'irc_channel', |
| 'mailing_list', 'twitter', 'web_page', 'notifications_email'] |
| |
| _ORG_PROFILE_PROPERTIES_FORM_KEYS = [ |
| 'description', 'ideas_page', 'logo_url', 'name', 'org_id', 'tags', |
| 'license', 'is_veteran'] |
| |
| # keys of the form fields which are relevant to profile creation only |
| ONLY_CREATE_ORG_PROFILE_FIELD_IDS = set([ |
| 'org_id', 'backup_admin', 'eligible_country', 'terms_of_service']) |
| |
| _TAG_TOO_LONG = translation.ugettext('Tag %s is too long: %s') |
| |
| _PROFILE_DOES_NOT_EXIST = translation.ugettext( |
| 'No profile exists for username %s.') |
| |
| _OTHER_PROFILE_IS_THE_CURRENT_PROFILE = translation.ugettext( |
| 'The currently logged in profile cannot be specified as ' |
| 'the other organization administrator.') |
| |
| _ORGANIZATION_LIST_DESCRIPTION = translation.ugettext('List of organizations') |
| |
| _ORG_PROFILE_CREATE_PAGE_NAME = translation.ugettext( |
| 'Create organization profile') |
| |
| _ORG_PROFILE_EDIT_PAGE_NAME = translation.ugettext( |
| 'Edit organization profile') |
| |
| _ORG_LOGO_EDIT_PAGE_NAME = translation.ugettext( |
| 'Organization logo') |
| |
| ORG_PREFERENCES_EDIT_PAGE_NAME = translation.ugettext( |
| 'Edit organization preferences') |
| |
| _ORG_APPLICATION_SUBMIT_PAGE_NAME = translation.ugettext( |
| 'Submit application') |
| |
| _ORG_APPLICATION_SHOW_PAGE_NAME = translation.ugettext( |
| 'Organization application - %s') |
| |
| _ORG_APPLICATION_RESPONSE_SHOW_PAGE_NAME = translation.ugettext( |
| 'Organization questionnaire - %s') |
| |
| APP_RESPONSE_GROUP_TITLE = translation.ugettext( |
| 'Application response') |
| |
| GENERAL_INFO_GROUP_DESCRIPTOR = readonly.GroupDescriptor( |
| translation.ugettext('General Info'), [ |
| readonly.FieldDescriptor( |
| org_model.Organization.org_id._name, ORG_ID_LABEL), |
| readonly.FieldDescriptor( |
| org_model.Organization.name._name, ORG_NAME_LABEL), |
| readonly.FieldDescriptor( |
| org_model.Organization.is_veteran._name, IS_VETERAN_LABEL), |
| readonly.FieldDescriptor( |
| org_model.Organization.description._name, DESCRIPTION_LABEL), |
| readonly.FieldDescriptor( |
| org_model.Organization.tags._name, TAGS_LABEL), |
| readonly.FieldDescriptor( |
| org_model.Organization.license._name, LICENSE_LABEL), |
| readonly.FieldDescriptor( |
| org_model.Organization.logo_url._name, LOGO_URL_LABEL), |
| readonly.FieldDescriptor( |
| org_model.Organization.ideas_page._name, IDEAS_PAGE_LABEL)]) |
| |
| CONTACT_GROUP_DESCRIPTOR = readonly.GroupDescriptor( |
| translation.ugettext('Contact'), [ |
| readonly.FieldDescriptor( |
| contact_model.Contact.mailing_list._name, MAILING_LIST_LABEL), |
| readonly.FieldDescriptor( |
| contact_model.Contact.email._name, NOTIFICATIONS_EMAIL_LABEL), |
| readonly.FieldDescriptor( |
| contact_model.Contact.web_page._name, WEB_PAGE_LABEL), |
| readonly.FieldDescriptor( |
| contact_model.Contact.irc_channel._name, IRC_CHANNEL_LABEL), |
| readonly.FieldDescriptor( |
| contact_model.Contact.feed_url._name, FEED_URL_LABEL), |
| readonly.FieldDescriptor( |
| contact_model.Contact.google_plus._name, GOOGLE_PLUS_LABEL), |
| readonly.FieldDescriptor( |
| contact_model.Contact.twitter._name, TWITTER_LABEL), |
| readonly.FieldDescriptor( |
| contact_model.Contact.blog._name, BLOG_LABEL), |
| readonly.FieldDescriptor( |
| contact_model.Contact.facebook._name, FACEBOOK_LABEL), |
| ]) |
| |
| |
| _SET_STATUS_BUTTON_ID = 'save' |
| |
| _APPLY_ORG_ADMISSION_DECISION_ID = 'apply_org_admission_decision_button_id' |
| |
| |
| def cleanOrgId(org_id): |
| """Cleans org_id field. |
| |
| Args: |
| org_id: The submitted organization ID. |
| |
| Returns: |
| Cleaned value for org_id field. |
| |
| Raises: |
| django_forms.ValidationError if the submitted value is not valid. |
| """ |
| if not org_id: |
| raise django_forms.ValidationError('This field is required.') |
| |
| cleaning.cleanLinkID(org_id) |
| |
| return org_id |
| |
| |
| def cleanTags(tags): |
| """Cleans tags field. |
| |
| Args: |
| tags: The submitted value, which is a comma separated string with tags. |
| |
| Returns: |
| A list of submitted tags. |
| |
| Raises: |
| django_forms.ValidationError if at least one of the tags is not valid. |
| """ |
| tag_list = [] |
| for tag in [tag for tag in tags.split(',') if tag]: |
| if len(tag) > TAG_MAX_LENGTH: |
| raise django_forms.ValidationError(_TAG_TOO_LONG % (tag, len(tag))) |
| tag_list.append(tag.strip()) |
| return tag_list |
| |
| |
| def cleanBackupAdmin(username, request_data): |
| """Cleans backup_admin field. |
| |
| Args: |
| username: Username of the user to assign as the backup administrator. |
| request_data: request_data.RequestData for the current request. |
| |
| Raises: |
| django_forms.ValidationError if no profile exists for at least one |
| of the submitted usernames. |
| """ |
| username = username.strip() |
| profile = profile_logic.getProfileForUsername( |
| username, request_data.program.key(), models=request_data.models) |
| if not profile: |
| raise django_forms.ValidationError(_PROFILE_DOES_NOT_EXIST % username) |
| elif profile.key == request_data.ndb_profile.key: |
| raise django_forms.ValidationError(_OTHER_PROFILE_IS_THE_CURRENT_PROFILE) |
| else: |
| return profile |
| |
| |
| class _OrgProfileForm(forms.ModelForm): |
| """Form to set properties of organization profile by organization |
| administrators. |
| """ |
| |
| org_id = django_forms.CharField( |
| required=True, label=ORG_ID_LABEL, help_text=ORG_ID_HELP_TEXT) |
| |
| name = django_forms.CharField( |
| required=True, label=ORG_NAME_LABEL, help_text=ORG_NAME_HELP_TEXT) |
| |
| # TODO(daniel): make sure this field is escaped properly |
| description = django_forms.CharField( |
| widget=django_forms.Textarea, required=True, label=DESCRIPTION_LABEL, |
| help_text=DESCRIPTION_HELP_TEXT) |
| |
| tags = django_forms.CharField( |
| required=False, label=TAGS_LABEL, |
| help_text=TAGS_HELP_TEXT % TAG_MAX_LENGTH) |
| |
| license = django_forms.CharField( |
| required=True, label=LICENSE_LABEL, help_text=LICENSE_HELP_TEXT, |
| widget=django_forms.Select(choices=_LICENSE_CHOICES)) |
| |
| logo_url = django_forms.URLField( |
| required=False, label=LOGO_URL_LABEL, help_text=LOGO_URL_HELP_TEXT) |
| |
| ideas_page = django_forms.URLField( |
| required=True, label=IDEAS_PAGE_LABEL) |
| |
| mailing_list = django_forms.CharField( |
| required=False, label=MAILING_LIST_LABEL, |
| help_text=MAILING_LIST_HELP_TEXT) |
| |
| notifications_email = django_forms.CharField( |
| required=False, label=NOTIFICATIONS_EMAIL_LABEL, |
| help_text=NOTIFICATIONS_EMAIL_HELP_TEXT) |
| |
| web_page = django_forms.URLField( |
| required=True, label=WEB_PAGE_LABEL, help_text=WEB_PAGE_HELP_TEXT) |
| |
| irc_channel = django_forms.CharField( |
| required=False, label=IRC_CHANNEL_LABEL, help_text=IRC_CHANNEL_HELP_TEXT) |
| |
| feed_url = django_forms.URLField( |
| required=False, label=FEED_URL_LABEL, help_text=FEED_URL_HELP_TEXT) |
| |
| google_plus = django_forms.URLField( |
| required=False, label=GOOGLE_PLUS_LABEL, help_text=GOOGLE_PLUS_HELP_TEXT) |
| |
| twitter = django_forms.URLField( |
| required=False, label=TWITTER_LABEL, help_text=TWITTER_HELP_TEXT) |
| |
| blog = django_forms.URLField( |
| required=False, label=BLOG_LABEL, help_text=BLOG_HELP_TEXT) |
| |
| facebook = django_forms.URLField( |
| required=False, label=FACEBOOK_LABEL, help_text=FACEBOOK_HELP_TEXT) |
| |
| is_veteran = django_forms.BooleanField( |
| required=False, label=IS_VETERAN_LABEL, help_text=IS_VETERAN_HELP_TEXT) |
| |
| backup_admin = django_forms.CharField( |
| label=BACKUP_ADMIN_LABEL, |
| help_text=BACKUP_ADMIN_HELP_TEXT) |
| |
| terms_of_service = django_forms.BooleanField( |
| required=True, label=TERMS_OF_SERVICE_LABEL) |
| |
| eligible_country = django_forms.BooleanField( |
| required=True, label=ELIGIBLE_COUNTRY_LABEL) |
| |
| def __init__(self, bound_field_class, request_data, |
| include_fields=None, **kwargs): |
| """Initializes a new form. |
| |
| Args: |
| bound_field_class: Subclass of BoundField class to be used for the from. |
| request_data: request_data.RequestData for the current request. |
| include_fields: Optional list of identifiers of the fields to include |
| in the form. |
| """ |
| super(_OrgProfileForm, self).__init__(bound_field_class, **kwargs) |
| self.request_data = request_data |
| |
| # set help text for ideas page. |
| help_page_key = program_model.Program.help_page.get_value_for_datastore( |
| self.request_data.program) |
| if help_page_key: |
| self.fields['ideas_page'].help_text = ( |
| IDEAS_PAGE_HELP_TEXT_WITH_FAQ % |
| self.request_data.redirect.document(help_page_key).url()) |
| else: |
| self.fields['ideas_page'].help_text = IDEAS_PAGE_HELP_TEXT |
| |
| # terms of service field should be included only if defined for the program |
| if not request_data.program.org_admin_agreement: |
| self.fields.pop('terms_of_service', None) |
| |
| # only keep fields explicitly included |
| if include_fields: |
| for field_id in self.fields.keys(): |
| if field_id not in include_fields: |
| self.fields.pop(field_id, None) |
| |
| # add the content of Terms Of Service document if still needed |
| if 'terms_of_service' in self.fields: |
| self.fields['terms_of_service'].widget = forms.TOSWidget( |
| tos_text=request_data.program.org_admin_agreement.content, |
| tos_agree_text=TERMS_OF_SERVICE_AGREE_TEXT) |
| |
| def clean_org_id(self): |
| """Cleans org_id field. |
| |
| Returns: |
| Cleaned value for org_id field. |
| |
| Raises: |
| django_forms.ValidationError if the submitted value is not valid. |
| """ |
| return cleanOrgId(self.cleaned_data['org_id']) |
| |
| def clean_backup_admin(self): |
| """Cleans backup_admin field. |
| |
| Returns: |
| Profile entity corresponding to the backup administrator. |
| |
| Raises: |
| django_forms.ValidationError if the submitted value is not valid. |
| """ |
| return cleanBackupAdmin( |
| self.cleaned_data['backup_admin'], self.request_data) |
| |
| def clean_tags(self): |
| """Cleans tags field. |
| |
| Returns: |
| A list of submitted tags. |
| |
| Raises: |
| django_forms.ValidationError if at least one of the tags is not valid. |
| """ |
| return cleanTags(self.cleaned_data['tags']) |
| |
| def getContactProperties(self): |
| """Returns properties of the contact information that were submitted in |
| the form. |
| |
| Returns: |
| A dict mapping contact properties to the corresponding values. |
| """ |
| return self._getPropertiesForFields(_CONTACT_PROPERTIES_FORM_KEYS) |
| |
| def getOrgProperties(self): |
| """Returns properties of the organization that were submitted in this form. |
| |
| Returns: |
| A dict mapping organization properties to the corresponding values. |
| """ |
| return self._getPropertiesForFields(_ORG_PROFILE_PROPERTIES_FORM_KEYS) |
| |
| |
| def adaptOrgProfilePropertiesForForm(organization): |
| """Adapts properties of the specified organization so that they can be |
| used in the form. |
| |
| Args: |
| organization: org_model.Organization entity. |
| |
| Returns: |
| A dict containing organization properties for the form. |
| """ |
| properties = organization.to_dict() |
| |
| # initialize list of tags as comma separated list of values |
| properties[org_model.Organization.tags._name] = ( |
| ', '.join(organization.tags)) |
| |
| if organization.contact: |
| properties.update(organization.contact.to_dict()) |
| |
| return properties |
| |
| |
| def _adaptOrgPreferencesPropertiesForForm(properties): |
| """Adapts properties of an organization and organization messages entities, |
| which are persisted in datastore, to representation which may be passed |
| to populate OrgPreferencesForm. |
| |
| Args: |
| properties: A dict containing organization properties as persisted |
| in datastore. |
| |
| Returns: |
| A dict mapping properties of organization models to values which can be |
| populated to an organization preferences form. |
| """ |
| if org_model.Organization.extra_fields._name in properties: |
| properties[org_model.Organization.extra_fields._name] = '\n'.join( |
| field for field |
| in properties[org_model.Organization.extra_fields._name]) |
| return properties |
| |
| |
| class OrgApplicationReminder(object): |
| """Reminder to be included in context if organization application has |
| yet to be submitted. |
| """ |
| |
| def __init__(self, url, deadline): |
| """Initializes a new instance of this class. |
| |
| Args: |
| url: URL to Submit Organization Application page. |
| deadline: a datetime by which the application has to be submitted. |
| """ |
| self.url = url |
| self.deadline = deadline |
| |
| |
| class OrgProfileFormFactory(object): |
| """Interface that defines a factory to create a form for |
| an organization profile. |
| """ |
| |
| def create(self, request_data, **kwargs): |
| """Creates a new instance of _OrgProfileForm for organization profile |
| for the specified parameters. |
| |
| Args: |
| request_data: request_data.RequestData for the current request. |
| |
| Returns: |
| _OrgProfileForm adjusted depending on the needs. |
| """ |
| raise NotImplementedError |
| |
| |
| class OrgProfileCreatePage(base.RequestHandler): |
| """View to create organization profile.""" |
| |
| # TODO(daniel): access checker should be customized |
| access_checker = access.ConjuctionAccessChecker([ |
| access.NON_STUDENT_PROFILE_ACCESS_CHECKER, |
| access.ORG_SIGNUP_ACTIVE_ACCESS_CHECKER]) |
| |
| def __init__(self, initializer, linker, renderer, error_handler, |
| url_pattern_constructor, url_names, template_path, form_factory): |
| """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. |
| form_factory: Implementation of OrgProfileFormFactory interface. |
| """ |
| super(OrgProfileCreatePage, self).__init__( |
| initializer, linker, renderer, error_handler) |
| self.url_pattern_constructor = url_pattern_constructor |
| self.url_names = url_names |
| self.template_path = template_path |
| self.form_factory = form_factory |
| |
| def templatePath(self): |
| """See base.RequestHandler.templatePath for specification.""" |
| return self.template_path |
| |
| def djangoURLPatterns(self): |
| """See base.RequestHandler.djangoURLPatterns for specification.""" |
| return [ |
| self.url_pattern_constructor.construct( |
| r'org/profile/create/%s$' % url_patterns.PROGRAM, |
| self, name=self.url_names.ORG_PROFILE_CREATE)] |
| |
| def context(self, data, check, mutator): |
| """See base.RequestHandler.context for specification.""" |
| form = self.form_factory.create(data, data=data.POST) |
| |
| return { |
| 'page_name': _ORG_PROFILE_CREATE_PAGE_NAME, |
| 'description': data.org_app.content, |
| 'forms': [form], |
| 'error': bool(form.errors), |
| 'form_below_header_msg': profile_form_below_header.Create( |
| data, agreement=data.program.org_admin_agreement), |
| } |
| |
| def post(self, data, check, mutator): |
| """See base.RequestHandler.post for specification.""" |
| form = self.form_factory.create( |
| data, data=data.POST, files=data.request.file_uploads) |
| |
| if not form.is_valid(): |
| # we are not storing this form, remove the uploaded blobs from the cloud |
| for blob_info in data.request.file_uploads.itervalues(): |
| blob_info.delete() |
| # TODO(nathaniel): problematic self-use. |
| return self.get(data, check, mutator) |
| else: |
| contact_properties = form.getContactProperties() |
| result = contact_logic.createContact(**contact_properties) |
| |
| if not result: |
| raise exception.BadRequest(message=result.extra) |
| else: |
| org_properties = form.getOrgProperties() |
| org_properties['contact'] = result.extra |
| |
| # org_id is a special property |
| org_id = org_properties['org_id'] |
| del org_properties['org_id'] |
| |
| result = createOrganizationTxn( |
| data, org_id, data.program.key(), org_properties, data.ndb_profile, |
| [data.ndb_profile.key, form.cleaned_data['backup_admin'].key], |
| data.models, tos_key=data.program.org_admin_agreement.key()) |
| |
| if not result: |
| raise exception.BadRequest(message=result.extra) |
| else: |
| url = links.LINKER.organization( |
| result.extra.key, self.url_names.ORG_APPLICATION_SUBMIT) |
| return http.HttpResponseRedirect(url) |
| |
| |
| class OrgProfileEditPage(base.RequestHandler): |
| """View to edit organization profile.""" |
| |
| access_checker = access.IS_USER_ORG_ADMIN_FOR_NDB_ORG |
| |
| def __init__(self, initializer, linker, renderer, error_handler, |
| url_pattern_constructor, url_names, template_path, form_factory): |
| """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. |
| form_factory: Implementation of OrgProfileFormFactory interface. |
| """ |
| super(OrgProfileEditPage, self).__init__( |
| initializer, linker, renderer, error_handler) |
| self.url_pattern_constructor = url_pattern_constructor |
| self.url_names = url_names |
| self.template_path = template_path |
| self.form_factory = form_factory |
| |
| def templatePath(self): |
| """See base.RequestHandler.templatePath for specification.""" |
| return self.template_path |
| |
| def djangoURLPatterns(self): |
| """See base.RequestHandler.djangoURLPatterns for specification.""" |
| return [ |
| self.url_pattern_constructor.construct( |
| r'org/profile/edit/%s$' % url_patterns.ORG, |
| self, name=self.url_names.ORG_PROFILE_EDIT)] |
| |
| def context(self, data, check, mutator): |
| """See base.RequestHandler.context for specification.""" |
| form_data = adaptOrgProfilePropertiesForForm(data.url_ndb_org) |
| |
| form = self.form_factory.create(data, data=data.POST or form_data) |
| |
| # add a reminder if no application has been submitted and it is still |
| # before the deadline |
| if (not org_logic.getApplicationResponse(data.url_ndb_org.key) and |
| time_utils.isBefore(data.org_app.survey_end)): |
| url = links.LINKER.organization( |
| data.url_ndb_org.key, self.url_names.ORG_APPLICATION_SUBMIT) |
| deadline = data.org_app.survey_end |
| org_application_reminder = OrgApplicationReminder(url, deadline) |
| else: |
| org_application_reminder = None |
| |
| # TODO(daniel): ugly, ugly ugly |
| org_tabs = ( |
| tabs.orgTabs(data, selected_tab_id=tabs.ORG_PROFILE_TAB_ID) |
| if data.program.prefix == 'gsoc_program' else None) |
| |
| return { |
| 'page_name': _ORG_PROFILE_EDIT_PAGE_NAME, |
| 'forms': [form], |
| 'error': bool(form.errors), |
| 'org_application_reminder': org_application_reminder, |
| 'tabs': org_tabs, |
| 'form_below_header_msg': profile_form_below_header.Create( |
| data, agreement=data.program.org_admin_agreement), |
| } |
| |
| def post(self, data, check, mutator): |
| """See base.RequestHandler.post for specification.""" |
| form = self.form_factory.create( |
| data, data=data.POST, files=data.request.file_uploads) |
| |
| if not form.is_valid(): |
| # we are not storing this form, remove the uploaded blobs from the cloud |
| for blob_info in data.request.file_uploads.itervalues(): |
| blob_info.delete() |
| |
| # TODO(nathaniel): problematic self-use. |
| return self.get(data, check, mutator) |
| else: |
| contact_properties = form.getContactProperties() |
| result = contact_logic.createContact(**contact_properties) |
| |
| if not result: |
| raise exception.BadRequest(message=result.extra) |
| else: |
| org_properties = form.getOrgProperties() |
| org_properties['contact'] = result.extra |
| |
| updateOrganizationTxn(data.url_ndb_org.key, org_properties) |
| |
| url = links.LINKER.organization( |
| data.url_ndb_org.key, self.url_names.ORG_PROFILE_EDIT) |
| return http.HttpResponseRedirect(url) |
| |
| |
| class OrgApplicationSubmitPage(base.RequestHandler): |
| """View to submit application to a program by organization representatives.""" |
| |
| access_checker = access.ConjuctionAccessChecker([ |
| access.IS_USER_ORG_ADMIN_FOR_NDB_ORG, |
| access.UrlOrgStatusAccessChecker( |
| [org_model.Status.APPLYING, org_model.Status.PRE_ACCEPTED, |
| org_model.Status.PRE_REJECTED])]) |
| |
| def __init__(self, initializer, linker, renderer, error_handler, |
| url_pattern_constructor, url_names, template_path, form_factory): |
| """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. |
| form_factory: |
| Implementation of survey_view.SurveyTakeFormFactory interface. |
| """ |
| super(OrgApplicationSubmitPage, self).__init__( |
| initializer, linker, renderer, error_handler) |
| self.url_pattern_constructor = url_pattern_constructor |
| self.url_names = url_names |
| self.template_path = template_path |
| self.form_factory = form_factory |
| |
| def djangoURLPatterns(self): |
| """See base.RequestHandler.djangoURLPatterns for specification.""" |
| return [ |
| self.url_pattern_constructor.construct( |
| r'org/application/submit/%s$' % url_patterns.ORG, |
| self, name=self.url_names.ORG_APPLICATION_SUBMIT)] |
| |
| def templatePath(self): |
| """See base.RequestHandler.templatePath for specification.""" |
| return self.template_path |
| |
| def context(self, data, check, mutator): |
| """See base.RequestHandler.context for specification.""" |
| application = org_logic.getApplicationResponse(data.url_ndb_org.key) |
| form_data = application.to_dict() if application else None |
| form = self.form_factory.create(data.org_app, data=data.POST or form_data) |
| |
| # TODO(daniel): ugly, ugly, ugly |
| org_tabs = ( |
| tabs.orgTabs(data, selected_tab_id=tabs.ORG_APP_RESPONSE_TAB_ID) |
| if data.program.prefix == 'gsoc_program' else None) |
| return { |
| 'page_name': _ORG_APPLICATION_SUBMIT_PAGE_NAME, |
| 'forms': [form], |
| 'error': bool(form.errors), |
| 'tabs': org_tabs, |
| 'form_top_msg': top_message.surveyResponseTopMessage( |
| data, bool(application)) |
| } |
| |
| def post(self, data, check, mutator): |
| """See base.RequestHandler.post for specification.""" |
| form = self.form_factory.create(data.org_app, data=data.POST) |
| if not form.is_valid(): |
| # TODO(nathaniel): problematic self-use. |
| return self.get(data, check, mutator) |
| else: |
| properties = form.getSurveyResponseProperties() |
| org_logic.setApplicationResponse( |
| data.url_ndb_org.key, data.org_app.key(), properties) |
| |
| url = links.LINKER.organization( |
| data.url_ndb_org.key, self.url_names.ORG_APPLICATION_SUBMIT) |
| return http.HttpResponseRedirect(url + '?validated=True') |
| |
| |
| class OrgApplicationReadonlyFactory(object): |
| """Interface that defines a factory to create read-only template for |
| the whole organization application. |
| """ |
| |
| def create(self, data, app_response): |
| """Creates a new instance of readonly.Readonly template for the organization |
| defined in the URL and the specified response. |
| |
| Args: |
| data: request_data.RequestData for the current request. |
| app_response: survey_model.SurveyResponse entity. |
| |
| Returns: |
| readonly.Readonly for organization application. |
| """ |
| raise NotImplementedError |
| |
| |
| class OrgApplicationShowPage(base.RequestHandler): |
| """Page to display an organization application for program administrators.""" |
| |
| access_checker = access.PROGRAM_ADMINISTRATOR_ACCESS_CHECKER |
| |
| def __init__(self, initializer, linker, renderer, error_handler, |
| url_pattern_constructor, url_names, template_path, readonly_factory): |
| """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. |
| readonly_factory: |
| Implementation of OrgApplicationReadonlyFactory interface. |
| """ |
| super(OrgApplicationShowPage, self).__init__( |
| initializer, linker, renderer, error_handler) |
| self.url_pattern_constructor = url_pattern_constructor |
| self.url_names = url_names |
| self.template_path = template_path |
| self.readonly_factory = readonly_factory |
| |
| def djangoURLPatterns(self): |
| """See base.RequestHandler.djangoURLPatterns for specification.""" |
| # TODO(daniel): remove 2 when the old view is removed. |
| return [ |
| self.url_pattern_constructor.construct( |
| r'org/application/show2/%s$' % url_patterns.ORG, |
| self, name=self.url_names.ORG_APPLICATION_SHOW)] |
| |
| def templatePath(self): |
| """See base.RequestHandler.templatePath for specification.""" |
| return self.template_path |
| |
| def context(self, data, check, mutator): |
| """See base.RequestHandler.context for specification.""" |
| app_response = org_logic.getApplicationResponse(data.url_ndb_org.key) |
| |
| return { |
| 'page_name': _ORG_APPLICATION_SHOW_PAGE_NAME % data.url_ndb_org.name, |
| 'record': self.readonly_factory.create(data, app_response) |
| } |
| |
| |
| class AppResponseReadonlyFactory(object): |
| """Interface that defines a factory to create read-only template for |
| the survey response of the organization. |
| """ |
| |
| def create(self, data, app_response): |
| """Creates a new instance of readonly.Readonly template for the organization |
| defined in the URL and the specified response. |
| |
| Args: |
| data: request_data.RequestData for the current request. |
| app_response: survey_model.SurveyResponse entity. |
| |
| Returns: |
| readonly.Readonly for organization application. |
| """ |
| raise NotImplementedError |
| |
| |
| class OrgApplicationResponseShowPage(base.RequestHandler): |
| """Page to display survey response.""" |
| |
| access_checker = access.IS_USER_ORG_ADMIN_FOR_NDB_ORG |
| |
| def __init__(self, initializer, linker, renderer, error_handler, |
| url_pattern_constructor, url_names, template_path, readonly_factory): |
| """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. |
| readonly_factory: Implementation of AppResponseReadonlyFactory interface. |
| """ |
| super(OrgApplicationResponseShowPage, self).__init__( |
| initializer, linker, renderer, error_handler) |
| self.url_pattern_constructor = url_pattern_constructor |
| self.url_names = url_names |
| self.template_path = template_path |
| self.readonly_factory = readonly_factory |
| |
| def djangoURLPatterns(self): |
| """See base.RequestHandler.djangoURLPatterns for specification.""" |
| return [ |
| self.url_pattern_constructor.construct( |
| r'org/survey_response/show/%s$' % url_patterns.ORG, |
| self, name=self.url_names.ORG_APPLICATION_RESPONSE_SHOW)] |
| |
| def templatePath(self): |
| """See base.RequestHandler.templatePath for specification.""" |
| return self.template_path |
| |
| def context(self, data, check, mutator): |
| """See base.RequestHandler.context for specification.""" |
| app_response = org_logic.getApplicationResponse(data.url_ndb_org.key) |
| |
| record = ( |
| self.readonly_factory.create(data, app_response) |
| if app_response else None) |
| |
| return { |
| 'page_name': _ORG_APPLICATION_RESPONSE_SHOW_PAGE_NAME % |
| data.url_ndb_org.name, |
| 'record': record |
| } |
| |
| |
| class OrgLogoForm(forms.ModelForm): |
| """Form to set logo image for the organization.""" |
| |
| logo_image = forms.ImageField( |
| required=False, label=LOGO_IMAGE_LABEL, help_text=LOGO_IMAGE_HELP_TEXT, |
| widget=forms.ImageWidget(width=256, height=256)) |
| |
| # See files_utils.DONT_USE_BLOBOSTORE_MIDDLEWARE for details on this field |
| dont_use_blobstore_middleware = forms.CharField( |
| required=False, widget=forms.HiddenInput()) |
| |
| |
| class OrgLogoFormFactory(object): |
| """Interface that defines a factory to create instances of OrgLogoForm |
| class. |
| """ |
| |
| def create(self, **kwargs): |
| """Creates a new instance of OrgLogoForm for the specified parameters. |
| |
| Returns: |
| The newly created OrgLogoForm instance. |
| """ |
| raise NotImplementedError |
| |
| |
| class OrgLogoEditPage(base.RequestHandler): |
| """View to set logo image for the organization""" |
| |
| access_checker = access.IS_USER_ORG_ADMIN_FOR_NDB_ORG |
| |
| def __init__(self, initializer, linker, renderer, error_handler, |
| url_pattern_constructor, url_names, template_path, form_factory): |
| """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. |
| form_factory: Implementation of OrgLogoFormFactory interface. |
| """ |
| super(OrgLogoEditPage, self).__init__( |
| initializer, linker, renderer, error_handler) |
| self.url_pattern_constructor = url_pattern_constructor |
| self.url_names = url_names |
| self.template_path = template_path |
| self.form_factory = form_factory |
| |
| def templatePath(self): |
| """See base.RequestHandler.templatePath for specification.""" |
| return self.template_path |
| |
| def djangoURLPatterns(self): |
| """See base.RequestHandler.djangoURLPatterns for specification.""" |
| return [ |
| self.url_pattern_constructor.construct( |
| r'org/logo/edit/%s$' % url_patterns.ORG, |
| self, name=self.url_names.ORG_LOGO_EDIT)] |
| |
| def context(self, data, check, mutator): |
| """See base.RequestHandler.context for specification.""" |
| initial = {'logo_image': data.url_ndb_org.logo_image_url} |
| form = self.form_factory.create( |
| data=data.POST, initial=initial, files=data.request.file_uploads) |
| |
| # this is hacky, but it is necessary because of the way how Django deals |
| # with file uploads: if logo image is in request.file_uploads and does not |
| # validate, the existing (pre-upload) image will not be rendered (because |
| # files takes precedence over initial). |
| # here, a new form instance is created and errors are explicitly set |
| # so that the error message is written out and the image is still rendered |
| if 'logo_image' in form.errors and data.url_ndb_org.logo_image_url: |
| error_dict = form.errors |
| form = self.form_factory.create(data=data.POST, initial=initial) |
| form._errors = error_dict |
| |
| return { |
| 'forms': [form], |
| 'error': bool(form.errors), |
| 'is_multipart': True, |
| 'page_name': _ORG_LOGO_EDIT_PAGE_NAME |
| } |
| |
| def post(self, data, check, mutator): |
| """See base.RequestHandler.post for specification.""" |
| form = self.form_factory.create( |
| data=data.POST, files=data.request.FILES) |
| |
| if not form.is_valid(): |
| # we are not storing this form, remove the uploaded blobs from the cloud |
| for blob_info in data.request.file_uploads.itervalues(): |
| blob_info.delete() |
| |
| # TODO(nathaniel): problematic self-use. |
| return self.get(data, check, mutator) |
| else: |
| org_logic.setOrgLogo( |
| data.url_ndb_org.key, form.cleaned_data.get('logo_image')) |
| |
| url = links.LINKER.organization( |
| data.url_ndb_org.key, self.url_names.ORG_LOGO_EDIT) |
| return http.HttpResponseRedirect(url) |
| |
| |
| class OrgPreferencesForm(forms.ModelForm): |
| """Form to set properties of organization preferences by organization |
| administrators. |
| """ |
| |
| def __init__(self, bound_field_class, org_properties_field_keys=None, |
| org_messages_properties_field_keys=None, **kwargs): |
| """Initializes a new form. |
| |
| Args: |
| bound_field_class: Subclass of BoundField class to be used for the from. |
| org_properties_field_keys: Field keys that correspond to properties |
| in organization model. |
| org_messages_properties_field_keys: Field keys that correspond to |
| properties in organization messages model. |
| """ |
| super(OrgPreferencesForm, self).__init__(bound_field_class, **kwargs) |
| self.org_properties_field_keys = org_properties_field_keys or [] |
| self.org_messages_properties_field_keys = ( |
| org_messages_properties_field_keys or []) |
| |
| def getOrgProperties(self): |
| """Returns properties of the organization that were submitted in this form. |
| |
| Returns: |
| A dict mapping organization preferences properties to |
| the corresponding values. |
| """ |
| return self._getPropertiesForFields(self.org_properties_field_keys) |
| |
| def getOrgMessagesProperties(self): |
| """Returns properties of the organization messages that were submitted |
| in this form. |
| |
| Returns: |
| A dict mapping organization messages properties to |
| the corresponding values. |
| """ |
| return self._getPropertiesForFields(self.org_messages_properties_field_keys) |
| |
| |
| class OrgPreferencesFormFactory(object): |
| """Interface that defines a factory to create a form for |
| organization preferences. |
| """ |
| |
| def create(self, request_data, **kwargs): |
| """Creates a new instance of a from for organization preferences |
| for the specified parameters. |
| |
| Args: |
| request_data: request_data.RequestData for the current request. |
| |
| Returns: |
| Implementation of OrgPreferencesForm adjusted depending on the needs. |
| """ |
| raise NotImplementedError |
| |
| |
| ORG_PREFERENCES_EDIT_PAGE_ACCESS_CHECKER = access.ConjuctionAccessChecker([ |
| access.IS_USER_ORG_ADMIN_FOR_NDB_ORG, |
| access.UrlOrgStatusAccessChecker([org_model.Status.ACCEPTED])]) |
| |
| class OrgPreferencesEditPage(base.RequestHandler): |
| """View to edit organization preferences.""" |
| |
| access_checker = ORG_PREFERENCES_EDIT_PAGE_ACCESS_CHECKER |
| |
| def __init__(self, initializer, linker, renderer, error_handler, |
| url_pattern_constructor, url_names, template_path, form_factory): |
| """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. |
| form_factory: Implementation of AppResponseReadonlyFactory interface. |
| """ |
| super(OrgPreferencesEditPage, self).__init__( |
| initializer, linker, renderer, error_handler) |
| self.url_pattern_constructor = url_pattern_constructor |
| self.url_names = url_names |
| self.template_path = template_path |
| self.form_factory = form_factory |
| |
| def templatePath(self): |
| """See base.RequestHandler.templatePath for specification.""" |
| return self.template_path |
| |
| def djangoURLPatterns(self): |
| """See base.RequestHandler.djangoURLPatterns for specification.""" |
| return [ |
| self.url_pattern_constructor.construct( |
| r'org/preferences/edit/%s$' % url_patterns.ORG, |
| self, name=self.url_names.ORG_PREFERENCES_EDIT)] |
| |
| def context(self, data, check, mutator): |
| """See base.RequestHandler.context for specification.""" |
| properties = data.url_ndb_org.to_dict() |
| properties.update( |
| org_logic.getOrganizationMessages(data.url_ndb_org.key).to_dict()) |
| form_data = _adaptOrgPreferencesPropertiesForForm(properties) |
| form = self.form_factory.create(data=data.POST or form_data) |
| |
| # TODO(daniel): ugly, ugly ugly |
| org_tabs = ( |
| tabs.orgTabs(data, selected_tab_id=tabs.ORG_PREFERENCES_TAB_ID) |
| if data.program.prefix == 'gsoc_program' else None) |
| |
| return { |
| 'error': bool(form.errors), |
| 'forms': [form], |
| 'page_name': ORG_PREFERENCES_EDIT_PAGE_NAME, |
| 'tabs': org_tabs, |
| } |
| |
| def post(self, data, check, mutator): |
| """See base.RequestHandler.post for specification.""" |
| form = self.form_factory.create(data=data.POST) |
| if not form.is_valid(): |
| # TODO(nathaniel): problematic self-use. |
| return self.get(data, check, mutator) |
| else: |
| org_properties = form.getOrgProperties() |
| org_messages_properties = form.getOrgMessagesProperties() |
| updateOrganizationTxn( |
| data.url_ndb_org.key, org_properties, |
| org_messages_properties=org_messages_properties) |
| |
| url = links.LINKER.organization( |
| data.url_ndb_org.key, self.url_names.ORG_PREFERENCES_EDIT) |
| return http.HttpResponseRedirect(url) |
| |
| |
| _STATUS_APPLYING_ID = translation.ugettext('Needs review') |
| _STATUS_PRE_ACCEPTED_ID = translation.ugettext('Pre-accepted') |
| _STATUS_PRE_REJECTED_ID = translation.ugettext('Pre-rejected') |
| _STATUS_ACCEPTED_ID = translation.ugettext('Accepted') |
| _STATUS_REJECTED_ID = translation.ugettext('Rejected') |
| |
| _STATUS_ID_TO_ENUM_LINK = ( |
| (_STATUS_APPLYING_ID, org_model.Status.APPLYING), |
| (_STATUS_PRE_ACCEPTED_ID, org_model.Status.PRE_ACCEPTED), |
| (_STATUS_PRE_REJECTED_ID, org_model.Status.PRE_REJECTED), |
| (_STATUS_ACCEPTED_ID, org_model.Status.ACCEPTED), |
| (_STATUS_REJECTED_ID, org_model.Status.REJECTED), |
| ) |
| _STATUS_ID_TO_ENUM_MAP = dict(_STATUS_ID_TO_ENUM_LINK) |
| _STATUS_ENUM_TO_ID_MAP = dict( |
| (v, k) for (k, v) in _STATUS_ID_TO_ENUM_LINK) |
| |
| |
| class OrgApplicationList(template.Template): |
| """List of organization applications that have been submitted for the program. |
| """ |
| |
| def __init__(self, data, survey, url_names, template_path, |
| idx=0, description=None): |
| """Creates a new OrgApplicationList template. |
| |
| Args: |
| data: request_data.RequestData object for the current request. |
| survey: Survey entity to show the responses for |
| url_names: Instance of url_names.UrlNames. |
| template_path: The path of the template to be used. |
| idx: The index of the list to use. |
| description: The (optional) description of the list. |
| """ |
| super(OrgApplicationList, self).__init__(data) |
| |
| self.idx = idx |
| self.description = description or '' |
| self.url_names = url_names |
| self.template_path = template_path |
| self.list_config = lists.ListConfiguration() |
| |
| self.list_config.addPlainTextColumn( |
| 'key', 'Key', lambda entity, *args: entity.key.parent().id(), |
| hidden=True) |
| self.list_config.addSimpleColumn( |
| 'created_on', 'Created On', column_type=lists.DATE) |
| self.list_config.addSimpleColumn( |
| 'modified_on', 'Last Modified On', column_type=lists.DATE) |
| self.list_config.addPlainTextColumn( |
| 'name', 'Name', lambda entity, *args: entity.key.parent().get().name) |
| self.list_config.addPlainTextColumn( |
| 'org_id', 'Organization ID', |
| lambda entity, *args: entity.key.parent().get().org_id) |
| self.list_config.addPlainTextColumn( |
| 'new_or_veteran', 'New/Veteran', |
| lambda entity, *args: |
| 'Veteran' if entity.key.parent().get().is_veteran else 'New') |
| self.list_config.addPlainTextColumn( |
| 'description', 'Description', |
| lambda entity, *args: entity.key.parent().get().description) |
| self.list_config.addPlainTextColumn( |
| 'license', 'License', |
| lambda entity, *args: entity.key.parent().get().license) |
| self.list_config.addPlainTextColumn( |
| 'ideas_page', 'Ideas Page', |
| lambda entity, *args: entity.key.parent().get().ideas_page) |
| |
| survey_response_list.addColumnsForSurvey(self.list_config, survey) |
| |
| # TODO(ljvderijk): Poke Mario during all-hands to see if we can separate |
| # "search options" and in-line selection options. |
| options = [ |
| ('', 'All'), |
| ('(%s)' % _STATUS_APPLYING_ID, _STATUS_APPLYING_ID), |
| ('(%s)' % _STATUS_PRE_ACCEPTED_ID, _STATUS_PRE_ACCEPTED_ID), |
| ('(%s)' % _STATUS_PRE_REJECTED_ID, _STATUS_PRE_REJECTED_ID), |
| # TODO(daniel): figure out how ignored state is used. |
| # ('(ignored)', 'ignored'), |
| ] |
| |
| self.list_config.addPlainTextColumn( |
| 'status', 'Status', |
| lambda entity, *args: |
| _STATUS_ENUM_TO_ID_MAP[entity.key.parent().get().status], |
| options=options) |
| self.list_config.setColumnEditable('status', True, 'select') |
| self.list_config.addPostEditButton(_SET_STATUS_BUTTON_ID, 'Save') |
| |
| self.list_config.setRowAction( |
| lambda entity, *args: links.LINKER.organization( |
| entity.key.parent(), self.url_names.ORG_APPLICATION_SHOW)) |
| |
| def templatePath(self): |
| """See template.Template.templatePath for specification.""" |
| return self.template_path |
| |
| 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]} |
| |
| def getListData(self): |
| """Returns data for the list.""" |
| query = org_logic.getApplicationResponsesQuery(self.data.org_app.key()) |
| |
| response_builder = lists.RawQueryContentResponseBuilder( |
| self.data.request, self.list_config, query, lists.keyStarter, |
| prefetcher=None) |
| return response_builder.buildNDB() |
| |
| |
| class OrgApplicationListPage(base.RequestHandler): |
| """View to list all applications that have been submitted in the program.""" |
| |
| # TODO(daniel): This list should be active only when org application is set. |
| access_checker = access.PROGRAM_ADMINISTRATOR_ACCESS_CHECKER |
| |
| def __init__(self, initializer, linker, renderer, error_handler, |
| url_pattern_constructor, url_names, template_path, list_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. |
| list_template_path: The path of the template to be used to render |
| the list of applications. |
| """ |
| super(OrgApplicationListPage, self).__init__( |
| initializer, linker, renderer, error_handler) |
| self.url_pattern_constructor = url_pattern_constructor |
| self.url_names = url_names |
| self.template_path = template_path |
| self.list_template_path = list_template_path |
| |
| def templatePath(self): |
| """See base.RequestHandler.templatePath for specification.""" |
| return self.template_path |
| |
| def djangoURLPatterns(self): |
| """See base.RequestHandler.djangoURLPatterns for specification.""" |
| return [ |
| self.url_pattern_constructor.construct( |
| r'org/application/list/%s$' % url_patterns.PROGRAM, self, |
| name=self.url_names.ORG_APPLICATION_LIST)] |
| |
| def jsonContext(self, data, check, mutator): |
| """See base.RequestHandler.jsonContext for specification.""" |
| idx = lists.getListIndex(data.request) |
| if idx == 0: |
| list_data = OrgApplicationList( |
| data, data.org_app, self.url_names, |
| self.list_template_path).getListData() |
| return list_data.content() |
| else: |
| raise exception.BadRequest(message='Invalid ID has been specified.') |
| |
| def context(self, data, check, mutator): |
| """See base.RequestHandler.context for specification.""" |
| record_list = OrgApplicationList( |
| data, data.org_app, self.url_names, self.list_template_path) |
| |
| page_name = translation.ugettext('Records - %s' % (data.org_app.title)) |
| context = { |
| 'page_name': page_name, |
| 'record_list': record_list, |
| 'apply_org_admission_decision_id':_APPLY_ORG_ADMISSION_DECISION_ID, |
| } |
| return context |
| |
| def post(self, data, check, mutator): |
| """See base.RequestHandler.post for specification.""" |
| button_id = data.POST.get('button_id') |
| if button_id is not None: |
| if button_id == _SET_STATUS_BUTTON_ID: |
| handler = SetOrganizationStatusHandler(self) |
| return handler.handle(data, check, mutator) |
| elif button_id == _APPLY_ORG_ADMISSION_DECISION_ID: |
| url = links.LINKER.program( |
| data.program, self.url_names.ORG_APPLICATION_LIST) |
| handler = ApplyOrgAdmissionDecisionHandler(self, url=url) |
| return handler.handle(data, check, mutator) |
| else: |
| raise exception.BadRequest( |
| message='Button id %s not supported.' % button_id) |
| else: |
| raise exception.BadRequest(message='Invalid POST data.') |
| |
| |
| class SetOrganizationStatusHandler(form_handler.FormHandler): |
| """Form handler implementation to set status of organizations based on |
| data which is sent in a request. |
| """ |
| |
| def handle(self, data, check, mutator): |
| """See form_handler.FormHandler.handle for specification.""" |
| post_data = data.POST.get('data') |
| |
| if not post_data: |
| raise exception.BadRequest(message='Missing data.') |
| |
| parsed_data = json.loads(post_data) |
| for org_key_id, properties in parsed_data.iteritems(): |
| org_key = ndb.Key( |
| data.models.ndb_org_model._get_kind(), org_key_id) |
| new_status = _STATUS_ID_TO_ENUM_MAP.get(properties.get('status')) |
| if not new_status: |
| raise exception.BadRequest( |
| message='Missing or invalid new status in POST data.') |
| else: |
| organization = org_key.get() |
| org_logic.setStatus( |
| organization, data.program, data.site, |
| data.program.getProgramMessages(), new_status) |
| return http.HttpResponse() |
| |
| |
| class ApplyOrgAdmissionDecisionHandler(form_handler.FormHandler): |
| """Form handler implementation to start map reduce job that will apply |
| current decisions regarding admission of organizations into the program. |
| """ |
| |
| def handle(self, data, check, mutator): |
| """See form_handler.FormHandler.handle for specification.""" |
| params = { |
| 'program_key': str(data.program.key()), |
| 'entity_kind': 'summerofcode.models.organization.Organization', |
| } |
| mapreduce_control.start_map('ApplyOrgAdmissionDecisions', params) |
| |
| return http.HttpResponseRedirect(self._url) |
| |
| |
| class PublicOrganizationListFactory(object): |
| """Interface that defines a factory to create lists of organizations.""" |
| |
| def create(self, data): |
| """Creates a new list of organizations. |
| |
| Returns: |
| A template with a list of organizations. |
| """ |
| raise NotImplementedError |
| |
| |
| class PublicOrganizationListRowRedirect(melange_lists.RedirectCustomRow): |
| """Class which provides redirects for rows of public organization list.""" |
| |
| def __init__(self, data, url_names): |
| """Initializes a new instance of the row redirect. |
| |
| See lists.RedirectCustomRow.__init__ for specification. |
| |
| Args: |
| data: request_data.RequestData for the current request. |
| url_names: Instance of url_names.UrlNames. |
| """ |
| super(PublicOrganizationListRowRedirect, self).__init__() |
| self.data = data |
| self.url_names = url_names |
| |
| def getLink(self, item): |
| """See lists.RedirectCustomRow.getLink for specification.""" |
| org_key = ndb.Key( |
| self.data.models.ndb_org_model._get_kind(), item['columns']['key']) |
| return links.LINKER.organization(org_key, self.url_names.ORG_HOME) |
| |
| |
| class PublicOrganizationListPage(base.RequestHandler): |
| """View to list all participating organizations in the program.""" |
| |
| # TODO(daniel): the page should be accessible after orgs are announced |
| access_checker = access.ALL_ALLOWED_ACCESS_CHECKER |
| |
| def __init__(self, initializer, linker, renderer, error_handler, |
| url_pattern_constructor, url_names, template_path, list_factory): |
| """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. |
| list_factory: Implementation of PublicOrganizationListFactory interface. |
| """ |
| super(PublicOrganizationListPage, self).__init__( |
| initializer, linker, renderer, error_handler) |
| self.url_pattern_constructor = url_pattern_constructor |
| self.url_names = url_names |
| self.template_path = template_path |
| self.list_factory = list_factory |
| |
| def templatePath(self): |
| """See base.GSoCRequestHandler.templatePath for specification.""" |
| return self.template_path |
| |
| def djangoURLPatterns(self): |
| """See base.GSoCRequestHandler.djangoURLPatterns for specification.""" |
| return [ |
| self.url_pattern_constructor.construct( |
| r'org/list/public/%s$' % url_patterns.PROGRAM, self, |
| name=self.url_names.ORG_PUBLIC_LIST)] |
| |
| |
| def jsonContext(self, data, check, mutator): |
| """See base.GSoCRequestHandler.jsonContext for specification.""" |
| query = data.models.ndb_org_model.query( |
| org_model.Organization.program == |
| ndb.Key.from_old_key(data.program.key()), |
| org_model.Organization.status == org_model.Status.ACCEPTED) |
| |
| response = melange_lists.JqgridResponse( |
| melange_lists.ORGANIZATION_LIST_ID, |
| row=PublicOrganizationListRowRedirect(data, self.url_names)) |
| |
| start = html.escape(data.GET.get('start', '')) |
| |
| return response.getData(query, start=start) |
| |
| def context(self, data, check, mutator): |
| """See base.GSoCRequestHandler.context for specification.""" |
| return { |
| 'page_name': "Accepted organizations for %s" % data.program.name, |
| 'accepted_orgs_list': self.list_factory.create(data), |
| } |
| |
| |
| # TODO(nathaniel): remove suppression when |
| # https://bitbucket.org/logilab/pylint.org/issue/6/false-positive-no |
| # is fixed. |
| @ndb.transactional(xg=True) # pylint: disable=no-value-for-parameter |
| def createOrganizationTxn( |
| data, org_id, program_key, org_properties, profile, admin_keys, models, |
| tos_key=None): |
| """Creates a new organization profile based on the specified properties. |
| |
| This function simply calls organization logic's function to do actual job |
| but ensures that the entire operation is executed within a transaction. |
| |
| Args: |
| data: request_data.RequestData for the current request. |
| org_id: Identifier of the new organization. Must be unique on |
| 'per program' basis. |
| program_key: Program key. |
| org_properties: A dict mapping organization properties to their values. |
| profile: profile_model.Profile entity of the administrator who creates the |
| organization profile. |
| admin_keys: List of profile keys of organization administrators for |
| this organization. |
| models: instance of types.Models that represent appropriate models. |
| |
| Returns: |
| RichBool whose value is set to True if organization has been successfully |
| created. In that case, extra part points to the newly created organization |
| entity. Otherwise, RichBool whose value is set to False and extra part is |
| a string that represents the reason why the action could not be completed. |
| """ |
| result = org_logic.createOrganization( |
| org_id, program_key, org_properties, models) |
| if not result: |
| raise exception.BadRequest(message=result.extra) |
| |
| for admin_key in admin_keys: |
| connection_view.createConnectionTxn( |
| data, admin_key, data.program, result.extra, |
| conversation_updater.ConversationUpdater(), |
| org_role=connection_model.ORG_ADMIN_ROLE, |
| user_role=connection_model.ROLE, seen_by_org=True, seen_by_user=True) |
| |
| if tos_key: |
| signature_logic.createSignature(ndb.Key.from_old_key(tos_key), profile) |
| |
| return result |
| |
| |
| @ndb.transactional(xg=True) |
| def updateOrganizationTxn( |
| org_key, org_properties, org_messages_properties=None): |
| """Updates the specified organization based on the specified properties. |
| |
| This function simply calls organization logic's function to do actual job |
| but ensures that the entire operation is executed within a transaction. |
| |
| Args: |
| org: Organization entity. |
| org_properties: A dict containing properties to be updated. |
| org_messages_properties: A optional dict containing properties of the |
| corresponding organization messages to be updated. |
| """ |
| org = org_key.get() |
| org_logic.updateOrganization( |
| org, org_properties=org_properties, |
| org_messages_properties=org_messages_properties) |