Repository: carpentries/amy

Issue:
Events assigned to RC's
Please improve the UI (either preselect currently logged in user, or provide some shortcut option labelled "Me") for RC' to see the workshops assigned to them. 

This is reference to [issue 1752](https://github.com/carpentries/amy/issues/1752 )


Discussion:
Option 1: preselect currently logged in user (may be difficult).
Option 2: a link after the select to assign to yourself.

Second will be less work.
@pbanaszkiewicz I am not sure I understand what the 2nd options would look like. Is it possible for you to show a mock? 

Resolving patch:
diff --git a/amy/dashboard/views.py b/amy/dashboard/views.py
--- a/amy/dashboard/views.py
+++ b/amy/dashboard/views.py
@@ -46,56 +46,59 @@ def dispatch(request):
     """If user is admin, then show them admin dashboard; otherwise redirect
     them to trainee dashboard."""
     if request.user.is_admin:
-        return redirect(reverse('admin-dashboard'))
+        return redirect(reverse("admin-dashboard"))
     else:
-        return redirect(reverse('trainee-dashboard'))
+        return redirect(reverse("trainee-dashboard"))
 
 
 @admin_required
 def admin_dashboard(request):
     """Home page for admins."""
-
-    assignment_form = AssignmentForm(request.GET)
+    data = request.GET.copy()
+    if "assigned_to" not in data:
+        data["assigned_to"] = request.user.id
+    assignment_form = AssignmentForm(data)
     assigned_to: Optional[Person] = None
     if assignment_form.is_valid():
         assigned_to = assignment_form.cleaned_data["assigned_to"]
 
-    current_events = (
-        Event.objects.upcoming_events() | Event.objects.ongoing_events()
-    ).active().prefetch_related('tags')
+    current_events = Event.objects.current_events().prefetch_related("tags")
 
     # This annotation may produce wrong number of instructors when
     # `unpublished_events` filters out events that contain a specific tag.
     # The bug was fixed in #1130.
     unpublished_events = (
-        Event.objects.active().unpublished_events().select_related('host').annotate(
+        Event.objects.active()
+        .unpublished_events()
+        .select_related("host")
+        .annotate(
             num_instructors=Count(
                 Case(
-                    When(task__role__name='instructor', then=Value(1)),
-                    output_field=IntegerField()
+                    When(task__role__name="instructor", then=Value(1)),
+                    output_field=IntegerField(),
                 )
             ),
-        ).order_by('-start')
+        )
+        .order_by("-start")
     )
 
     # assigned events that have unaccepted changes
     updated_metadata = Event.objects.active().filter(metadata_changed=True)
 
-    if assigned_to is not None:
-        current_events = current_events.filter(assigned_to=assigned_to)
-        unpublished_events = unpublished_events.filter(assigned_to=assigned_to)
-        updated_metadata = updated_metadata.filter(assigned_to=assigned_to)
+    current_events = current_events.filter(assigned_to=assigned_to)
+    unpublished_events = unpublished_events.filter(assigned_to=assigned_to)
+    updated_metadata = updated_metadata.filter(assigned_to=assigned_to)
 
     context = {
-        'title': None,
-        'assignment_form': assignment_form,
-        'assigned_to': assigned_to,
-        'current_events': current_events,
-        'unpublished_events': unpublished_events,
-        'updated_metadata': updated_metadata.count(),
-        'main_tags': Tag.objects.main_tags(),
+        "title": None,
+        "assignment_form": assignment_form,
+        "assigned_to": assigned_to,
+        "current_events": current_events,
+        "unpublished_events": unpublished_events,
+        "updated_metadata": updated_metadata.count(),
+        "main_tags": Tag.objects.main_tags(),
     }
-    return render(request, 'dashboard/admin_dashboard.html', context)
+    return render(request, "dashboard/admin_dashboard.html", context)
 
 
 # ------------------------------------------------------------
@@ -105,13 +108,13 @@ def admin_dashboard(request):
 @login_required
 def trainee_dashboard(request):
     # Workshops person taught at
-    workshops = request.user.task_set.select_related('role', 'event')
+    workshops = request.user.task_set.select_related("role", "event")
 
     context = {
-        'title': 'Your profile',
-        'workshops': workshops,
+        "title": "Your profile",
+        "workshops": workshops,
     }
-    return render(request, 'dashboard/trainee_dashboard.html', context)
+    return render(request, "dashboard/trainee_dashboard.html", context)
 
 
 @login_required
@@ -119,32 +122,32 @@ def autoupdate_profile(request):
     person = request.user
     form = AutoUpdateProfileForm(instance=person)
 
-    if request.method == 'POST':
+    if request.method == "POST":
         form = AutoUpdateProfileForm(request.POST, instance=person)
 
         if form.is_valid() and form.instance == person:
             # save lessons
             person.lessons.clear()
-            for lesson in form.cleaned_data['lessons']:
+            for lesson in form.cleaned_data["lessons"]:
                 q = Qualification(lesson=lesson, person=person)
                 q.save()
 
             # don't save related lessons
-            del form.cleaned_data['lessons']
+            del form.cleaned_data["lessons"]
 
             person = form.save()
 
-            messages.success(request, 'Your profile was updated.')
+            messages.success(request, "Your profile was updated.")
 
-            return redirect(reverse('trainee-dashboard'))
+            return redirect(reverse("trainee-dashboard"))
         else:
-            messages.error(request, 'Fix errors below.')
+            messages.error(request, "Fix errors below.")
 
     context = {
-        'title': 'Update Your Profile',
-        'form': form,
+        "title": "Update Your Profile",
+        "form": form,
     }
-    return render(request, 'dashboard/autoupdate_profile.html', context)
+    return render(request, "dashboard/autoupdate_profile.html", context)
 
 
 @login_required
@@ -152,38 +155,54 @@ def training_progress(request):
     homework_form = SendHomeworkForm()
 
     # Add information about instructor training progress to request.user.
-    request.user = Person.objects \
-        .annotate_with_instructor_eligibility() \
-        .prefetch_related(Prefetch(
-            'badges',
-            to_attr='instructor_badges',
-            queryset=Badge.objects.instructor_badges()),
-        ).get(pk=request.user.pk)
+    request.user = (
+        Person.objects.annotate_with_instructor_eligibility()
+        .prefetch_related(
+            Prefetch(
+                "badges",
+                to_attr="instructor_badges",
+                queryset=Badge.objects.instructor_badges(),
+            ),
+        )
+        .get(pk=request.user.pk)
+    )
 
     progresses = request.user.trainingprogress_set.filter(discarded=False)
-    last_swc_homework = progresses.filter(
-        requirement__name='SWC Homework').order_by('-created_at').first()
+    last_swc_homework = (
+        progresses.filter(requirement__name="SWC Homework")
+        .order_by("-created_at")
+        .first()
+    )
     request.user.swc_homework_in_evaluation = (
-        last_swc_homework is not None and last_swc_homework.state == 'n')
-    last_dc_homework = progresses.filter(
-        requirement__name='DC Homework').order_by('-created_at').first()
+        last_swc_homework is not None and last_swc_homework.state == "n"
+    )
+    last_dc_homework = (
+        progresses.filter(requirement__name="DC Homework")
+        .order_by("-created_at")
+        .first()
+    )
     request.user.dc_homework_in_evaluation = (
-        last_dc_homework is not None and last_dc_homework.state == 'n')
-    last_lc_homework = progresses.filter(
-        requirement__name='LC Homework').order_by('-created_at').first()
+        last_dc_homework is not None and last_dc_homework.state == "n"
+    )
+    last_lc_homework = (
+        progresses.filter(requirement__name="LC Homework")
+        .order_by("-created_at")
+        .first()
+    )
     request.user.lc_homework_in_evaluation = (
-        last_lc_homework is not None and last_lc_homework.state == 'n')
+        last_lc_homework is not None and last_lc_homework.state == "n"
+    )
 
-    if request.method == 'POST':
+    if request.method == "POST":
         homework_form = SendHomeworkForm(data=request.POST)
         if homework_form.is_valid():
             # read homework type from POST
-            hw_type = homework_form.cleaned_data['requirement']
+            hw_type = homework_form.cleaned_data["requirement"]
 
             # create "empty" progress object and fill out
             progress = TrainingProgress(
                 trainee=request.user,
-                state='n',  # not evaluated yet
+                state="n",  # not evaluated yet
                 requirement=hw_type,
             )
 
@@ -191,15 +210,16 @@ def training_progress(request):
             form = SendHomeworkForm(data=request.POST, instance=progress)
             if form.is_valid():
                 form.save()
-                messages.success(request, "Your homework submission will be "
-                                          "evaluated soon.")
-                return redirect(reverse('training-progress'))
+                messages.success(
+                    request, "Your homework submission will be " "evaluated soon."
+                )
+                return redirect(reverse("training-progress"))
 
     context = {
-        'title': 'Your training progress',
-        'homework_form': homework_form,
+        "title": "Your training progress",
+        "homework_form": homework_form,
     }
-    return render(request, 'dashboard/training_progress.html', context)
+    return render(request, "dashboard/training_progress.html", context)
 
 
 # ------------------------------------------------------------
@@ -308,7 +328,8 @@ def search(request):
             if only_result and not form.cleaned_data["no_redirect"]:
                 msg = format_html(
                     "You were moved to this page, because your search <i>{}</i> "
-                    "yields only this result.", term
+                    "yields only this result.",
+                    term,
                 )
                 if isinstance(only_result, Comment):
                     messages.success(request, msg)
diff --git a/amy/workshops/models.py b/amy/workshops/models.py
--- a/amy/workshops/models.py
+++ b/amy/workshops/models.py
@@ -998,6 +998,15 @@ def ongoing_events(self):
 
         return queryset
 
+    def current_events(self):
+        """Return current events.
+
+        Current events are active ongoing events and active upcoming events
+        (see `ongoing_events` and `upcoming_events` above).
+        """
+        queryset = (self.upcoming_events() | self.ongoing_events()).active()
+        return queryset
+
     def unpublished_conditional(self):
         """Return conditional for events without: start OR country OR venue OR
         url OR are marked as 'cancelled' (ie. unpublished events). This will be


Test patch:
diff --git a/amy/dashboard/tests/test_landing_page.py b/amy/dashboard/tests/test_landing_page.py
--- a/amy/dashboard/tests/test_landing_page.py
+++ b/amy/dashboard/tests/test_landing_page.py
@@ -17,44 +17,65 @@ def setUp(self):
         self._setUpEvents()
         self._setUpUsersAndLogin()
 
+        # assign an upcoming event to the logged in user
+        self.assigned_upcoming_event = Event.objects.active().upcoming_events().first()
+        self.assigned_upcoming_event.assigned_to = self.admin
+        self.assigned_upcoming_event.save()
+
+        # assign an unpublished event to the logged in user
+        self.assigned_unpublished_event = (
+            Event.objects.active().unpublished_events().first()
+        )
+        self.assigned_unpublished_event.assigned_to = self.admin
+        self.assigned_unpublished_event.save()
+
     def test_has_upcoming_events(self):
         """Test that the admin dashboard is passed some
         upcoming_events in the context."""
 
-        response = self.client.get(reverse('admin-dashboard'))
+        # clear out assigned_to to avoid the default filtering
+        response = self.client.get(f"{reverse('admin-dashboard')}?assigned_to=")
 
         # This will fail if the context variable doesn't exist
-        events = response.context['current_events']
+        events = response.context["current_events"]
+
+        # all unassigned current events should be shown
+        num_current_events = (
+            Event.objects.current_events().filter(assigned_to=None).count()
+        )
+        self.assertEqual(len(events), num_current_events)
 
         # They should all be labeled 'upcoming'.
-        assert all([('upcoming' in e.slug or 'ongoing' in e.slug)
-                    for e in events])
+        assert all([("upcoming" in e.slug or "ongoing" in e.slug) for e in events])
 
     def test_no_inactive_events(self):
         """Make sure we don't display stalled or completed events on the
         dashboard."""
-        stalled_tag = Tag.objects.get(name='stalled')
-        unresponsive_tag = Tag.objects.get(name='unresponsive')
-        cancelled_tag = Tag.objects.get(name='unresponsive')
+        stalled_tag = Tag.objects.get(name="stalled")
+        unresponsive_tag = Tag.objects.get(name="unresponsive")
+        cancelled_tag = Tag.objects.get(name="unresponsive")
 
         stalled = Event.objects.create(
-            slug='stalled-event', host=Organization.objects.first(),
+            slug="stalled-event",
+            host=Organization.objects.first(),
         )
         stalled.tags.add(stalled_tag)
 
         unresponsive = Event.objects.create(
-            slug='unresponsive-event', host=Organization.objects.first(),
+            slug="unresponsive-event",
+            host=Organization.objects.first(),
         )
         unresponsive.tags.add(unresponsive_tag)
 
         cancelled = Event.objects.create(
-            slug='cancelled-event', host=Organization.objects.first(),
+            slug="cancelled-event",
+            host=Organization.objects.first(),
         )
         cancelled.tags.add(cancelled_tag)
 
-        completed = Event.objects.create(slug='completed-event',
-                                         completed=True,
-                                         host=Organization.objects.first())
+        completed = Event.objects.create(
+            slug="completed-event", completed=True, host=Organization.objects.first()
+        )
 
         # stalled event appears in unfiltered list of events
         self.assertNotIn(stalled, Event.objects.unpublished_events())
@@ -62,11 +83,96 @@ def test_no_inactive_events(self):
         self.assertNotIn(cancelled, Event.objects.unpublished_events())
         self.assertNotIn(completed, Event.objects.unpublished_events())
 
-        response = self.client.get(reverse('admin-dashboard'))
-        self.assertNotIn(stalled, response.context['unpublished_events'])
-        self.assertNotIn(unresponsive, response.context['unpublished_events'])
-        self.assertNotIn(cancelled, response.context['unpublished_events'])
-        self.assertNotIn(completed, response.context['unpublished_events'])
+        response = self.client.get(f"{reverse('admin-dashboard')}?assigned_to=")
+        self.assertNotIn(stalled, response.context["unpublished_events"])
+        self.assertNotIn(unresponsive, response.context["unpublished_events"])
+        self.assertNotIn(cancelled, response.context["unpublished_events"])
+        self.assertNotIn(completed, response.context["unpublished_events"])
+
+    def test_events_default(self):
+        """The admin dashboard shows the events assigned to the current logged in
+        user by default (when the url is /dashboard/admin/)."""
+        upcoming_event = Event.objects.upcoming_events().first()
+        upcoming_event.assigned_to = self.admin
+        upcoming_event.save()
+
+        unpublished_event = Event.objects.active().unpublished_events().first()
+        unpublished_event.assigned_to = self.admin
+        unpublished_event.save()
+
+        response = self.client.get(reverse("admin-dashboard"))
+
+        # This will fail if the context variable doesn't exist
+        current_events = response.context["current_events"]
+        unpublished_events = response.context["unpublished_events"]
+        assigned_to = response.context["assigned_to"]
+
+        self.assertEqual(list(current_events), [upcoming_event])
+        self.assertEqual(list(unpublished_events), [unpublished_event])
+        self.assertEqual(assigned_to, self.admin)
+
+    def test_events_assigned_to_none(self):
+        """The admin dashboard shows all events if assigned to is None
+        (when the url is /dashboard/admin/?assigned_to=)."""
+        response = self.client.get(reverse("admin-dashboard"))
+
+        # This will fail if the context variable doesn't exist
+        current_events = response.context["current_events"]
+        unpublished_events = response.context["unpublished_events"]
+        assigned_to = response.context["assigned_to"]
+
+        self.assertEqual(list(current_events), [self.assigned_upcoming_event])
+        self.assertEqual(list(unpublished_events), [self.assigned_unpublished_event])
+        self.assertEqual(assigned_to, self.admin)
+
+    def test_events_assigned_to_another_user(self):
+        """The admin dashboard shows all events assigned to a particular user
+        if that user is specified in the assigned_to
+        (when the url is /dashboard/admin/?assigned_to=other_user)."""
+        other_admin = Person.objects.create_superuser(
+            username="other_admin",
+            personal="OtherSuper",
+            family="User",
+            email="other_sudo@example.org",
+            password="admin",
+        )
+        other_admin.data_privacy_agreement = True  # TODO does this need to be here?
+        other_admin.save()
+
+        # Add an upcoming event to the logged in admin (self.admin)
+        # and one to other_admin
+        upcoming_event = (
+            Event.objects.active()
+            .filter(assigned_to__isnull=True)
+            .upcoming_events()
+            .first()
+        )
+        upcoming_event.assigned_to = other_admin
+        upcoming_event.save()
+
+        # Add an unpublished event to logged in admin (self.admin)
+        # and another to other_admin
+        unpublished_event = (
+            Event.objects.active()
+            .filter(assigned_to__isnull=True)
+            .unpublished_events()
+            .first()
+        )
+        unpublished_event.assigned_to = other_admin
+        unpublished_event.save()
+
+        response = self.client.get(
+            f"{reverse('admin-dashboard')}?assigned_to={other_admin.id}"
+        )
+
+        # This will fail if the context variable doesn't exist
+        current_events = response.context["current_events"]
+        unpublished_events = response.context["unpublished_events"]
+        assigned_to = response.context["assigned_to"]
+
+        self.assertEqual(list(current_events), [upcoming_event])
+        self.assertEqual(list(unpublished_events), [unpublished_event])
+        self.assertEqual(assigned_to, other_admin)
 
 
 class TestDispatch(TestBase):
@@ -75,56 +181,72 @@ class TestDispatch(TestBase):
 
     def test_superuser_logs_in(self):
         person = Person.objects.create_superuser(
-            username='admin', personal='', family='',
-            email='admin@example.org', password='pass')
+            username="admin",
+            personal="",
+            family="",
+            email="admin@example.org",
+            password="pass",
+        )
         person.data_privacy_agreement = True
         person.save()
 
-        rv = self.client.post(reverse('login'),
-                              {'username':'admin', 'password':'pass'},
-                              follow=True)
+        rv = self.client.post(
+            reverse("login"), {"username": "admin", "password": "pass"}, follow=True
+        )
 
-        self.assertEqual(rv.resolver_match.view_name, 'admin-dashboard')
+        self.assertEqual(rv.resolver_match.view_name, "admin-dashboard")
 
     def test_mentor_logs_in(self):
         mentor = Person.objects.create_user(
-            username='user', personal='', family='',
-            email='mentor@example.org', password='pass')
-        admins = Group.objects.get(name='administrators')
+            username="user",
+            personal="",
+            family="",
+            email="mentor@example.org",
+            password="pass",
+        )
+        admins = Group.objects.get(name="administrators")
         mentor.groups.add(admins)
         mentor.data_privacy_agreement = True
         mentor.save()
 
-        rv = self.client.post(reverse('login'),
-                              {'username': 'user', 'password': 'pass'},
-                              follow=True)
+        rv = self.client.post(
+            reverse("login"), {"username": "user", "password": "pass"}, follow=True
+        )
 
-        self.assertEqual(rv.resolver_match.view_name, 'admin-dashboard')
+        self.assertEqual(rv.resolver_match.view_name, "admin-dashboard")
 
     def test_steering_committee_member_logs_in(self):
         mentor = Person.objects.create_user(
-            username='user', personal='', family='',
-            email='user@example.org', password='pass')
-        steering_committee= Group.objects.get(name='steering committee')
+            username="user",
+            personal="",
+            family="",
+            email="user@example.org",
+            password="pass",
+        )
+        steering_committee = Group.objects.get(name="steering committee")
         mentor.groups.add(steering_committee)
         mentor.data_privacy_agreement = True
         mentor.save()
 
-        rv = self.client.post(reverse('login'),
-                              {'username': 'user', 'password': 'pass'},
-                              follow=True)
+        rv = self.client.post(
+            reverse("login"), {"username": "user", "password": "pass"}, follow=True
+        )
 
-        self.assertEqual(rv.resolver_match.view_name, 'admin-dashboard')
+        self.assertEqual(rv.resolver_match.view_name, "admin-dashboard")
 
     def test_trainee_logs_in(self):
         self.trainee = Person.objects.create_user(
-            username='trainee', personal='', family='',
-            email='trainee@example.org', password='pass')
+            username="trainee",
+            personal="",
+            family="",
+            email="trainee@example.org",
+            password="pass",
+        )
         self.trainee.data_privacy_agreement = True
         self.trainee.save()
 
-        rv = self.client.post(reverse('login'),
-                              {'username': 'trainee', 'password': 'pass'},
-                              follow=True)
+        rv = self.client.post(
+            reverse("login"), {"username": "trainee", "password": "pass"}, follow=True
+        )
 
-        self.assertEqual(rv.resolver_match.view_name, 'trainee-dashboard')
+        self.assertEqual(rv.resolver_match.view_name, "trainee-dashboard")