| # Copyright 2013 the Melange authors. |
| # |
| # Licensed under the Apache License, Version 2.0 (the "License"); |
| # you may not use this file except in compliance with the License. |
| # You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| # See the License for the specific language governing permissions and |
| # limitations under the License. |
| |
| """Module containing the profile related views for Summer Of Code.""" |
| |
| import collections |
| |
| from google.appengine.ext import blobstore |
| 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 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 readonly |
| from melange.utils import countries |
| from melange.views.helper import form_handler |
| |
| from soc.logic import cleaning |
| from soc.logic import validate |
| from soc.models import universities |
| |
| from soc.views import forms as soc_forms |
| from soc.views.helper import url_patterns |
| |
| from soc.modules.gsoc.views import base |
| from soc.modules.gsoc.views import forms as gsoc_forms |
| from soc.modules.gsoc.views.helper import url_patterns as soc_url_patterns |
| |
| from summerofcode.logic import profile as soc_profile_logic |
| from summerofcode.templates import tabs |
| from summerofcode.templates import top_message |
| from summerofcode.views.helper import urls |
| |
| |
| _ALPHANUMERIC_CHARACTERS_ONLY = unicode( |
| 'Please use alphanumeric characters (A-z, 0-9) and whitespaces only.') |
| |
| PROFILE_ORG_MEMBER_CREATE_PAGE_NAME = translation.ugettext( |
| 'Create organization member profile') |
| |
| PROFILE_STUDENT_CREATE_PAGE_NAME = translation.ugettext( |
| 'Create student profile') |
| |
| PROFILE_EDIT_PAGE_NAME = translation.ugettext( |
| 'Edit profile') |
| |
| # names of structures to group related fields together |
| _PUBLIC_INFORMATION_GROUP = translation.ugettext('1. Public 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 Of Service') |
| |
| 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( |
| 'Human-readable name (UTF-8) 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. Please note this information is kept ' |
| 'private and visible only to program administrators.') |
| |
| PHONE_HELP_TEXT = translation.ugettext( |
| 'Phone number of the participant. Please use digits only and remember ' |
| 'to include the country code. Please note this information is kept ' |
| 'private and visible only to program administrators who may need to ' |
| 'contact you occasionally.') |
| |
| 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( |
| 'Please 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. Please note 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.' |
| '<br>' |
| '<a href="' |
| 'https://docs.google.com/file/d/0B7BZHIBakF7POVIwWXJHYmkwOTg/edit">' |
| 'Men\'s</a> and <a href="' |
| 'https://docs.google.com/file/d/0B7BZHIBakF7PWUZmWjAxYnVFQmc/edit">' |
| 'Women\'s</a> size charts are available to help you choose.' |
| ) |
| |
| GENDER_HELP_TEXT = translation.ugettext( |
| 'Gender information of the participant. Please note this information' |
| 'is kept private and visible only to program administrators for ' |
| 'statistical purposes.') |
| |
| PROGRAM_KNOWLEDGE_HELP_TEXT = translation.ugettext( |
| 'Please 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( |
| 'Please enter the full name of your school, college or university in ' |
| 'this field. Please use the complete formal name of your school, e.g. ' |
| 'University of California at Berkeley, instead of Cal or UCB. It would ' |
| 'be most wonderful if you could provide your school\'s name in English, ' |
| 'as all the program administrators speak English as their first ' |
| 'language and it will make it much easier for us to assemble program ' |
| 'statistics, etc., later if we can easily read the name of your school. ' |
| 'Also, please try to select your school\'s name from the dropdown. If ' |
| 'your school is not listed, just enter it manually.') |
| |
| 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.') |
| |
| EXPECTED_GRADUATION_HELP_TEXT = translation.ugettext( |
| 'Please 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') |
| |
| 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( |
| 'I have read and agree to the terms of service') |
| |
| 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') |
| |
| 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 of Service') |
| |
| INSUFFICIENT_AGE = translation.ugettext( |
| 'Your age does not allow you to participate in the program.') |
| |
| _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_XXS_ID = 'xxs' |
| _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_XXS_ID, 'XXS'), |
| (_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_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_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', |
| 'expected_graduation'] |
| |
| 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): |
| """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. |
| |
| 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 not validate.isAgeSufficientForProgram(birth_date, program): |
| raise django_forms.ValidationError(INSUFFICIENT_AGE) |
| else: |
| return birth_date |
| |
| |
| class _UserProfileForm(gsoc_forms.GSoCModelForm): |
| """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, 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=[('', 'Not Applicable')] + [ |
| (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_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_SIZE_CHOICES)) |
| |
| gender = django_forms.CharField( |
| required=True, label=GENDER_LABEL, help_text=GENDER_HELP_TEXT, |
| widget=django_forms.Select(choices=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, 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=False, 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)) |
| |
| # 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) |
| |
| Meta = object |
| |
| def __init__(self, program, terms_of_service=None, |
| has_student_data=None, **kwargs): |
| """Initializes a new form. |
| |
| Args: |
| program: Program entity for which a profile form is constructed. |
| 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. |
| """ |
| super(_UserProfileForm, self).__init__(**kwargs) |
| self.program = program |
| self.terms_of_service = terms_of_service |
| self.has_student_data = has_student_data |
| |
| # group public information related fields together |
| self.fields['public_name'].group = _PUBLIC_INFORMATION_GROUP |
| self.fields['web_page'].group = _PUBLIC_INFORMATION_GROUP |
| self.fields['blog'].group = _PUBLIC_INFORMATION_GROUP |
| self.fields['photo_url'].group = _PUBLIC_INFORMATION_GROUP |
| |
| # 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: |
| self.fields['terms_of_service'].widget = soc_forms.TOSWidget( |
| self.terms_of_service.content) |
| 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['expected_graduation'].group = _EDUCATION_GROUP |
| |
| 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.program) |
| |
| 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_XXS_ID, profile_model.TeeSize.XXS), |
| (_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) |
| ) |
| _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): |
| """Adopts 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. |
| |
| 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']]) |
| |
| if 'terms_of_service' in form_data: |
| properties[profile_model.Profile.accepted_tos._name] = ( |
| [form_data.get('terms_of_service')]) |
| |
| return properties |
| |
| |
| def _adaptStudentDataPropertiesForDatastore(form_data): |
| """Adopts 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')] |
| expected_graduation = form_data.get('expected_graduation') |
| major = form_data.get('major') |
| web_page = form_data.get('school_web_page') |
| |
| result = education_logic.createPostSecondaryEducation( |
| school_id, school_country, expected_graduation, major, degree, web_page) |
| if not result: |
| raise exception.BadRequest(message=result.extra) |
| else: |
| return {profile_model.StudentData.education._name: result.extra} |
| |
| |
| def _adoptContactPropertiesForForm(contact_properties): |
| """Adopts 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 _adoptResidentialAddressPropertiesForForm(address_properties): |
| """Adopts 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 _adoptShippingAddressPropertiesForForm(address_properties): |
| """Adopts 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 _adoptStudentDataPropertiesForForm(student_data_properties): |
| """Adopts 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)), |
| 'expected_graduation': education.get( |
| education_model.Education.expected_graduation._name), |
| } |
| |
| |
| def _adoptProfilePropertiesForForm(profile_properties): |
| """Adopts 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. |
| |
| 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']} |
| |
| # residential address information |
| form_data.update( |
| _adoptResidentialAddressPropertiesForForm( |
| profile_properties[profile_model.Profile.residential_address._name])) |
| |
| # shipping address information |
| form_data.update( |
| _adoptShippingAddressPropertiesForForm( |
| profile_properties[profile_model.Profile.shipping_address._name])) |
| |
| # contact information |
| if profile_model.Profile.contact._name in profile_properties: |
| form_data.update(_adoptContactPropertiesForForm( |
| 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(_adoptStudentDataPropertiesForForm( |
| profile_properties[profile_model.Profile.student_data._name])) |
| |
| return form_data |
| |
| |
| def _profileFormToRegisterProfile( |
| register_user, program, terms_of_service, has_student_data=None, **kwargs): |
| """Returns a Django form to register a new profile. |
| |
| Args: |
| register_user: If set to True, the constructed form will also be used to |
| create a new User entity along with a new Profile entity. |
| program: Program entity for which a profile form is constructed. |
| 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. |
| |
| Returns: |
| _UserProfileForm adjusted to create a new profile. |
| """ |
| form = _UserProfileForm( |
| program, terms_of_service=terms_of_service, |
| has_student_data=has_student_data, **kwargs) |
| |
| if not register_user: |
| del form.fields['user_id'] |
| |
| return form |
| |
| |
| def _profileFormToEditProfile(program, has_student_data=None, **kwargs): |
| """Returns a Django form to edit an existing profile. |
| |
| Args: |
| program: Program entity for which a profile form is constructed. |
| has_student_data: If specified to True, the form will contain fields |
| related to student data for the profile. |
| |
| Returns: |
| _UserProfileForm adjusted to edit an existing profile. |
| """ |
| form = _UserProfileForm(program, has_student_data=has_student_data, **kwargs) |
| |
| del form.fields['user_id'] |
| |
| return form |
| |
| |
| class ProfileRegisterAsOrgMemberPage(base.GSoCRequestHandler): |
| """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 templatePath(self): |
| """See base.RequestHandler.templatePath for specification.""" |
| return 'summerofcode/profile/profile_edit.html' |
| |
| def djangoURLPatterns(self): |
| """See base.RequestHandler.djangoURLPatterns for specification.""" |
| return [ |
| soc_url_patterns.url( |
| r'profile/register/org_member/%s$' % url_patterns.PROGRAM, |
| self, name=urls.UrlNames.PROFILE_REGISTER_AS_ORG_MEMBER)] |
| |
| def context(self, data, check, mutator): |
| """See base.RequestHandler.context for specification.""" |
| form = _profileFormToRegisterProfile( |
| data.ndb_user is None, data.program, data.program.org_admin_agreement, |
| 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), |
| } |
| |
| def post(self, data, check, mutator): |
| """See base.RequestHandler.post for specification.""" |
| form = _profileFormToRegisterProfile( |
| data.ndb_user is None, data.program, |
| data.program.org_admin_agreement, data=data.POST) |
| |
| # TODO(daniel): eliminate passing self object. |
| handler = CreateProfileFormHandler(self, form) |
| return handler.handle(data, check, mutator) |
| |
| |
| class ProfileRegisterAsStudentPage(base.GSoCRequestHandler): |
| """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. |
| """ |
| |
| access_checker = access.ConjuctionAccessChecker([ |
| access.HAS_NO_PROFILE_ACCESS_CHECKER, |
| access.STUDENT_SIGNUP_ACTIVE_ACCESS_CHECKER]) |
| |
| def templatePath(self): |
| """See base.RequestHandler.templatePath for specification.""" |
| return 'summerofcode/profile/profile_edit.html' |
| |
| def djangoURLPatterns(self): |
| """See base.RequestHandler.djangoURLPatterns for specification.""" |
| return [ |
| soc_url_patterns.url( |
| r'profile/register/student/%s$' % url_patterns.PROGRAM, |
| self, name=urls.UrlNames.PROFILE_REGISTER_AS_STUDENT)] |
| |
| def context(self, data, check, mutator): |
| """See base.RequestHandler.context for specification.""" |
| form = _profileFormToRegisterProfile( |
| data.ndb_user is None, data.program, data.program.student_agreement, |
| has_student_data=True, data=data.POST) |
| |
| return { |
| 'page_name': PROFILE_STUDENT_CREATE_PAGE_NAME, |
| 'forms': [form], |
| 'error': bool(form.errors) |
| } |
| |
| def post(self, data, check, mutator): |
| """See base.RequestHandler.post for specification.""" |
| form = _profileFormToRegisterProfile( |
| data.ndb_user is None, data.program, data.program.org_admin_agreement, |
| has_student_data=True, data=data.POST) |
| |
| # TODO(daniel): eliminate passing self object. |
| handler = CreateProfileFormHandler(self, form) |
| 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): |
| """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. |
| """ |
| super(CreateProfileFormHandler, self).__init__(view) |
| self.form = form |
| |
| 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, |
| models=data.models) |
| |
| url = links.LINKER.program(data.program, urls.UrlNames.PROFILE_EDIT) |
| return http.HttpResponseRedirect(url + '?validated=true') |
| |
| |
| class ProfileEditPage(base.GSoCRequestHandler): |
| """View to edit user profiles.""" |
| |
| access_checker = access.HAS_PROFILE_ACCESS_CHECKER |
| |
| def templatePath(self): |
| """See base.RequestHandler.templatePath for specification.""" |
| return 'summerofcode/profile/profile_edit.html' |
| |
| def djangoURLPatterns(self): |
| """See base.RequestHandler.djangoURLPatterns for specification.""" |
| return [ |
| soc_url_patterns.url( |
| r'profile/edit/%s$' % url_patterns.PROGRAM, |
| self, name=urls.UrlNames.PROFILE_EDIT)] |
| |
| def context(self, data, check, mutator): |
| """See base.RequestHandler.context for specification.""" |
| form_data = _adoptProfilePropertiesForForm(data.ndb_profile.to_dict()) |
| form = _profileFormToEditProfile( |
| data.program, data=data.POST or form_data, |
| has_student_data=data.ndb_profile.is_student) |
| |
| return { |
| 'page_name': PROFILE_EDIT_PAGE_NAME, |
| 'forms': [form], |
| 'error': bool(form.errors) |
| } |
| |
| def post(self, data, check, mutator): |
| """See base.RequestHandler.post for specification.""" |
| form = _profileFormToEditProfile( |
| data.program, data=data.POST, |
| has_student_data=data.ndb_profile.is_student) |
| |
| 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) |
| |
| |
| editProfileTxn(data.ndb_profile.key, profile_properties) |
| |
| url = links.LINKER.program(data.program, urls.UrlNames.PROFILE_EDIT) |
| return http.HttpResponseRedirect(url + '?validated=true') |
| |
| def jsonContext(self, data, check, mutator): |
| """See base.RequestHandler.jsonContext for specification.""" |
| return universities.UNIVERSITIES |
| |
| |
| class ProfileShowPage(base.GSoCRequestHandler): |
| """View to display the read-only profile page.""" |
| |
| access_checker = access.HAS_PROFILE_ACCESS_CHECKER |
| |
| def djangoURLPatterns(self): |
| """See base.RequestHandler.djangoURLPatterns for specification.""" |
| return [ |
| soc_url_patterns.url( |
| r'profile/show/%s$' % url_patterns.PROGRAM, |
| self, name=urls.UrlNames.PROFILE_SHOW)] |
| |
| def templatePath(self): |
| """See base.RequestHandler.templatePath for specification.""" |
| return 'summerofcode/profile/profile_show.html' |
| |
| def context(self, data, check, mutator): |
| """See soc.views.base.RequestHandler.context for specification.""" |
| profile_template = _getShowProfileTemplate(data, data.ndb_profile) |
| |
| return { |
| 'page_name': '%s Profile - %s' % ( |
| data.program.short_name, data.ndb_profile.public_name), |
| 'program_name': data.program.name, |
| 'profile_template': profile_template, |
| 'tabs': tabs.profileTabs( |
| data, selected_tab_id=tabs.VIEW_PROFILE_TAB_ID) |
| } |
| |
| |
| class ProfileAdminPage(base.GSoCRequestHandler): |
| """View to display the read-only profile page to program administrators.""" |
| |
| access_checker = access.PROGRAM_ADMINISTRATOR_ACCESS_CHECKER |
| |
| def djangoURLPatterns(self): |
| """See base.RequestHandler.djangoURLPatterns for specification.""" |
| return [ |
| soc_url_patterns.url( |
| r'profile/admin/%s$' % url_patterns.PROFILE, |
| self, name=urls.UrlNames.PROFILE_ADMIN)] |
| |
| def templatePath(self): |
| """See base.RequestHandler.templatePath for specification.""" |
| return 'summerofcode/profile/profile_show.html' |
| |
| def context(self, data, check, mutator): |
| """See soc.views.base.RequestHandler.context for specification.""" |
| profile_template = _getShowProfileTemplate(data, data.url_ndb_profile) |
| |
| return { |
| 'page_name': '%s Profile - %s' % ( |
| data.program.short_name, data.url_ndb_profile.public_name), |
| 'program_name': data.program.name, |
| 'profile_template': profile_template, |
| 'submit_tax_link': links.LINKER.profile( |
| data.url_ndb_profile, 'gsoc_tax_form_admin'), |
| 'submit_enrollment_link': links.LINKER.profile( |
| data.url_ndb_profile, 'gsoc_enrollment_form_admin') |
| } |
| |
| |
| def _getShowProfileTemplate(data, profile): |
| """Returns a template to show the specified profile. |
| |
| Args: |
| data: request_data.RequestData object for the current request. |
| profile: Profile entity. |
| |
| Returns: |
| Instance of readonly.Readonly to show the specified project. |
| """ |
| groups = [] |
| |
| fields = collections.OrderedDict() |
| fields[USER_ID_LABEL] = profile.key.parent().id() |
| fields[PUBLIC_NAME_LABEL] = profile.public_name |
| fields[WEB_PAGE_LABEL] = profile.contact.web_page |
| fields[BLOG_LABEL] = profile.contact.blog |
| fields[PHOTO_URL_LABEL] = profile.photo_url |
| groups.append(readonly.Group(_PUBLIC_INFORMATION_GROUP, fields.items())) |
| |
| fields = collections.OrderedDict() |
| fields[FIRST_NAME_LABEL] = profile.first_name |
| fields[LAST_NAME_LABEL] = profile.last_name |
| fields[EMAIL_LABEL] = profile.contact.email |
| fields[PHONE_LABEL] = profile.contact.phone |
| groups.append(readonly.Group(_CONTACT_GROUP, fields.items())) |
| |
| fields = collections.OrderedDict() |
| fields[RESIDENTIAL_STREET_LABEL] = _streetValue(profile.residential_address) |
| fields[RESIDENTIAL_CITY_LABEL] = profile.residential_address.city |
| fields[RESIDENTIAL_PROVINCE_LABEL] = ( |
| profile.residential_address.province) |
| fields[RESIDENTIAL_POSTAL_CODE_LABEL] = ( |
| profile.residential_address.postal_code) |
| fields[RESIDENTIAL_COUNTRY_LABEL] = ( |
| profile.residential_address.country) |
| groups.append(readonly.Group(_RESIDENTIAL_ADDRESS_GROUP, fields.items())) |
| |
| if profile.shipping_address: |
| fields = collections.OrderedDict() |
| fields[SHIPPING_STREET_LABEL] = _streetValue(profile.shipping_address) |
| fields[SHIPPING_CITY_LABEL] = profile.shipping_address.city |
| fields[SHIPPING_PROVINCE_LABEL] = ( |
| profile.shipping_address.province) |
| fields[SHIPPING_POSTAL_CODE_LABEL] = ( |
| profile.shipping_address.postal_code) |
| fields[SHIPPING_COUNTRY_LABEL] = ( |
| profile.shipping_address.country) |
| groups.append(readonly.Group(_SHIPPING_ADDRESS_GROUP, fields.items())) |
| |
| fields = collections.OrderedDict() |
| fields[BIRTH_DATE_LABEL] = profile.birth_date |
| fields[TEE_STYLE_LABEL] = profile.tee_style |
| fields[TEE_SIZE_LABEL] = profile.tee_size |
| fields[GENDER_LABEL] = _GENDER_ENUM_TO_VERBOSE_MAP[profile.gender] |
| fields[PROGRAM_KNOWLEDGE_LABEL] = profile.program_knowledge |
| groups.append(readonly.Group(_OTHER_INFORMATION_GROUP, fields.items())) |
| |
| if profile.is_student: |
| fields = collections.OrderedDict() |
| fields[ENROLLMENT_FORM_LABEL] = _getEnrollmentFormItemValue(data, profile) |
| fields[TAX_FORM_LABEL] = _getTaxFormItemValue(data, profile) |
| |
| groups.append(readonly.Group(_FORMS_GROUP, fields.items())) |
| |
| return readonly.Readonly( |
| data, 'summerofcode/_readonly_template.html', groups) |
| |
| |
| def _getEnrollmentFormItemValue(data, profile): |
| """Returns a value to be displayed for Enrollment Form item in readonly |
| views for profiles. |
| |
| Args: |
| data: request_data.RequestData object for the current request. |
| profile: Profile entity. |
| |
| Returns: |
| A string to be rendered for Enrollment Form item. |
| """ |
| if data.ndb_profile.key == profile.key: |
| # the form may be still re-uploaded provided the program has not ended. |
| upload_url = ( |
| links.ABSOLUTE_LINKER.program( |
| data.program, 'gsoc_enrollment_form', secure=True) |
| if data.timeline.programActive() |
| else None) |
| download_url = links.ABSOLUTE_LINKER.program( |
| data.program, 'gsoc_enrollment_form_download') |
| else: |
| upload_url = links.ABSOLUTE_LINKER.profile( |
| profile, 'gsoc_enrollment_form_admin', secure=True) |
| download_url = links.ABSOLUTE_LINKER.profile( |
| profile, 'gsoc_enrollment_form_download_admin') |
| |
| if profile.student_data.enrollment_form: |
| blob_info = blobstore.BlobInfo.get(profile.student_data.enrollment_form) |
| return _formSubmittedValue(blob_info.filename, download_url, upload_url) |
| else: |
| return _formNotSubmittedValue(upload_url) |
| |
| |
| def _getTaxFormItemValue(data, profile): |
| """Returns a value to be displayed for Tax Form item in readonly |
| views for profiles. |
| |
| Args: |
| data: request_data.RequestData object for the current request. |
| profile: Profile entity. |
| |
| Returns: |
| A string to be rendered for Tax Form item. |
| """ |
| if data.is_host: |
| upload_url = links.ABSOLUTE_LINKER.profile( |
| profile, 'gsoc_tax_form_admin', secure=True) |
| download_url = links.ABSOLUTE_LINKER.profile( |
| profile, 'gsoc_tax_form_download_admin') |
| else: |
| # the form may be still re-uploaded provided the program has not ended. |
| upload_url = ( |
| links.ABSOLUTE_LINKER.program( |
| data.program, 'gsoc_tax_form', secure=True) |
| if data.timeline.programActive() |
| and data.timeline.afterFormSubmissionStart() |
| else None) |
| download_url = links.ABSOLUTE_LINKER.program( |
| data.program, 'gsoc_tax_form_download') |
| |
| if profile.student_data.tax_form: |
| blob_info = blobstore.BlobInfo.get(profile.student_data.tax_form) |
| return _formSubmittedValue(blob_info.filename, download_url, upload_url) |
| elif (not data.timeline.afterFormSubmissionStart() |
| or not soc_profile_logic.hasProject(profile)): |
| return _formNotRequired() |
| else: |
| return _formNotSubmittedValue(upload_url) |
| |
| |
| def _streetValue(address): |
| if not address.street_extra: |
| return address.street |
| else: |
| return html.format_html( |
| _STREET_VALUE % (address.street, address.street_extra)) |
| |
| |
| def _formSubmittedValue(filename, download_url, upload_url): |
| """Returns a value to be displayed for a form which has already |
| been submitted. |
| |
| Args: |
| filename: A string containing name of the uploaded file. |
| download_url: A string containing URL to download the uploaded file. |
| upload_url: An string containing URL to re-upload the form. A link |
| is not included, if this parameter is set to None. |
| |
| Returns: |
| A string to be displayed for the form. |
| """ |
| return ( |
| html.format_html( |
| _FORM_SUBMITTED_UPLOAD_FORM_OPTION % ( |
| filename, html.mark_safe(upload_url), |
| html.mark_safe(download_url))) |
| if upload_url |
| else html.format_html( |
| _FORM_SUBMITTED_NO_UPLOAD_FORM_OPTION % ( |
| filename, html.mark_safe(download_url)))) |
| |
| |
| def _formNotSubmittedValue(upload_url=None): |
| """Returns a value to be displayed for a form which has not been submitted. |
| |
| Args: |
| upload_url: A string containing URL to upload the form. A link is not |
| included, if this parameter is set to None. |
| |
| Returns: |
| A string to be displayed for the form. |
| """ |
| return ( |
| html.format_html( |
| _NO_FORM_SUBMITTED_UPLOAD_FORM_OPTION % html.mark_safe(upload_url)) |
| if upload_url |
| else _NO_FORM_SUBMITTED) |
| |
| |
| def _formNotRequired(): |
| """Returns a value to be displayed for a form when it is not required |
| at this time. |
| |
| Returns: |
| A string to be displayed for the form. |
| """ |
| return _NO_FORM_REQUIRED |
| |
| |
| 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()) |
| |
| 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 |
| |
| |
| @ndb.transactional |
| def createProfileTxn( |
| program_key, profile_properties, username=None, user=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. |
| 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, models=models) |
| if not result: |
| raise exception.BadRequest(message=result.extra) |
| else: |
| return result.extra |
| |
| |
| @ndb.transactional |
| def editProfileTxn(profile_key, profile_properties): |
| """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. |
| """ |
| result = profile_logic.editProfile(profile_key, profile_properties) |
| if not result: |
| raise exception.BadRequest(message=result.extra) |
| else: |
| return result.extra |