Repository: carpentries/amy

Issue:
Notify admins about changed metatags
If cronjob discovers updated metatags, there should be some notification popup for admins.

This is a continuation from #752 and #562.



Discussion:

Resolving patch:
diff --git a/workshops/management/commands/check_for_workshop_websites_updates.py b/workshops/management/commands/check_for_workshop_websites_updates.py
--- a/workshops/management/commands/check_for_workshop_websites_updates.py
+++ b/workshops/management/commands/check_for_workshop_websites_updates.py
@@ -2,9 +2,12 @@
 import datetime
 from functools import partial
 import json
+import socket
+import sys
 
 from django.core.management.base import BaseCommand
 from github import Github
+from github.GithubException import GithubException
 import requests
 from rest_framework.utils.encoders import JSONEncoder
 
@@ -78,7 +81,7 @@ def datetime_decode(obj):
 
 
 class Command(BaseCommand):
-    help = ''
+    help = 'Check if events have had their metadata updated.'
 
     def add_arguments(self, parser):
         parser.add_argument(
@@ -90,13 +93,23 @@ def add_arguments(self, parser):
         parser.add_argument(
             '--init', action='store_true', help='Run for the first time.',
         )
+        parser.add_argument(
+            '--cutoff-days', default=180, type=int,
+            help='Age (in days) of the oldest events that can be checked.  '
+                 'Default: 180'
+        )
 
-    def get_events(self):
+    def get_events(self, cutoff_days=180):
         """Get all active events.
 
         This method is used for getting all events that should be checked
         against up-to-date data."""
         events = Event.objects.active().filter(url__isnull=False)
+
+        # events as old as 2014 are still marked as active, so we impose age
+        # limit of half a year
+        half_a_year = datetime.timedelta(days=cutoff_days)
+        events = events.filter(start__gte=datetime.date.today() - half_a_year)
         return events
 
     def parse_github_url(self, url):
@@ -201,11 +214,12 @@ def handle(self, *args, **options):
         token = options['token']
         initial_run = options['init']
         slug = options['slug']
+        cutoff_days = options['cutoff_days']
 
         g = Github(token)
 
         # get all events
-        events = self.get_events()
+        events = self.get_events(cutoff_days)
 
         if slug:
             events = events.filter(slug=slug)
@@ -228,8 +242,21 @@ def handle(self, *args, **options):
                         events_for_update[event.slug] = changes
                         print('Detected changes in {}'.format(event.slug))
 
+            except GithubException:
+                print('GitHub error when accessing {} repo'.format(event.slug),
+                      file=sys.stderr)
+
+            except socket.timeout:
+                print('Timeout when accessing {} repo'.format(event.slug),
+                      file=sys.stderr)
+
             except WrongWorkshopURL:
-                print('Wrong URL for {}'.format(event.slug))
+                print('Wrong URL for {}'.format(event.slug), file=sys.stderr)
 
             except requests.exceptions.RequestException:
-                print('Network error when accessing {}'.format(event.slug))
+                print('Network error when accessing {}'.format(event.slug),
+                      file=sys.stderr)
+
+            except Exception as e:
+                print('Unknown error ({}): {}'.format(event.slug, e),
+                      file=sys.stderr)
diff --git a/workshops/urls.py b/workshops/urls.py
--- a/workshops/urls.py
+++ b/workshops/urls.py
@@ -42,6 +42,7 @@
     url(r'^event/(?P<event_ident>[\w-]+)/delete$', views.event_delete, name='event_delete'),
     url(r'^events/add/$', views.EventCreate.as_view(), name='event_add'),
     url(r'^event/(?P<event_ident>[\w-]+)/validate/?$', views.validate_event, name='validate_event'),
+    url(r'^events/tag_changed/?$', views.events_tag_changed, name='events_tag_changed'),
     url(r'^event/(?P<event_ident>[\w-]+)/review_tag_changes/?$', views.event_review_repo_changes, name='event_review_repo_changes'),
     url(r'^event/(?P<event_ident>[\w-]+)/review_tag_changes/accept/?$', views.event_review_repo_changes_accept, name='event_review_repo_changes_accept'),
     url(r'^event/(?P<event_ident>[\w-]+)/review_tag_changes/dismiss/?$', views.event_review_repo_changes_dismiss, name='event_review_repo_changes_dismiss'),
diff --git a/workshops/views.py b/workshops/views.py
--- a/workshops/views.py
+++ b/workshops/views.py
@@ -237,6 +237,16 @@ def dashboard(request):
         # no filtering
         pass
 
+    else:
+        # no filtering
+        pass
+
+    # assigned events that have unaccepted changes
+    updated_metatags = Event.objects.active() \
+                                    .filter(assigned_to=request.user) \
+                                    .filter(tags_changed=True) \
+                                    .count()
+
     context = {
         'title': None,
         'is_admin': is_admin,
@@ -246,6 +256,7 @@ def dashboard(request):
         'unpublished_events': unpublished_events,
         'todos_start_date': TodoItemQuerySet.current_week_dates()[0],
         'todos_end_date': TodoItemQuerySet.next_week_dates()[1],
+        'updated_metatags': updated_metatags,
     }
     return render(request, 'workshops/dashboard.html', context)
 
@@ -1306,6 +1317,36 @@ def event_invoice(request, event_ident):
     return render(request, 'workshops/event_invoice.html', context)
 
 
+@login_required
+def events_tag_changed(request):
+    """List events with metatags changed."""
+    events = Event.objects.active().filter(tags_changed=True)
+
+    assigned_to, is_admin = assignment_selection(request)
+
+    if assigned_to == 'me':
+        events = events.filter(assigned_to=request.user)
+
+    elif assigned_to == 'noone':
+        events = events.filter(assigned_to=None)
+
+    elif assigned_to == 'all':
+        # no filtering
+        pass
+
+    else:
+        # no filtering
+        pass
+
+    context = {
+        'title': 'Events with metatags changed',
+        'events': events,
+        'is_admin': is_admin,
+        'assigned_to': assigned_to,
+    }
+    return render(request, 'workshops/events_tag_changed.html', context)
+
+
 @login_required
 @permission_required('workshops.change_event', raise_exception=True)
 def event_review_repo_changes(request, event_ident):
@@ -1908,6 +1949,10 @@ def workshop_issues(request):
         # no filtering
         pass
 
+    else:
+        # no filtering
+        pass
+
     for e in events:
         e.missing_attendance_ = (not e.attendance)
         e.missing_location_ = (


Test patch:
diff --git a/workshops/test/test_commands.py b/workshops/test/test_commands.py
--- a/workshops/test/test_commands.py
+++ b/workshops/test/test_commands.py
@@ -283,7 +283,7 @@ def setUp(self):
 
     def test_getting_events(self):
         """Ensure only active events with URL are returned."""
-        self.fake_cmd.fake_current_events(self.faker, count=4)
+        self.fake_cmd.fake_current_events(self.faker, count=6)
 
         # one active event with URL and one without
         e1, e2 = Event.objects.all()[0:2]
@@ -303,9 +303,19 @@ def test_getting_events(self):
         e4.url = None
         e4.save()
 
+        # both active but one very old
+        e5, e6 = Event.objects.all()[4:6]
+        e5.completed = False
+        e5.url = 'https://swcarpentry.github.io/workshop-template2/'
+        e5.start = date(2014, 1, 1)
+        e5.save()
+        e6.completed = False
+        e6.url = 'https://datacarpentry.github.io/workshop-template2/'
+        e6.save()
+
         # check
         events = set(self.cmd.get_events())
-        self.assertEqual({e1}, events)
+        self.assertEqual({e1, e6}, events)
 
     def test_parsing_github_url(self):
         """Ensure `parse_github_url()` correctly parses repository URL."""
diff --git a/workshops/test/test_event.py b/workshops/test/test_event.py
--- a/workshops/test/test_event.py
+++ b/workshops/test/test_event.py
@@ -822,6 +822,15 @@ def setUp(self):
         session['tags_from_event_website'] = self.tags_serialized
         session.save()
 
+    def test_showing_all_events_with_changed_metatags(self):
+        """Ensure `events_tag_changed` only shows events with changed
+        metatags."""
+        url = reverse('events_tag_changed')
+        rv = self.client.get(url)
+        self.assertEqual(rv.status_code, 200)
+
+        self.assertEqual(list(rv.context['events']), [self.event])
+
     def test_accepting_changes(self):
         """Ensure `event_review_repo_changes_accept`:
         * updates changed values in event