| # Copyright 2008 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. |
| |
| """Generic cleaning methods. |
| """ |
| |
| |
| import re |
| |
| from HTMLParser import HTMLParseError |
| |
| from html5lib import HTMLParser |
| from html5lib import sanitizer |
| from html5lib.html5parser import ParseError |
| |
| from google.appengine.api import users |
| |
| from django import forms |
| from django.core import validators |
| from django.utils.translation import ugettext |
| |
| from soc.models.user import User |
| from soc.logic import validate |
| from soc.logic import user |
| |
| |
| DEF_VALID_SHIPPING_CHARS = re.compile('^[A-Za-z0-9\s-]+$') |
| |
| DEF_LINK_ID_IN_USE = ugettext( |
| 'This username is already in use, please specify another one') |
| |
| DEF_NO_RIGHTS_FOR_ACL = ugettext( |
| 'You do not have the required rights for that ACL.') |
| |
| DEF_ORGANZIATION_NOT_ACTIVE = ugettext( |
| "This organization is not active or doesn't exist.") |
| |
| DEF_NO_SUCH_DOCUMENT = ugettext( |
| "There is no such document with that username under this entity.") |
| |
| DEF_MUST_BE_ABOVE_AGE_LIMIT = ugettext( |
| "To sign up as a student for this program, you " |
| "must be at least %d years of age, as of %s.") |
| |
| DEF_MUST_BE_ABOVE_LIMIT = ugettext( |
| "Must be at least %d characters, it has %d characters.") |
| |
| DEF_MUST_BE_UNDER_LIMIT = ugettext( |
| "Must be under %d characters, it has %d characters.") |
| |
| DEF_2_LETTER_STATE = ugettext( |
| "State should be 2-letter field since country is '%s'.") |
| |
| |
| DEF_INVALID_SHIPPING_CHARS = ugettext( |
| 'Invalid characters, only A-z, 0-9, - and whitespace are allowed. ' |
| 'See also <a href="http://code.google.com/p/soc/issues/detail?id=903">' |
| 'Issue 903</a>, in particular <a href="' |
| 'http://code.google.com/p/soc/issues/detail?id=903#c16">comment 16</a>. ' |
| 'Please <em>do not</em> create a new issue about this.') |
| |
| |
| DEF_ROLE_TARGET_COUNTRY = "United States" |
| |
| DEF_ROLE_COUNTRY_PAIRS = [("res_country", "res_state"), |
| ("ship_country", "ship_state")] |
| |
| |
| def check_field_is_empty(field_name): |
| """Returns decorator that bypasses cleaning for empty fields. |
| """ |
| |
| def decorator(fun): |
| """Decorator that checks if a field is empty if so doesn't do the cleaning. |
| |
| Note Django will capture errors concerning required fields that are empty. |
| """ |
| from functools import wraps |
| |
| @wraps(fun) |
| def wrapper(self): |
| """Decorator wrapper method. |
| """ |
| field_content = self.cleaned_data.get(field_name) |
| |
| if not field_content: |
| # field has no content so bail out |
| return u'' |
| else: |
| # field has contents |
| return fun(self) |
| return wrapper |
| |
| return decorator |
| |
| |
| def clean_empty_field(field_name): |
| """Incorporates the check_field_is_empty as regular cleaner. |
| """ |
| |
| @check_field_is_empty(field_name) |
| def wrapper(self): |
| """Decorator wrapper method. |
| """ |
| return self.cleaned_data.get(field_name) |
| |
| return wrapper |
| |
| |
| def clean_email(field_name): |
| """Checks if the field_name value is in an email format. |
| """ |
| @check_field_is_empty(field_name) |
| def wrapper(self): |
| """Decorator wrapper method. |
| """ |
| # convert to lowercase for user comfort |
| email = self.cleaned_data.get(field_name) |
| validator = validators.validate_email |
| |
| try: |
| validator(email) |
| except forms.ValidationError, e: |
| if e.code == 'invalid': |
| msg = ugettext(u'The email address %s is not valid.' % email) |
| raise forms.ValidationError(msg, code='invalid') |
| return email |
| |
| return wrapper |
| |
| |
| def clean_link_id(field_name): |
| """Checks if the field_name value is in a valid link ID format. |
| """ |
| |
| @check_field_is_empty(field_name) |
| def wrapper(self): |
| """Decorator wrapper method. |
| """ |
| # convert to lowercase for user comfort |
| link_id = self.cleaned_data.get(field_name).lower() |
| if not validate.isLinkIdFormatValid(link_id): |
| raise forms.ValidationError( |
| "The username %s is in wrong format." % link_id, |
| code='invalid') |
| return link_id |
| return wrapper |
| |
| |
| def clean_existing_user(field_name): |
| """Check if the field_name field is a valid user. |
| """ |
| |
| @check_field_is_empty(field_name) |
| def wrapped(self): |
| """Decorator wrapper method. |
| """ |
| link_id = clean_link_id(field_name)(self) |
| |
| user_entity = User.get_by_key_name(link_id) |
| |
| if not user_entity: |
| # user does not exist |
| raise forms.ValidationError("This user does not exist.") |
| |
| return user_entity |
| return wrapped |
| |
| |
| def clean_user_is_current(field_name, as_user=True): |
| """Check if the field_name value is a valid link_id and resembles the |
| current user. |
| """ |
| |
| @check_field_is_empty(field_name) |
| def wrapped(self): |
| """Decorator wrapper method. |
| """ |
| link_id = clean_link_id(field_name)(self) |
| |
| user_entity = user.current() |
| # pylint: disable=E1103 |
| if not user_entity or user_entity.link_id != link_id: |
| # this user is not the current user |
| raise forms.ValidationError("This user is not you.") |
| |
| return user_entity if as_user else link_id |
| return wrapped |
| |
| |
| def clean_user_not_exist(field_name): |
| """Check if the field_name value is a valid link_id and a user with the |
| link id does not exist. |
| """ |
| |
| @check_field_is_empty(field_name) |
| def wrapped(self): |
| """Decorator wrapper method. |
| """ |
| link_id = clean_link_id(field_name)(self) |
| |
| user_entity = User.get_by_key_name(link_id) |
| |
| if user_entity: |
| # user exists already |
| raise forms.ValidationError("There is already a user with this username.") |
| |
| return link_id |
| return wrapped |
| |
| |
| def clean_users_not_same(field_name): |
| """Check if the field_name field is a valid user and is not |
| equal to the current user. |
| """ |
| |
| @check_field_is_empty(field_name) |
| def wrapped(self): |
| """Decorator wrapper method. |
| """ |
| clean_user_field = clean_existing_user(field_name) |
| user_entity = clean_user_field(self) |
| |
| current_user_entity = user.current() |
| # pylint: disable=E1103 |
| if user_entity.key() == current_user_entity.key(): |
| # users are equal |
| raise forms.ValidationError("You cannot enter yourself here.") |
| |
| return user_entity |
| return wrapped |
| |
| |
| def clean_user_account(field_name): |
| """Returns the User with the given field_name value. |
| """ |
| |
| @check_field_is_empty(field_name) |
| def wrapped(self): |
| """Decorator wrapper method. |
| """ |
| email_adress = self.cleaned_data[field_name] |
| return users.User(email_adress) |
| |
| return wrapped |
| |
| |
| def clean_user_account_not_in_use(field_name): |
| """Check if the field_name value contains an email |
| address that hasn't been used for an existing account. |
| """ |
| |
| @check_field_is_empty(field_name) |
| def wrapped(self): |
| """Decorator wrapper method. |
| """ |
| email_adress = self.cleaned_data.get(field_name).lower() |
| |
| # get the user account for this email and check if it's in use |
| user_account = users.User(email_adress) |
| |
| user_entity = User.all().filter('account', user_account).get() |
| |
| if user_entity or user.isFormerAccount(user_account): |
| raise forms.ValidationError("There is already a user " |
| "with this email address.") |
| |
| return user_account |
| return wrapped |
| |
| |
| def clean_valid_shipping_chars(field_name): |
| """Clean method for cleaning a field that must comply with Google's character |
| requirements for shipping. |
| """ |
| |
| @check_field_is_empty(field_name) |
| def wrapper(self): |
| """Decorator wrapper method. |
| """ |
| value = self.cleaned_data.get(field_name) |
| |
| if value and not DEF_VALID_SHIPPING_CHARS.match(value): |
| raise forms.ValidationError(DEF_INVALID_SHIPPING_CHARS) |
| |
| return value |
| return wrapper |
| |
| |
| def clean_content_length(field_name, min_length=0, max_length=500): |
| """Clean method for cleaning a field which must contain at least min and |
| not more then max length characters. |
| |
| Args: |
| field_name: the name of the field needed cleaning |
| min_length: the minimum amount of allowed characters |
| max_length: the maximum amount of allowed characters |
| """ |
| |
| @check_field_is_empty(field_name) |
| def wrapper(self): |
| """Decorator wrapper method. |
| """ |
| |
| value = self.cleaned_data[field_name] |
| value_length = len(value) |
| |
| if value_length < min_length: |
| raise forms.ValidationError(DEF_MUST_BE_ABOVE_LIMIT %( |
| min_length, value_length)) |
| |
| if value_length > max_length: |
| raise forms.ValidationError(DEF_MUST_BE_UNDER_LIMIT %( |
| max_length, value_length)) |
| |
| return value |
| return wrapper |
| |
| |
| def clean_phone_number(field_name): |
| """Clean method for cleaning a field that may only contain numerical values. |
| """ |
| |
| @check_field_is_empty(field_name) |
| def wrapper(self): |
| """Decorator wrapped method. |
| """ |
| |
| value = self.cleaned_data.get(field_name) |
| |
| # allow for a '+' prefix which means '00' |
| if value[0] == '+': |
| value = '00' + value[1:] |
| |
| if not value.isdigit(): |
| raise forms.ValidationError("Only numerical characters are allowed") |
| |
| return value |
| return wrapper |
| |
| |
| def clean_feed_url(field_name): |
| """Clean method for cleaning feed url. |
| """ |
| |
| def wrapper(self): |
| """Decorator wrapped method. |
| """ |
| feed_url = self.cleaned_data.get(field_name) |
| |
| if feed_url == '': |
| # feed url not supplied (which is OK), so do not try to validate it |
| return None |
| |
| if not validate.isFeedURLValid(feed_url): |
| raise forms.ValidationError('This URL is not a valid ATOM or RSS feed.') |
| |
| return feed_url |
| return wrapper |
| |
| |
| def clean_birth_date(field_name): |
| """Clean method for cleaning birth date. |
| |
| Args: |
| field_name: the name of the field needed cleaning |
| program: program entity that the field refers to |
| check_age: whether the birth date should be checked against the minimal |
| date for the program |
| """ |
| |
| def wrapper(form): |
| """Decorator wrapped method. |
| """ |
| birth_date = form.cleaned_data.get(field_name) |
| |
| if form.program and not validate.isAgeSufficientForProgram( |
| birth_date, form.program): |
| raise forms.ValidationError( |
| 'Your age does not allow you to participate in the program.') |
| |
| return birth_date |
| return wrapper |
| |
| |
| def sanitize_html_string(content): |
| """Sanitizes the given html string. |
| |
| Raises: |
| forms.ValidationError in case of an error. |
| """ |
| try: |
| parser = HTMLParser(tokenizer=sanitizer.HTMLSanitizer) |
| parsed = parser.parseFragment(content, encoding='utf-8') |
| cleaned_content = ''.join([tag.toxml() for tag in parsed.childNodes]) |
| except (HTMLParseError, ParseError), msg: |
| raise forms.ValidationError(msg) |
| |
| return cleaned_content |
| |
| |
| def clean_html_content(field_name): |
| """Clean method for cleaning HTML content. |
| """ |
| |
| @check_field_is_empty(field_name) |
| def wrapped(self): |
| """Decorator wrapper method. |
| """ |
| content = self.cleaned_data.get(field_name) |
| |
| # clean_html_content is called when writing data into GAE rather than |
| # when reading data from GAE. This short-circuiting of the sanitizer |
| # only affects html authored by developers. The isDeveloper test for |
| # example allows developers to add javascript. |
| if user.isDeveloper(): |
| return content |
| |
| content = sanitize_html_string(content) |
| |
| return content |
| |
| return wrapped |
| |
| |
| def clean_url(field_name): |
| """Clean method for cleaning a field belonging to a LinkProperty. |
| """ |
| |
| @check_field_is_empty(field_name) |
| def wrapped(self): |
| """Decorator wrapper method. |
| """ |
| |
| value = self.cleaned_data.get(field_name) |
| validator = validators.URLValidator() |
| |
| # call the Django URLField cleaning method to |
| # properly clean/validate this field |
| try: |
| validator(value) |
| except forms.ValidationError, e: |
| if e.code == 'invalid': |
| msg = ugettext(u'Enter a valid URL.') |
| raise forms.ValidationError(msg, code='invalid') |
| return value |
| return wrapped |
| |
| |
| def clean_irc(field_name): |
| """Clean method for cleaning an irc field. |
| """ |
| |
| @check_field_is_empty(field_name) |
| def wrapped(self): |
| """Decorator wrapper method. |
| """ |
| |
| value = self.cleaned_data.get(field_name) |
| validator = validators.URLValidator() |
| |
| to_clean = value |
| |
| if value.startswith("irc://"): |
| to_clean = value.replace("irc://", "http://", 1) |
| |
| # call the Django URLField cleaning method to |
| # properly clean/validate this field |
| try: |
| validator(to_clean) |
| except forms.ValidationError, e: |
| if e.code == 'invalid': |
| msg = ugettext(u'Enter a valid URL or irc:// url.') |
| raise forms.ValidationError(msg, code='invalid') |
| return value |
| return wrapped |
| |
| |
| def clean_mailto(field_name): |
| @check_field_is_empty(field_name) |
| def wrapped(self): |
| """Decorator wrapper method. |
| """ |
| |
| value = self.cleaned_data.get(field_name) |
| validator = validators.URLValidator() |
| |
| to_clean = value |
| |
| if value.startswith("mailto:"): |
| to_clean = value.replace("mailto:", "", 1) |
| validator = validators.validate_email |
| |
| # call the Django URLField cleaning method to |
| # properly clean/validate this field |
| try: |
| validator(to_clean) |
| except forms.ValidationError, e: |
| if e.code == 'invalid': |
| msg = ugettext(u'Enter a valid URL or mailto: link.') |
| raise forms.ValidationError(msg, code='invalid') |
| return value |
| return wrapped |
| |
| def str2set(string_field, separator=','): |
| """Clean method for cleaning comma separated strings. |
| |
| Obtains the separated string from the form and returns it as |
| a set of strings. |
| """ |
| |
| def wrapper(self): |
| """Decorator wrapper method. |
| """ |
| cleaned_data = self.cleaned_data |
| |
| string_data = cleaned_data.get(string_field) |
| |
| list_data = [] |
| for string in string_data.split(separator): |
| string_strip = string.strip() |
| if string_strip and string_strip not in list_data: |
| list_data.append(string_strip) |
| |
| return list_data |
| |
| return wrapper |