| # Copyright 2014 the Melange authors. |
| # |
| # Licensed under the Apache License, Version 2.0 (the "License"); |
| # you may not use this file except in compliance with the License. |
| # You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| # See the License for the specific language governing permissions and |
| # limitations under the License. |
| |
| """Module with generalized profile related views. |
| |
| The classes defined here are supposed to be instantiated with dependencies that |
| are specific to actual programs. |
| """ |
| |
| from google.appengine.ext import ndb |
| |
| from django import http |
| from django import forms as django_forms |
| from django.utils import html |
| from django.utils import translation |
| |
| from melange import types |
| from melange.appengine import db as melange_db |
| from melange.logic import address as address_logic |
| from melange.logic import contact as contact_logic |
| from melange.logic import education as education_logic |
| from melange.logic import profile as profile_logic |
| from melange.logic import signature as signature_logic |
| from melange.logic import user as user_logic |
| from melange.models import education as education_model |
| from melange.models import profile as profile_model |
| from melange.request import access |
| from melange.request import exception |
| from melange.request import links |
| from melange.templates import profile_form_below_header |
| from melange.utils import countries |
| from melange.utils import rich_bool |
| from melange.views import student_forms as student_forms_view |
| from melange.views.helper import form_handler |
| |
| from soc.logic import cleaning |
| from soc.logic import program as program_logic |
| from soc.logic import validate |
| from soc.models import universities |
| from soc.views import base |
| from soc.views import forms as soc_forms |
| from soc.views import toggle_button |
| from soc.views.helper import url_patterns |
| |
| # TODO(daniel): tabs make sense only for Summer Of Code |
| from summerofcode.templates import tabs |
| # TODO(daniel): move these messages out of summerofcode package |
| from summerofcode.templates import top_message |
| |
| |
| _ALPHANUMERIC_CHARACTERS_ONLY = unicode( |
| 'Use alphanumeric characters (A-z, 0-9) and whitespaces only.') |
| |
| _INVALID_EDUCATION_DATA = unicode( |
| 'Invalid education data submitted in the form.') |
| |
| PROFILE_ORG_MEMBER_CREATE_PAGE_NAME = translation.ugettext( |
| 'Create mentor profile') |
| |
| PROFILE_STUDENT_CREATE_PAGE_NAME = translation.ugettext( |
| 'Create student profile') |
| |
| PROFILE_EDIT_PAGE_NAME = translation.ugettext( |
| 'Edit profile') |
| |
| PROFILE_DELETE_PAGE_NAME = translation.ugettext('Delete profile') |
| |
| # names of structures to group related fields together |
| _BASIC_INFORMATION_GROUP = translation.ugettext('1. Basic information') |
| _CONTACT_GROUP = translation.ugettext('2. Contact information') |
| _RESIDENTIAL_ADDRESS_GROUP = translation.ugettext('3. Residential address') |
| _SHIPPING_ADDRESS_GROUP = translation.ugettext('4. Shipping address') |
| _OTHER_INFORMATION_GROUP = translation.ugettext('5. Other information') |
| _EDUCATION_GROUP = translation.ugettext('6. Education') |
| _FORMS_GROUP = translation.ugettext('7. Forms') |
| _TERMS_OF_SERVICE_GROUP = translation.ugettext('8. Terms and conditions') |
| |
| USER_ID_HELP_TEXT = translation.ugettext( |
| 'Used as part of various URL links throughout the site. ' |
| 'ASCII lower case letters, digits, and underscores only.') |
| |
| PUBLIC_NAME_HELP_TEXT = translation.ugettext( |
| 'Name that will be displayed publicly on the site.') |
| |
| WEB_PAGE_HELP_TEXT = translation.ugettext( |
| 'URL to your personal web page, if you have one.') |
| |
| BLOG_HELP_TEXT = translation.ugettext( |
| 'URL to a page with your personal blog, if you have one.') |
| |
| PHOTO_URL_HELP_TEXT = translation.ugettext( |
| 'URL to 64x64 pixel thumbnail image.') |
| |
| FIRST_NAME_HELP_TEXT = translation.ugettext( |
| 'First name of the participant. ' + _ALPHANUMERIC_CHARACTERS_ONLY) |
| |
| LAST_NAME_HELP_TEXT = translation.ugettext( |
| 'Last name of the participant. ' + _ALPHANUMERIC_CHARACTERS_ONLY) |
| |
| EMAIL_HELP_TEXT = translation.ugettext( |
| 'Email address of the participant. All program related emails ' |
| 'will be sent to this address. This information is kept ' |
| 'private and visible only to program administrators.') |
| |
| PHONE_HELP_TEXT = translation.ugettext( |
| 'Phone number of the participant. Use digits only and remember ' |
| 'to include the country code. This information is kept ' |
| 'private and used only for shipping purposes by program administrators.') |
| |
| RESIDENTIAL_STREET_HELP_TEXT = translation.ugettext( |
| 'Street number and name information plus optional suite/apartment number. ' |
| + _ALPHANUMERIC_CHARACTERS_ONLY) |
| |
| RESIDENTIAL_CITY_HELP_TEXT = translation.ugettext( |
| 'City information. ' + _ALPHANUMERIC_CHARACTERS_ONLY) |
| |
| RESIDENTIAL_PROVINCE_HELP_TEXT = translation.ugettext( |
| 'State or province information. In case you live in the United States, ' |
| 'type the two letter state abbreviation. ' + _ALPHANUMERIC_CHARACTERS_ONLY) |
| |
| RESIDENTIAL_COUNTRY_HELP_TEXT = translation.ugettext('Country information.') |
| |
| RESIDENTIAL_POSTAL_CODE_HELP_TEXT = translation.ugettext( |
| 'ZIP/Postal code information. ' + _ALPHANUMERIC_CHARACTERS_ONLY) |
| |
| IS_SHIPPING_ADDRESS_DIFFERENT_HELP_TEXT = translation.ugettext( |
| 'Check this box if your shipping address is different than ' |
| 'the residential address provided above.') |
| |
| SHIPPING_NAME_HELP_TEXT = translation.ugettext( |
| 'Fill in the name of the person who should be receiving your packages. ' |
| + _ALPHANUMERIC_CHARACTERS_ONLY) |
| |
| SHIPPING_STREET_HELP_TEXT = translation.ugettext( |
| 'Street number and name information plus optional suite/apartment number. ' |
| + _ALPHANUMERIC_CHARACTERS_ONLY) |
| |
| SHIPPING_CITY_HELP_TEXT = translation.ugettext( |
| 'City information. ' + _ALPHANUMERIC_CHARACTERS_ONLY) |
| |
| SHIPPING_PROVINCE_HELP_TEXT = translation.ugettext( |
| 'State or province information. In case packages should be sent to ' |
| 'the United States, type the two letter state abbreviation. ' |
| + _ALPHANUMERIC_CHARACTERS_ONLY) |
| |
| SHIPPING_COUNTRY_HELP_TEXT = translation.ugettext('Country information.') |
| |
| SHIPPING_POSTAL_CODE_HELP_TEXT = translation.ugettext( |
| 'ZIP/Postal code information. ' + _ALPHANUMERIC_CHARACTERS_ONLY) |
| |
| BIRTH_DATE_HELP_TEXT = translation.ugettext( |
| 'Birth date of the participant. Use YYYY-MM-DD format. This ' |
| 'information is kept private and visible only to program administrators ' |
| 'in order to determine program eligibility.') |
| |
| TEE_STYLE_HELP_TEXT = translation.ugettext( |
| 'Style of a T-Shirt that may be sent to you upon program completion.') |
| |
| TEE_SIZE_HELP_TEXT = translation.ugettext( |
| 'Size of a T-Shirt that may be sent to you upon program completion.') |
| |
| GENDER_HELP_TEXT = translation.ugettext( |
| 'Gender information of the participant. This information ' |
| 'is kept private and visible only to program administrators for ' |
| 'statistical purposes.') |
| |
| PROGRAM_KNOWLEDGE_HELP_TEXT = translation.ugettext( |
| 'Be as specific as possible, e.g. blog post (include URL ' |
| 'if possible), mailing list (please include list address), information ' |
| 'session (please include location and speakers if you can), etc.') |
| |
| SCHOOL_COUNTRY_HELP_TEXT = translation.ugettext( |
| 'Country in which the school to which you are enrolled is located.') |
| |
| SCHOOL_NAME_HELP_TEXT = translation.ugettext( |
| 'Start typing your school name and use the autocomplete dropdown. ' |
| 'If your school, college or university is not listed, ' |
| 'enter the full English name. ' |
| 'Use the complete formal name of your school, e.g. ' |
| '"University of California at Berkeley", instead of "Cal" or "UCB".') |
| |
| SCHOOL_WEB_PAGE_HELP_TEXT = translation.ugettext( |
| 'URL to the home page of your school.') |
| |
| MAJOR_HELP_TEXT = translation.ugettext('Your major at the university.') |
| |
| DEGREE_HELP_TEXT = translation.ugettext( |
| 'Select degree that is the one you are working towards today.') |
| |
| GRADE_HELP_TEXT = translation.ugettext( |
| 'Enter your grade in the school as a number, e.g. 8 if you are ' |
| 'in the 8th grade.') |
| |
| EXPECTED_GRADUATION_HELP_TEXT = translation.ugettext( |
| 'Provide the year in which you are expected to graduate ' |
| 'from your current program.') |
| |
| USER_ID_LABEL = translation.ugettext('Username') |
| |
| PUBLIC_NAME_LABEL = translation.ugettext('Public name') |
| |
| WEB_PAGE_LABEL = translation.ugettext('Home page URL') |
| |
| BLOG_LABEL = translation.ugettext('Blog URL') |
| |
| PHOTO_URL_LABEL = translation.ugettext('Photo URL') |
| |
| FIRST_NAME_LABEL = translation.ugettext('First name') |
| |
| LAST_NAME_LABEL = translation.ugettext('Last name') |
| |
| EMAIL_LABEL = translation.ugettext('Email') |
| |
| PHONE_LABEL = translation.ugettext('Phone number') |
| |
| RESIDENTIAL_STREET_LABEL = translation.ugettext('Street address') |
| |
| RESIDENTIAL_CITY_LABEL = translation.ugettext('City') |
| |
| RESIDENTIAL_PROVINCE_LABEL = translation.ugettext('State/Province') |
| |
| RESIDENTIAL_COUNTRY_LABEL = translation.ugettext('Country/Territory') |
| |
| COUNTRY_DEFAULT = translation.ugettext('Please select a country:') |
| |
| RESIDENTIAL_POSTAL_CODE_LABEL = translation.ugettext('ZIP/Postal code') |
| |
| IS_SHIPPING_ADDRESS_DIFFERENT_LABEL = translation.ugettext( |
| 'Shipping address is different than residential address') |
| |
| SHIPPING_NAME_LABEL = translation.ugettext('Full recipient name') |
| |
| SHIPPING_STREET_LABEL = translation.ugettext('Street address') |
| |
| SHIPPING_CITY_LABEL = translation.ugettext('City') |
| |
| SHIPPING_PROVINCE_LABEL = translation.ugettext('State/Province') |
| |
| SHIPPING_COUNTRY_LABEL = translation.ugettext('Country/Territory') |
| |
| SHIPPING_POSTAL_CODE_LABEL = translation.ugettext('ZIP/Postal code') |
| |
| BIRTH_DATE_LABEL = translation.ugettext('Birth date') |
| |
| TEE_STYLE_LABEL = translation.ugettext('T-Shirt style') |
| |
| TEE_SIZE_LABEL = translation.ugettext('T-Shirt size') |
| |
| GENDER_LABEL = translation.ugettext('Gender') |
| |
| PROGRAM_KNOWLEDGE_LABEL = translation.ugettext( |
| 'How did you hear about the program?') |
| |
| TERMS_OF_SERVICE_LABEL = translation.ugettext( |
| 'Read the terms and conditions and acknowledge by selecting the checkbox ' |
| 'at the end.') |
| |
| TERMS_OF_SERVICE_READONLY_LABEL = translation.ugettext( |
| 'You have already agreed to the terms and conditions.') |
| |
| TERMS_OF_SERVICE_AGREE_TEXT = translation.ugettext( |
| 'I have read, understand, and agree to these terms and conditions.') |
| |
| SCHOOL_COUNTRY_LABEL = translation.ugettext('School country') |
| |
| SCHOOL_NAME_LABEL = translation.ugettext('School name') |
| |
| SCHOOL_WEB_PAGE_LABEL = translation.ugettext('School web page') |
| |
| MAJOR_LABEL = translation.ugettext('Major') |
| |
| DEGREE_LABEL = translation.ugettext('Degree') |
| |
| GRADE_LABEL = translation.ugettext('Grade') |
| |
| EXPECTED_GRADUATION_LABEL = translation.ugettext('Expected graduation') |
| |
| ENROLLMENT_FORM_LABEL = translation.ugettext('Enrollment form') |
| |
| TAX_FORM_LABEL = translation.ugettext('Tax form') |
| |
| _NO_FORM_SUBMITTED = translation.ugettext('No form submitted.') |
| |
| _NO_FORM_REQUIRED = translation.ugettext('No form required at this time.') |
| |
| _FORM_SUBMITTED_NO_UPLOAD_FORM_OPTION = translation.ugettext( |
| '%s <a href="%s">Download</a>') |
| |
| _FORM_SUBMITTED_UPLOAD_FORM_OPTION = translation.ugettext( |
| '%s <a href="%s">Re-upload</a> <a href="%s">Download</a>') |
| |
| _NO_FORM_SUBMITTED_UPLOAD_FORM_OPTION = translation.ugettext( |
| 'No form submitted. <a href="%s">Upload</a>') |
| |
| _STREET_VALUE = '%s<br>%s' |
| |
| TERMS_OF_SERVICE_NOT_ACCEPTED = translation.ugettext( |
| 'You cannot register without agreeing to the terms and conditions') |
| |
| INSUFFICIENT_AGE = translation.ugettext( |
| 'Your age does not allow you to participate in the program.') |
| |
| AGE_ALREADY_SET = translation.ugettext( |
| 'Age is already set for your profile.') |
| |
| _TEE_DEFAULT_VERBOSE = translation.ugettext('Please Select') |
| TEE_DEFAULT_CHOICE = (('', _TEE_DEFAULT_VERBOSE),) |
| |
| _TEE_STYLE_FEMALE_ID = 'female' |
| _TEE_STYLE_MALE_ID = 'male' |
| |
| TEE_STYLE_CHOICES = ( |
| (_TEE_STYLE_FEMALE_ID, 'Female'), |
| (_TEE_STYLE_MALE_ID, 'Male')) |
| |
| _TEE_SIZE_XS_ID = 'xs' |
| _TEE_SIZE_S_ID = 's' |
| _TEE_SIZE_M_ID = 'm' |
| _TEE_SIZE_L_ID = 'l' |
| _TEE_SIZE_XL_ID = 'xl' |
| _TEE_SIZE_XXL_ID = 'xxl' |
| _TEE_SIZE_XXXL_ID = 'xxxl' |
| |
| TEE_SIZE_CHOICES = ( |
| (_TEE_SIZE_XS_ID, 'XS'), |
| (_TEE_SIZE_S_ID, 'S'), |
| (_TEE_SIZE_M_ID, 'M'), |
| (_TEE_SIZE_L_ID, 'L'), |
| (_TEE_SIZE_XL_ID, 'XL'), |
| (_TEE_SIZE_XXL_ID, 'XXL'), |
| (_TEE_SIZE_XXXL_ID, 'XXXL')) |
| |
| _GENDER_FEMALE_ID = 'female' |
| _GENDER_MALE_ID = 'male' |
| _GENDER_OTHER_ID = 'other' |
| _GENDER_NOT_DISCLOSED_ID = 'not_answered' |
| |
| _GENDER_DEFAULT_VERBOSE = translation.ugettext('Please select a gender') |
| _GENDER_FEMALE_VERBOSE = translation.ugettext('Female') |
| _GENDER_MALE_VERBOSE = translation.ugettext('Male') |
| _GENDER_OTHER_VERBOSE = translation.ugettext('Other') |
| _GENDER_NOT_DISCLOSED_VERBOSE = translation.ugettext( |
| 'I would prefer not to answer') |
| |
| GENDER_DEFAULT_CHOICE = (('', _GENDER_DEFAULT_VERBOSE),) |
| |
| GENDER_CHOICES = ( |
| (_GENDER_FEMALE_ID, _GENDER_FEMALE_VERBOSE), |
| (_GENDER_MALE_ID, _GENDER_MALE_VERBOSE), |
| (_GENDER_OTHER_ID, _GENDER_OTHER_VERBOSE), |
| (_GENDER_NOT_DISCLOSED_ID, _GENDER_NOT_DISCLOSED_VERBOSE)) |
| |
| _DEGREE_UNDERGRADUATE_ID = 'undergraduate' |
| _DEGREE_MASTERS_ID = 'masters' |
| _DEGREE_PHD_ID = 'phd' |
| |
| DEGREE_CHOICES = ( |
| (_DEGREE_UNDERGRADUATE_ID, translation.ugettext('Undergraduate')), |
| (_DEGREE_MASTERS_ID, translation.ugettext('Master\'s')), |
| (_DEGREE_PHD_ID, translation.ugettext('PhD'))) |
| |
| _USER_PROPERTIES_FORM_KEYS = ['user_id'] |
| |
| _PROFILE_PROPERTIES_FORM_KEYS = [ |
| 'public_name', 'photo_url', 'first_name', 'last_name', 'birth_date', |
| 'tee_style', 'tee_size', 'gender', 'terms_of_service', 'program_knowledge'] |
| |
| _CONTACT_PROPERTIES_FORM_KEYS = ['web_page', 'blog', 'email', 'phone'] |
| |
| _RESIDENTIAL_ADDRESS_PROPERTIES_FORM_KEYS = [ |
| 'residential_street', 'residential_street_extra', 'residential_city', |
| 'residential_province', 'residential_country', 'residential_postal_code'] |
| |
| _SHIPPING_ADDRESS_PROPERTIES_FORM_KEYS = [ |
| 'shipping_name', 'shipping_street', 'shipping_street_extra', |
| 'shipping_city', 'shipping_province', 'shipping_country', |
| 'shipping_postal_code'] |
| |
| _STUDENT_DATA_PROPERTIES_FORM_FIELDS = [ |
| 'school_country', 'school_name', 'school_web_page', 'major', 'degree', |
| 'grade', 'expected_graduation'] |
| |
| |
| def streetValue(address): |
| """Returns full street information for the given address. |
| |
| Args: |
| address: address_model.Address entity. |
| |
| Returns: |
| A string containing the street information. |
| """ |
| if not address.street_extra: |
| return address.street |
| else: |
| return html.format_html( |
| _STREET_VALUE % (address.street, address.street_extra)) |
| |
| |
| def cleanUserId(user_id): |
| """Cleans user_id field. |
| |
| Args: |
| user_id: The submitted user ID. |
| |
| Returns: |
| Cleaned value for user_id field. |
| |
| Raises: |
| django_forms.ValidationError if the submitted value is not valid. |
| """ |
| if not user_id: |
| raise django_forms.ValidationError('This field is required.') |
| |
| cleaning.cleanLinkID(user_id) |
| |
| return user_id |
| |
| |
| def _cleanShippingAddressPart( |
| is_shipping_address_different, value, is_required): |
| """Cleans a field that represents a part of the shipping address. |
| |
| Args: |
| is_shipping_address_different: A bool indicating if the shipping address |
| to provide is different than the residential address. |
| value: The actual submitted value for the cleaned field. |
| is_required: Whether a value for the cleaned field is required or not. |
| |
| Returns: |
| Cleaned value for the field. |
| |
| Raises: |
| django_forms.ValidationError if the submitted value is not valid. |
| """ |
| if not is_shipping_address_different and value: |
| raise django_forms.ValidationError( |
| 'This field cannot be specified if the shipping address is the same ' |
| 'as the residential address.') |
| elif is_shipping_address_different and not value and is_required: |
| raise django_forms.ValidationError('This field is required.') |
| else: |
| return cleaning.cleanValidAddressCharacters(value) |
| |
| |
| def cleanTermsOfService(is_accepted, terms_of_service): |
| """Cleans terms_of_service field. |
| |
| Args: |
| is_accepted: A bool determining whether the user has accepted the terms |
| of service of not. |
| terms_of_service: Document entity that contains the terms of service that |
| need to be accepted. |
| |
| Returns: |
| Cleaned value of terms_of_service field. Specifically, it is a key |
| of a document entity that contains the accepted terms of service. |
| |
| Raises: |
| django_forms.ValidationError if the submitted value is not valid. |
| """ |
| if not terms_of_service: |
| return None |
| elif not is_accepted: |
| raise django_forms.ValidationError(TERMS_OF_SERVICE_NOT_ACCEPTED) |
| else: |
| return ndb.Key.from_old_key(terms_of_service.key()) |
| |
| |
| def cleanBirthDate(birth_date, program, is_student): |
| """Cleans birth_date field. |
| |
| Args: |
| birth_date: datetime.date that represents the examined birth date. |
| program: Program entity for which the specified birth date is checked. |
| is_student: Whether age is checked for a student or not. |
| |
| Returns: |
| Cleaned value of birth_date field. Specifically, datetime.date object |
| that represents the birth date. |
| |
| Raises: |
| django_forms.ValidationError if the submitted value is not valid, i.e. |
| the birth date does not permit to register or participate in the program. |
| """ |
| if is_student and not validate.isAgeSufficientForStudent(birth_date, program): |
| raise django_forms.ValidationError(INSUFFICIENT_AGE) |
| elif (not is_student and |
| not validate.isAgeSufficientForOrgMember(birth_date, program)): |
| raise django_forms.ValidationError(INSUFFICIENT_AGE) |
| else: |
| return birth_date |
| |
| |
| class _UserProfileForm(soc_forms.ModelForm): |
| """Form to set profile properties by a user.""" |
| |
| user_id = django_forms.CharField( |
| required=True, label=USER_ID_LABEL, help_text=USER_ID_HELP_TEXT) |
| |
| public_name = django_forms.CharField( |
| required=True, label=PUBLIC_NAME_LABEL, help_text=PUBLIC_NAME_HELP_TEXT) |
| |
| web_page = django_forms.URLField( |
| required=False, label=WEB_PAGE_LABEL, help_text=WEB_PAGE_HELP_TEXT) |
| |
| blog = django_forms.URLField( |
| required=False, label=BLOG_LABEL, help_text=BLOG_HELP_TEXT) |
| |
| photo_url = django_forms.URLField( |
| required=False, label=PHOTO_URL_LABEL, help_text=PHOTO_URL_HELP_TEXT) |
| |
| first_name = django_forms.CharField( |
| required=True, label=FIRST_NAME_LABEL, help_text=FIRST_NAME_HELP_TEXT) |
| |
| last_name = django_forms.CharField( |
| required=True, label=LAST_NAME_LABEL, help_text=LAST_NAME_HELP_TEXT) |
| |
| email = django_forms.EmailField( |
| required=True, label=EMAIL_LABEL, help_text=EMAIL_HELP_TEXT) |
| |
| phone = django_forms.CharField( |
| required=True, label=PHONE_LABEL, help_text=PHONE_HELP_TEXT) |
| |
| residential_street = django_forms.CharField( |
| required=True, label=RESIDENTIAL_STREET_LABEL) |
| |
| residential_street_extra = django_forms.CharField( |
| required=False, help_text=RESIDENTIAL_STREET_HELP_TEXT) |
| |
| residential_city = django_forms.CharField( |
| required=True, label=RESIDENTIAL_CITY_LABEL, |
| help_text=RESIDENTIAL_CITY_HELP_TEXT) |
| |
| residential_province = django_forms.CharField( |
| required=False, label=RESIDENTIAL_PROVINCE_LABEL, |
| help_text=RESIDENTIAL_PROVINCE_HELP_TEXT) |
| |
| residential_country = django_forms.CharField( |
| required=True, label=RESIDENTIAL_COUNTRY_LABEL, |
| help_text=RESIDENTIAL_COUNTRY_HELP_TEXT, |
| widget=django_forms.Select( |
| choices=[('', COUNTRY_DEFAULT)] + [ |
| (country, country) |
| for country in countries.COUNTRIES_AND_TERRITORIES])) |
| |
| residential_postal_code = django_forms.CharField( |
| required=True, label=RESIDENTIAL_POSTAL_CODE_LABEL, |
| help_text=RESIDENTIAL_POSTAL_CODE_HELP_TEXT) |
| |
| is_shipping_address_different = django_forms.BooleanField( |
| required=False, label=IS_SHIPPING_ADDRESS_DIFFERENT_LABEL, |
| help_text=IS_SHIPPING_ADDRESS_DIFFERENT_HELP_TEXT) |
| |
| shipping_name = django_forms.CharField( |
| required=False, label=SHIPPING_NAME_LABEL, |
| help_text=SHIPPING_NAME_HELP_TEXT) |
| |
| shipping_street = django_forms.CharField( |
| required=False, label=SHIPPING_STREET_LABEL) |
| |
| shipping_street_extra = django_forms.CharField( |
| required=False, help_text=SHIPPING_STREET_HELP_TEXT) |
| |
| shipping_city = django_forms.CharField( |
| required=False, label=SHIPPING_CITY_LABEL, |
| help_text=SHIPPING_CITY_HELP_TEXT) |
| |
| shipping_province = django_forms.CharField( |
| required=False, label=SHIPPING_PROVINCE_LABEL, |
| help_text=SHIPPING_PROVINCE_HELP_TEXT) |
| |
| shipping_country = django_forms.CharField( |
| required=False, label=SHIPPING_COUNTRY_LABEL, |
| help_text=SHIPPING_COUNTRY_HELP_TEXT, |
| widget=django_forms.Select( |
| choices=[('', COUNTRY_DEFAULT)] + [ |
| (country, country) |
| for country in countries.COUNTRIES_AND_TERRITORIES])) |
| |
| shipping_postal_code = django_forms.CharField( |
| required=False, label=SHIPPING_POSTAL_CODE_LABEL, |
| help_text=SHIPPING_POSTAL_CODE_HELP_TEXT) |
| |
| birth_date = django_forms.DateField( |
| required=True, label=BIRTH_DATE_LABEL, help_text=BIRTH_DATE_HELP_TEXT) |
| |
| tee_style = django_forms.CharField( |
| required=False, label=TEE_STYLE_LABEL, help_text=TEE_STYLE_HELP_TEXT, |
| widget=django_forms.Select(choices=TEE_DEFAULT_CHOICE + TEE_STYLE_CHOICES)) |
| |
| tee_size = django_forms.CharField( |
| required=False, label=TEE_SIZE_LABEL, help_text=TEE_SIZE_HELP_TEXT, |
| widget=django_forms.Select(choices=TEE_DEFAULT_CHOICE + TEE_SIZE_CHOICES)) |
| |
| gender = django_forms.CharField( |
| required=True, label=GENDER_LABEL, help_text=GENDER_HELP_TEXT, |
| widget=django_forms.Select(choices=GENDER_DEFAULT_CHOICE + GENDER_CHOICES)) |
| |
| program_knowledge = django_forms.CharField( |
| required=True, label=PROGRAM_KNOWLEDGE_LABEL, |
| help_text=PROGRAM_KNOWLEDGE_HELP_TEXT, |
| widget=django_forms.Textarea()) |
| |
| terms_of_service = django_forms.BooleanField( |
| required=True, label=TERMS_OF_SERVICE_LABEL) |
| |
| school_country = django_forms.CharField( |
| required=True, label=SCHOOL_COUNTRY_LABEL, |
| help_text=SCHOOL_COUNTRY_HELP_TEXT, |
| widget=django_forms.Select( |
| choices=[('', COUNTRY_DEFAULT)] + [ |
| (country, country) |
| for country in countries.COUNTRIES_AND_TERRITORIES])) |
| |
| school_name = django_forms.CharField( |
| required=True, label=SCHOOL_NAME_LABEL, help_text=SCHOOL_NAME_HELP_TEXT) |
| |
| school_web_page = django_forms.URLField( |
| required=True, label=SCHOOL_WEB_PAGE_LABEL, |
| help_text=SCHOOL_WEB_PAGE_HELP_TEXT) |
| |
| major = django_forms.CharField( |
| required=True, label=MAJOR_LABEL, help_text=MAJOR_HELP_TEXT) |
| |
| degree = django_forms.CharField( |
| required=True, label=DEGREE_LABEL, help_text=DEGREE_HELP_TEXT, |
| widget=django_forms.Select(choices=DEGREE_CHOICES)) |
| |
| grade = django_forms.IntegerField( |
| required=True, label=GRADE_LABEL, help_text=GRADE_HELP_TEXT, |
| min_value=1, max_value=15) |
| |
| # TODO(daniel): add better, dynamic validation |
| expected_graduation = django_forms.IntegerField( |
| required=True, label=EXPECTED_GRADUATION_LABEL, |
| help_text=EXPECTED_GRADUATION_HELP_TEXT, min_value=2009, |
| max_value=2025, error_messages={'invalid': 'Enter a year.'}) |
| |
| Meta = object |
| |
| def __init__(self, bound_field_class, request_data, terms_of_service=None, |
| has_student_data=None, skip_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.Request data for the current request. |
| terms_of_service: Document with Terms of Service that has to be accepted |
| by the user. |
| has_student_data: If specified to True, the form will contain fields |
| related to student data for the profile. |
| skip_fields: Fields to remove from the form. |
| """ |
| super(_UserProfileForm, self).__init__(bound_field_class, **kwargs) |
| self.request_data = request_data |
| self.terms_of_service = terms_of_service |
| self.has_student_data = has_student_data |
| |
| # group contact information related fields together |
| self.fields['first_name'].group = _CONTACT_GROUP |
| self.fields['last_name'].group = _CONTACT_GROUP |
| self.fields['email'].group = _CONTACT_GROUP |
| self.fields['phone'].group = _CONTACT_GROUP |
| |
| # group residential address related fields together |
| self.fields['residential_street'].group = _RESIDENTIAL_ADDRESS_GROUP |
| self.fields['residential_street_extra'].group = _RESIDENTIAL_ADDRESS_GROUP |
| self.fields['residential_city'].group = _RESIDENTIAL_ADDRESS_GROUP |
| self.fields['residential_province'].group = _RESIDENTIAL_ADDRESS_GROUP |
| self.fields['residential_country'].group = _RESIDENTIAL_ADDRESS_GROUP |
| self.fields['residential_postal_code'].group = _RESIDENTIAL_ADDRESS_GROUP |
| |
| # group residential address related fields together |
| self.fields['is_shipping_address_different'].group = _SHIPPING_ADDRESS_GROUP |
| self.fields['shipping_name'].group = _SHIPPING_ADDRESS_GROUP |
| self.fields['shipping_street'].group = _SHIPPING_ADDRESS_GROUP |
| self.fields['shipping_street_extra'].group = _SHIPPING_ADDRESS_GROUP |
| self.fields['shipping_city'].group = _SHIPPING_ADDRESS_GROUP |
| self.fields['shipping_province'].group = _SHIPPING_ADDRESS_GROUP |
| self.fields['shipping_country'].group = _SHIPPING_ADDRESS_GROUP |
| self.fields['shipping_postal_code'].group = _SHIPPING_ADDRESS_GROUP |
| |
| # group other information related fields together |
| self.fields['birth_date'].group = _OTHER_INFORMATION_GROUP |
| self.fields['tee_style'].group = _OTHER_INFORMATION_GROUP |
| self.fields['tee_size'].group = _OTHER_INFORMATION_GROUP |
| self.fields['gender'].group = _OTHER_INFORMATION_GROUP |
| self.fields['program_knowledge'].group = _OTHER_INFORMATION_GROUP |
| |
| # remove terms of service field if no document is defined |
| if not self.terms_of_service: |
| del self.fields['terms_of_service'] |
| else: |
| if (request_data.ndb_profile and |
| ndb.Key.from_old_key(self.terms_of_service.key()) |
| in request_data.ndb_profile.accepted_tos): |
| self._makeTermsOfServiceReadonlyField() |
| else: |
| self.fields['terms_of_service'].widget = soc_forms.TOSWidget( |
| tos_popout=request_data.redirect.document( |
| self.terms_of_service).url(), |
| tos_text=self.terms_of_service.content, |
| tos_agree_text=TERMS_OF_SERVICE_AGREE_TEXT) |
| |
| self.fields['terms_of_service'].group = _TERMS_OF_SERVICE_GROUP |
| |
| if not self.has_student_data: |
| # remove all fields associated with student data |
| for field_name in _STUDENT_DATA_PROPERTIES_FORM_FIELDS: |
| del self.fields[field_name] |
| else: |
| # group education related fields together |
| self.fields['school_country'].group = _EDUCATION_GROUP |
| self.fields['school_name'].group = _EDUCATION_GROUP |
| self.fields['school_web_page'].group = _EDUCATION_GROUP |
| self.fields['major'].group = _EDUCATION_GROUP |
| self.fields['degree'].group = _EDUCATION_GROUP |
| self.fields['grade'].group = _EDUCATION_GROUP |
| self.fields['expected_graduation'].group = _EDUCATION_GROUP |
| |
| if skip_fields: |
| for field in skip_fields: |
| self.fields.pop(field, None) |
| |
| def _makeTermsOfServiceReadonlyField(self): |
| """Inserts a readonly field to display Terms Of Service document |
| and removes the initial editable field. |
| """ |
| # create a new readonly field that displays content of the original |
| # terms of service document along with a disabled checkbox whose value |
| # is always set to True |
| tos_readonly = django_forms.BooleanField( |
| required=False, label=TERMS_OF_SERVICE_READONLY_LABEL) |
| tos_readonly.widget = soc_forms.TOSWidget( |
| tos_popout=self.request_data.redirect.document( |
| self.terms_of_service).url(), |
| tos_text=self.terms_of_service.content, |
| tos_agree_text=TERMS_OF_SERVICE_AGREE_TEXT) |
| tos_readonly.widget.attrs['disabled'] = 'disabled' |
| tos_readonly.widget.attrs['checked'] = 'checked' |
| tos_readonly.group = _TERMS_OF_SERVICE_GROUP |
| |
| # find the index of terms_of_service field and insert the readonly |
| # field just above that field. |
| index = self.fields.keyOrder.index('terms_of_service') |
| self.fields.insert(index, 'readonly_tos', tos_readonly) |
| |
| # get rid of the regular field; there is no need to keep it |
| del self.fields['terms_of_service'] |
| |
| def clean_user_id(self): |
| """Cleans user_id field. |
| |
| Returns: |
| Cleaned value for user_id field. |
| |
| Raises: |
| django_forms.ValidationError if the submitted value is not valid. |
| """ |
| return cleanUserId(self.cleaned_data['user_id']) |
| |
| def clean_first_name(self): |
| """Cleans first_name field. |
| |
| Returns: |
| Cleaned value for first_name field. |
| |
| Raises: |
| django_forms.ValidationError if the submitted value is not valid. |
| """ |
| return cleaning.cleanValidAddressCharacters( |
| self.cleaned_data['first_name']) |
| |
| def clean_last_name(self): |
| """Cleans last_name field. |
| |
| Returns: |
| Cleaned value for last_name field. |
| |
| Raises: |
| django_forms.ValidationError if the submitted value is not valid. |
| """ |
| return cleaning.cleanValidAddressCharacters( |
| self.cleaned_data['last_name']) |
| |
| def clean_residential_street(self): |
| """Cleans residential_street field. |
| |
| Returns: |
| Cleaned value for residential_street field. |
| |
| Raises: |
| django_forms.ValidationError if the submitted value is not valid. |
| """ |
| return cleaning.cleanValidAddressCharacters( |
| self.cleaned_data['residential_street']) |
| |
| def clean_residential_street_extra(self): |
| """Cleans residential_street_extra field. |
| |
| Returns: |
| Cleaned value for residential_street_extra field. |
| |
| Raises: |
| django_forms.ValidationError if the submitted value is not valid. |
| """ |
| return cleaning.cleanValidAddressCharacters( |
| self.cleaned_data['residential_street_extra']) |
| |
| def clean_residential_city(self): |
| """Cleans residential_city field. |
| |
| Returns: |
| Cleaned value for residential_city field. |
| |
| Raises: |
| django_forms.ValidationError if the submitted value is not valid. |
| """ |
| return cleaning.cleanValidAddressCharacters( |
| self.cleaned_data['residential_city']) |
| |
| def clean_residential_province(self): |
| """Cleans residential_province field. |
| |
| Returns: |
| Cleaned value for residential_province field. |
| |
| Raises: |
| django_forms.ValidationError if the submitted value is not valid. |
| """ |
| return cleaning.cleanValidAddressCharacters( |
| self.cleaned_data['residential_province']) |
| |
| def clean_residential_postal_code(self): |
| """Cleans residential_postal_code field. |
| |
| Returns: |
| Cleaned value for residential_postal_code field. |
| |
| Raises: |
| django_forms.ValidationError if the submitted value is not valid. |
| """ |
| return cleaning.cleanValidAddressCharacters( |
| self.cleaned_data['residential_postal_code']) |
| |
| def clean_residential_country(self): |
| """Cleans residential_country field. |
| |
| Returns: |
| Cleaned value for residential_country field. |
| |
| Raises: |
| django_forms.ValidationError if the submitted value is not valid. |
| """ |
| return cleaning.cleanValidAddressCharacters( |
| self.cleaned_data['residential_country']) |
| |
| def clean_shipping_name(self): |
| """Cleans shipping_name field. |
| |
| Returns: |
| Cleaned value for shipping_name field. |
| |
| Raises: |
| django_forms.ValidationError if the submitted value is not valid. |
| """ |
| return _cleanShippingAddressPart( |
| self.cleaned_data['is_shipping_address_different'], |
| self.cleaned_data['shipping_name'], True) |
| |
| def clean_shipping_street(self): |
| """Cleans shipping_street field. |
| |
| Returns: |
| Cleaned value for shipping_street field. |
| |
| Raises: |
| django_forms.ValidationError if the submitted value is not valid. |
| """ |
| return _cleanShippingAddressPart( |
| self.cleaned_data['is_shipping_address_different'], |
| self.cleaned_data['shipping_street'], True) |
| |
| def clean_shipping_street_extra(self): |
| """Cleans shipping_street_extra field. |
| |
| Returns: |
| Cleaned value for shipping_street_extra field. |
| |
| Raises: |
| django_forms.ValidationError if the submitted value is not valid. |
| """ |
| return _cleanShippingAddressPart( |
| self.cleaned_data['is_shipping_address_different'], |
| self.cleaned_data['shipping_street_extra'], False) |
| |
| def clean_shipping_city(self): |
| """Cleans shipping_city field. |
| |
| Returns: |
| Cleaned value for shipping_city field. |
| |
| Raises: |
| django_forms.ValidationError if the submitted value is not valid. |
| """ |
| return _cleanShippingAddressPart( |
| self.cleaned_data['is_shipping_address_different'], |
| self.cleaned_data['shipping_city'], True) |
| |
| def clean_shipping_province(self): |
| """Cleans shipping_province field. |
| |
| Returns: |
| Cleaned value for shipping_province field. |
| |
| Raises: |
| django_forms.ValidationError if the submitted value is not valid. |
| """ |
| return _cleanShippingAddressPart( |
| self.cleaned_data['is_shipping_address_different'], |
| self.cleaned_data['shipping_province'], False) |
| |
| def clean_shipping_country(self): |
| """Cleans shipping_country field. |
| |
| Returns: |
| Cleaned value for shipping_country field. |
| |
| Raises: |
| django_forms.ValidationError if the submitted value is not valid. |
| """ |
| return _cleanShippingAddressPart( |
| self.cleaned_data['is_shipping_address_different'], |
| self.cleaned_data['shipping_country'], True) |
| |
| def clean_shipping_postal_code(self): |
| """Cleans shipping_postal_code field. |
| |
| Returns: |
| Cleaned value for shipping_postal_code field. |
| |
| Raises: |
| django_forms.ValidationError if the submitted value is not valid. |
| """ |
| return _cleanShippingAddressPart( |
| self.cleaned_data['is_shipping_address_different'], |
| self.cleaned_data['shipping_postal_code'], True) |
| |
| def clean_terms_of_service(self): |
| """Cleans terms_of_service_field. |
| |
| Returns: |
| Cleaned value of terms_of_service field. Specifically, it is a key |
| of a document entity that contains the accepted terms of service. |
| |
| Raises: |
| django_forms.ValidationError if the submitted value is not valid. |
| """ |
| return cleanTermsOfService( |
| self.cleaned_data['terms_of_service'], self.terms_of_service) |
| |
| def clean_birth_date(self): |
| """Cleans birth_date field. |
| |
| 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. |
| """ |
| return cleanBirthDate( |
| self.cleaned_data['birth_date'], |
| self.request_data.program, self.has_student_data) |
| |
| def getUserProperties(self): |
| """Returns properties of the user that were submitted in this form. |
| |
| Returns: |
| A dict mapping user properties to the corresponding values. |
| """ |
| return self._getPropertiesForFields(_USER_PROPERTIES_FORM_KEYS) |
| |
| def getProfileProperties(self): |
| """Returns properties of the profile that were submitted in this form. |
| |
| Returns: |
| A dict mapping profile properties to the corresponding values. |
| """ |
| return self._getPropertiesForFields(_PROFILE_PROPERTIES_FORM_KEYS) |
| |
| def getContactProperties(self): |
| """Returns properties of the contact information that were submitted |
| in this form. |
| |
| Returns: |
| A dict mapping profile properties to the corresponding values. |
| """ |
| return self._getPropertiesForFields(_CONTACT_PROPERTIES_FORM_KEYS) |
| |
| def getResidentialAddressProperties(self): |
| """Returns properties of the residential address that were submitted |
| in this form. |
| |
| Returns: |
| A dict mapping residential address properties to the corresponding values. |
| """ |
| return self._getPropertiesForFields( |
| _RESIDENTIAL_ADDRESS_PROPERTIES_FORM_KEYS) |
| |
| def getShippingAddressProperties(self): |
| """Returns properties of the shipping address that were submitted in |
| this form. |
| |
| Returns: |
| A dict mapping shipping address properties to the corresponding values |
| or None, if the shipping address has not been specified. |
| """ |
| return (self._getPropertiesForFields(_SHIPPING_ADDRESS_PROPERTIES_FORM_KEYS) |
| if self.cleaned_data['is_shipping_address_different'] else None) |
| |
| def getStudentDataProperties(self): |
| """Returns properties of the student data that were submitted in this form. |
| |
| Returns: |
| A dict mapping student data properties to the corresponding values |
| or None, if student data does not apply to this form. |
| """ |
| return (self._getPropertiesForFields(_STUDENT_DATA_PROPERTIES_FORM_FIELDS) |
| if self.has_student_data else None) |
| |
| |
| _TEE_STYLE_ID_TO_ENUM_LINK = ( |
| (_TEE_STYLE_FEMALE_ID, profile_model.TeeStyle.FEMALE), |
| (_TEE_STYLE_MALE_ID, profile_model.TeeStyle.MALE) |
| ) |
| _TEE_STYLE_ID_TO_ENUM_MAP = dict(_TEE_STYLE_ID_TO_ENUM_LINK) |
| _TEE_STYLE_ENUM_TO_ID_MAP = dict( |
| (v, k) for (k, v) in _TEE_STYLE_ID_TO_ENUM_LINK) |
| |
| _TEE_SIZE_ID_TO_ENUM_LINK = ( |
| (_TEE_SIZE_XS_ID, profile_model.TeeSize.XS), |
| (_TEE_SIZE_S_ID, profile_model.TeeSize.S), |
| (_TEE_SIZE_M_ID, profile_model.TeeSize.M), |
| (_TEE_SIZE_L_ID, profile_model.TeeSize.L), |
| (_TEE_SIZE_XL_ID, profile_model.TeeSize.XL), |
| (_TEE_SIZE_XXL_ID, profile_model.TeeSize.XXL), |
| (_TEE_SIZE_XXXL_ID, profile_model.TeeSize.XXXL) |
| ) |
| _TEE_SIZE_ID_TO_ENUM_MAP = dict(_TEE_SIZE_ID_TO_ENUM_LINK) |
| _TEE_SIZE_ENUM_TO_ID_MAP = dict((v, k) for (k, v) in _TEE_SIZE_ID_TO_ENUM_LINK) |
| |
| |
| _GENDER_ID_TO_ENUM_LINK = ( |
| (_GENDER_FEMALE_ID, profile_model.Gender.FEMALE), |
| (_GENDER_MALE_ID, profile_model.Gender.MALE), |
| (_GENDER_OTHER_ID, profile_model.Gender.OTHER), |
| (_GENDER_NOT_DISCLOSED_ID, profile_model.Gender.NOT_DISCLOSED) |
| ) |
| _GENDER_ID_TO_ENUM_MAP = dict(_GENDER_ID_TO_ENUM_LINK) |
| _GENDER_ENUM_TO_ID_MAP = dict((v, k) for (k, v) in _GENDER_ID_TO_ENUM_LINK) |
| _GENDER_ENUM_TO_ID_MAP[None] = _GENDER_NOT_DISCLOSED_ID |
| |
| _GENDER_ENUM_TO_VERBOSE_MAP = { |
| profile_model.Gender.FEMALE: _GENDER_FEMALE_VERBOSE, |
| profile_model.Gender.MALE: _GENDER_MALE_VERBOSE, |
| profile_model.Gender.OTHER: _GENDER_OTHER_VERBOSE, |
| profile_model.Gender.NOT_DISCLOSED: _GENDER_NOT_DISCLOSED_VERBOSE, |
| None: _GENDER_NOT_DISCLOSED_VERBOSE, |
| } |
| |
| _DEGREE_ID_TO_ENUM_LINK = ( |
| (_DEGREE_UNDERGRADUATE_ID, education_model.Degree.UNDERGRADUATE), |
| (_DEGREE_MASTERS_ID, education_model.Degree.MASTERS), |
| (_DEGREE_PHD_ID, education_model.Degree.PHD), |
| (None, None), |
| ) |
| _DEGREE_ID_TO_ENUM_MAP = dict(_DEGREE_ID_TO_ENUM_LINK) |
| _DEGREE_ENUM_TO_ID_MAP = dict((v, k) for (k, v) in _DEGREE_ID_TO_ENUM_LINK) |
| |
| |
| def _adaptProfilePropertiesForDatastore(form_data, profile=None): |
| """Adapts properties corresponding to profile's properties, which |
| have been submitted in a form, to the format that is compliant with |
| profile_model.Profile model. |
| |
| Args: |
| form_data: A dict containing data submitted in a form. |
| profile: Optional profile_model.Profile entity whose properties are |
| updated. |
| |
| Returns: |
| A dict mapping properties of profile model to values based on |
| data submitted in a form. |
| """ |
| properties = { |
| profile_model.Profile.public_name._name: form_data.get('public_name'), |
| profile_model.Profile.first_name._name: form_data.get('first_name'), |
| profile_model.Profile.last_name._name: form_data.get('last_name'), |
| profile_model.Profile.photo_url._name: form_data.get('photo_url'), |
| profile_model.Profile.birth_date._name: form_data.get('birth_date'), |
| profile_model.Profile.program_knowledge._name: |
| form_data.get('program_knowledge'), |
| } |
| |
| if 'tee_style' in form_data: |
| properties[profile_model.Profile.tee_style._name] = ( |
| _TEE_STYLE_ID_TO_ENUM_MAP[form_data['tee_style']]) |
| |
| if 'tee_size' in form_data: |
| properties[profile_model.Profile.tee_size._name] = ( |
| _TEE_SIZE_ID_TO_ENUM_MAP[form_data['tee_size']]) |
| |
| if 'gender' in form_data: |
| properties[profile_model.Profile.gender._name] = ( |
| _GENDER_ID_TO_ENUM_MAP[form_data['gender']]) |
| |
| properties[profile_model.Profile.accepted_tos._name] = ( |
| _adaptAcceptedTosForDatastore( |
| form_data, list(profile.accepted_tos) if profile else [])) |
| |
| return properties |
| |
| |
| def _adaptAcceptedTosForDatastore(form_data, current_tos=None): |
| """Adapts accepted_tos data for datastore. |
| |
| Args: |
| form_data: A dict containing data submitted in a form. |
| current_tos: Optional list of ndb.Keys of ToS documents which have been |
| signed by the profile before the from was submitted. |
| |
| Returns: |
| List of ndb.Keys of ToS documents which have been signed by the profile. |
| """ |
| current_tos = current_tos or [] |
| if 'terms_of_service' in form_data: |
| current_tos.append(form_data.get('terms_of_service')) |
| |
| return list(set(current_tos)) |
| |
| |
| def _adaptStudentDataPropertiesForDatastore(form_data): |
| """Adapts properties corresponding to profile's student data properties, which |
| have been submitted in a form, to the format that is compliant with |
| profile_model.StudentData model. |
| |
| Args: |
| form_data: A dict containing data submitted in a form. |
| |
| Returns: |
| A dict mapping properties of student data model to values based on |
| data submitted in a form. |
| """ |
| school_id = form_data.get('school_name') |
| school_country = form_data.get('school_country') |
| degree = _DEGREE_ID_TO_ENUM_MAP[form_data.get('degree')] |
| grade = form_data.get('grade') |
| expected_graduation = form_data.get('expected_graduation') |
| major = form_data.get('major') |
| web_page = form_data.get('school_web_page') |
| |
| # if both degree and major are present in the form data, we are dealing |
| # with post secondary education for which grade level is not collected |
| if degree and major and not grade: |
| result = education_logic.createPostSecondaryEducation( |
| school_id, school_country, expected_graduation, major, degree, web_page) |
| # if grade is present in the form data, we are dealing with secondary |
| # education. Degree and major are irrelevant in this case |
| elif not degree and not major: |
| result = education_logic.createSecondaryEducation( |
| school_id, school_country, expected_graduation, grade, web_page) |
| # every other combination is invalid |
| else: |
| result = rich_bool.RichBool(False, _INVALID_EDUCATION_DATA) |
| |
| if not result: |
| raise exception.BadRequest(message=result.extra) |
| else: |
| return {profile_model.StudentData.education._name: result.extra} |
| |
| |
| def _adaptContactPropertiesForForm(contact_properties): |
| """Adapts properties of a contact entity, which are persisted in datastore, |
| to representation which may be passed to populate _UserProfileForm. |
| |
| Args: |
| contact_properties: A dict containing contact properties as persisted |
| in datastore. |
| |
| Returns: |
| A dict mapping properties of contact model to values which can be |
| populated to a user profile form. |
| """ |
| return { |
| key: contact_properties.get(key) for key in _CONTACT_PROPERTIES_FORM_KEYS |
| } |
| |
| |
| def _adaptResidentialAddressPropertiesForForm(address_properties): |
| """Adapts properties of a address entity, which are persisted in datastore |
| as residential address, to representation which may be passed to |
| populate _UserProfileForm. |
| |
| Args: |
| address_properties: A dict containing residential address properties |
| as persisted in datastore. |
| |
| Returns: |
| A dict mapping properties of address model to values which can be |
| populated to a user profile form. |
| """ |
| return { |
| 'residential_street': address_properties['street'], |
| 'residential_street_extra': address_properties['street_extra'], |
| 'residential_city': address_properties['city'], |
| 'residential_country': address_properties['country'], |
| 'residential_postal_code': address_properties['postal_code'], |
| 'residential_province': address_properties['province'], |
| } |
| |
| |
| def _adaptShippingAddressPropertiesForForm(address_properties): |
| """Adapts properties of an address entity, which are persisted in datastore |
| as shipping address, to representation which may be passed to |
| populate _UserProfileForm. |
| |
| Args: |
| address_properties: A dict containing shipping address properties |
| as persisted in datastore or None, if no shipping address is specified. |
| |
| Returns: |
| A dict mapping properties of address model to values which can be |
| populated to a user profile form. |
| """ |
| address_properties = address_properties or {} |
| return { |
| 'is_shipping_address_different': bool(address_properties), |
| 'shipping_name': address_properties.get('name'), |
| 'shipping_street': address_properties.get('street'), |
| 'shipping_street_extra': address_properties.get('street_extra'), |
| 'shipping_city': address_properties.get('city'), |
| 'shipping_country': address_properties.get('country'), |
| 'shipping_postal_code': address_properties.get('postal_code'), |
| 'shipping_province': address_properties.get('province'), |
| } |
| |
| |
| def _adaptStudentDataPropertiesForForm(student_data_properties): |
| """Adapts properties of a student data entity, which are persisted in |
| datastore, to representation which may be passed to populate _UserProfileForm. |
| |
| Args: |
| student_data_properties: A dict containing student data properties as |
| persisted in datastore. |
| |
| Returns: |
| A dict mapping properties of student profile model to values which can be |
| populated to a user profile form. |
| """ |
| student_data_properties = student_data_properties or {} |
| |
| education = student_data_properties[profile_model.StudentData.education._name] |
| return { |
| 'school_country': education.get( |
| education_model.Education.school_country._name), |
| 'school_name': education.get(education_model.Education.school_id._name), |
| 'school_web_page': education.get( |
| education_model.Education.web_page._name), |
| 'major': education.get( |
| education_model.Education.major._name), |
| 'degree': _DEGREE_ENUM_TO_ID_MAP.get( |
| education.get(education_model.Education.degree._name)), |
| 'grade': education.get(education_model.Education.grade._name), |
| 'expected_graduation': education.get( |
| education_model.Education.expected_graduation._name), |
| } |
| |
| |
| def _adaptProfilePropertiesForForm(profile_properties, terms_of_service): |
| """Adapts properties of a profile entity, which are persisted in datastore, |
| to representation which may be passed to populate _UserProfileForm. |
| |
| Args: |
| profile_properties: A dict containing profile properties as |
| persisted in datastore. |
| terms_of_service: Document entity that contains the terms of service that |
| need to be accepted. |
| |
| Returns: |
| A dict mapping properties of profile model to values which can be |
| populated to a user profile form. |
| """ |
| form_data = { |
| key: profile_properties.get(key) |
| for key in [ |
| 'first_name', 'last_name', 'photo_url', 'birth_date', |
| 'public_name', 'program_knowledge'] |
| } |
| |
| # terms of service information |
| form_data['terms_of_service'] = ( |
| terms_of_service and |
| ndb.Key.from_old_key(terms_of_service.key()) |
| in profile_properties.get('accepted_tos', [])) |
| |
| # residential address information |
| form_data.update( |
| _adaptResidentialAddressPropertiesForForm( |
| profile_properties[profile_model.Profile.residential_address._name])) |
| |
| # shipping address information |
| form_data.update( |
| _adaptShippingAddressPropertiesForForm( |
| profile_properties[profile_model.Profile.shipping_address._name])) |
| |
| # contact information |
| if profile_model.Profile.contact._name in profile_properties: |
| form_data.update(_adaptContactPropertiesForForm( |
| profile_properties[profile_model.Profile.contact._name])) |
| |
| if profile_properties.get('tee_style') is not None: |
| form_data['tee_style'] = ( |
| _TEE_STYLE_ENUM_TO_ID_MAP[profile_properties['tee_style']]) |
| |
| if profile_properties.get('tee_size') is not None: |
| form_data['tee_size'] = ( |
| _TEE_SIZE_ENUM_TO_ID_MAP[profile_properties['tee_size']]) |
| form_data['gender'] = ( |
| _GENDER_ENUM_TO_ID_MAP[profile_properties['gender']]) |
| |
| # student information |
| if profile_properties.get(profile_model.Profile.student_data._name): |
| form_data.update(_adaptStudentDataPropertiesForForm( |
| profile_properties[profile_model.Profile.student_data._name])) |
| |
| return form_data |
| |
| |
| def _getProfileEntityPropertiesFromForm(form, models, profile=None): |
| """Extracts properties for a profile entity from the specified form. |
| |
| Args: |
| form: Instance of _UserProfileForm. |
| models: instance of types.Models that represent appropriate models. |
| profile: Optional profile_model.Profile entity whose properties are |
| updated. |
| |
| Returns: |
| A dict with complete set of properties of profile entity. |
| """ |
| profile_properties = _adaptProfilePropertiesForDatastore( |
| form.getProfileProperties(), profile=profile) |
| |
| address_properties = form.getResidentialAddressProperties() |
| result = address_logic.createAddress( |
| address_properties['residential_street'], |
| address_properties['residential_city'], |
| address_properties['residential_country'], |
| address_properties['residential_postal_code'], |
| province=address_properties.get('residential_province'), |
| street_extra=address_properties.get('residential_street_extra') |
| ) |
| if not result: |
| raise exception.BadRequest(message=result.extra) |
| else: |
| profile_properties['residential_address'] = result.extra |
| |
| address_properties = form.getShippingAddressProperties() |
| if address_properties: |
| result = address_logic.createAddress( |
| address_properties['shipping_street'], |
| address_properties['shipping_city'], |
| address_properties['shipping_country'], |
| address_properties['shipping_postal_code'], |
| province=address_properties.get('shipping_province'), |
| name=address_properties.get('shipping_name'), |
| street_extra=address_properties.get('shipping_street_extra')) |
| if not result: |
| raise exception.BadRequest(message=result.extra) |
| else: |
| profile_properties['shipping_address'] = result.extra |
| else: |
| profile_properties['shipping_address'] = None |
| |
| contact_properties = form.getContactProperties() |
| result = contact_logic.createContact(**contact_properties) |
| if not result: |
| raise exception.BadRequest(message=result.extra) |
| else: |
| profile_properties['contact'] = result.extra |
| |
| student_data_properties = form.getStudentDataProperties() |
| if student_data_properties: |
| profile_properties['student_data'] = ( |
| melange_db.toDict(profile.student_data, exclude_computed=True) |
| if profile and profile.is_student |
| else {}) |
| profile_properties['student_data'].update( |
| _adaptStudentDataPropertiesForDatastore(student_data_properties)) |
| |
| return profile_properties |
| |
| |
| class ProfileFormFactory(object): |
| """Interface that defines a factory to create a form to register a profile.""" |
| |
| def create(self, request_data, terms_of_service=None, |
| include_user_fields=None, include_student_fields=None, **kwargs): |
| """Creates a new instance of _UserProfileForm to register a new profile |
| for the specified parameters. |
| |
| Args: |
| request_data: Program entity for which a profile form is constructed. |
| terms_of_service: Optional document entity with Terms of Service that |
| has to be accepted by the user. |
| include_user_fields: If set to True, the constructed form will also include |
| fields to be used to create a new User entity along with a new |
| Profile entity. |
| include_student_fields: If set to True, the form will include fields |
| related to student data for the profile. |
| |
| Returns: |
| _UserProfileForm adjusted to create a new profile. |
| """ |
| raise NotImplementedError |
| |
| |
| class ProfileRegisterAsOrgMemberPage(base.RequestHandler): |
| """View to create organization member profile. |
| |
| It will be used by prospective organization members. Users with such profiles |
| will be eligible to connect with organizations and participate in the program |
| as administrators or mentors. |
| """ |
| |
| access_checker = access.ConjuctionAccessChecker([ |
| access.HAS_NO_PROFILE_ACCESS_CHECKER, |
| access.ORG_SIGNUP_STARTED_ACCESS_CHECKER, |
| access.PROGRAM_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 ProfileFormFactory interface. |
| """ |
| super(ProfileRegisterAsOrgMemberPage, 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'profile/register/org_member/%s$' % url_patterns.PROGRAM, |
| self, name=self.url_names.PROFILE_REGISTER_AS_ORG_MEMBER)] |
| |
| def context(self, data, check, mutator): |
| """See base.RequestHandler.context for specification.""" |
| form = self.form_factory.create( |
| data, terms_of_service=data.program.mentor_agreement, |
| include_user_fields=data.ndb_user is None, data=data.POST) |
| |
| return { |
| 'page_name': PROFILE_ORG_MEMBER_CREATE_PAGE_NAME, |
| 'forms': [form], |
| 'error': bool(form.errors), |
| 'form_top_msg': top_message.orgMemberRegistrationTopMessage(data), |
| 'form_below_header_msg': profile_form_below_header.Create( |
| data, agreement=data.program.mentor_agreement), |
| } |
| |
| def post(self, data, check, mutator): |
| """See base.RequestHandler.post for specification.""" |
| form = self.form_factory.create( |
| data, terms_of_service=data.program.mentor_agreement, |
| include_user_fields=data.ndb_user is None, data=data.POST) |
| |
| # TODO(daniel): eliminate passing self object. |
| handler = CreateProfileFormHandler(self, form) |
| return handler.handle(data, check, mutator) |
| |
| |
| class ProfileRegisterAsStudentPage(base.RequestHandler): |
| """View to create student profile. |
| |
| It will be used by prospective students. Users with such profiles will be |
| eligible to submit proposals to organizations and work on projects |
| upon acceptance. |
| """ |
| |
| def __init__(self, initializer, linker, renderer, error_handler, |
| url_pattern_constructor, url_names, template_path, access_checker, |
| form_factory, persist_age_only=None): |
| """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 ProfileFormFactory interface. |
| persist_age_only: If specified and equals True, only age of the student is |
| going to be persisted instead of the full birth date. |
| """ |
| super(ProfileRegisterAsStudentPage, 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.access_checker = access_checker |
| self.form_factory = form_factory |
| self.persist_age_only = persist_age_only |
| |
| 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'profile/register/student/%s$' % url_patterns.PROGRAM, |
| self, name=self.url_names.PROFILE_REGISTER_AS_STUDENT)] |
| |
| def context(self, data, check, mutator): |
| """See base.RequestHandler.context for specification.""" |
| form = self.form_factory.create( |
| data, terms_of_service=data.program.student_agreement, |
| include_user_fields=data.ndb_user is None, |
| include_student_fields=True, data=data.POST) |
| |
| return { |
| 'page_name': PROFILE_STUDENT_CREATE_PAGE_NAME, |
| 'forms': [form], |
| 'error': bool(form.errors), |
| 'form_below_header_msg': profile_form_below_header.Create( |
| data, agreement=data.program.student_agreement), |
| } |
| |
| def post(self, data, check, mutator): |
| """See base.RequestHandler.post for specification.""" |
| form = self.form_factory.create( |
| data, terms_of_service=data.program.student_agreement, |
| include_user_fields=data.ndb_user is None, |
| include_student_fields=True, data=data.POST) |
| |
| # TODO(daniel): eliminate passing self object. |
| handler = CreateProfileFormHandler( |
| self, form, persist_age_only=self.persist_age_only) |
| return handler.handle(data, check, mutator) |
| |
| def jsonContext(self, data, check, mutator): |
| """See base.RequestHandler.jsonContext for specification.""" |
| return universities.UNIVERSITIES |
| |
| |
| class CreateProfileFormHandler(form_handler.FormHandler): |
| """Form handler implementation to handle incoming data that is supposed to |
| create new profiles. |
| """ |
| |
| def __init__(self, view, form, persist_age_only=None): |
| """Initializes new instance of form handler. |
| |
| Args: |
| view: Callback to implementation of base.RequestHandler |
| that creates this object. |
| form: Instance of _UserProfileForm whose data is to be handled. |
| persist_age_only: If specified and equals True, only age of the student is |
| going to be persisted instead of the full birth date, even if it is |
| present the dictionary with properties. |
| """ |
| super(CreateProfileFormHandler, self).__init__(view) |
| self.form = form |
| self.persist_age_only = persist_age_only |
| |
| def handle(self, data, check, mutator): |
| """Creates and persists a new profile based on the data that was sent |
| in the current request and supplied to the form. |
| |
| See form_handler.FormHandler.handle for specification. |
| """ |
| if not self.form.is_valid(): |
| # TODO(nathaniel): problematic self-use. |
| return self._view.get(data, check, mutator) |
| else: |
| profile_properties = _getProfileEntityPropertiesFromForm( |
| self.form, data.models) |
| |
| user = data.ndb_user |
| if not user: |
| # try to make sure that no user entity exists for the current account. |
| # it should be guaranteed by the condition above evaluating to None, |
| # but there is a slim chance that an entity has been created in |
| # the meantime. |
| user = user_logic.getByCurrentAccount() |
| |
| username = self.form.getUserProperties()['user_id'] if not user else None |
| |
| createProfileTxn( |
| data.program.key(), profile_properties, username=username, user=user, |
| persist_age_only=self.persist_age_only, models=data.models) |
| |
| url = links.LINKER.program( |
| data.program, self._view.url_names.PROFILE_SHOW) |
| return http.HttpResponseRedirect(url + '?validated=true') |
| |
| |
| class ProfileEditPage(base.RequestHandler): |
| """View to edit user profiles.""" |
| |
| access_checker = access.HAS_PROFILE_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 ProfileFormFactory interface. |
| """ |
| super(ProfileEditPage, 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'profile/edit/%s$' % url_patterns.PROGRAM, |
| self, name=self.url_names.PROFILE_EDIT)] |
| |
| def context(self, data, check, mutator): |
| """See base.RequestHandler.context for specification.""" |
| terms_of_service = program_logic.getTermsOfService( |
| data.program, data.ndb_profile) |
| |
| form_data = _adaptProfilePropertiesForForm( |
| data.ndb_profile.to_dict(), terms_of_service) |
| form = self.form_factory.create( |
| data, terms_of_service, |
| include_student_fields=data.ndb_profile.is_student, |
| data=data.POST or form_data) |
| |
| # TODO(daniel): Code In specific items should not be created here |
| delete_profile_url = ( |
| links.LINKER.program(data.program, self.url_names.PROFILE_DELETE) |
| if data.program.prefix == 'gci_program' else None) |
| |
| return { |
| 'page_name': PROFILE_EDIT_PAGE_NAME, |
| 'forms': [form], |
| 'error': bool(form.errors), |
| 'delete_profile_url': delete_profile_url, |
| 'form_below_header_msg': profile_form_below_header.Create( |
| data, agreement=terms_of_service), |
| } |
| |
| def post(self, data, check, mutator): |
| """See base.RequestHandler.post for specification.""" |
| terms_of_service = program_logic.getTermsOfService( |
| data.program, data.ndb_profile) |
| form = self.form_factory.create( |
| data, terms_of_service, |
| include_student_fields=data.ndb_profile.is_student, |
| data=data.POST) |
| |
| if not form.is_valid(): |
| # TODO(nathaniel): problematic self-use. |
| return self.get(data, check, mutator) |
| else: |
| profile_properties = _getProfileEntityPropertiesFromForm( |
| form, data.models, data.ndb_profile) |
| |
| # if new Terms Of Service document was signed during this request, |
| # signature must be created |
| if (terms_of_service and |
| not data.ndb_profile.isTermsOfServiceSigned(terms_of_service.key())): |
| accepted_tos = ndb.Key.from_old_key(terms_of_service.key()) |
| else: |
| accepted_tos = None |
| |
| editProfileTxn( |
| data.ndb_profile.key, profile_properties, accepted_tos=accepted_tos) |
| |
| url = links.LINKER.program(data.program, self.url_names.PROFILE_SHOW) |
| return http.HttpResponseRedirect(url + '?validated=true') |
| |
| def jsonContext(self, data, check, mutator): |
| """See base.RequestHandler.jsonContext for specification.""" |
| return universities.UNIVERSITIES |
| |
| |
| def _getStudentFormsContext(data, profile, url_names): |
| """Returns context that is applicable to student forms. |
| |
| Args: |
| data: request_data.RequestData for the current request. |
| url_names: Instance of url_names.UrlNames. |
| |
| Returns: |
| A dict that contains the context for student forms. |
| """ |
| context = {} |
| # TODO(daniel): these elements below do not really belong here. |
| # They are only needed to pass the context that is needed by JS module |
| # that supports StudentFormsTemplate. It should be possible to define |
| # these items in that sub-template |
| if data.program.prefix == 'gci_program': |
| button_type = ( |
| toggle_button.ButtonType.on_off.value |
| if data.is_host |
| else toggle_button.ButtonType.disabled.value) |
| |
| context['verify_consent_form_url'] = links.LINKER.profile( |
| profile, url_names.STUDENT_FORM_VERIFY, |
| action='verify', form_type=student_forms_view.CONSENT_FORM_TYPE) |
| context['unverify_consent_form_url'] = links.LINKER.profile( |
| profile, url_names.STUDENT_FORM_VERIFY, |
| action='unverify', form_type=student_forms_view.CONSENT_FORM_TYPE) |
| context['is_consent_form_verified'] = ( |
| profile.student_data.is_consent_form_verified |
| if profile.is_student |
| else None) |
| context['consent_form_button_type'] = button_type |
| |
| context['verify_enrollment_form_url'] = links.LINKER.profile( |
| profile, url_names.STUDENT_FORM_VERIFY, |
| action='verify', form_type=student_forms_view.ENROLLMENT_FORM_TYPE) |
| context['unverify_enrollment_form_url'] = links.LINKER.profile( |
| profile, url_names.STUDENT_FORM_VERIFY, |
| action='unverify', form_type=student_forms_view.ENROLLMENT_FORM_TYPE) |
| context['is_enrollment_form_verified'] = ( |
| profile.student_data.is_enrollment_form_verified |
| if profile.is_student |
| else None) |
| context['enrollment_form_button_type'] = button_type |
| |
| context['freeze_awaiting_forms_url'] = links.LINKER.profile( |
| profile, url_names.STUDENT_FORM_VERIFY, |
| action='verify', form_type=student_forms_view.AWAITING_FORMS_TYPE) |
| context['unfreeze_awaiting_forms_form_url'] = links.LINKER.profile( |
| profile, url_names.STUDENT_FORM_VERIFY, |
| action='unverify', form_type=student_forms_view.AWAITING_FORMS_TYPE) |
| context['is_awaiting_forms'] = ( |
| profile.flags.awaiting_forms if profile.is_student else None) |
| context['awaiting_forms_button_type'] = button_type |
| |
| return context |
| |
| |
| class ProfileReadonlyFactory(object): |
| """Interface that defines a factory to create read-only template |
| for profiles. |
| """ |
| |
| def create(self, data, profile): |
| """Creates a new instance of readonly.Readonly template for the specified |
| profile. |
| |
| Args: |
| data: request_data.RequestData for the current request. |
| profile: profile_model.Profile entity. |
| |
| Returns: |
| readonly.Readonly for the specified profile. |
| """ |
| raise NotImplementedError |
| |
| |
| class ProfileShowPage(base.RequestHandler): |
| """View to display the read-only profile page.""" |
| |
| access_checker = access.HAS_PROFILE_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 ProfileReadonlyFactory interface. |
| """ |
| super(ProfileShowPage, 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'profile/show/%s$' % url_patterns.PROGRAM, |
| self, name=self.url_names.PROFILE_SHOW)] |
| |
| def templatePath(self): |
| """See base.RequestHandler.templatePath for specification.""" |
| return self.template_path |
| |
| def context(self, data, check, mutator): |
| """See soc.views.base.RequestHandler.context for specification.""" |
| profile_template = self.readonly_factory.create(data, data.ndb_profile) |
| |
| profile_tabs = ( |
| tabs.profileTabs(data, selected_tab_id=tabs.VIEW_PROFILE_TAB_ID) |
| if data.program.prefix == 'gsoc_program' else None) |
| |
| context = { |
| 'page_name': '%s Profile - %s' % ( |
| data.program.short_name, data.ndb_profile.public_name), |
| 'program_name': data.program.name, |
| 'profile_template': profile_template, |
| 'tabs': profile_tabs, |
| } |
| context.update(_getStudentFormsContext( |
| data, data.ndb_profile, self.url_names)) |
| return context |
| |
| |
| class ProfileAdminPage(base.RequestHandler): |
| """View to display the read-only profile page to 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 ProfileReadonlyFactory interface. |
| """ |
| super(ProfileAdminPage, 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'profile/admin/%s$' % url_patterns.PROFILE, |
| self, name=self.url_names.PROFILE_ADMIN)] |
| |
| def templatePath(self): |
| """See base.RequestHandler.templatePath for specification.""" |
| return self.template_path |
| |
| def context(self, data, check, mutator): |
| """See soc.views.base.RequestHandler.context for specification.""" |
| profile_template = self.readonly_factory.create(data, data.url_ndb_profile) |
| |
| context = { |
| 'page_name': '%s Profile - %s' % ( |
| data.program.short_name, data.url_ndb_profile.public_name), |
| 'program_name': data.program.name, |
| 'profile_template': profile_template, |
| } |
| context.update(_getStudentFormsContext( |
| data, data.url_ndb_profile, self.url_names)) |
| return context |
| |
| |
| class ProfileDeletePage(base.RequestHandler): |
| """Page to request profile deletion.""" |
| |
| access_checker = access.HAS_PROFILE_ACCESS_CHECKER |
| |
| def __init__(self, initializer, linker, renderer, error_handler, |
| url_pattern_constructor, url_names, template_path): |
| """Initializes a new instance of the request handler for the specified |
| parameters. |
| |
| Args: |
| initializer: Implementation of initialize.Initializer interface. |
| linker: Instance of links.Linker class. |
| renderer: Implementation of render.Renderer interface. |
| error_handler: Implementation of error.ErrorHandler interface. |
| url_pattern_constructor: |
| Implementation of url_patterns.UrlPatternConstructor. |
| url_names: Instance of url_names.UrlNames. |
| template_path: The path of the template to be used. |
| """ |
| super(ProfileDeletePage, self).__init__( |
| initializer, linker, renderer, error_handler) |
| self.url_pattern_constructor = url_pattern_constructor |
| self.url_names = url_names |
| self.template_path = template_path |
| |
| def djangoURLPatterns(self): |
| """See base.RequestHandler.djangoURLPatterns for specification.""" |
| return [ |
| self.url_pattern_constructor.construct( |
| r'profile/delete/%s$' % url_patterns.PROGRAM, |
| self, name=self.url_names.PROFILE_DELETE) |
| ] |
| |
| 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.""" |
| return {'page_name': PROFILE_DELETE_PAGE_NAME} |
| |
| def post(self, data, check, mutator): |
| """See base.RequestHandler.post for specification.""" |
| profile_logic.requestProfileDeletion(data.ndb_profile) |
| |
| return http.HttpResponseRedirect( |
| links.LINKER.program(data.program, self.url_names.PROFILE_DELETE) |
| + '?validated=True') |
| |
| |
| class AgeNotSetAccessChecker(access.AccessChecker): |
| """AccessChecker that ensures that the currently logged-in user has a profile |
| for which neither age nor birth_date is not set. |
| """ |
| |
| def checkAccess(self, data, check): |
| """See AccessChecker.checkAccess for specification.""" |
| access.ensureLoggedIn(data) |
| if data.ndb_profile.age or data.ndb_profile.birth_date: |
| raise exception.Forbidden(message=AGE_ALREADY_SET) |
| |
| AGE_NOT_SET_ACCESS_CHECKER = AgeNotSetAccessChecker() |
| |
| |
| PROFILE_AGE_CONFIRMATION_ACCESS_CHECKER = access.ConjuctionAccessChecker([ |
| access.STUDENT_PROFILE_ACCESS_CHECKER, |
| AGE_NOT_SET_ACCESS_CHECKER]) |
| |
| class ProfileAgeConfirmationHandler(base.RequestHandler): |
| """Handler to set age for students.""" |
| |
| access_checker = PROFILE_AGE_CONFIRMATION_ACCESS_CHECKER |
| |
| def __init__(self, initializer, linker, renderer, error_handler, |
| url_pattern_constructor, url_names): |
| """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. |
| """ |
| super(ProfileAgeConfirmationHandler, self).__init__( |
| initializer, linker, renderer, error_handler) |
| self.url_pattern_constructor = url_pattern_constructor |
| self.url_names = url_names |
| |
| |
| def djangoURLPatterns(self): |
| """See base.RequestHandler.djangoURLPatterns for specification.""" |
| return [ |
| self.url_pattern_constructor.construct( |
| r'profile/age_confirmation/%s$' % url_patterns.PROGRAM, |
| self, name=self.url_names.PROFILE_AGE_CONFIRMATION), |
| ] |
| |
| def post(self, data, check, mutator): |
| """See base.RequestHandler.post for specification.""" |
| if 'age' not in data.POST: |
| raise exception.BadRequest(message='Age not present in POST data.') |
| |
| age = data.POST['age'] |
| if not age.isdigit(): |
| raise exception.BadRequest(message='Age must be a number: %s' % age) |
| |
| age = int(age) |
| if data.program.student_min_age and age < data.program.student_min_age: |
| raise exception.BadRequest(message='Age is too low: %s' % age) |
| elif data.program.student_max_age and age > data.program.student_max_age: |
| raise exception.BadRequest(message='Age is too large: %s' % age) |
| else: |
| editProfileTxn(data.ndb_profile.key, {'age': age}) |
| return http.HttpResponse() |
| |
| |
| @ndb.transactional |
| def createProfileTxn( |
| program_key, profile_properties, username=None, user=None, |
| persist_age_only=None, models=types.MELANGE_MODELS): |
| """Creates a new user profile based on the specified properties. |
| |
| Args: |
| program_key: Program key. |
| profile_properties: A dict mapping profile properties to their values. |
| username: Username for a new User entity that will be created along with |
| the new Profile. May only be passed if user argument is omitted. |
| user: User entity for the profile. May only be passed if username argument |
| is omitted. |
| persist_age_only: If specified and equals True, only age of the student is |
| going to be persisted instead of the full birth date, even if it is |
| present the dictionary with properties. |
| models: instance of types.Models that represent appropriate models. |
| """ |
| if username and user: |
| raise ValueError('Username and user arguments cannot be set together.') |
| elif not (username or user): |
| raise ValueError('Exactly one of username or user argument must be set.') |
| |
| if username: |
| result = user_logic.createUser(username) |
| if not result: |
| raise exception.BadRequest(message=result.extra) |
| else: |
| user = result.extra |
| |
| result = profile_logic.createProfile( |
| user.key, program_key, profile_properties, |
| persist_age_only=persist_age_only, models=models) |
| if not result: |
| raise exception.BadRequest(message=result.extra) |
| else: |
| # create a signature for the accepted Terms Of Service document |
| if result.extra.accepted_tos: |
| signature_logic.createSignature( |
| result.extra.accepted_tos[0], result.extra) |
| return result.extra |
| |
| |
| @ndb.transactional |
| def editProfileTxn(profile_key, profile_properties, accepted_tos=None): |
| """Edits an existing profile based on the specified properties. |
| |
| Args: |
| profile_key: Profile key of an existing profile to edit. |
| profile_properties: A dict mapping profile properties to their values. |
| accepted_tos: Optional ndb.Key of the Terms Of Service document that was |
| accepted by the user and for which a signature must be created. |
| """ |
| result = profile_logic.editProfile(profile_key, profile_properties) |
| if not result: |
| raise exception.BadRequest(message=result.extra) |
| else: |
| if accepted_tos: |
| signature_logic.createSignature(accepted_tos, result.extra) |
| return result.extra |