From 5ed976d53b0bf68bacaa3ff0679c2f8293623aee Mon Sep 17 00:00:00 2001 From: "Ernest W. Durbin III" Date: Mon, 16 Nov 2020 07:07:08 -0500 Subject: [PATCH 01/12] disable "New" display, since everything is new this year --- templates/sponsors/sponsorship_benefits_form.html | 2 -- 1 file changed, 2 deletions(-) diff --git a/templates/sponsors/sponsorship_benefits_form.html b/templates/sponsors/sponsorship_benefits_form.html index ba3d8815b..cdeba33c5 100644 --- a/templates/sponsors/sponsorship_benefits_form.html +++ b/templates/sponsors/sponsorship_benefits_form.html @@ -34,7 +34,6 @@

Sponsorship Application

- {{ benefit_model.NEW_MESSAGE }} {{ benefit_model.PACKAGE_ONLY_MESSAGE }} {{ benefit_model.NO_CAPACITY_MESSAGE }}
@@ -50,7 +49,6 @@

{{ field.label }}

From 0288d2ed3bdd7f272cf4f236d790173950c9d6d1 Mon Sep 17 00:00:00 2001 From: "Ernest W. Durbin III" Date: Mon, 16 Nov 2020 07:35:50 -0500 Subject: [PATCH 02/12] only display the at capacity indicators when any benefit has reached capacity --- sponsors/tests/test_views.py | 14 +++++++++++++ sponsors/views.py | 20 +++++++++++++++++-- .../sponsors/sponsorship_benefits_form.html | 2 +- 3 files changed, 33 insertions(+), 3 deletions(-) diff --git a/sponsors/tests/test_views.py b/sponsors/tests/test_views.py index 5d8a68529..d125b8ce3 100644 --- a/sponsors/tests/test_views.py +++ b/sponsors/tests/test_views.py @@ -112,6 +112,20 @@ def test_populate_form_initial_with_values_from_cookie(self): self.assertEqual(initial, r.context["form"].initial) + def test_capacity_flag(self): + psf_package = baker.make("sponsors.SponsorshipPackage") + r = self.client.get(self.url) + self.assertEqual(False, r.context["capacities_met"]) + + def test_capacity_flag_when_needed(self): + at_capacity_benefit = baker.make( + SponsorshipBenefit, program=self.psf, capacity=0, soft_capacity=False + ) + psf_package = baker.make("sponsors.SponsorshipPackage") + + r = self.client.get(self.url) + self.assertEqual(True, r.context["capacities_met"]) + class NewSponsorshipApplicationViewTests(TestCase): url = reverse_lazy("new_sponsorship_application") diff --git a/sponsors/views.py b/sponsors/views.py index 071aefc08..cd07b4e2d 100644 --- a/sponsors/views.py +++ b/sponsors/views.py @@ -11,7 +11,13 @@ from django.urls import reverse_lazy, reverse from django.shortcuts import redirect -from .models import Sponsor, SponsorshipBenefit, SponsorshipPackage, Sponsorship +from .models import ( + Sponsor, + SponsorshipBenefit, + SponsorshipPackage, + SponsorshipProgram, + Sponsorship, +) from sponsors.forms import SponsorshiptBenefitsForm, SponsorshipApplicationForm from sponsors import cookies @@ -23,10 +29,20 @@ class SelectSponsorshipApplicationBenefitsView(FormView): template_name = "sponsors/sponsorship_benefits_form.html" def get_context_data(self, *args, **kwargs): + programs = SponsorshipProgram.objects.all() + packages = SponsorshipPackage.objects.all() + benefits_qs = SponsorshipBenefit.objects.select_related("program") + capacities_met = any( + [ + any([not b.has_capacity for b in benefits_qs.filter(program=p)]) + for p in programs + ] + ) kwargs.update( { "benefit_model": SponsorshipBenefit, - "sponsorship_packages": SponsorshipPackage.objects.all(), + "sponsorship_packages": packages, + "capacities_met": capacities_met, } ) return super().get_context_data(*args, **kwargs) diff --git a/templates/sponsors/sponsorship_benefits_form.html b/templates/sponsors/sponsorship_benefits_form.html index cdeba33c5..a94952a8d 100644 --- a/templates/sponsors/sponsorship_benefits_form.html +++ b/templates/sponsors/sponsorship_benefits_form.html @@ -35,7 +35,7 @@

Sponsorship Application

{{ benefit_model.PACKAGE_ONLY_MESSAGE }} - {{ benefit_model.NO_CAPACITY_MESSAGE }} + {% if capacities_met %} {{ benefit_model.NO_CAPACITY_MESSAGE }}{% endif %}
From f32ae3bd073c0c02a1a702cdaf29e08d2efbd1a1 Mon Sep 17 00:00:00 2001 From: "Ernest W. Durbin III" Date: Mon, 16 Nov 2020 07:39:46 -0500 Subject: [PATCH 03/12] update formatting for sponsor benefit selection and application - bullets for selected benefits - format as $ USD with commas for thousands separators:w --- pydotorg/settings/base.py | 1 + static/js/sponsors/applicationForm.js | 2 +- templates/sponsors/new_sponsorship_application_form.html | 8 +++++--- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/pydotorg/settings/base.py b/pydotorg/settings/base.py index cc48d6b12..3f067eda5 100644 --- a/pydotorg/settings/base.py +++ b/pydotorg/settings/base.py @@ -144,6 +144,7 @@ 'django.contrib.staticfiles', 'django.contrib.admin', 'django.contrib.admindocs', + 'django.contrib.humanize', 'pipeline', 'sitetree', diff --git a/static/js/sponsors/applicationForm.js b/static/js/sponsors/applicationForm.js index cadc43ada..dbcf91fbe 100644 --- a/static/js/sponsors/applicationForm.js +++ b/static/js/sponsors/applicationForm.js @@ -34,7 +34,7 @@ $(document).ready(function(){ let data = $("form").serialize(); let cost = packageInfo.attr("data-cost"); - costLabel.html('Sponsorship cost is $' + cost + '.00') + costLabel.html('Sponsorship cost is $' + cost.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",") + ' USD') }); $("input[id^=id_benefits_]").change(function(){ diff --git a/templates/sponsors/new_sponsorship_application_form.html b/templates/sponsors/new_sponsorship_application_form.html index 438a32356..be633a465 100644 --- a/templates/sponsors/new_sponsorship_application_form.html +++ b/templates/sponsors/new_sponsorship_application_form.html @@ -1,5 +1,6 @@ {% extends "psf/full-width.html" %} {% load boxes widget_tweaks %} +{% load humanize %} {% block page_title %}Submit Sponsorship Information{% endblock %} @@ -9,16 +10,17 @@

Submit Sponsorship Information

-

We need more information to proceed with your sponsorship application. +

We need more information to proceed with your sponsorship application.

+

{% if sponsorship_package %} - You selected the {{ sponsorship_package.name }} package {% if sponsorship_price %}costing ${{ sponsorship_price }}.00 {% endif %}and the following benefits: + You selected the {{ sponsorship_package.name }} package {% if sponsorship_price %}costing ${{ sponsorship_price|intcomma }} USD {% endif %}and the following benefits: {% else %} You selected the following benefits: {% endif %}

    {% for benefit in sponsorship_benefits %} -

    {{ benefit.name }}

    +
  • {{ benefit.name }}
  • {% endfor %}
| Back to select benefits From 214451d87540d9f5d4d272547dc3d343e8be0c59 Mon Sep 17 00:00:00 2001 From: Bernardo Fontes Date: Mon, 16 Nov 2020 10:29:54 -0300 Subject: [PATCH 04/12] Display icon text only if hover on icon, not on the input checkbox --- templates/sponsors/sponsorship_benefits_form.html | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/templates/sponsors/sponsorship_benefits_form.html b/templates/sponsors/sponsorship_benefits_form.html index a94952a8d..500f77d69 100644 --- a/templates/sponsors/sponsorship_benefits_form.html +++ b/templates/sponsors/sponsorship_benefits_form.html @@ -46,11 +46,11 @@

{{ field.label }}

    {% for benefit in field.field.queryset %}
  • -
  • {% endfor %} From 94fd9b6fe32d5e84d0d27e11634a5a3e22ead417 Mon Sep 17 00:00:00 2001 From: Bernardo Fontes Date: Mon, 16 Nov 2020 10:31:47 -0300 Subject: [PATCH 05/12] Benefit description on hover --- templates/sponsors/sponsorship_benefits_form.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/sponsors/sponsorship_benefits_form.html b/templates/sponsors/sponsorship_benefits_form.html index 500f77d69..219f149ff 100644 --- a/templates/sponsors/sponsorship_benefits_form.html +++ b/templates/sponsors/sponsorship_benefits_form.html @@ -48,7 +48,7 @@

    {{ field.label }}

  • From d1ad135a94806416b52e963bc005ce780c73908c Mon Sep 17 00:00:00 2001 From: Bernardo Fontes Date: Mon, 16 Nov 2020 10:50:54 -0300 Subject: [PATCH 06/12] Explicitly add the active class to the benefit checkbox's label --- static/js/sponsors/applicationForm.js | 2 ++ templates/sponsors/sponsorship_benefits_form.html | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/static/js/sponsors/applicationForm.js b/static/js/sponsors/applicationForm.js index dbcf91fbe..6858bd48d 100644 --- a/static/js/sponsors/applicationForm.js +++ b/static/js/sponsors/applicationForm.js @@ -47,6 +47,8 @@ $(document).ready(function(){ let packageOnlyBenefit = $(this).attr("package_only"); if (packageOnlyBenefit) $(this).attr("disabled", true); return; + } else { + $('label[benefit_id=' + benefit + ']').addClass("active"); } diff --git a/templates/sponsors/sponsorship_benefits_form.html b/templates/sponsors/sponsorship_benefits_form.html index 219f149ff..a65a66778 100644 --- a/templates/sponsors/sponsorship_benefits_form.html +++ b/templates/sponsors/sponsorship_benefits_form.html @@ -46,7 +46,7 @@

    {{ field.label }}

      {% for benefit in field.field.queryset %}
    • -
    • {{ benefit.name }}
    • {% endfor %}
    +

    Please complete the form below.

    | Back to select benefits
@@ -39,7 +39,7 @@

Submit Sponsorship Information

{% render_field form.sponsor %}
- You have already filled previous sponsorship application forms. Select an existing sponsor or create a new one. + Your user account is already associated with existing PSF Sponsors. Select an existing sponsor to submit this application on their behalf or create a new one.

{% endif %} From 4b9921249fcce221a63f90344c83a6db1ff5f6ed Mon Sep 17 00:00:00 2001 From: "Ernest W. Durbin III" Date: Mon, 16 Nov 2020 14:12:10 -0500 Subject: [PATCH 10/12] use raw_id_field for User relation on SponsorContactInline --- sponsors/admin.py | 1 + 1 file changed, 1 insertion(+) diff --git a/sponsors/admin.py b/sponsors/admin.py index 93b07da77..094423dc7 100644 --- a/sponsors/admin.py +++ b/sponsors/admin.py @@ -75,6 +75,7 @@ class SponsorshipPackageAdmin(OrderedModelAdmin): class SponsorContactInline(admin.TabularInline): model = SponsorContact + raw_id_fields = ["user"] extra = 0 From 6a0360f9b5deeb1e71e63b1895b08133bafdf2ff Mon Sep 17 00:00:00 2001 From: "Ernest W. Durbin III" Date: Mon, 16 Nov 2020 14:24:52 -0500 Subject: [PATCH 11/12] update the benefits selection screen with copy and ability for cms managed box header --- static/js/sponsors/applicationForm.js | 4 ++-- templates/sponsors/sponsorship_benefits_form.html | 9 +++++++-- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/static/js/sponsors/applicationForm.js b/static/js/sponsors/applicationForm.js index b277d0b22..1944e2d68 100644 --- a/static/js/sponsors/applicationForm.js +++ b/static/js/sponsors/applicationForm.js @@ -10,7 +10,7 @@ $(document).ready(function(){ checkboxesContainer.find(':checkbox').each(function(){ $(this).prop('checked', false); }); - $("#cost_label").html("Select a package or customize the benefits"); + $("#cost_label").html(""); }); $("input[name=package]").change(function(){ @@ -44,7 +44,7 @@ $(document).ready(function(){ $("input[id^=id_benefits_]").change(function(){ let benefit = this.value; if (benefit.length == 0) return; - if (costLabel.html() != "Updating cost...") costLabel.html("Submit your application and we'll get in touch..."); + if (costLabel.html() != "Updating cost...") costLabel.html("Please submit your customized sponsorship package application and we'll contact you within 2 business days."); let active = checkboxesContainer.find('[value=' + benefit + ']').prop("checked"); if (!active) { diff --git a/templates/sponsors/sponsorship_benefits_form.html b/templates/sponsors/sponsorship_benefits_form.html index a65a66778..53fd9e820 100644 --- a/templates/sponsors/sponsorship_benefits_form.html +++ b/templates/sponsors/sponsorship_benefits_form.html @@ -10,7 +10,12 @@ {% block content %}
-

Sponsorship Application

+ +
+ {% box 'sponsorship-application' %} +
+ +
{% csrf_token %} @@ -24,7 +29,7 @@

Sponsorship Application

- Select a package or customize the benefits +
From b743a59fea363a66725cf4b71889aeb3d14e3d13 Mon Sep 17 00:00:00 2001 From: "Ernest W. Durbin III" Date: Mon, 16 Nov 2020 14:45:00 -0500 Subject: [PATCH 12/12] maintain staff access to new application, but allow for preview access as well --- sponsors/tests/test_views.py | 16 +++++++++++++++- sponsors/views.py | 21 +++++++++++++++++---- 2 files changed, 32 insertions(+), 5 deletions(-) diff --git a/sponsors/tests/test_views.py b/sponsors/tests/test_views.py index d125b8ce3..797ccb467 100644 --- a/sponsors/tests/test_views.py +++ b/sponsors/tests/test_views.py @@ -3,6 +3,7 @@ from itertools import chain from django.contrib import messages +from django.contrib.auth.models import Group from django.conf import settings from django.urls import reverse, reverse_lazy from django.test import TestCase @@ -43,6 +44,9 @@ def setUp(self): self.user = baker.make(settings.AUTH_USER_MODEL, is_staff=True, is_active=True) self.client.force_login(self.user) + self.group = Group(name="Sponsorship Preview") + self.group.save() + def test_display_template_with_form_and_context(self): psf_package = baker.make("sponsors.SponsorshipPackage") extra_package = baker.make("sponsors.SponsorshipPackage") @@ -73,7 +77,7 @@ def test_login_required(self): self.assertRedirects(r, redirect_url) - def test_staff_required(self): + def test_not_staff_no_group_not_allowed(self): redirect_url = f"{settings.LOGIN_URL}?next={self.url}" self.user.is_staff = False self.user.save() @@ -83,6 +87,16 @@ def test_staff_required(self): self.assertRedirects(r, redirect_url, fetch_redirect_response=False) + def test_group_allowed(self): + redirect_url = f"{settings.LOGIN_URL}?next={self.url}" + self.user.groups.add(self.group) + self.user.save() + self.client.force_login(self.user) + + r = self.client.get(self.url) + + self.assertEqual(r.status_code, 200, "user in group should have access") + def test_valid_post_redirect_user_to_next_form_step_and_save_info_in_cookies(self): package = baker.make("sponsors.SponsorshipPackage") for benefit in self.program_1_benefits: diff --git a/sponsors/views.py b/sponsors/views.py index cd07b4e2d..1d4438492 100644 --- a/sponsors/views.py +++ b/sponsors/views.py @@ -3,7 +3,7 @@ from django.conf import settings from django.contrib import messages from django.contrib.auth.decorators import login_required -from django.contrib.admin.views.decorators import staff_member_required +from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin from django.db import transaction from django.utils.decorators import method_decorator from django.http import JsonResponse @@ -23,11 +23,24 @@ from sponsors import cookies -@method_decorator(staff_member_required(login_url=settings.LOGIN_URL), name="dispatch") -class SelectSponsorshipApplicationBenefitsView(FormView): +class SelectSponsorshipApplicationBenefitsView(UserPassesTestMixin, FormView): form_class = SponsorshiptBenefitsForm template_name = "sponsors/sponsorship_benefits_form.html" + # TODO: Remove UserPassesTestMixin when launched, also remove following methods + def test_func(self): + return ( + self.request.user.is_staff + or self.request.user.groups.filter(name="Sponsorship Preview").exists() + ) + + def permission_denied_message(self): + msg = "New Sponsorship Application is not yet generally available, check back soon!" + messages.add_message(self.request, messages.INFO, msg) + return msg + + # END TODO + def get_context_data(self, *args, **kwargs): programs = SponsorshipProgram.objects.all() packages = SponsorshipPackage.objects.all() @@ -51,7 +64,7 @@ def get_success_url(self): if self.request.user.is_authenticated: return reverse_lazy("new_sponsorship_application") else: - # TODO unit test this scenario after removing staff_member_required decortor + # TODO unit test this scenario after removing UserPassesTestMixin return settings.LOGIN_URL def get_initial(self):