| # 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 containing the profile related views for Code In.""" |
| |
| import collections |
| import datetime |
| import re |
| |
| from django.utils import translation |
| |
| from codein.request import error |
| from codein.request import render |
| from codein.views.helper import urls as ci_urls |
| |
| from melange.request import access |
| from melange.request import exception |
| from melange.request import links |
| from melange.templates import readonly |
| from melange.views import profile as profile_view |
| |
| from soc.logic import validate |
| from soc.modules.gci.views import base |
| from soc.modules.gci.views import forms as gci_forms |
| from soc.modules.gci.views.helper import url_patterns as ci_url_patterns |
| |
| |
| # Code-In programs do not collect these fields: |
| _SKIP_FIELDS = ['blog', 'web_page', 'photo_url', 'major', 'degree', 'grade', |
| 'expected_graduation'] |
| |
| TEE_SIZE_HELP_TEXT = translation.gettext( |
| 'Size of a T-Shirt that may be sent to you upon program completion. ' |
| '(<a href="https://docs.google.com/document/d/' |
| '1OSRFXJm9ENs8TUpHY9sggdkzC3XiIeSGfNDiMUM7znQ/pub">Sizing Chart</a>)') |
| |
| # CI uses "Contest Rules" instead of "Terms of Service" |
| STUDENT_TERMS_OF_SERVICE_LABEL = translation.ugettext( |
| 'Read the Contest Rules and acknowledge by selecting the checkbox ' |
| 'at the end.') |
| |
| STUDENT_TERMS_OF_SERVICE_READONLY_LABEL = translation.ugettext( |
| 'You have already signed the Contest Rules.') |
| |
| STUDENT_TERMS_OF_SERVICE_GROUP = translation.ugettext('8. Contest Rules') |
| |
| ORG_MEMBER_TERMS_OF_SERVICE_LABEL = translation.ugettext( |
| 'Read the Mentor Agreement and acknowledge by selecting the checkbox ' |
| 'at the end.') |
| |
| ORG_MEMBER_TERMS_OF_SERVICE_READONLY_LABEL = translation.ugettext( |
| 'You have already signed the Mentor Agreement.') |
| |
| ORG_MEMBER_TERMS_OF_SERVICE_GROUP = translation.ugettext('8. Mentor Agreement') |
| |
| SCHOOL_NAME_HELP_TEXT = translation.gettext( |
| 'Enter the English name of your school. Write out the whole school name. ' |
| 'Please do not abbreviate.') |
| |
| |
| def getBirthDateFromCookie(data): |
| """Returns birth date from the data which is stored in a cookie. |
| |
| Args: |
| data: request_data.RequestData for the current request. |
| |
| Raises: |
| expception.Redirect: if no cookie containing birth date is present. |
| """ |
| |
| # make sure we have a date shaped value |
| birth_date = data.request.COOKIES.get('age_check') |
| if (not birth_date or not re.match(r'\d{4}-\d{1,2}-\d{1,2}', birth_date)): |
| raise exception.Redirect( |
| links.ABSOLUTE_LINKER.program( |
| data.program, 'gci_age_check', secure=True)) |
| |
| # if we can't parse the birthdate, prompt for it again |
| try: |
| birth_date_datetime = datetime.datetime.strptime(birth_date, '%Y-%m-%d') |
| except ValueError: |
| raise exception.Redirect( |
| links.ABSOLUTE_LINKER.program( |
| data.program, 'gci_age_check', secure=True)) |
| |
| return birth_date_datetime.date() |
| |
| |
| def getTermsOfServiceLabel(is_student=None, is_readonly=None): |
| """Returns the appropriate Terms of Service label. |
| |
| Args: |
| is_student - bool, is it a student or not? |
| is_readonly - bool, have they already agreed? |
| |
| Returns: |
| a string representing the terms of service |
| """ |
| |
| if is_student: |
| if is_readonly: |
| return STUDENT_TERMS_OF_SERVICE_READONLY_LABEL |
| else: |
| return STUDENT_TERMS_OF_SERVICE_LABEL |
| else: |
| if is_readonly: |
| return ORG_MEMBER_TERMS_OF_SERVICE_READONLY_LABEL |
| else: |
| return ORG_MEMBER_TERMS_OF_SERVICE_LABEL |
| |
| |
| def getTermsOfServiceGroup(is_student=None): |
| """Returns the appropriate Terms of Service group name. |
| |
| Args: |
| is_student - bool, is it a student or not? |
| is_readonly - bool, have they already agreed? |
| |
| Returns: |
| a string representing the terms of service |
| """ |
| |
| if is_student: |
| return STUDENT_TERMS_OF_SERVICE_GROUP |
| else: |
| return ORG_MEMBER_TERMS_OF_SERVICE_GROUP |
| |
| |
| class _CreateStudentProfileForm(profile_view._UserProfileForm): |
| """Form to set profile properties by a user that makes sure that birth date |
| comes from preinitialized cookie data. |
| """ |
| |
| def clean_birth_date(self): |
| """Cleans birth_date field. |
| |
| The data submitted in the POST data is discarded. Instead the value is |
| taken from the pre-existing cookie. |
| |
| Returns: |
| Cleaned value of birth_date field. Specifically, datetime.date object |
| that represents the submitted birth date. |
| |
| Raises: |
| django_forms.ValidationError if the submitted value is not valid. |
| """ |
| birth_date = getBirthDateFromCookie(self.request_data) |
| return profile_view.cleanBirthDate( |
| birth_date, self.request_data.program, self.has_student_data) |
| |
| |
| class CIRegisterProfileFormFactory(profile_view.ProfileFormFactory): |
| """Implementation of profile_view.ProfileFormFactory to be used to register |
| profiles for Code In programs. |
| """ |
| |
| def create(self, request_data, terms_of_service, include_user_fields=None, |
| include_student_fields=None, data=None, **kwargs): |
| """See profile_view.ProfileFormFactory.create for specification.""" |
| if include_student_fields: |
| |
| # when a new student profile is registered, the birth date always |
| # comes from the cookie |
| if data: |
| data['birth_date'] = getBirthDateFromCookie(request_data) |
| initial = None |
| else: |
| initial = {'birth_date': getBirthDateFromCookie(request_data)} |
| |
| form = _CreateStudentProfileForm( |
| gci_forms.GCIBoundField, request_data, |
| terms_of_service=terms_of_service, |
| has_student_data=True, |
| template_path=gci_forms.TEMPLATE_PATH, skip_fields=_SKIP_FIELDS, |
| initial=initial, data=data, **kwargs) |
| form.fields['birth_date'].widget.attrs['readonly'] = True |
| else: |
| # birth date is not collected for organization members |
| skip_fields = _SKIP_FIELDS + ['birth_date'] |
| form = profile_view._UserProfileForm( |
| gci_forms.GCIBoundField, request_data, |
| terms_of_service=terms_of_service, |
| has_student_data=False, |
| template_path=gci_forms.TEMPLATE_PATH, skip_fields=skip_fields, |
| data=data, **kwargs) |
| |
| if not include_user_fields: |
| del form.fields['user_id'] |
| |
| # set CI specific values |
| form.fields['tee_size'].help_text = TEE_SIZE_HELP_TEXT |
| if 'terms_of_service' in form.fields: |
| form.fields['terms_of_service'].group = getTermsOfServiceGroup( |
| include_student_fields) |
| form.fields['terms_of_service'].label = getTermsOfServiceLabel( |
| include_student_fields, False) |
| if include_student_fields: |
| form.fields['school_name'].help_text = SCHOOL_NAME_HELP_TEXT |
| |
| return form |
| |
| CI_REGISTER_PROFILE_FORM_FACTORY = CIRegisterProfileFormFactory() |
| |
| |
| class CIEditProfileFormFactory(profile_view.ProfileFormFactory): |
| """Implementation of profile_view.ProfileFormFactory to be used to edit |
| profiles for Code In programs. |
| """ |
| |
| def create(self, request_data, terms_of_service, include_user_fields=None, |
| include_student_fields=None, data=None, **kwargs): |
| """See profile_view.ProfileFormFactory.create for specification.""" |
| skip_fields = _SKIP_FIELDS |
| |
| if include_student_fields: |
| # birth date cannot be updated for students, so override the incoming |
| # value with the one that exists in the datastore |
| if data: |
| data['birth_date'] = request_data.ndb_profile.birth_date |
| else: |
| # birth date is not collected for organization members |
| skip_fields += ['birth_date'] |
| |
| form = profile_view._UserProfileForm( |
| gci_forms.GCIBoundField, request_data, |
| terms_of_service=terms_of_service, |
| has_student_data=include_student_fields, |
| template_path=gci_forms.TEMPLATE_PATH, skip_fields=skip_fields, |
| data=data, **kwargs) |
| |
| if include_student_fields: |
| form.fields['birth_date'].widget.attrs['readonly'] = True |
| |
| if not include_user_fields: |
| del form.fields['user_id'] |
| |
| # set CI specific values |
| # TODO(robert): Cleanup duplication between here and the Create |
| # page code above. |
| form.fields['tee_size'].help_text = TEE_SIZE_HELP_TEXT |
| if 'terms_of_service' in form.fields: |
| form.fields['terms_of_service'].group = getTermsOfServiceGroup( |
| include_user_fields) |
| form.fields['terms_of_service'].label = getTermsOfServiceLabel( |
| include_student_fields, False) |
| if 'readonly_tos' in form.fields: |
| form.fields['readonly_tos'].group = getTermsOfServiceGroup( |
| include_user_fields) |
| form.fields['readonly_tos'].label = getTermsOfServiceLabel( |
| include_student_fields, True) |
| # TODO(robert): This can't reach into the disabled checkbox inside |
| # the readonly TOS widget, so that still says TOS. |
| if include_student_fields: |
| form.fields['school_name'].help_text = SCHOOL_NAME_HELP_TEXT |
| |
| return form |
| |
| CI_EDIT_PROFILE_FORM_FACTORY = CIEditProfileFormFactory() |
| |
| |
| class CIProfileReadonlyFactory(profile_view.ProfileReadonlyFactory): |
| """Implementation of profile_view.ProfileReadonlyFactory to be used for |
| Code In programs. |
| """ |
| |
| def create(self, data, profile): |
| """See profile_view.ProfileReadonlyFactory.create for specification.""" |
| groups = [] |
| |
| fields = collections.OrderedDict() |
| fields[profile_view.USER_ID_LABEL] = profile.key.parent().id() |
| fields[profile_view.PUBLIC_NAME_LABEL] = profile.public_name |
| groups.append( |
| readonly.Group( |
| data, 'codein/readonly/_group.html', |
| profile_view._BASIC_INFORMATION_GROUP, fields)) |
| |
| fields = collections.OrderedDict() |
| fields[profile_view.FIRST_NAME_LABEL] = profile.first_name |
| fields[profile_view.LAST_NAME_LABEL] = profile.last_name |
| fields[profile_view.EMAIL_LABEL] = profile.contact.email |
| fields[profile_view.PHONE_LABEL] = profile.contact.phone |
| groups.append( |
| readonly.Group( |
| data, 'codein/readonly/_group.html', |
| profile_view._CONTACT_GROUP, fields)) |
| |
| fields = collections.OrderedDict() |
| fields[profile_view.RESIDENTIAL_STREET_LABEL] = ( |
| profile_view.streetValue(profile.residential_address)) |
| fields[profile_view.RESIDENTIAL_CITY_LABEL] = ( |
| profile.residential_address.city) |
| fields[profile_view.RESIDENTIAL_PROVINCE_LABEL] = ( |
| profile.residential_address.province) |
| fields[profile_view.RESIDENTIAL_POSTAL_CODE_LABEL] = ( |
| profile.residential_address.postal_code) |
| fields[profile_view.RESIDENTIAL_COUNTRY_LABEL] = ( |
| profile.residential_address.country) |
| groups.append( |
| readonly.Group( |
| data, 'codein/readonly/_group.html', |
| profile_view._RESIDENTIAL_ADDRESS_GROUP, fields)) |
| |
| if profile.shipping_address: |
| fields = collections.OrderedDict() |
| fields[profile_view.SHIPPING_STREET_LABEL] = ( |
| profile_view.streetValue(profile.shipping_address)) |
| fields[profile_view.SHIPPING_CITY_LABEL] = profile.shipping_address.city |
| fields[profile_view.SHIPPING_PROVINCE_LABEL] = ( |
| profile.shipping_address.province) |
| fields[profile_view.SHIPPING_POSTAL_CODE_LABEL] = ( |
| profile.shipping_address.postal_code) |
| fields[profile_view.SHIPPING_COUNTRY_LABEL] = ( |
| profile.shipping_address.country) |
| groups.append( |
| readonly.Group( |
| data, 'codein/readonly/_group.html', |
| profile_view._SHIPPING_ADDRESS_GROUP, fields)) |
| |
| fields = collections.OrderedDict() |
| fields[profile_view.BIRTH_DATE_LABEL] = profile.birth_date |
| fields[profile_view.TEE_STYLE_LABEL] = profile.tee_style |
| fields[profile_view.TEE_SIZE_LABEL] = profile.tee_size |
| fields[profile_view.GENDER_LABEL] = ( |
| profile_view._GENDER_ENUM_TO_VERBOSE_MAP[profile.gender]) |
| fields[profile_view.PROGRAM_KNOWLEDGE_LABEL] = profile.program_knowledge |
| groups.append( |
| readonly.Group( |
| data, 'codein/readonly/_group.html', |
| profile_view._OTHER_INFORMATION_GROUP, fields)) |
| |
| return readonly.Readonly( |
| data, 'codein/readonly/_readonly_template.html', groups) |
| |
| CI_PROFILE_READONLY_FACTORY = CIProfileReadonlyFactory() |
| |
| |
| class EligibleBirthDateInCookie(access.AccessChecker): |
| """Access checker that ensures that a birth date, which is eligible for |
| a student participant, is present in the cookie. |
| """ |
| |
| def checkAccess(self, data, check): |
| """See access.AccessChecker.checkAccess for specification.""" |
| birth_date = getBirthDateFromCookie(data) |
| if not validate.isAgeSufficientForStudent(birth_date, data.program): |
| raise exception.Forbidden(message=profile_view.INSUFFICIENT_AGE) |
| |
| ELIGIBLE_BIRTH_DATE_IN_COOKIE = EligibleBirthDateInCookie() |
| |
| |
| PROFILE_REGISTER_AS_STUDENT_ACCESS_CHECKER = access.ConjuctionAccessChecker([ |
| access.STUDENT_SIGNUP_ACTIVE_ACCESS_CHECKER, |
| ELIGIBLE_BIRTH_DATE_IN_COOKIE, |
| access.HAS_NO_PROFILE_ACCESS_CHECKER, |
| ]) |
| |
| PROFILE_REGISTER_AS_ORG_MEMBER_PAGE = ( |
| profile_view.ProfileRegisterAsOrgMemberPage( |
| base._GCI_INITIALIZER, links.LINKER, render.CI_RENDERER, |
| error.CI_ERROR_HANDLER, ci_url_patterns.CI_URL_PATTERN_CONSTRUCTOR, |
| ci_urls.UrlNames, 'codein/profile/profile_edit.html', |
| CI_REGISTER_PROFILE_FORM_FACTORY)) |
| |
| PROFILE_REGISTER_AS_STUDENT_PAGE = profile_view.ProfileRegisterAsStudentPage( |
| base._GCI_INITIALIZER, links.LINKER, render.CI_RENDERER, |
| error.CI_ERROR_HANDLER, ci_url_patterns.CI_URL_PATTERN_CONSTRUCTOR, |
| ci_urls.UrlNames, 'codein/profile/profile_edit.html', |
| PROFILE_REGISTER_AS_STUDENT_ACCESS_CHECKER, |
| CI_REGISTER_PROFILE_FORM_FACTORY) |
| |
| PROFILE_DELETE_PAGE = profile_view.ProfileDeletePage( |
| base._GCI_INITIALIZER, links.LINKER, render.CI_RENDERER, |
| error.CI_ERROR_HANDLER, ci_url_patterns.CI_URL_PATTERN_CONSTRUCTOR, |
| ci_urls.UrlNames, 'codein/profile/profile_delete.html') |
| |
| PROFILE_EDIT_PAGE = profile_view.ProfileEditPage( |
| base._GCI_INITIALIZER, links.LINKER, render.CI_RENDERER, |
| error.CI_ERROR_HANDLER, ci_url_patterns.CI_URL_PATTERN_CONSTRUCTOR, |
| ci_urls.UrlNames, 'codein/profile/profile_edit.html', |
| CI_EDIT_PROFILE_FORM_FACTORY) |
| |
| PROFILE_SHOW_PAGE = profile_view.ProfileShowPage( |
| base._GCI_INITIALIZER, links.LINKER, render.CI_RENDERER, |
| error.CI_ERROR_HANDLER, ci_url_patterns.CI_URL_PATTERN_CONSTRUCTOR, |
| ci_urls.UrlNames, 'codein/profile/profile_show.html', |
| CI_PROFILE_READONLY_FACTORY) |
| |
| PROFILE_ADMIN_PAGE = profile_view.ProfileAdminPage( |
| base._GCI_INITIALIZER, links.LINKER, render.CI_RENDERER, |
| error.CI_ERROR_HANDLER, ci_url_patterns.CI_URL_PATTERN_CONSTRUCTOR, |
| ci_urls.UrlNames, 'codein/profile/profile_show.html', |
| CI_PROFILE_READONLY_FACTORY) |