Repository: carpentries/amy

Issue:
Extend ProfileUpdateRequests to add people who aren't in AMY (yet)
If people, who aren't in AMY, submit the profile update form, it's hard to add them to the database from the GUI.



Discussion:

Resolving patch:
diff --git a/workshops/urls.py b/workshops/urls.py
--- a/workshops/urls.py
+++ b/workshops/urls.py
@@ -111,6 +111,7 @@
     url(r'^profile_update/(?P<request_id>\d+)/?$', views.profileupdaterequest_details, name='profileupdaterequest_details'),
     url(r'^profile_update/(?P<request_id>\d+)/fix/?$', views.ProfileUpdateRequestFix.as_view(), name='profileupdaterequest_fix'),
     url(r'^profile_update/(?P<request_id>\d+)/discard/?$', views.profileupdaterequest_discard, name='profileupdaterequest_discard'),
+    url(r'^profile_update/(?P<request_id>\d+)/accept/?$', views.profileupdaterequest_accept, name='profileupdaterequest_accept'),
     url(r'^profile_update/(?P<request_id>\d+)/accept/(?P<person_id>[\w\.-]+)/?$', views.profileupdaterequest_accept, name='profileupdaterequest_accept'),
     url(r'^update_profile/$', views.profileupdaterequest_create, name='profileupdate_request'),
 
diff --git a/workshops/views.py b/workshops/views.py
--- a/workshops/views.py
+++ b/workshops/views.py
@@ -2264,9 +2264,9 @@ def profileupdaterequest_discard(request, request_id):
 
 
 @login_required
-@permission_required(['workshops.change_profileupdaterequest',
-                      'workshops.change_person'], raise_exception=True)
-def profileupdaterequest_accept(request, request_id, person_id):
+@permission_required('workshops.change_profileupdaterequest',
+                     raise_exception=True)
+def profileupdaterequest_accept(request, request_id, person_id=None):
     """
     Accept the profile update by rewriting values to selected user's profile.
 
@@ -2277,11 +2277,24 @@ def profileupdaterequest_accept(request, request_id, person_id):
     """
     profileupdate = get_object_or_404(ProfileUpdateRequest, active=True,
                                       pk=request_id)
-    person = get_object_or_404(Person, pk=person_id)
-    person_name = str(person)
-
     airport = get_object_or_404(Airport, iata=profileupdate.airport_iata)
 
+    if person_id is None:
+        person = Person()
+        # since required perms change depending on `person_id`, we have to
+        # check the perms programmatically; here user is required
+        # `workshops.add_person` in order to add a new person
+        if not request.user.has_perm('workshops.add_person'):
+            raise PermissionDenied
+    else:
+        person = get_object_or_404(Person, pk=person_id)
+        person_name = str(person)
+        # since required perms change depending on `person_id`, we have to
+        # check the perms programmatically; here user is required
+        # `workshops.change_person` in order to set existing person's fields
+        if not request.user.has_perm('workshops.change_person'):
+            raise PermissionDenied
+
     person.personal = profileupdate.personal
     person.family = profileupdate.family
     person.email = profileupdate.email
@@ -2298,6 +2311,12 @@ def profileupdaterequest_accept(request, request_id, person_id):
         person.occupation = profileupdate.get_occupation_display()
     person.orcid = profileupdate.orcid
     person.gender = profileupdate.gender
+
+    # we need person to exist in the database in order to set domains and
+    # lessons
+    if not person.id:
+        person.save()
+
     person.domains = list(profileupdate.domains.all())
 
     # Since Person.lessons uses a intermediate model Qualification, we ought to
@@ -2317,8 +2336,13 @@ def profileupdaterequest_accept(request, request_id, person_id):
     profileupdate.active = False
     profileupdate.save()
 
-    messages.success(request,
-                     '{} was updated successfully.'.format(person_name))
+    if person_id is None:
+        messages.success(request,
+                         'New person was added successfully.')
+    else:
+        messages.success(request,
+                         '{} was updated successfully.'.format(person_name))
+
     return redirect(person.get_absolute_url())
 
 


Test patch:
diff --git a/workshops/test/test_profile_update_request.py b/workshops/test/test_profile_update_request.py
--- a/workshops/test/test_profile_update_request.py
+++ b/workshops/test/test_profile_update_request.py
@@ -132,6 +132,44 @@ def test_incomplete_selection_of_matching_person(self):
         rv = self.client.get(url)
         self.assertNotEqual(rv.status_code, 500)
 
+    def test_request_accepted_new_person_added(self):
+        """Ensure new person is added when no-one matches the profile update
+        request."""
+        Person.objects.exclude(username='admin').delete()
+        self.assertEqual(Person.objects.count(), 1)
+
+        pr = ProfileUpdateRequest.objects.create(
+            active=True,
+            personal='Warry', family='Trotter', email='warry@trotter.com',
+            affiliation='Auror at Ministry of Magic', airport_iata='AAA',
+            occupation='', occupation_other='Auror',
+            github='hpotter', twitter='hpotter',
+            orcid='0000-1111', website='http://warry.trotter.com/', gender='M',
+        )
+        pr.domains.add(*KnowledgeDomain.objects.all()[0:2]),
+        pr.lessons.add(*Lesson.objects.all()[0:2]),
+        rv = self.client.get(reverse('profileupdaterequest_accept',
+                                     args=[pr.pk]))
+        self.assertEqual(rv.status_code, 302, rv.status_code)
+
+        self.assertEqual(Person.objects.count(), 2)
+        person = Person.objects.get(personal='Warry', family='Trotter')
+        self.assertEqual(person.personal, 'Warry')
+        self.assertEqual(person.family, 'Trotter')
+        self.assertEqual(person.email, 'warry@trotter.com')
+        self.assertEqual(person.affiliation, 'Auror at Ministry of Magic')
+        self.assertEqual(person.airport.iata, 'AAA')
+        self.assertEqual(person.github, 'hpotter')
+        self.assertEqual(person.twitter, 'hpotter')
+        self.assertEqual(person.url, 'http://warry.trotter.com/')
+        self.assertEqual(person.gender, 'M')
+        self.assertEqual(person.occupation, 'Auror')
+        self.assertEqual(person.orcid, '0000-1111')
+        self.assertEqual(set(person.domains.all()),
+                         set(KnowledgeDomain.objects.all()[0:2]))
+        self.assertEqual(set(person.lessons.all()),
+                         set(Lesson.objects.all()[0:2]))
+
 
 class TestProfileUpdateRequestsViews(TestBase):
     def setUp(self):