Repository: carpentries/amy

Issue:
Bug: wrong indication of errors during validation / importing from URL
1. Import from URL help text is half wrong
2. Validation doesn't show anything if we fail to validate



Discussion:

Resolving patch:
diff --git a/workshops/util.py b/workshops/util.py
--- a/workshops/util.py
+++ b/workshops/util.py
@@ -366,6 +366,17 @@ def merge_persons(person_from, person_to):
     person_from.delete()
 
 
+class WrongWorkshopURL(ValueError):
+    """Raised when we fall back to reading tags from event's YAML front matter,
+    which requires a link to GitHub raw hosted file, but we can't get that link
+    because provided URL doesn't match Event.WEBSITE_REGEX
+    (see `generate_url_to_event_index` below)."""
+
+    def __str__(self):
+        return ('Event\'s URL doesn\'t match Github website format '
+                '"http://user.github.io/2015-12-08-workshop".')
+
+
 def generate_url_to_event_index(website_url):
     """Given URL to workshop's website, generate a URL to its raw `index.html`
     file in GitHub repository."""
@@ -375,8 +386,8 @@ def generate_url_to_event_index(website_url):
 
     results = regex.match(website_url)
     if results:
-        return template.format(**results.groupdict())
-    raise ValueError('URL does not match Event.WEBSITE_REGEX.')
+        return template.format(**results.groupdict()), results.group('repo')
+    raise WrongWorkshopURL()
 
 ALLOWED_TAG_NAMES = [
     'slug', 'startdate', 'enddate', 'country', 'venue', 'address',
diff --git a/workshops/views.py b/workshops/views.py
--- a/workshops/views.py
+++ b/workshops/views.py
@@ -16,11 +16,11 @@
 from django.core.exceptions import (
     ObjectDoesNotExist,
     PermissionDenied,
-    SuspiciousOperation,
 )
 from django.core.mail import EmailMultiAlternatives
 from django.conf import settings
 from django.http import Http404, HttpResponse, JsonResponse
+from django.http import HttpResponseBadRequest
 from django.db import IntegrityError
 from django.db.models import Count, Sum, Q, F, Model, ProtectedError
 from django.db.models import Case, When, Value, IntegerField
@@ -65,6 +65,7 @@
     upload_person_task_csv,  verify_upload_person_task,
     create_uploaded_persons_tasks, InternalError, Paginator, merge_persons,
     update_event_attendance_from_tasks,
+    WrongWorkshopURL,
     generate_url_to_event_index,
     find_tags_on_event_index,
     find_tags_on_event_website,
@@ -910,7 +911,7 @@ def validate_event(request, event_ident):
 
         if 'slug' not in tags:
             # there are no HTML tags, so let's try the old method
-            page_url = generate_url_to_event_index(page_url)
+            page_url, _ = generate_url_to_event_index(page_url)
 
             # fetch page
             response = requests.get(page_url)
@@ -923,6 +924,9 @@ def validate_event(request, event_ident):
         # validate them
         error_messages = validate_tags_from_event_website(tags)
 
+    except WrongWorkshopURL as e:
+        error_messages.append(str(e))
+
     except requests.exceptions.HTTPError as e:
         error_messages.append(
             'Request for "{0}" returned status code {1}'
@@ -1062,7 +1066,7 @@ def event_import(request):
 
         if 'slug' not in tags:
             # there are no HTML tags, so let's try the old method
-            index_url = generate_url_to_event_index(url)
+            index_url, repository = generate_url_to_event_index(url)
 
             # fetch page
             response = requests.get(index_url)
@@ -1073,15 +1077,7 @@ def event_import(request):
                 tags = find_tags_on_event_index(content)
 
                 if 'slug' not in tags:
-                    # `url` should match WEBSITE_REGEX because of the check
-                    # performed in `generate_url_to_event_website`,
-                    # but, just in case someone removes that code, let's
-                    # throw ValueError here too
-                    try:
-                        tags['slug'] = Event.WEBSITE_REGEX.match(url) \
-                                                          .group('repo')
-                    except AttributeError:
-                        raise ValueError()
+                    tags['slug'] = repository
 
         # normalize (parse) them
         tags = parse_tags_from_event_website(tags)
@@ -1089,22 +1085,20 @@ def event_import(request):
         return JsonResponse(tags)
 
     except requests.exceptions.HTTPError as e:
-        raise SuspiciousOperation(
+        return HttpResponseBadRequest(
             'Request for "{0}" returned status code {1}.'
             .format(url, e.response.status_code)
         )
 
     except (requests.exceptions.ConnectionError,
             requests.exceptions.Timeout):
-        raise SuspiciousOperation('Network connection error.')
+        return HttpResponseBadRequest('Network connection error.')
 
-    except ValueError:
-        # probably matching url with Event.WEBSITE_REGEX (either here or in
-        # `generate_url_to_event_index`) failed
-        raise SuspiciousOperation('Event\'s url is in wrong format.')
+    except WrongWorkshopURL as e:
+        return HttpResponseBadRequest(str(e))
 
     except KeyError:
-        raise SuspiciousOperation('Missing or wrong "url" POST parameter.')
+        return HttpResponseBadRequest('Missing or wrong "url" POST parameter.')
 
 
 @login_required


Test patch:
diff --git a/workshops/test/test_util.py b/workshops/test/test_util.py
--- a/workshops/test/test_util.py
+++ b/workshops/test/test_util.py
@@ -387,11 +387,14 @@ def test_generating_url_to_index(self):
             'https://swcarpentry.github.com/workshop-template',
             'http://swcarpentry.github.com/workshop-template/',
         ]
-        expected = ('https://raw.githubusercontent.com/swcarpentry/'
-                    'workshop-template/gh-pages/index.html')
+        expected_url = ('https://raw.githubusercontent.com/swcarpentry/'
+                        'workshop-template/gh-pages/index.html')
+        expected_repo = 'workshop-template'
         for url in tests:
             with self.subTest(url=url):
-                self.assertEqual(expected, generate_url_to_event_index(url))
+                url, repo = generate_url_to_event_index(url)
+                self.assertEqual(expected_url, url)
+                self.assertEqual(expected_repo, repo)
 
     def test_finding_tags_on_index(self):
         content = """---