Repository: OCHA-DAP/hdx-ckan

Issue:
Prod bug: signup was not possible for different email addresses
Reported by user
It seems like there is a timeout when doing the email validation step, more precisely when checking the DNS lookup for the hostname. 
A quick fix might be to put check_mx=False  (instead of True).



Discussion:

Resolving patch:
diff --git a/ckan/logic/validators.py b/ckan/logic/validators.py
--- a/ckan/logic/validators.py
+++ b/ckan/logic/validators.py
@@ -19,6 +19,7 @@
 Missing = df.Missing
 missing = df.missing
 
+
 def owner_org_validator(key, data, errors, context):
 
     value = data.get(key)
@@ -32,13 +33,14 @@ def owner_org_validator(key, data, errors, context):
     model = context['model']
     user = context['user']
     user = model.User.get(user)
-    if value == '' :
+    if value == '':
         if not new_authz.check_config_permission('create_unowned_dataset'):
             raise Invalid(_('A organization must be supplied'))
         package = context.get('package')
         # only sysadmins can remove datasets from org
         if package and package.owner_org and not user.sysadmin:
-            raise Invalid(_('You cannot remove a dataset from an existing organization'))
+            raise Invalid(
+                _('You cannot remove a dataset from an existing organization'))
         return
 
     group = model.Group.get(value)
@@ -60,6 +62,7 @@ def package_id_not_changed(value, context):
                       'This key is read-only' % (package.id, value))
     return value
 
+
 def int_validator(value, context):
     if isinstance(value, int):
         return value
@@ -70,18 +73,21 @@ def int_validator(value, context):
     except (AttributeError, ValueError), e:
         raise Invalid(_('Invalid integer'))
 
+
 def natural_number_validator(value, context):
     value = int_validator(value, context)
     if value < 0:
         raise Invalid(_('Must be a natural number'))
     return value
 
+
 def is_positive_integer(value, context):
     value = int_validator(value, context)
     if value < 1:
         raise Invalid(_('Must be a postive integer'))
     return value
 
+
 def boolean_validator(value, context):
     if isinstance(value, bool):
         return value
@@ -89,6 +95,7 @@ def boolean_validator(value, context):
         return True
     return False
 
+
 def isodate(value, context):
     if isinstance(value, datetime.datetime):
         return value
@@ -100,6 +107,7 @@ def isodate(value, context):
         raise Invalid(_('Date format incorrect'))
     return date
 
+
 def no_http(value, context):
 
     model = context['model']
@@ -109,6 +117,7 @@ def no_http(value, context):
         raise Invalid(_('No links are allowed in the log_message.'))
     return value
 
+
 def package_id_exists(value, context):
 
     model = context['model']
@@ -119,6 +128,7 @@ def package_id_exists(value, context):
         raise Invalid('%s: %s' % (_('Not found'), _('Dataset')))
     return value
 
+
 def package_name_exists(value, context):
 
     model = context['model']
@@ -130,6 +140,7 @@ def package_name_exists(value, context):
         raise Invalid(_('Not found') + ': %s' % value)
     return value
 
+
 def package_id_or_name_exists(package_id_or_name, context):
     '''Return the given package_id_or_name if such a package exists.
 
@@ -145,13 +156,14 @@ def package_id_or_name_exists(package_id_or_name, context):
         return package_id_or_name
 
     result = session.query(model.Package).filter_by(
-            name=package_id_or_name).first()
+        name=package_id_or_name).first()
 
     if not result:
         raise Invalid('%s: %s' % (_('Not found'), _('Dataset')))
 
     return package_id_or_name
 
+
 def user_id_exists(user_id, context):
     """Raises Invalid if the given user_id does not exist in the model given
     in the context, otherwise returns the given user_id.
@@ -165,6 +177,7 @@ def user_id_exists(user_id, context):
         raise Invalid('%s: %s' % (_('Not found'), _('User')))
     return user_id
 
+
 def user_id_or_name_exists(user_id_or_name, context):
     '''Return the given user_id_or_name if such a user exists.
 
@@ -182,6 +195,7 @@ def user_id_or_name_exists(user_id_or_name, context):
         raise Invalid('%s: %s' % (_('Not found'), _('User')))
     return user_id_or_name
 
+
 def group_id_exists(group_id, context):
     """Raises Invalid if the given group_id does not exist in the model given
     in the context, otherwise returns the given group_id.
@@ -209,6 +223,7 @@ def related_id_exists(related_id, context):
         raise Invalid('%s: %s' % (_('Not found'), _('Related')))
     return related_id
 
+
 def group_id_or_name_exists(reference, context):
     """
     Raises Invalid if a group identified by the name or id cannot be found.
@@ -219,6 +234,7 @@ def group_id_or_name_exists(reference, context):
         raise Invalid(_('That group name or ID does not exist.'))
     return reference
 
+
 def activity_type_exists(activity_type):
     """Raises Invalid if there is no registered activity renderer for the
     given activity_type. Otherwise returns the given activity_type.
@@ -232,6 +248,7 @@ def activity_type_exists(activity_type):
     else:
         raise Invalid('%s: %s' % (_('Not found'), _('Activity type')))
 
+
 def resource_id_exists(value, context):
 
     model = context['model']
@@ -245,24 +262,25 @@ def resource_id_exists(value, context):
 # A dictionary mapping activity_type values from activity dicts to functions
 # for validating the object_id values from those same activity dicts.
 object_id_validators = {
-    'new package' : package_id_exists,
-    'changed package' : package_id_exists,
-    'deleted package' : package_id_exists,
-    'follow dataset' : package_id_exists,
-    'new user' : user_id_exists,
-    'changed user' : user_id_exists,
-    'follow user' : user_id_exists,
-    'new group' : group_id_exists,
-    'changed group' : group_id_exists,
-    'deleted group' : group_id_exists,
-    'new organization' : group_id_exists,
-    'changed organization' : group_id_exists,
-    'deleted organization' : group_id_exists,
-    'follow group' : group_id_exists,
+    'new package': package_id_exists,
+    'changed package': package_id_exists,
+    'deleted package': package_id_exists,
+    'follow dataset': package_id_exists,
+    'new user': user_id_exists,
+    'changed user': user_id_exists,
+    'follow user': user_id_exists,
+    'new group': group_id_exists,
+    'changed group': group_id_exists,
+    'deleted group': group_id_exists,
+    'new organization': group_id_exists,
+    'changed organization': group_id_exists,
+    'deleted organization': group_id_exists,
+    'follow group': group_id_exists,
     'new related item': related_id_exists,
     'deleted related item': related_id_exists,
     'changed related item': related_id_exists,
-    }
+}
+
 
 def object_id_validator(key, activity_dict, errors, context):
     """Validate the 'object_id' value of an activity_dict.
@@ -284,7 +302,8 @@ def object_id_validator(key, activity_dict, errors, context):
         return object_id_validators[activity_type](object_id, context)
     else:
         raise Invalid('There is no object_id validator for '
-            'activity type "%s"' % activity_type)
+                      'activity type "%s"' % activity_type)
+
 
 def extras_unicode_convert(extras, context):
     for extra in extras:
@@ -292,6 +311,8 @@ def extras_unicode_convert(extras, context):
     return extras
 
 name_match = re.compile('[a-z0-9_\-]*$')
+
+
 def name_validator(value, context):
     '''Return the given value if it's a valid name, otherwise raise Invalid.
 
@@ -319,13 +340,14 @@ def name_validator(value, context):
     if len(value) < 2:
         raise Invalid(_('Name must be at least %s characters long') % 2)
     if len(value) > PACKAGE_NAME_MAX_LENGTH:
-        raise Invalid(_('Name must be a maximum of %i characters long') % \
+        raise Invalid(_('Name must be a maximum of %i characters long') %
                       PACKAGE_NAME_MAX_LENGTH)
     if not name_match.match(value):
         raise Invalid(_('Url must be purely lowercase alphanumeric '
                         '(ascii) characters and these symbols: -_'))
     return value
 
+
 def package_name_validator(key, data, errors, context):
     model = context["model"]
     session = context["session"]
@@ -345,20 +367,24 @@ def package_name_validator(key, data, errors, context):
     value = data[key]
     if len(value) < PACKAGE_NAME_MIN_LENGTH:
         raise Invalid(
-            _('Name "%s" length is less than minimum %s') % (value, PACKAGE_NAME_MIN_LENGTH)
+            _('Name "%s" length is less than minimum %s') % (
+                value, PACKAGE_NAME_MIN_LENGTH)
         )
     if len(value) > PACKAGE_NAME_MAX_LENGTH:
         raise Invalid(
-            _('Name "%s" length is more than maximum %s') % (value, PACKAGE_NAME_MAX_LENGTH)
+            _('Name "%s" length is more than maximum %s') % (
+                value, PACKAGE_NAME_MAX_LENGTH)
         )
 
+
 def package_version_validator(value, context):
 
     if len(value) > PACKAGE_VERSION_MAX_LENGTH:
-        raise Invalid(_('Version must be a maximum of %i characters long') % \
+        raise Invalid(_('Version must be a maximum of %i characters long') %
                       PACKAGE_VERSION_MAX_LENGTH)
     return value
 
+
 def duplicate_extras_key(key, data, errors, context):
 
     unflattened = df.unflatten(data)
@@ -375,6 +401,7 @@ def duplicate_extras_key(key, data, errors, context):
         assert key_ not in errors
         errors[key_] = [_('Duplicate key "%s"') % extras_keys[0]]
 
+
 def group_name_validator(key, data, errors, context):
     model = context['model']
     session = context['session']
@@ -391,18 +418,22 @@ def group_name_validator(key, data, errors, context):
     if result:
         errors[key].append(_('Group name already exists in database'))
 
+
 def tag_length_validator(value, context):
 
     if len(value) < MIN_TAG_LENGTH:
         raise Invalid(
-            _('Tag "%s" length is less than minimum %s') % (value, MIN_TAG_LENGTH)
+            _('Tag "%s" length is less than minimum %s') % (
+                value, MIN_TAG_LENGTH)
         )
     if len(value) > MAX_TAG_LENGTH:
         raise Invalid(
-            _('Tag "%s" length is more than maximum %i') % (value, MAX_TAG_LENGTH)
+            _('Tag "%s" length is more than maximum %i') % (
+                value, MAX_TAG_LENGTH)
         )
     return value
 
+
 def tag_name_validator(value, context):
 
     tagname_match = re.compile('[\w \-.]*$', re.UNICODE)
@@ -411,6 +442,7 @@ def tag_name_validator(value, context):
                         'characters or symbols: -_.') % (value))
     return value
 
+
 def tag_not_uppercase(value, context):
 
     tagname_uppercase = re.compile('[A-Z]')
@@ -418,31 +450,35 @@ def tag_not_uppercase(value, context):
         raise Invalid(_('Tag "%s" must not be uppercase' % (value)))
     return value
 
+
 def tag_string_convert(key, data, errors, context):
     '''Takes a list of tags that is a comma-separated string (in data[key])
     and parses tag names. These are added to the data dict, enumerated. They
     are also validated.'''
 
     if isinstance(data[key], basestring):
-        tags = [tag.strip() \
-                for tag in data[key].split(',') \
+        tags = [tag.strip()
+                for tag in data[key].split(',')
                 if tag.strip()]
     else:
         tags = data[key]
 
-    current_index = max( [int(k[1]) for k in data.keys() if len(k) == 3 and k[0] == 'tags'] + [-1] )
+    current_index = max(
+        [int(k[1]) for k in data.keys() if len(k) == 3 and k[0] == 'tags'] + [-1])
 
-    for num, tag in zip(count(current_index+1), tags):
+    for num, tag in zip(count(current_index + 1), tags):
         data[('tags', num, 'name')] = tag
 
     for tag in tags:
         tag_length_validator(tag, context)
         tag_name_validator(tag, context)
 
+
 def ignore_not_admin(key, data, errors, context):
     # Deprecated in favour of ignore_not_package_admin
     return ignore_not_package_admin(key, data, errors, context)
 
+
 def ignore_not_package_admin(key, data, errors, context):
     '''Ignore if the user is not allowed to administer the package specified.'''
 
@@ -459,7 +495,7 @@ def ignore_not_package_admin(key, data, errors, context):
     pkg = context.get('package')
     if pkg:
         try:
-            logic.check_access('package_change_state',context)
+            logic.check_access('package_change_state', context)
             authorized = True
         except logic.NotAuthorized:
             authorized = False
@@ -499,7 +535,7 @@ def ignore_not_group_admin(key, data, errors, context):
     group = context.get('group')
     if group:
         try:
-            logic.check_access('group_change_state',context)
+            logic.check_access('group_change_state', context)
             authorized = True
         except logic.NotAuthorized:
             authorized = False
@@ -509,6 +545,7 @@ def ignore_not_group_admin(key, data, errors, context):
 
     data.pop(key)
 
+
 def user_name_validator(key, data, errors, context):
     '''Validate a new user name.
 
@@ -542,15 +579,17 @@ def user_name_validator(key, data, errors, context):
             # existing user's name to that name.
             errors[key].append(_('That login name is not available.'))
 
-## ADDED FOR HDX, HDX HACK
+# ADDED FOR HDX, HDX HACK
+
+
 def user_email_validator(key, data, errors, context):
     model = context['model']
     email = data[key]
 
     from validate_email import validate_email
-    if not validate_email(email, check_mx=True, verify=False):
+    if not validate_email(email, check_mx=False, verify=False):
         raise Invalid(_('Email address is not valid'))
-    
+
     if not isinstance(email, basestring):
         raise Invalid(_('User names must be strings'))
 
@@ -569,13 +608,14 @@ def user_email_validator(key, data, errors, context):
 
 def user_both_passwords_entered(key, data, errors, context):
 
-    password1 = data.get(('password1',),None)
-    password2 = data.get(('password2',),None)
+    password1 = data.get(('password1',), None)
+    password2 = data.get(('password2',), None)
 
     if password1 is None or password1 == '' or \
        password2 is None or password2 == '':
         errors[('password',)].append(_('Please enter both passwords'))
 
+
 def user_password_validator(key, data, errors, context):
     value = data[key]
 
@@ -586,41 +626,47 @@ def user_password_validator(key, data, errors, context):
     elif value == '':
         pass
     elif len(value) < 4:
-        errors[('password',)].append(_('Your password must be 4 characters or longer'))
+        errors[('password',)].append(
+            _('Your password must be 4 characters or longer'))
+
 
 def user_passwords_match(key, data, errors, context):
 
-    password1 = data.get(('password1',),None)
-    password2 = data.get(('password2',),None)
+    password1 = data.get(('password1',), None)
+    password2 = data.get(('password2',), None)
 
     if not password1 == password2:
         errors[key].append(_('The passwords you entered do not match'))
     else:
-        #Set correct password
+        # Set correct password
         data[('password',)] = password1
 
+
 def user_password_not_empty(key, data, errors, context):
     '''Only check if password is present if the user is created via action API.
        If not, user_both_passwords_entered will handle the validation'''
 
     if not ('password1',) in data and not ('password2',) in data:
-        password = data.get(('password',),None)
+        password = data.get(('password',), None)
         if not password:
             errors[key].append(_('Missing value'))
 
-def user_about_validator(value,context):
+
+def user_about_validator(value, context):
     if 'http://' in value or 'https://' in value:
-        raise Invalid(_('Edit not allowed as it looks like spam. Please avoid links in your description.'))
+        raise Invalid(
+            _('Edit not allowed as it looks like spam. Please avoid links in your description.'))
 
     return value
 
+
 def vocabulary_name_validator(name, context):
     model = context['model']
     session = context['session']
 
     if len(name) < VOCABULARY_NAME_MIN_LENGTH:
         raise Invalid(_('Name must be at least %s characters long') %
-            VOCABULARY_NAME_MIN_LENGTH)
+                      VOCABULARY_NAME_MIN_LENGTH)
     if len(name) > VOCABULARY_NAME_MAX_LENGTH:
         raise Invalid(_('Name must be a maximum of %i characters long') %
                       VOCABULARY_NAME_MAX_LENGTH)
@@ -630,6 +676,7 @@ def vocabulary_name_validator(name, context):
         raise Invalid(_('That vocabulary name is already in use.'))
     return name
 
+
 def vocabulary_id_not_changed(value, context):
     vocabulary = context.get('vocabulary')
     if vocabulary and value != vocabulary.id:
@@ -637,6 +684,7 @@ def vocabulary_id_not_changed(value, context):
                         'This key is read-only') % (vocabulary.id, value))
     return value
 
+
 def vocabulary_id_exists(value, context):
     model = context['model']
     session = context['session']
@@ -645,19 +693,22 @@ def vocabulary_id_exists(value, context):
         raise Invalid(_('Tag vocabulary was not found.'))
     return value
 
+
 def tag_in_vocabulary_validator(value, context):
     model = context['model']
     session = context['session']
     vocabulary = context.get('vocabulary')
     if vocabulary:
         query = session.query(model.Tag)\
-            .filter(model.Tag.vocabulary_id==vocabulary.id)\
-            .filter(model.Tag.name==value)\
+            .filter(model.Tag.vocabulary_id == vocabulary.id)\
+            .filter(model.Tag.name == value)\
             .count()
         if not query:
-            raise Invalid(_('Tag %s does not belong to vocabulary %s') % (value, vocabulary.name))
+            raise Invalid(
+                _('Tag %s does not belong to vocabulary %s') % (value, vocabulary.name))
     return value
 
+
 def tag_not_in_vocabulary(key, tag_dict, errors, context):
     tag_name = tag_dict[('name',)]
     if not tag_name:
@@ -670,15 +721,16 @@ def tag_not_in_vocabulary(key, tag_dict, errors, context):
     session = context['session']
 
     query = session.query(model.Tag)
-    query = query.filter(model.Tag.vocabulary_id==vocabulary_id)
-    query = query.filter(model.Tag.name==tag_name)
+    query = query.filter(model.Tag.vocabulary_id == vocabulary_id)
+    query = query.filter(model.Tag.name == tag_name)
     count = query.count()
     if count > 0:
         raise Invalid(_('Tag %s already belongs to vocabulary %s') %
-                (tag_name, vocabulary_id))
+                      (tag_name, vocabulary_id))
     else:
         return
 
+
 def url_validator(key, data, errors, context):
     """ Checks that the provided value (if it is present) is a valid URL """
     import urlparse
@@ -695,12 +747,11 @@ def url_validator(key, data, errors, context):
     if all([pieces.scheme, pieces.netloc]) and \
        set(pieces.netloc) <= set(string.letters + string.digits + '-.') and \
        pieces.scheme in ['http', 'https']:
-       return
+        return
 
     errors[key].append(_('Please provide a valid URL'))
 
 
-
 def user_name_exists(user_name, context):
     model = context['model']
     session = context['session']
@@ -717,7 +768,7 @@ def role_exists(role, context):
 
 
 def datasets_with_no_organization_cannot_be_private(key, data, errors,
-        context):
+                                                    context):
 
     dataset_id = data.get(('id',))
     owner_org = data.get(('owner_org',))
@@ -732,7 +783,7 @@ def datasets_with_no_organization_cannot_be_private(key, data, errors,
         # Check if the dataset actually has an owner_org, even if not provided
         try:
             dataset_dict = logic.get_action('package_show')({},
-                            {'id': dataset_id})
+                                                            {'id': dataset_id})
             if not dataset_dict.get('owner_org'):
                 check_passed = False
 
@@ -741,7 +792,7 @@ def datasets_with_no_organization_cannot_be_private(key, data, errors,
 
     if not check_passed:
         errors[key].append(
-                _("Datasets with no organization can't be private."))
+            _("Datasets with no organization can't be private."))
 
 
 def list_of_strings(key, data, errors, context):
@@ -752,6 +803,7 @@ def list_of_strings(key, data, errors, context):
         if not isinstance(x, basestring):
             raise Invalid('%s: %s' % (_('Not a string'), x))
 
+
 def no_loops_in_hierarchy(key, data, errors, context):
     '''Checks that the parent groups specified in the data would not cause
     a loop in the group hierarchy, and therefore cause the recursion up/down
@@ -762,7 +814,7 @@ def no_loops_in_hierarchy(key, data, errors, context):
         return
     group = context['model'].Group.get(data['id'])
     allowable_parents = group.\
-                        groups_allowed_to_be_its_parent(type=group.type)
+        groups_allowed_to_be_its_parent(type=group.type)
     for parent in data['groups']:
         parent_name = parent['name']
         # a blank name signifies top level, which is always allowed
@@ -770,4 +822,3 @@ def no_loops_in_hierarchy(key, data, errors, context):
                 not in allowable_parents:
             raise Invalid(_('This parent would create a loop in the '
                             'hierarchy'))
-
diff --git a/ckanext-hdx_theme/ckanext/hdx_theme/version.py b/ckanext-hdx_theme/ckanext/hdx_theme/version.py
--- a/ckanext-hdx_theme/ckanext/hdx_theme/version.py
+++ b/ckanext-hdx_theme/ckanext/hdx_theme/version.py
@@ -1 +1 @@
-hdx_version = 'v0.5.12'
+hdx_version = 'v0.5.13'


Test patch: