Skip to content

Commit

Permalink
Merge pull request #2553 from carpentries/feature/2533-Member-codes-A…
Browse files Browse the repository at this point in the history
…dd-further-checks-for-invalid-code-to-all-workshop-request-forms

Add further checks for invalid code to workshop request forms
  • Loading branch information
elichad authored Nov 16, 2023
2 parents 2d2f53c + 9798916 commit b962bd5
Show file tree
Hide file tree
Showing 16 changed files with 346 additions and 29 deletions.
9 changes: 3 additions & 6 deletions amy/extrequests/base_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@

from django.core.exceptions import ImproperlyConfigured

from extrequests.utils import get_membership_or_none_from_code
from workshops.base_views import AMYCreateView
from workshops.models import Curriculum, Membership, Tag
from workshops.models import Curriculum, Tag


class WRFInitial:
Expand Down Expand Up @@ -53,11 +54,7 @@ def get_initial(self):

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
initial["membership"] = get_membership_or_none_from_code(code)

return initial

Expand Down
29 changes: 23 additions & 6 deletions amy/extrequests/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,11 @@
SelfOrganisedSubmission,
WorkshopInquiryRequest,
)
from extrequests.utils import MemberCodeValidationError, member_code_valid_training
from extrequests.utils import (
MemberCodeValidationError,
member_code_valid,
member_code_valid_training,
)
from workshops.fields import (
CheckboxSelectMultipleWithOthers,
CurriculumModelMultipleChoiceField,
Expand Down Expand Up @@ -452,15 +456,28 @@ def validate_member_code(
self, request: HttpRequest
) -> None | dict[str, ValidationError]:
errors = dict()
code = self.cleaned_data.get("member_code", "")
member_code = self.cleaned_data.get("member_code", "")
error_msg = (
"This code is invalid. "
"Please contact your Member Affiliate to verify your code."
"This may be due to a typo, an expired code, "
"or a code that has not yet been activated."
"Please confirm that you have copied the code correctly, "
"or contact your Member Affiliate to verify your code."
)
# ensure that code belongs to a membership

if not member_code:
return None

# confirm that membership is active at the time of submission
# grace period: 60 days before, 0 days after
try:
Membership.objects.get(registration_code=code)
except Membership.DoesNotExist:
member_code_valid(
code=member_code,
date=datetime.date.today(),
grace_before=60,
grace_after=0,
)
except MemberCodeValidationError:
errors["member_code"] = ValidationError(error_msg)

return errors
Expand Down
Empty file.
22 changes: 22 additions & 0 deletions amy/extrequests/templatetags/request_membership.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
from datetime import date

from django import template

from workshops.models import Membership

register = template.Library()


@register.simple_tag
def membership_alert_type(membership: Membership) -> str:
workshops_remaining = membership.workshops_without_admin_fee_remaining
active = membership.active_on_date(date.today())
if workshops_remaining <= 0 or not active:
return "warning"
else:
return "info"


@register.simple_tag
def membership_active(membership: Membership) -> bool:
return membership.active_on_date(date.today())
100 changes: 100 additions & 0 deletions amy/extrequests/tests/test_template_tags.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
from datetime import date, timedelta

from django.test import TestCase

from amy.extrequests.templatetags.request_membership import (
membership_active,
membership_alert_type,
)
from workshops.models import Membership


class TestMembershipAlertType(TestCase):
def setUp(self):
self.membership = Membership.objects.create(
name="Alpha Organization",
variant="bronze",
agreement_start=date.today() - timedelta(weeks=26),
agreement_end=date.today() + timedelta(weeks=26),
contribution_type="financial",
registration_code="valid123",
workshops_without_admin_fee_per_agreement=2,
)

def test_active_and_has_workshops(self):
# Arrange
expected = "info"

# Act
result = membership_alert_type(self.membership)

# Assert
self.assertEqual(expected, result)

def test_active_and_no_workshops(self):
# Arrange
self.membership.workshops_without_admin_fee_per_agreement = 0
expected = "warning"

# Act
result = membership_alert_type(self.membership)

# Assert
self.assertEqual(expected, result)

def test_inactive_and_no_workshops(self):
# Arrange
self.membership.workshops_without_admin_fee_per_agreement = 0
self.membership.agreement_end = date.today() - timedelta(days=1)
expected = "warning"

# Act
result = membership_alert_type(self.membership)

# Assert
self.assertEqual(expected, result)

def test_inactive_and_has_workshops(self):
# Arrange
self.membership.agreement_end = date.today() - timedelta(days=1)
expected = "warning"

# Act
result = membership_alert_type(self.membership)

# Assert
self.assertEqual(expected, result)


class TestMembershipActive(TestCase):
def setUp(self):
self.membership = Membership.objects.create(
name="Alpha Organization",
variant="bronze",
agreement_start=date.today() - timedelta(weeks=26),
agreement_end=date.today() + timedelta(weeks=26),
contribution_type="financial",
registration_code="valid123",
workshops_without_admin_fee_per_agreement=2,
)

def test_active(self):
# Arrange
expected = True

# Act
result = membership_active(self.membership)

# Assert
self.assertEqual(expected, result)

def test_inactive(self):
# Arrange
self.membership.agreement_end = date.today() - timedelta(days=1)
expected = False

# Act
result = membership_active(self.membership)

# Assert
self.assertEqual(expected, result)
39 changes: 39 additions & 0 deletions amy/extrequests/tests/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from extrequests.utils import (
MemberCodeValidationError,
get_membership_or_none_from_code,
member_code_valid,
member_code_valid_training,
)
Expand Down Expand Up @@ -244,3 +245,41 @@ def test_member_code_validation__code_only_inhouse_seats_remaining(self):

# Assert
self.assertTrue(result)


class TestGetMembershipFromCodeIfExists(TestBase):
def setUp(self):
self.valid_code = "valid123"
self.membership = Membership.objects.create(
name="Alpha Organization",
variant="bronze",
agreement_start=date.today() - timedelta(weeks=26),
agreement_end=date.today() + timedelta(weeks=26),
contribution_type="financial",
registration_code=self.valid_code,
public_instructor_training_seats=1,
inhouse_instructor_training_seats=1,
)

def test_returns_none_if_code_empty(self):
# Act
result_empty_string = get_membership_or_none_from_code("")
result_none = get_membership_or_none_from_code(None)

# Assert
self.assertIsNone(result_empty_string)
self.assertIsNone(result_none)

def test_returns_none_if_no_match(self):
# Act
result = get_membership_or_none_from_code("invalid")

# Assert
self.assertIsNone(result)

def test_returns_matching_membership(self):
# Act
result = get_membership_or_none_from_code(self.valid_code)

# Assert
self.assertEqual(result, self.membership)
89 changes: 88 additions & 1 deletion amy/extrequests/tests/test_workshop_requests.py
Original file line number Diff line number Diff line change
Expand Up @@ -371,15 +371,31 @@ def setUp(self):
self._setUpUsersAndLogin()

def setUpMembership(self):
Membership.objects.create(
self.membership = Membership.objects.create(
name="Alpha Organization",
variant="bronze",
agreement_start=date.today() - timedelta(weeks=26),
agreement_end=date.today() + timedelta(weeks=26),
workshops_without_admin_fee_per_agreement=2,
contribution_type="financial",
registration_code="valid123",
)

@override_settings(FLAGS={"ENFORCE_MEMBER_CODES": [("boolean", False)]})
def test_member_code_validation__not_enforced(self):
"""Invalid code should pass if enforcement is not enabled."""
# Arrange
data = {
"member_code": "invalid",
}

# Act
rv = self.client.post(reverse("training_request"), data=data)

# Assert
self.assertEqual(rv.status_code, 200)
self.assertNotContains(rv, self.INVALID_CODE_ERROR)

@override_settings(FLAGS={"ENFORCE_MEMBER_CODES": [("boolean", True)]})
def test_member_code_validation__code_valid(self):
"""valid code - no error"""
Expand Down Expand Up @@ -411,6 +427,77 @@ def test_member_code_validation__code_no_match(self):
self.assertEqual(rv.status_code, 200)
self.assertContains(rv, self.INVALID_CODE_ERROR)

@override_settings(FLAGS={"ENFORCE_MEMBER_CODES": [("boolean", True)]})
def test_member_code_validation__code_too_early(self):
"""code used before the membership is active - error on code"""
# Arrange
self.setUpMembership()
self.membership.agreement_start = date.today() + timedelta(days=61)
self.membership.save()
data = {
"member_code": "valid123",
}

# Act
rv = self.client.post(reverse("workshop_request"), data=data)

# Assert
self.assertEqual(rv.status_code, 200)
self.assertContains(rv, self.INVALID_CODE_ERROR)

@override_settings(FLAGS={"ENFORCE_MEMBER_CODES": [("boolean", True)]})
def test_member_code_validation__code_too_late(self):
"""code used after the membership ends - error on code"""
# Arrange
self.setUpMembership()
self.membership.agreement_end = date.today() - timedelta(days=1)
self.membership.save()
data = {
"member_code": "valid123",
}

# Act
rv = self.client.post(reverse("workshop_request"), data=data)

# Assert
self.assertEqual(rv.status_code, 200)
self.assertContains(rv, self.INVALID_CODE_ERROR)

@override_settings(FLAGS={"ENFORCE_MEMBER_CODES": [("boolean", True)]})
def test_member_code_validation__code_no_workshops_remaining(self):
"""code matches a membership with no workshops remaining - no error"""
# Arrange
self.setUpMembership()
data = {
"member_code": "valid123",
}
# create some past events for this membership
swc = Organization.objects.create(
domain="software-carpentry.org",
fullname="Software Carpentry",
)
Event.objects.create(
slug="test-membership-1",
host=self.org_alpha,
administrator=swc,
membership=self.membership,
start=date.today() - timedelta(weeks=1),
)
Event.objects.create(
slug="test-membership-2",
host=self.org_alpha,
administrator=swc,
membership=self.membership,
start=date.today() - timedelta(weeks=2),
)

# Act
rv = self.client.post(reverse("workshop_request"), data=data)

# Assert
self.assertEqual(rv.status_code, 200)
self.assertNotContains(rv, self.INVALID_CODE_ERROR)

@override_settings(FLAGS={"ENFORCE_MEMBER_CODES": [("boolean", True)]})
def test_member_code_validation_code_empty(self):
"""empty code - no error"""
Expand Down
Loading

0 comments on commit b962bd5

Please sign in to comment.