Repository: carpentries/amy

Issue:
Fill workshop membership based on member registration code 
The "membership" field for the workshop event should automatically fill based on the associated membership code in the request form that event was created from.

NOTE:  We should still also follow #2393  and warn the admin accepting the form that the member may not have any available workshops remaining.


Discussion:

Resolving patch:
diff --git a/amy/extrequests/base_views.py b/amy/extrequests/base_views.py
--- a/amy/extrequests/base_views.py
+++ b/amy/extrequests/base_views.py
@@ -3,7 +3,7 @@
 from django.core.exceptions import ImproperlyConfigured
 
 from workshops.base_views import AMYCreateView
-from workshops.models import Curriculum, Tag
+from workshops.models import Curriculum, Membership, Tag
 
 
 class WRFInitial:
@@ -51,6 +51,14 @@ def get_initial(self):
         if end:
             initial["end"] = end
 
+        if hasattr(self.other_object, "member_code"):
+            code = self.other_object.member_code
+            try:
+                membership = Membership.objects.get(registration_code=code)
+                initial["membership"] = membership
+            except Membership.DoesNotExist:
+                pass
+
         return initial
 
 
diff --git a/amy/workshops/management/commands/fake_database.py b/amy/workshops/management/commands/fake_database.py
--- a/amy/workshops/management/commands/fake_database.py
+++ b/amy/workshops/management/commands/fake_database.py
@@ -474,13 +474,16 @@ def fake_memberships(self, count=10):
             start = self.faker.date_time_between(start_date="-5y").date()
             organization_count = randint(1, 4)
             organization_generator = iter(Organization.objects.all().order_by("?"))
+            name = self.faker.company()
+            registration_code = name[:5] + str(randint(10, 99))
             membership = Membership.objects.create(
-                name=self.faker.company(),
+                name=name,
                 consortium=organization_count > 1,
                 variant=choice(Membership.MEMBERSHIP_CHOICES)[0],
                 agreement_start=start,
                 agreement_end=start + timedelta(days=365),
                 contribution_type=choice(Membership.CONTRIBUTION_CHOICES)[0],
+                registration_code=registration_code,
                 workshops_without_admin_fee_per_agreement=randint(5, 15),
                 public_instructor_training_seats=randint(5, 15),
                 inhouse_instructor_training_seats=randint(5, 15),
@@ -621,6 +624,21 @@ def fake_duplicated_people(self, count=5):
 
             p.save()
 
+    def get_or_invent_member_code(self):
+        member_affiliation = choice(WorkshopRequest.MEMBER_AFFILIATION_CHOICES)[0]
+        member_code = ""
+        if member_affiliation != "no":
+            if randbool(0.75):
+                # 75% of time, use an existing member code
+                # may or may not be a valid choice depending on membership dates
+                membership = choice(Membership.objects.all())
+                member_code = membership.registration_code
+            else:
+                # 25% of time, make up an invalid code
+                member_code = self.faker.word()
+
+        return member_affiliation, member_code
+
     def fake_workshop_requests(self, count=10):
         self.stdout.write("Generating {} fake " "workshop requests...".format(count))
 
@@ -664,6 +682,7 @@ def fake_workshop_requests(self, count=10):
                 self.faker.sentence() if institution_restrictions == "" else ""
             )
 
+            member_affiliation, member_code = self.get_or_invent_member_code()
             req = WorkshopRequest.objects.create(
                 state=choice(["p", "d", "a"]),
                 data_privacy_agreement=randbool(0.5),
@@ -676,6 +695,8 @@ def fake_workshop_requests(self, count=10):
                 institution_other_name=org_name,
                 institution_other_URL=org_url,
                 institution_department="",
+                member_affiliation=member_affiliation,
+                member_code=member_code,
                 online_inperson=online_inperson,
                 public_event=public_event,
                 public_event_other=public_event_other,
@@ -748,6 +769,8 @@ def fake_workshop_inquiries(self, count=10):
                 self.faker.sentence() if institution_restrictions == "" else ""
             )
 
+            member_affiliation, member_code = self.get_or_invent_member_code()
+
             req = WorkshopInquiryRequest.objects.create(
                 state=choice(["p", "d", "a"]),
                 data_privacy_agreement=randbool(0.5),
@@ -760,6 +783,8 @@ def fake_workshop_inquiries(self, count=10):
                 institution_other_name=org_name,
                 institution_other_URL=org_url,
                 institution_department="",
+                member_affiliation=member_affiliation,
+                member_code=member_code,
                 online_inperson=online_inperson,
                 public_event=public_event,
                 public_event_other=public_event_other,
@@ -838,6 +863,8 @@ def fake_selforganised_submissions(self, count=10):
                 workshop_types = sample(curricula)
                 workshop_types_explain = ""
 
+            member_affiliation, member_code = self.get_or_invent_member_code()
+
             req = SelfOrganisedSubmission.objects.create(
                 state=choice(["p", "d", "a"]),
                 data_privacy_agreement=randbool(0.5),
@@ -850,6 +877,8 @@ def fake_selforganised_submissions(self, count=10):
                 institution_other_name=org_name,
                 institution_other_URL=org_url,
                 institution_department="",
+                member_affiliation=member_affiliation,
+                member_code=member_code,
                 online_inperson=online_inperson,
                 public_event=public_event,
                 public_event_other=public_event_other,


Test patch:
diff --git a/amy/extrequests/tests/test_selforganised_submissions.py b/amy/extrequests/tests/test_selforganised_submissions.py
--- a/amy/extrequests/tests/test_selforganised_submissions.py
+++ b/amy/extrequests/tests/test_selforganised_submissions.py
@@ -11,7 +11,16 @@
 from extrequests.models import SelfOrganisedSubmission
 import extrequests.views
 from workshops.forms import EventCreateForm
-from workshops.models import Curriculum, Event, Language, Organization, Role, Tag, Task
+from workshops.models import (
+    Curriculum,
+    Event,
+    Language,
+    Membership,
+    Organization,
+    Role,
+    Tag,
+    Task,
+)
 from workshops.tests.base import FormTestHelper, TestBase
 
 
@@ -340,6 +349,58 @@ def test_pending_request_accepted_with_event(self):
         ).selforganisedsubmission
         self.assertEqual(request, self.sos1)
 
+    def test_accept_with_event_autofill(self):
+        """Ensure that fields are autofilled correctly when creating an Event from a
+        SelfOrganisedSubmission."""
+        # Arrange
+        expected_membership = Membership.objects.create(
+            name="Hogwarts",
+            agreement_start=date.today() - timedelta(weeks=26),
+            agreement_end=date.today() + timedelta(weeks=26),
+            registration_code="hogwarts55",
+        )
+        sos = SelfOrganisedSubmission.objects.create(
+            # required fields
+            state="p",
+            personal="Harry",
+            family="Potter",
+            email="harry@potter.com",
+            country="GB",
+            language=Language.objects.get(name="English"),
+            # fields that should be autofilled
+            institution=Organization.objects.first(),
+            start=date.today(),
+            end=date.today() + timedelta(days=1),
+            online_inperson="online",
+            workshop_listed=False,
+            additional_contact="hermione@granger.com",
+            member_affiliation="yes",
+            member_code="hogwarts55",
+        )
+        curriculum = Curriculum.objects.filter(name__contains="Data Carpentry").first()
+        sos.workshop_types.set([curriculum])
+
+        expected_tags = Tag.objects.filter(name__in=["private-event", "online", "dc"])
+
+        # Act
+        rv = self.client.get(
+            reverse("selforganisedsubmission_accept_event", args=[sos.pk])
+        )
+        form_initial = rv.context["form"].initial
+
+        # Assert
+        self.assertEqual(rv.status_code, 200)
+        self.assertQuerysetEqual(
+            form_initial["curricula"].all(), sos.workshop_types.all()
+        )
+        self.assertQuerysetEqual(form_initial["tags"], expected_tags)
+        self.assertEqual(form_initial["public_status"], "private")
+        self.assertEqual(form_initial["contact"], sos.additional_contact)
+        self.assertEqual(form_initial["host"].pk, sos.institution.pk)
+        self.assertEqual(form_initial["start"], sos.start)
+        self.assertEqual(form_initial["end"], sos.end)
+        self.assertEqual(form_initial["membership"].pk, expected_membership.pk)
+
     def test_discarded_request_not_accepted_with_event(self):
         rv = self.client.get(
             reverse("selforganisedsubmission_accept_event", args=[self.sos2.pk])
diff --git a/amy/extrequests/tests/test_workshop_inquiries.py b/amy/extrequests/tests/test_workshop_inquiries.py
--- a/amy/extrequests/tests/test_workshop_inquiries.py
+++ b/amy/extrequests/tests/test_workshop_inquiries.py
@@ -16,6 +16,7 @@
     Event,
     KnowledgeDomain,
     Language,
+    Membership,
     Organization,
     Role,
     Tag,
@@ -506,6 +507,59 @@ def test_pending_request_accepted_with_event(self):
         request = Event.objects.get(slug="2018-10-28-test-event").workshopinquiryrequest
         self.assertEqual(request, self.wi1)
 
+    def test_accept_with_event_autofill(self):
+        """Ensure that fields are autofilled correctly when creating an Event from a
+        WorkshopInquiryRequest."""
+        # Arrange
+        expected_membership = Membership.objects.create(
+            name="Hogwarts",
+            agreement_start=date.today() - timedelta(weeks=26),
+            agreement_end=date.today() + timedelta(weeks=26),
+            registration_code="hogwarts55",
+        )
+        wi = WorkshopInquiryRequest.objects.create(
+            # required fields
+            state="p",
+            personal="Harry",
+            family="Potter",
+            email="harry@potter.com",
+            location="Scotland",
+            country="GB",
+            language=Language.objects.get(name="English"),
+            audience_description="Students of Hogwarts",
+            administrative_fee="forprofit",
+            travel_expences_management="reimbursed",
+            # fields that should be autofilled
+            institution=Organization.objects.first(),
+            preferred_dates=date.today(),
+            online_inperson="online",
+            workshop_listed=False,
+            additional_contact="hermione@granger.com",
+            member_affiliation="yes",
+            member_code="hogwarts55",
+        )
+        curriculum = Curriculum.objects.filter(name__contains="Data Carpentry").first()
+        wi.requested_workshop_types.set([curriculum])
+
+        expected_tags = Tag.objects.filter(name__in=["private-event", "online", "dc"])
+
+        # Act
+        rv = self.client.get(reverse("workshopinquiry_accept_event", args=[wi.pk]))
+        form_initial = rv.context["form"].initial
+
+        # Assert
+        self.assertEqual(rv.status_code, 200)
+        self.assertQuerysetEqual(
+            form_initial["curricula"].all(), wi.requested_workshop_types.all()
+        )
+        self.assertQuerysetEqual(form_initial["tags"], expected_tags)
+        self.assertEqual(form_initial["public_status"], "private")
+        self.assertEqual(form_initial["contact"], wi.additional_contact)
+        self.assertEqual(form_initial["host"].pk, wi.institution.pk)
+        self.assertEqual(form_initial["start"], wi.preferred_dates)
+        self.assertEqual(form_initial["end"], wi.preferred_dates + timedelta(days=1))
+        self.assertEqual(form_initial["membership"].pk, expected_membership.pk)
+
     def test_discarded_request_not_accepted_with_event(self):
         rv = self.client.get(
             reverse("workshopinquiry_accept_event", args=[self.wi2.pk])
diff --git a/amy/extrequests/tests/test_workshop_requests.py b/amy/extrequests/tests/test_workshop_requests.py
--- a/amy/extrequests/tests/test_workshop_requests.py
+++ b/amy/extrequests/tests/test_workshop_requests.py
@@ -438,7 +438,6 @@ def test_member_code_validation__affiliation_yes_code_empty(self):
         rv = self.client.post(reverse("workshop_request"), data=data)
 
         # Assert
-        print(rv.content)
         self.assertEqual(rv.status_code, 200)
         self.assertNotContains(rv, self.INVALID_CODE_ERROR)
         self.assertIn(self.MUST_ENTER_CODE, rv.content.decode("utf-8"))
@@ -636,6 +635,59 @@ def test_pending_request_accepted_with_event(self):
         request = Event.objects.get(slug="2018-10-28-test-event").workshoprequest
         self.assertEqual(request, self.wr1)
 
+    def test_accept_with_event_autofill(self):
+        """Ensure that fields are autofilled correctly when creating an Event from a
+        WorkshopRequest."""
+        # Arrange
+        expected_membership = Membership.objects.create(
+            name="Hogwarts",
+            agreement_start=date.today() - timedelta(weeks=26),
+            agreement_end=date.today() + timedelta(weeks=26),
+            registration_code="hogwarts55",
+        )
+        wr = WorkshopRequest.objects.create(
+            # required fields
+            state="p",
+            personal="Harry",
+            family="Potter",
+            email="harry@potter.com",
+            location="Scotland",
+            country="GB",
+            language=Language.objects.get(name="English"),
+            audience_description="Students of Hogwarts",
+            administrative_fee="forprofit",
+            travel_expences_management="reimbursed",
+            # fields that should be autofilled
+            institution=Organization.objects.first(),
+            preferred_dates=date.today(),
+            online_inperson="online",
+            workshop_listed=False,
+            additional_contact="hermione@granger.com",
+            member_affiliation="yes",
+            member_code="hogwarts55",
+        )
+        curriculum = Curriculum.objects.filter(name__contains="Data Carpentry").first()
+        wr.requested_workshop_types.set([curriculum])
+
+        expected_tags = Tag.objects.filter(name__in=["private-event", "online", "dc"])
+
+        # Act
+        rv = self.client.get(reverse("workshoprequest_accept_event", args=[wr.pk]))
+        form_initial = rv.context["form"].initial
+
+        # Assert
+        self.assertEqual(rv.status_code, 200)
+        self.assertQuerysetEqual(
+            form_initial["curricula"].all(), wr.requested_workshop_types.all()
+        )
+        self.assertQuerysetEqual(form_initial["tags"], expected_tags)
+        self.assertEqual(form_initial["public_status"], "private")
+        self.assertEqual(form_initial["contact"], wr.additional_contact)
+        self.assertEqual(form_initial["host"].pk, wr.institution.pk)
+        self.assertEqual(form_initial["start"], wr.preferred_dates)
+        self.assertEqual(form_initial["end"], wr.preferred_dates + timedelta(days=1))
+        self.assertEqual(form_initial["membership"].pk, expected_membership.pk)
+
     def test_discarded_request_not_accepted_with_event(self):
         rv = self.client.get(
             reverse("workshoprequest_accept_event", args=[self.wr2.pk])